/*
 * XBoing - An X11 blockout style computer game
 *
 * (c) Copyright 1993, 1994, 1995, Justin C. Kibell, All Rights Reserved
 *
 * The X Consortium, and any party obtaining a copy of these files from
 * the X Consortium, directly or indirectly, is granted, free of charge, a
 * full and unrestricted irrevocable, world-wide, paid up, royalty-free,
 * nonexclusive right and license to deal in this software and
 * documentation files (the "Software"), including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons who receive
 * copies from any such party to do so.  This license includes without
 * limitation a license to do the foregoing actions under any patents of
 * the party supplying this software to the X Consortium.
 *
 * In no event shall the author be liable to any party for direct, indirect,
 * special, incidental, or consequential damages arising out of the use of
 * this software and its documentation, even if the author has been advised
 * of the possibility of such damage.
 *
 * The author specifically disclaims any warranties, including, but not limited
 * to, the implied warranties of merchantability and fitness for a particular
 * purpose.  The software provided hereunder is on an "AS IS" basis, and the
 * author has no obligation to provide maintenance, support, updates,
 * enhancements, or modifications.
 */

/* 
 * =========================================================================
 *
 * $Log: VMSaudio.c,v $
 * Revision 1.2  2002/03/27  19:10:23  mvb
 * The XBoing distribution requires configuration management. This is why the
 * cvs utility is being used. This is the initial import of all source etc..
 *
 *
 * =========================================================================
 */

/* This is the audio support for VMS systems using MMOV services
 *
 * Build instructions
 *
 * DEFINE MME [],SYS$LIBRARY,MMOV$INCLUDE
 * CC/DEFINE=IPC_VMS/INCLUDE=MME VMSAUDIO
 *
 * It is necessary to build the example applications included in MMOV$EXAMPLES.
 * The following files then need to be included in the .OPT file when linking:
 *	mmov$examples:[video]readavi.obj
 *	mmov$examples:[common]commonlib/lib
 *	sys$share:mmov.olb/lib
 *	sys$share:cma$open_rtl.exe/share
 *
 * Define the logical XBOING_SOUND_DIR to point to the location of the sound
 * files.  If this logical is not defined, then the logical XBOING24_DIR is
 * used.
 *
 */

/*
 *  Include file dependencies:
 */

#include <limits.h>
#include <stat.h>
#include <stdlib.h>
#include <string.h>
#include <mme/mme_api.h>
#include <mmov$examples:[common]mmc_wave_file.h>

#include "include/error.h"
#include "include/audio.h"

/*
 *  Internal macro definitions:
 */
#ifndef needFunctionPrototypes
#define NeedFunctionPrototypes 1
#endif
#ifndef SOUNDS_DIR
#define SOUNDS_DIR "XBOING24_DIR"
#endif
/*
 *  Internal type declarations:
 */
typedef struct _sounds {
	char		*soundName;
	LPWAVEHDR	 waveHdr;
	LPSTR		 localBuf;
	DWORD		 localBufSize;
	struct _sounds	*next;
} soundBufs;

/*
 *  Internal variable declarations:
 */
static struct _channel {
    HWAVEOUT hWaveOut;		// Output channel handles
    UINT busy;		 	// Output channel busy flags
    LPSTR lpData;		// Output channel buffer
    DWORD lpDataSize;		// Size of output buffer
} channel[4];

static UINT deviceID = 65535;	// Assume no system has >65000 audio interfaces
static int maxVolume = 100;	// Default to allow full volume
static LPDWORD curVolume = NULL;// Current volume setting saved here
static LPWAVEFORMATEX lpFormat;	// Describes format of all sounds to be played
static soundBufs *soundList;	// Each sound played is kept in memory here

#if NeedFunctionPrototypes
static void audioCallback(HWAVEOUT hwaveout, UINT umsg, DWORD dwinstance,
			LPARAM lparam1, LPARAM lparam2)
#else
static void audioCallback(hwaveout, umsg, dwinstance, lparam1, lparam2)
	HWAVEOUT hwaveout;
	UINT umsg;
	DWORD dwinstance;
	LPARAM lparam1, lparam2;
#endif
/*
 * This is the callback routine invoked by the driver when it has finished
 * with a buffer.  Since we have no need of buffer maintenance at this point,
 * the callback simply indicates that a channel is now free for use.
 */
{
    switch (umsg)
    {
	case WOM_DONE:
		channel[dwinstance].busy = False;
		break;

	case WOM_OPEN:
	case WOM_CLOSE:
		break;
    }
	return;
}

#if NeedFunctionPrototypes
void setNewVolume(unsigned int Volume)
#else
void setNewVolume(Volume)
	unsigned int Volume;
#endif
/*
 * Adjust volume.
 *
 * Each sound specifies its own volume setting when it is played using a
 * scale from 1 to 100.  MMOV uses a scale from 0 to 65535 to control
 * volume (although not all devices support that level of granularity).
 * This routine scales the 1-100 range used by xboing through the 0-65535
 * range used by MMOV.
 */
{
    DWORD left, newVolume;

    left = ((Volume < maxVolume ? Volume : maxVolume) * 655);
    newVolume = (left << 16) | left;		// Set left and right equally
    waveOutSetVolume(deviceID, newVolume);
}

#if NeedFunctionPrototypes
int SetUpAudioSystem(Display *display)
#else
int SetUpAudioSystem(display)
	Display *display;
#endif
/*
 * Setup and open a connection to the output audio device.
 *
 * Since each connection must specify the format of the audio to be used
 * on the connection and, since all of the XBOING sounds are of the same
 * format, we simply open one connection for that format and use it for
 * all sounds rather than testing each individual sound for its format.
 *
 */
{
    int i;
    MMRESULT status;
    DWORD dwFlags = CALLBACK_FUNCTION | WAVE_OPEN_SHAREABLE;

    if (channel[0].hWaveOut) return True;	// Already open
    curVolume = mmeAllocMem(sizeof(DWORD));	// Volume must be in shared mem
    lpFormat = mmeAllocMem(sizeof(WAVEFORMAT));	//
    if ( !curVolume || !lpFormat) {
	if (curVolume) mmeFreeMem(curVolume);
	if (lpFormat) mmeFreeMem(lpFormat);
	return False;				// Couldn't get memory
    }

/*  Specify the format of the sound files we will be playing */
    lpFormat->wFormatTag = WAVE_FORMAT_MULAW;
    lpFormat->nChannels = 1;
    lpFormat->nSamplesPerSec = 8000;
    lpFormat->wBitsPerSample = 8;
    
/*  Open any output audio device that supports this format */
    status = waveOutOpen(&channel[0].hWaveOut, WAVE_MAPPER,
			(LPWAVEFORMAT)lpFormat, audioCallback,
			 (DWORD) 0, dwFlags);
    if (status == MMSYSERR_NOERROR) {		//Successfully opened a device
	waveOutGetID(channel[0].hWaveOut,&deviceID);	//Which device is this?
	waveOutGetVolume(deviceID, curVolume);	//Save current volume
	setNewVolume(maxVolume);		//Set initial volume
	channel[0].busy=0;			//Show not busy
	channel[0].lpDataSize=0;		//and bufferless
						// and open additional channels
	for (i=1; i<4; i++) {
	    waveOutOpen(&channel[i].hWaveOut, deviceID,
			(LPWAVEFORMAT)lpFormat, audioCallback,
			(DWORD) i, dwFlags);
	    channel[i].busy=0;
	    channel[i].lpDataSize=0;
	}
	return True;
    }
    mmeFreeMem(lpFormat); lpFormat = 0;		//No appropriate audio device
    mmeFreeMem(curVolume); curVolume = NULL;	//Free memory and return
    return False;
}

#if NeedFunctionPrototypes
void FreeAudioSystem(void)
#else
void FreeAudioSystem()
#endif
/*
 * Cleanup and freeup.
 *
 * This entry is called to close any and all devices and perform any needed
 * cleanup functions.  Close the output device and free all memory we have
 * allocated.
 */
{
    int i;
    soundBufs *soundEntry;

/*  If an output device was opened, restore its volume setting and close it  */
    if (channel[0].hWaveOut) {
	waveOutSetVolume(deviceID, *curVolume);
	for (i=0; i<4; i++) {
	    waveOutReset(channel[i].hWaveOut);
	    waveOutClose(channel[i].hWaveOut); channel[i].hWaveOut = 0;
	    mmeFreeBuffer(channel[i].lpData); channel[i].lpData = NULL;
	    channel[i].busy = 0; channel[i].lpDataSize = 0;
	}
	mmeFreeMem(lpFormat); lpFormat = 0;
	mmeFreeMem(curVolume); curVolume = NULL;
    }

/*  Now walk the list of all the sounds played and free up the memory used  */
    while (soundList) {
	soundEntry = soundList;
	soundList = soundEntry->next;
	mmeFreeMem(soundEntry->waveHdr);
	free(soundEntry->soundName);
	free(soundEntry);
    }
}

#if NeedFunctionPrototypes
static void playOneSound( soundBufs *desc, int unit)
#else
static void playOneSound(*desc, unit)
    soundBufs *desc;
    int unit;
#endif
{   DWORD soundLength;

    soundLength = desc->waveHdr->dwBufferLength;
    if (soundLength > channel[unit].lpDataSize) {
	mmeFreeBuffer(channel[unit].lpData);	//buffer too small, increase
	channel[unit].lpData = mmeAllocBuffer(soundLength);
    }
    memcpy(channel[unit].lpData, desc->localBuf, soundLength);
    desc->waveHdr->lpData = channel[unit].lpData;
    waveOutWrite(channel[unit].hWaveOut, desc->waveHdr, sizeof(WAVEHDR));
    channel[unit].busy = True;
}

#if NeedFunctionPrototypes
void playSoundFile(char *filename, int volume)
#else
void playSoundFile(filename, volume)
	char *filename;
	int volume;
#endif
/*
 * The workhorse routine, where sounds actually get played.
 *
 * This routine maintains a link list of all sounds that have been played
 * previously.  If a sound is replayed, it is replayed from this list and
 * no file I/O is involved.
 *
 * If a sound is being played for the first time, the sound file is opened
 * and copied into a buffer in memory shared with MMOV.  This buffer is
 * then added to the linked list and played.
 *
 * In testing, it was determined that being able to play up to 4 simultaneous
 * sounds resulted in a fairly good play.  If 4 sounds are currently active,
 * the sound being requested is skipped.
 *
 * Volume is currently kept at a constant level rather than adjusted with
 * each sound.  The user can control this level with the -/+ keys.
 */
{
    MMRESULT status;
    char soundfile[PATH_MAX];	// Full soundfile path name built here
    char *str;
    struct stat sb;
    mmcWaveFileState_t FileState;
    soundBufs *soundEntry, *last;
    int unitSel;

    if (mmeCheckForCallbacks()) mmeProcessCallbacks(); //Flush callbacks
    for (unitSel=0; unitSel<4; unitSel++) {
	if (!channel[unitSel].busy) break;
    }
    if (channel[unitSel].busy) return;	// No channels free, skip this sound

/*
 *  If the sound has been played before the sound data can be found on the
 *  soundList.  Grab that entry, replay it, and exit.
 */
    soundEntry = soundList;	// Point to list of previously played sounds
    last = NULL;
    while (soundEntry) {
	if (!strcmp(soundEntry->soundName, filename)) {
	    playOneSound(soundEntry,unitSel);
	    return;
	}
	last = soundEntry;	// Will end up pointing to last entry on list
	soundEntry = soundEntry->next;
    }

/*  First time for this file, read it into a buffer */

    if ((str = getenv("XBOING_SOUND_DIR")) != NULL)
	sprintf(soundfile, "%s%s.au", str, filename);
    else            
	sprintf(soundfile, "%s:%s.au", SOUNDS_DIR, filename);
    sb.st_size = 0;			// assume no such file
    stat(soundfile, &sb);		// get (among other things) file size
    if (!sb.st_size) return;		// 0-length (or no such file) return

/*  Call the mmc support routines to open the file.  These support routines
 *  will make an effort to determine the format of the data within the file.
 *  If the format is Sun Audio (or RAW) then a File Pointer will be associated
 *  with the file and any header data will have been skipped.
 */
    if (mmcWaveInFileOpen(soundfile, lpFormat, &FileState)) return;
    if (!FileState.fp) {		// Not the correct sound format
	mmcWaveInFileClose(&FileState);
	return;
    }
    soundEntry = malloc(sizeof(soundBufs));
    soundEntry->localBuf = malloc(sb.st_size);		//Note: not shared mem
    soundEntry->localBufSize = (DWORD)sb.st_size;	//Probable size of data
    soundEntry->soundName = strdup(filename);
    soundEntry->waveHdr = mmeAllocMem(sizeof(WAVEHDR));

    status = fread(soundEntry->localBuf, 1, sb.st_size, FileState.fp);
    mmcWaveInFileClose(&FileState);

    if (status > 0) {			// There was some actual sound data
	soundEntry->waveHdr->dwBufferLength = status;	//Actual size of data
	playOneSound(soundEntry,unitSel);		//Go play it
	soundEntry->next = 0;		// insert at end of list of sounds
	if (last) {
	    last->next = soundEntry;
	} else {
	    soundList = soundEntry;
	}
    } else {				// No sounds to play, garbage collect
	mmeFreeMem(soundEntry->waveHdr);
	free(soundEntry->soundName);
	free(soundEntry->localBuf);
	free(soundEntry);
    }
}

#if NeedFunctionPrototypes
void audioDeviceEvents(void)
#else
void audioDeviceEvents()
#endif
{
    /* Function not used by this program */
}

#if NeedFunctionPrototypes
void SetMaximumVolume(int Volume)
#else
void SetMaximumVolume(Volume)
    int Volume;
#endif
/*
 * The user is allowed to specify his/her desired maximum volume.  The main
 * program already sanity checks this value to we'll accept whatever is sent
 * us.  If zero is passed then the main program wants it set to the system
 * default, which is full volume (100).
 */
{
    maxVolume = Volume ? Volume : 100;
    setNewVolume(maxVolume);
}

#if NeedFunctionPrototypes
int GetMaximumVolume(void)
#else
int GetMaximumVolume()
#endif
{
    return maxVolume;	// Return whatever max volume has been set to.
}

