/* ====================================================================
 * Copyright (c) 1999-2001 Carnegie Mellon University.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer. 
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * This work was supported in part by funding from the Defense Advanced 
 * Research Projects Agency and the National Science Foundation of the 
 * United States of America, and the CMU Sphinx Speech Consortium.
 *
 * THIS SOFTWARE IS PROVIDED BY CARNEGIE MELLON UNIVERSITY ``AS IS'' AND 
 * ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY
 * NOR ITS EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * ====================================================================
 *
 */

/*
 * rec.c -- low level audio recording for Windows NT/95.
 *
 * HISTORY
 * 
 * 19-Jan-1999	M K Ravishankar (rkm@cs.cmu.edu) at Carnegie Mellon University
 * 		Added AD_ return codes.  Added ad_open_sps_bufsize(), and
 * 		ad_rec_t.n_buf.
 * 
 * 07-Mar-98	M K Ravishankar (rkm@cs.cmu.edu) at Carnegie Mellon University
 * 		Added ad_open_sps(), and made ad_open() call it.
 * 
 * 10-Jun-96	M K Ravishankar (rkm@cs.cmu.edu) at Carnegie Mellon University
 * 		Added ad_rec_t type to all calls.
 * 
 * 03-Jun-96	M K Ravishankar (rkm@cs.cmu.edu) at Carnegie Mellon University
 * 		Created.
 */


#include <windows.h>
#include <mmsystem.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <s3types.h>
#include "ad.h"


#define DEFAULT_N_WI_BUF	32	/* #Recording bufs */
#define WI_BUFSIZE		2500	/* Samples/buf (Why this specific value??
					   So that at reasonable sampling rates
					   data is returned frequently enough.) */


static void wavein_error (char *src, int32 ret)
{
    char errbuf[1024];
    
    waveInGetErrorText(ret, errbuf, sizeof(errbuf));
    fprintf(stderr, "%s error %d: %s\n", src, ret, errbuf);
}


static void wavein_free_buf (ad_wbuf_t *b)
{
    GlobalUnlock (b->h_whdr);
    GlobalFree (b->h_whdr);
    GlobalUnlock (b->h_buf);
    GlobalFree (b->h_buf);
}


static int32 wavein_alloc_buf (ad_wbuf_t *b, int32 samples_per_buf)
{
    HGLOBAL h_buf;	/* handle to data buffer */
    LPSTR p_buf;	/* pointer to data buffer */
    HGLOBAL h_whdr;	/* handle to header */
    LPWAVEHDR p_whdr;	/* pointer to header */
    
    /* Allocate data buffer */
    h_buf = GlobalAlloc (GMEM_MOVEABLE | GMEM_SHARE, samples_per_buf*sizeof(int16));
    if (! h_buf) {
	fprintf(stderr, "GlobalAlloc failed\n");
	return -1;
    }
    if ((p_buf = GlobalLock (h_buf)) == NULL) {
	GlobalFree (h_buf);
	fprintf(stderr, "GlobalLock failed\n");
	return -1;
    }
    
    /* Allocate WAVEHDR structure */
    h_whdr = GlobalAlloc (GMEM_MOVEABLE | GMEM_SHARE, sizeof(WAVEHDR));
    if (h_whdr == NULL) {
	GlobalUnlock (h_buf);
	GlobalFree (h_buf);

	fprintf(stderr, "GlobalAlloc failed\n");
	return -1;
    }
    if ((p_whdr = GlobalLock (h_whdr)) == NULL) {
	GlobalUnlock (h_buf);
	GlobalFree (h_buf);
	GlobalFree (h_whdr);

	fprintf(stderr, "GlobalLock failed\n");
	return -1;
    }
    
    b->h_buf = h_buf;
    b->p_buf = p_buf;
    b->h_whdr = h_whdr;
    b->p_whdr = p_whdr;
    
    p_whdr->lpData = p_buf;
    p_whdr->dwBufferLength = samples_per_buf * sizeof(int16);
    p_whdr->dwUser = 0L;
    p_whdr->dwFlags = 0L;
    p_whdr->dwLoops = 0L;
    
    return 0;
}


static int32 wavein_enqueue_buf (HWAVEIN h, LPWAVEHDR whdr)
{
    int32 st;
    
    if ((st = waveInPrepareHeader (h, whdr, sizeof(WAVEHDR))) != 0) {
	wavein_error("waveInPrepareHeader", st);
	return -1;
    }
    if ((st = waveInAddBuffer (h, whdr, sizeof(WAVEHDR))) != 0) {
	wavein_error("waveInAddBuffer", st);
	return -1;
    }
    
    return 0;
}


static HWAVEIN wavein_open (int32 samples_per_sec, int32 bytes_per_sample)
{
    WAVEFORMATEX wfmt;
    int32 st;
    HWAVEIN h;
    
    if (bytes_per_sample != sizeof(int16)) {
	fprintf(stderr, "bytes/sample != %d\n", sizeof(int16));
	return NULL;
    }
    
    wfmt.wFormatTag      =  WAVE_FORMAT_PCM;
    wfmt.nChannels       =  1;
    wfmt.nSamplesPerSec  =  samples_per_sec;
    wfmt.nAvgBytesPerSec =  samples_per_sec * bytes_per_sample;
    wfmt.nBlockAlign     =  bytes_per_sample;
    wfmt.wBitsPerSample     =  8 * bytes_per_sample;
    
    /* There should be a check here for a device of the desired type; later... */
    
    st = waveInOpen ((LPHWAVEIN) &h, WAVE_MAPPER, (LPWAVEFORMATEX) &wfmt,
		     (DWORD) 0L, 0L, (DWORD) CALLBACK_NULL);
    if (st != 0) {
	wavein_error ("waveInOpen", st);
	return NULL;
    }
    
    return h;
}


static int32 wavein_close (ad_rec_t *r)
{
    int32 i, st;

    /* Unprepare all buffers; multiple unprepares of the same buffer are benign */
    for (i = 0; i < r->n_buf; i++) {
	st = waveInUnprepareHeader(r->h_wavein, r->wi_buf[i].p_whdr, sizeof(WAVEHDR));
	if (st != 0) {
	    wavein_error("waveInUnprepareHeader", st);
	    return -1;
	}
    }
    
    /* Free buffers */
    for (i = 0; i < r->n_buf; i++)
	wavein_free_buf (&(r->wi_buf[i]));
    free (r->wi_buf);
    
    if ((st = waveInClose (r->h_wavein)) != 0) {
    	wavein_error("waveInClose", st);
	return -1;
    }

    free (r);
    
    return 0;
}


ad_rec_t *ad_open_sps_bufsize (int32 sps, int32 bufsize_msec)
{
    ad_rec_t *r;
    int32 i, j;
    HWAVEIN h;
    
    if ((h = wavein_open (sps, sizeof(int16))) == NULL)
	return NULL;
    
    if ((r = (ad_rec_t *) malloc (sizeof (ad_rec_t))) == NULL) {
	fprintf(stderr, "malloc(%d) failed\n", sizeof(ad_rec_t));
	waveInClose (h);
	return NULL;
    }

    r->n_buf = ((sps * bufsize_msec) / 1000) / WI_BUFSIZE;
    if (r->n_buf < DEFAULT_N_WI_BUF)
	r->n_buf = DEFAULT_N_WI_BUF;
    printf("Allocating %d buffers of %d samples each\n", r->n_buf, WI_BUFSIZE);
    
    if ((r->wi_buf = (ad_wbuf_t *) calloc (r->n_buf, sizeof(ad_wbuf_t))) == NULL) {
	fprintf(stderr, "calloc(%d,%d) failed\n", r->n_buf, sizeof(ad_wbuf_t));
	free (r);
	waveInClose (h);

	return NULL;
    }
    for (i = 0; i < r->n_buf; i++) {
	if (wavein_alloc_buf (&(r->wi_buf[i]), WI_BUFSIZE) < 0) {
	    for (j = 0; j < i; j++)
		wavein_free_buf (&(r->wi_buf[j]));
	    free (r->wi_buf);
	    free (r);
	    waveInClose (h);

	    return NULL;
	}
    }
    
    r->h_wavein = h;
    r->opened = 1;
    r->recording = 0;
    r->curbuf = r->n_buf-1;	/* current buffer with data for application */
    r->curlen = 0;		/* #samples in curbuf remaining to be consumed */
    r->lastbuf = r->curbuf;
    r->sps = sps;
    r->bps = sizeof(int16);	/* HACK!! Hardwired value for bytes/sec */

    return r;
}


ad_rec_t *ad_open_sps (int32 sps)
{
    return (ad_open_sps_bufsize (sps, WI_BUFSIZE*DEFAULT_N_WI_BUF*1000/sps));
}


ad_rec_t *ad_open ( void )
{
    return (ad_open_sps (DEFAULT_SAMPLES_PER_SEC));	/* HACK!! Rename this constant */
}


int32 ad_close (ad_rec_t *r)
{
    if (! r->opened)
	return AD_ERR_NOT_OPEN;

    if (r->recording)
	if (ad_stop_rec(r) < 0)
	    return AD_ERR_WAVE;

    if (wavein_close (r) < 0)
	return AD_ERR_WAVE;

    return 0;
}


int32 ad_start_rec (ad_rec_t *r)
{
    int32 i;
    
    if ((! r->opened) || r->recording)
	return -1;

    for (i = 0; i < r->n_buf; i++)
	if (wavein_enqueue_buf (r->h_wavein, r->wi_buf[i].p_whdr) < 0)
	    return AD_ERR_WAVE;
    r->curbuf = r->n_buf-1;	/* current buffer with data for application */
    r->curlen = 0;		/* #samples in curbuf remaining to be consumed */
    
    if (waveInStart (r->h_wavein) != 0)
	return AD_ERR_WAVE;
    
    r->recording = 1;

    return 0;
}


int32 ad_stop_rec (ad_rec_t *r)
{
    int32 i, st;

    if ((! r->opened) || (! r->recording))
	return -1;
    
    if (waveInStop (r->h_wavein) != 0)
	return AD_ERR_WAVE;
    
    if ((st = waveInReset (r->h_wavein)) != 0) {
	wavein_error("waveInReset", st);
	return AD_ERR_WAVE;
    }
    
    /* Wait until all buffers marked done */
    for (i = 0; i < r->n_buf; i++)
	while (! (r->wi_buf[i].p_whdr->dwFlags & WHDR_DONE));
    
    if ((r->lastbuf = r->curbuf-1) < 0)
	r->lastbuf = r->n_buf-1;
    
    r->recording = 0;

    return 0;
}


int32 ad_read (ad_rec_t *r, int16 *buf, int32 max)
{
    int32 t, st, len;
    LPWAVEHDR whdr;
    int16 *sysbufp;

    if (! r->opened)
	return AD_ERR_NOT_OPEN;

    /* Check if all recorded data exhausted */
    if ((! r->recording) && (r->curbuf == r->lastbuf) && (r->curlen == 0))
	return AD_EOF;

    len = 0;
    while (max > 0) {
	/* Look for next buffer with recording data */
	if (r->curlen == 0) {
	    /* No current buffer with data; get next buffer in sequence if available */
	    t = r->curbuf+1;
	    if (t >= r->n_buf)
		t = 0;
	    
	    if (! (r->wi_buf[t].p_whdr->dwFlags & WHDR_DONE))
		return len;

	    r->curbuf = t;
	    r->curlen = r->wi_buf[t].p_whdr->dwBytesRecorded >> 1;
	    r->curoff = 0;
	}

	/* Copy data from curbuf to buf */
	whdr = r->wi_buf[r->curbuf].p_whdr;
	t = (max < r->curlen) ? max : r->curlen;	/* #Samples to copy */

	if (t > 0) {
	    sysbufp = (int16 *) (whdr->lpData);
	    memcpy (buf, sysbufp + r->curoff, t * sizeof(int16));

	    buf += t;
	    max -= t;
	    r->curoff += t;
	    r->curlen -= t;
	    len += t;
	}

	/* If curbuf empty recycle it to system if still recording */
	if (r->curlen == 0) {
	    if (r->recording) {
		/* Return empty buffer to system */
		st = waveInUnprepareHeader (r->h_wavein, whdr, sizeof(WAVEHDR));
		if (st != 0) {
		    wavein_error("waveInUnprepareHeader", st);
		    return AD_ERR_WAVE;
		}

		if (wavein_enqueue_buf (r->h_wavein, whdr) < 0)
		    return AD_ERR_WAVE;

	    } else if (r->curbuf == r->lastbuf) {
		return len;
	    }
	}
    }

    return len;
}
