/*

    TiMidity -- Experimental MIDI to WAVE converter
    Copyright (C) 1995 Tuukka Toivonen <toivonen@clinet.fi>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.
    
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
    
    win_audio.c
    
    Functions to play sound on the Win32 audio driver (Win 95 or Win NT).

*/

#include "config.h"
#include "output.h"
#include <mmsystem.h>

static int open_output (void);	/* 0=success, 1=warning, -1=fatal error */
static void close_output (void);
static void output_data (int32b * buf, int32b count);
static void flush_output (void);
static void purge_output (void);

/* export the playback mode */

#define dpm win32_play_mode

PlayMode dpm = {
	DEFAULT_RATE,
	PE_16BIT | PE_SIGNED,
	-1,
	{16},
	"Win32 audio driver",
	'd',
	NULL,
	open_output,
	close_output,
	output_data,
	flush_output,
	purge_output
};

/* Max audio blocks waiting to be played */

static HWAVEOUT dev;
static int nBlocks;

extern CRITICAL_SECTION critSect;

static void
wait (void)
{
	while (nBlocks)
		Sleep (1);
}

static int
play (void *mem, int len)
{
	HGLOBAL hg;
	LPWAVEHDR wh;
	MMRESULT res;
	
	while (nBlocks >= dpm.extra_param[0])
		Sleep (1);
	
	hg = GlobalAlloc (GMEM_MOVEABLE | GMEM_ZEROINIT, sizeof (WAVEHDR));
	if (!hg)
    {
		return FALSE;
    }
	wh = (LPWAVEHDR)GlobalLock (hg);
	wh->dwBufferLength = len;
	wh->lpData = (char *)mem;
	
	res = waveOutPrepareHeader (dev, wh, sizeof (WAVEHDR));
	if (res)
    {
		GlobalUnlock (hg);
		GlobalFree (hg);
		return TRUE;
    }
	res = waveOutWrite (dev, wh, sizeof (WAVEHDR));
	if (res != MMSYSERR_NOERROR)
    {
		GlobalUnlock (hg);
		GlobalFree (hg);
		return TRUE;
    }
	EnterCriticalSection (&critSect);
	nBlocks++;
	LeaveCriticalSection (&critSect);
	//      cmsg (CMSG_INFO,VERB_NOISY, "Play: %d blocks", nBlocks);
	return FALSE;
}

static void CALLBACK
wave_callback (HWAVE hWave, UINT uMsg,
			   DWORD dwInstance, DWORD dwParam1, DWORD dwParam2)
{
	WAVEHDR *wh;
	HGLOBAL hg;
	
	if (uMsg == WOM_DONE)
    {
		EnterCriticalSection (&critSect);
		wh = (WAVEHDR *) dwParam1;
		waveOutUnprepareHeader (dev, wh, sizeof (WAVEHDR));
		hg = GlobalHandle (wh->lpData);
		GlobalUnlock (hg);
		GlobalFree (hg);
		hg = GlobalHandle (wh);
		GlobalUnlock (hg);
		GlobalFree (hg);
		nBlocks--;
		LeaveCriticalSection (&critSect);
		//              cmsg (CMSG_INFO, VERB_NOISY, "Callback: %d blocks", nBlocks);
    }
}

static int
open_output (void)
{
	int i, j, mono, eight_bit, warnings = 0;
	PCMWAVEFORMAT pcm;
	MMRESULT res;
	int retryCount=0;
	
	/* Check if there is at least one audio device */
	if (!waveOutGetNumDevs ())
    {
		return -1;
    }
	
	/* They can't mean these */
	dpm.encoding &= ~(PE_ULAW | PE_BYTESWAP);
	
	if (dpm.encoding & PE_16BIT)
		dpm.encoding |= PE_SIGNED;
	else
		dpm.encoding &= ~PE_SIGNED;
	
	mono = (dpm.encoding & PE_MONO);
	eight_bit = !(dpm.encoding & PE_16BIT);
	
	pcm.wf.wFormatTag = WAVE_FORMAT_PCM;
	pcm.wf.nChannels = mono ? 1 : 2;
	pcm.wf.nSamplesPerSec = i = dpm.rate;
	j = 1;
	if (!mono)
    {
		i *= 2;
		j *= 2;
    }
	if (!eight_bit)
    {
		i *= 2;
		j *= 2;
    }
	pcm.wf.nAvgBytesPerSec = i;
	pcm.wf.nBlockAlign = j;
	pcm.wBitsPerSample = eight_bit ? 8 : 16;
	
	res =
		waveOutOpen (NULL, 0, (LPWAVEFORMATEX) & pcm, 0, 0, WAVE_FORMAT_QUERY);
	if (res)
    {
		return -1;
    }
	/* dirty hack */
	while (retryCount<100) {
		res=waveOutOpen (&dev, 0, (LPWAVEFORMATEX) & pcm, (DWORD) wave_callback, 0,CALLBACK_FUNCTION);
		retryCount++;
		if (res==MMSYSERR_ALLOCATED) {
			Sleep(100);
			continue;
		} else {
			break;
		}
	}
	if (res)
    {
		return -1;
    }
	nBlocks = 0;
	return warnings;
}

static void
output_data (int32b * buf, int32b count)
{
	int len = count;
	HGLOBAL hg;
	void *b;
	
	if (!(dpm.encoding & PE_MONO))	/* Stereo sample */
    {
		count *= 2;
		len *= 2;
    }
	
	if (dpm.encoding & PE_16BIT)
		len *= 2;
	
	hg = GlobalAlloc (GMEM_MOVEABLE, len);
	if (!hg)
    {
		return;
    }
	b = GlobalLock (hg);
	
	if (dpm.encoding & PE_16BIT)
		/* Convert data to signed 16-bit PCM */
		s32tos16 (buf, count);
	else
		/* Convert to 8-bit unsigned. */
		s32tou8 (buf, count);
	
	CopyMemory (b, buf, len);
	if (play (b, len))
    {
		GlobalUnlock (hg);
		GlobalFree (hg);
    }
}

static void
close_output (void)
{
	wait ();
	waveOutClose (dev);
}

static void
flush_output (void)
{
	wait ();
}

static void
purge_output (void)
{
	waveOutReset (dev);
	wait ();
}
