/*	MikMod sound library
	(c) 1998, 1999, 2000 Miodrag Vallat and others - see file AUTHORS for
	complete list.

	This library is free software; you can redistribute it and/or modify
	it under the terms of the GNU Library 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 Library General Public License for more details.

	You should have received a copy of the GNU Library General Public
	License along with this library; if not, write to the Free Software
	Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
	02111-1307, USA.
*/

/*==============================================================================

  $Id$

  Driver for output on OS/2 MMPM/2 using direct audio (DART)

==============================================================================*/

/*

	Written by Miodrag Vallat <miod@mikmod.org>
	Lots of improvements (basically made usable...) by Andrew Zabolotny
	                                                   <bit@eltech.ru>

*/

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "mikmod_internals.h"

#ifdef DRV_DART

#undef VERSION /* stop conflict with os2medef.h */
#define INCL_DOS
#define INCL_OS2MM
#include <os2.h>
/* Prevent a warning: PPFN redefined */
#define PPFN _PPFN
#include <os2me.h>
#undef PPFN

#include <stdlib.h>
#include <string.h>

#define MAX_BUFFERCOUNT 8

static MCI_MIX_BUFFER MixBuffers[MAX_BUFFERCOUNT];
static MCI_MIXSETUP_PARMS MixSetupParms;
static MCI_BUFFER_PARMS BufferParms;
static ULONG DeviceIndex = 0;			/* use default waveaudio device */
static ULONG BufferSize = (ULONG)-1;	/* autodetect buffer size */
static ULONG BufferCount = 2;			/* use two buffers */
static ULONG DeviceID = 0;

static void Dart_CommandLine(const CHAR *cmdline)
{
	char *ptr;
	int buf;

	if ((ptr = MD_GetAtom("buffer", cmdline, 0)) != NULL) {
		buf = atoi(ptr);
		if (buf >= 12 && buf <= 16)
			BufferSize = 1 << buf;
		MikMod_free(ptr);
	}

	if ((ptr = MD_GetAtom("count", cmdline, 0)) != NULL) {
		buf = atoi(ptr);
		if (buf >= 2 && buf <= MAX_BUFFERCOUNT)
			BufferCount = buf;
		MikMod_free(ptr);
	}

	if ((ptr = MD_GetAtom("device", cmdline, 0)) != NULL) {
		buf = atoi(ptr);
		if (buf >= 0 && buf <= 8)
			DeviceIndex = buf;
		MikMod_free(ptr);
	}
}

/* Buffer update thread (created and called by DART)
   This is a high-priority thread used to compute and update the audio stream,
   automatically created by the DART subsystem. We compute the next audio
   buffer and feed it to the waveaudio device. */
static LONG APIENTRY Dart_UpdateBuffers(ULONG ulStatus, PMCI_MIX_BUFFER pBuffer, ULONG ulFlags)
{
	/* sanity check */
	if (!pBuffer)
		return TRUE;

	/* if we have finished a buffer, we're ready to play a new one */
	if ((ulFlags == MIX_WRITE_COMPLETE) ||
		((ulFlags == (MIX_WRITE_COMPLETE | MIX_STREAM_ERROR)) &&
		 (ulStatus == ERROR_DEVICE_UNDERRUN))) {
		/* refill this audio buffer and feed it again */
		MUTEX_LOCK(vars);
		if (Player_Paused_internal())
			pBuffer->ulBufferLength =
					VC_SilenceBytes((SBYTE*)pBuffer->pBuffer, BufferSize);
		else
			pBuffer->ulBufferLength =
					VC_WriteBytes((SBYTE*)pBuffer->pBuffer, BufferSize);
		MUTEX_UNLOCK(vars);
		MixSetupParms.pmixWrite(MixSetupParms.ulMixHandle, pBuffer, 1);
	}
	return TRUE;
}

static BOOL Dart_IsPresent(void)
{
	MCI_AMP_OPEN_PARMS AmpOpenParms;
	MCI_GENERIC_PARMS GenericParms;

	memset(&AmpOpenParms, 0, sizeof(MCI_AMP_OPEN_PARMS));
	AmpOpenParms.pszDeviceType = (PSZ) MAKEULONG(MCI_DEVTYPE_AUDIO_AMPMIX,
					(USHORT) DeviceIndex);

	if (mciSendCommand(0, MCI_OPEN, MCI_WAIT | MCI_OPEN_SHAREABLE | MCI_OPEN_TYPE_ID,
					   (PVOID) &AmpOpenParms, 0) != MCIERR_SUCCESS)
		return 0;

	mciSendCommand(AmpOpenParms.usDeviceID, MCI_CLOSE, MCI_WAIT,
				   (PVOID) &GenericParms, 0);
	return 1;
}

static int Dart_Init(void)
{
	MCI_AMP_OPEN_PARMS AmpOpenParms;
	MCI_GENERIC_PARMS GenericParms;

	MixBuffers[0].pBuffer = NULL;	/* marker */
	DeviceID = 0;
	memset(&GenericParms, 0, sizeof(MCI_GENERIC_PARMS));

	/* open AMP device */
	memset(&AmpOpenParms, 0, sizeof(MCI_AMP_OPEN_PARMS));
	AmpOpenParms.usDeviceID = 0;
	AmpOpenParms.pszDeviceType = (PSZ) MAKEULONG(MCI_DEVTYPE_AUDIO_AMPMIX,
					(USHORT) DeviceIndex);


	if (mciSendCommand(0, MCI_OPEN, MCI_WAIT | MCI_OPEN_SHAREABLE | MCI_OPEN_TYPE_ID,
					   (PVOID) & AmpOpenParms, 0) != MCIERR_SUCCESS) {
		_mm_errno = MMERR_OPENING_AUDIO;
		return 1;
	}

	DeviceID = AmpOpenParms.usDeviceID;

	/* setup playback parameters */
	memset(&MixSetupParms, 0, sizeof(MCI_MIXSETUP_PARMS));
	MixSetupParms.ulBitsPerSample = (md_mode & DMODE_16BITS) ? 16 : 8;
	MixSetupParms.ulFormatTag = MCI_WAVE_FORMAT_PCM;
	MixSetupParms.ulSamplesPerSec = md_mixfreq;
	MixSetupParms.ulChannels = (md_mode & DMODE_STEREO) ? 2 : 1;
	MixSetupParms.ulFormatMode = MCI_PLAY;
	MixSetupParms.ulDeviceType = MCI_DEVTYPE_WAVEFORM_AUDIO;
	MixSetupParms.pmixEvent = Dart_UpdateBuffers;

	if (mciSendCommand(DeviceID, MCI_MIXSETUP,
					   MCI_WAIT | MCI_MIXSETUP_INIT,
					   (PVOID) & MixSetupParms, 0) != MCIERR_SUCCESS) {
		_mm_errno = MMERR_OS2_MIXSETUP;
		return 1;
	}

	/* Compute audio buffer size now if necessary */
	if (BufferSize == (ULONG)-1) {
		/* DART suggested buffer size is somewhat too big. We compute a size
		   for circa 1/4" of playback. */
		int bit;

		BufferSize = md_mixfreq >> 2;
		if (md_mode & DMODE_STEREO)
			BufferSize <<= 1;
		if (md_mode & DMODE_16BITS)
			BufferSize <<= 1;
		for (bit = 15; bit >= 12; bit--)
			if (BufferSize & (1 << bit))
				break;
		BufferSize = (1 << bit);
	}
	/* make sure buffer is not greater than 64 Kb, as DART can't handle this
	   situation. */
	if (BufferSize > 65536)
		BufferSize = 65536;

	BufferParms.ulStructLength = sizeof(BufferParms);
	BufferParms.ulNumBuffers = BufferCount;
	BufferParms.ulBufferSize = BufferSize;
	BufferParms.pBufList = MixBuffers;

	if (mciSendCommand(DeviceID, MCI_BUFFER,
					   MCI_WAIT | MCI_ALLOCATE_MEMORY,
					   (PVOID) &BufferParms, 0) != MCIERR_SUCCESS) {
		_mm_errno = MMERR_OUT_OF_MEMORY;
		return 1;
	}

	return VC_Init();
}

static void Dart_Exit(void)
{
	MCI_GENERIC_PARMS GenericParms;

	if (MixBuffers[0].pBuffer) {
		mciSendCommand(DeviceID, MCI_BUFFER, MCI_WAIT | MCI_DEALLOCATE_MEMORY,
					   &BufferParms, 0);
		MixBuffers[0].pBuffer = NULL;
	}
	if (DeviceID) {
		mciSendCommand(DeviceID, MCI_CLOSE, MCI_WAIT, (PVOID) &GenericParms, 0);
		DeviceID = 0;
	}
	VC_Exit();
}

static int Dart_PlayStart(void)
{
	MCI_GENERIC_PARMS GenericParms;
	int i;

	if (VC_PlayStart())
		return 1;

	/* silence buffers */
	for (i = 0; i < BufferCount; i++) {
		VC_SilenceBytes((SBYTE*) MixBuffers[i].pBuffer, BufferSize);
		MixBuffers[i].ulBufferLength = BufferSize;
	}

	/* grab exclusive rights to device instance (not entire device) */
	GenericParms.hwndCallback = 0;  /* Not needed, so set to 0 */
	if (mciSendCommand(DeviceID, MCI_ACQUIREDEVICE, MCI_EXCLUSIVE_INSTANCE,
	                   (PVOID) &GenericParms, 0) != MCIERR_SUCCESS) {
		mciSendCommand(DeviceID, MCI_CLOSE, MCI_WAIT, (PVOID) &GenericParms, 0);
		_mm_errno = MMERR_OPENING_AUDIO;
		return 1;
	}

	/* wake DART up */
	MixSetupParms.pmixWrite(MixSetupParms.ulMixHandle, MixBuffers, BufferCount);

	return 0;
}

static void Dart_Update(void)
{
	/* Nothing to do: everything is done in DART_UpdateBuffers() */
}

static void Dart_PlayStop(void)
{
	MCI_GENERIC_PARMS GenericParms;

	GenericParms.hwndCallback = 0;
	mciSendCommand(DeviceID, MCI_STOP, MCI_WAIT, (PVOID) &GenericParms, 0);

	VC_PlayStop();
}

MIKMODAPI MDRIVER drv_dart = {
	NULL,
	"Direct audio (DART)",
	"OS/2 DART driver v1.2",
	0, 255,
	"dart",
	"device:r:0,8,0:Waveaudio device index to use (0 - default)\n"
        "buffer:r:12,16:Audio buffer log2 size\n"
        "count:r:2,8,2:Number of audio buffers\n",
	Dart_CommandLine,
	Dart_IsPresent,
	VC_SampleLoad,
	VC_SampleUnload,
	VC_SampleSpace,
	VC_SampleLength,
	Dart_Init,
	Dart_Exit,
	NULL,
	VC_SetNumVoices,
	Dart_PlayStart,
	Dart_PlayStop,
	Dart_Update,
	NULL,
	VC_VoiceSetVolume,
	VC_VoiceGetVolume,
	VC_VoiceSetFrequency,
	VC_VoiceGetFrequency,
	VC_VoiceSetPanning,
	VC_VoiceGetPanning,
	VC_VoicePlay,
	VC_VoiceStop,
	VC_VoiceStopped,
	VC_VoiceGetPosition,
	VC_VoiceRealVolume
};

#else

MISSING(drv_dart);

#endif

/* ex:set ts=4: */
