/*
THE COMPUTER CODE CONTAINED HEREIN IS THE SOLE PROPERTY OF PARALLAX
SOFTWARE CORPORATION ("PARALLAX").  PARALLAX, IN DISTRIBUTING THE CODE TO
END-USERS, AND SUBJECT TO ALL OF THE TERMS AND CONDITIONS HEREIN, GRANTS A
ROYALTY-FREE, PERPETUAL LICENSE TO SUCH END-USERS FOR USE BY SUCH END-USERS
IN USING, DISPLAYING,  AND CREATING DERIVATIVE WORKS THEREOF, SO LONG AS
SUCH USE, DISPLAY OR CREATION IS FOR NON-COMMERCIAL, ROYALTY OR REVENUE
FREE PURPOSES.  IN NO EVENT SHALL THE END-USER USE THE COMPUTER CODE
CONTAINED HEREIN FOR REVENUE-BEARING PURPOSES.  THE END-USER UNDERSTANDS
AND AGREES TO THE TERMS HEREIN AND ACCEPTS THE SAME BY USE OF THIS FILE.  
COPYRIGHT 1993-1999 PARALLAX SOFTWARE CORPORATION.  ALL RIGHTS RESERVED.
*/


#pragma off (unreferenced)
static char rcsid[] = "$Id: windigi.c 1.26 1996/09/24 23:26:12 samir Exp $";
#pragma on (unreferenced)

#include "desw.h"
#include "win\ds.h"
#include <mmsystem.h>
#include <mmreg.h>

#include<stdlib.h>
#include<stdio.h>
#include<dos.h>
#include<fcntl.h> 
#include<malloc.h> 
#include<bios.h>
#include<io.h>
#include<string.h>
#include<ctype.h>

#include "fix.h"
#include "object.h"
#include "mono.h"
#include "timer.h"
#include "joy.h"
#include "digi.h"
#include "sounds.h"
#include "args.h"
#include "key.h"
#include "newdemo.h"
#include "game.h"
#include "error.h"
#include "wall.h"
#include "cfile.h"
#include "piggy.h"
#include "text.h"

#pragma pack (4);						// Use 32-bit packing!
#pragma off (check_stack);			// No stack checking!

#include "win\winmidi.h"
#include "config.h"

#define MAX_CHANNELS 32

typedef struct digi_channel {
	ubyte		used;				// Non-zero if a sound is playing on this channel 
	int		soundnum;		// Which sound effect is on this channel, -1 if none
	WORD		handle;			// What SS handle this is playing on
	int		soundobj;		// Which soundobject is on this channel
	int 		persistant;		// This can't be pre-empted
	int		volume;			// the volume of this channel
} digi_channel;


//	Defines
//	----------------------------------------------------------------------------

#define DIGI_PAUSE_BROKEN 1		//if this is defined, don't pause MIDI songs
#define _DIGI_SAMPLE_FLAGS (0)
#define _DIGI_MAX_VOLUME (16384)
#define MAX_SOUND_OBJECTS 22



//	Variables
//	----------------------------------------------------------------------------

int	Digi_initialized 				= 0;
static int	digi_atexit_called	= 0;						// Set to 1 if atexit(digi_close) was called

int digi_sample_rate					= SAMPLE_RATE_22K;	// rate to use driver at
int digi_max_channels		= 8;

int digi_total_locks = 0;

static int digi_volume				= _DIGI_MAX_VOLUME;	// Max volume
static int digi_system_initialized		= 0;
static int digi_sound_locks[MAX_SOUNDS];
BOOL DIGIDriverInit = FALSE;


// MIDI stuff
//	----------------------------------------------------------------------------
WMIDISONG WMidiSong;
int		WMidiHandle=0;
ubyte		*SongData=NULL;
HGLOBAL	hSongData=NULL;
int		SongSize;

char digi_last_midi_song[16] = "";

static BOOL 							MIDIDriverInit	= FALSE;

static int midi_system_initialized	= 0;
static int midi_volume					= 128/2;	// Max volume

extern int Redbook_playing;


/* Obsolete */
WORD		hSOSDigiDriver = 0xffff;				// handle for the SOS driver being used 
WORD		hSOSMidiDriver = 0xffff;			// handle for the loaded MIDI driver
WORD		hTimerEventHandle = 0xffff;			// handle for the timer function
int digi_driver_dma											= 0;
int digi_driver_board										= 0;
int digi_driver_port             						= 0;
int digi_driver_irq              						= 0;
int digi_midi_type               						= 0;
int digi_midi_port											= 0;
LPSTR digi_driver_path = NULL;
/* Not Obsolete */


//	Sound Handle stuff
//	----------------------------------------------------------------------------
digi_channel channels[MAX_CHANNELS];
int next_channel = 0;
int channels_inited = 0;


//	Functions
//	----------------------------------------------------------------------------
int verify_sound_channel_free(int channel);

void * digi_load_file( char * szFileName, HGLOBAL *hmem, int * length );



//	FUNCTIONS!!!
//		DIGI SYSTEM
//			Initialization
//			Play
//			Sound System
//
//		MIDI SYSTEM
//	----------------------------------------------------------------------------

int digi_init(void)
{
	int i;

	Digi_initialized = 1;

//	Perform MIDI Detection

// Initialize MIDI driver
	if (! FindArg( "-nomusic" )) {
		midi_system_initialized = 1;
		if (digi_init_midi()) {
			mprintf((1, "Couldn't initialize MIDI system.\n"));
			midi_system_initialized = 0;
			return 1;
		}
	}

// Initialize DIGI driver
	if (! FindArg( "-nosound" )) {
		digi_system_initialized = 1;
		if (digi_init_digi()) {
			mprintf((1, "Couldn't initialize digital sound system.\n"));
			digi_close();
	  		return 2;
		}
	}
		
	if (!digi_atexit_called) {
		atexit(digi_close);
		digi_atexit_called = 1;
	}

//	Miscellaneous Initialization
	digi_init_sounds();

	for (i = 0; i < MAX_SOUNDS; i++)
		digi_sound_locks[i] = 0;

	digi_stop_all_channels();

	return 0; 
}


void digi_close(void)
{

	if (!Digi_initialized) return;
	Digi_initialized = 0;

	digi_close_midi();
	digi_close_digi();

	digi_system_initialized = 0;
	midi_system_initialized	= 0;
}


int digi_init_digi(void)
{
	SSCaps sscaps;

	Assert(digi_sample_rate == SAMPLE_RATE_11K || digi_sample_rate == SAMPLE_RATE_22K);

	if (!digi_system_initialized) 
		return 1;

//	Determine board type?
	digi_driver_board = 1;

//	Initialize Sound System
	if (!SSInit(_hAppWnd, 32, DSSCL_NORMAL)) {
		DIGIDriverInit = FALSE;
		return 1;
	}

	SSGetCaps(&sscaps);
	if (sscaps.sample_rate < SAMPLE_RATE_22K) 
		digi_sample_rate = SAMPLE_RATE_11K;

//	logentry("Detected sound card using (%d Hz).  Using (%d Hz) samples.\n", sscaps.sample_rate, digi_sample_rate);

//	Crank up the WAV volume for Wave-Table and full range of FX volumes
	DIGIDriverInit = TRUE;
	return 0;
}


void digi_close_digi()
{
   if (DIGIDriverInit)   {
		SSDestroy();	 						// resets WAV vol to SSInit value.
		DIGIDriverInit = FALSE;
	}
}


int digi_init_midi(void)
{
	DWORD res;

//	check to see if MIDI is going to be used.
	if (!midi_system_initialized) 
		return 1;

//	Initialize MIDI system and driver
	res = wmidi_init(MIDI_MAPPER);

	if (!res) {
		MIDIDriverInit = FALSE;
		return 1;
	}
	else {
//@@		switch(sMIDICaps.wTechnology) 
//@@		{
//@@			case MOD_SYNTH:
//@@				mprintf((0, "Using SB/SYNTH for MIDI.\n"));	break;
//@@
//@@			case MOD_FMSYNTH:
//@@				mprintf((0, "Using ADLIB FM for MIDI.\n")); break;
//@@			
//@@			case MOD_MAPPER:
//@@				mprintf((0, "Using MIDI mapping.\n")); break;
//@@
//@@			case MOD_MIDIPORT:
//@@				mprintf((0, "Using SB/External MIDI port.\n")); break;
//@@
//@@			case MOD_SQSYNTH:
//@@				mprintf((0, "Using Wave Synthesis for MIDI.\n")); break;
//@@
//@@			default:
//@@				mprintf((0, "Using another method (%d) for MIDI.\n", sMIDICaps.wMID)); break;
//@@		}

		digi_midi_type = 1;
	
		MIDIDriverInit = TRUE;
	}

	digi_set_midi_volume(Config_midi_volume*16);

	return 0;
}		


void digi_close_midi()
{
	if (!midi_system_initialized) return;

	if (MIDIDriverInit)   {
      if (WMidiHandle)  {
         // stop the last MIDI song from playing
			wmidi_stop();
			wmidi_close_song();
         WMidiHandle = 0;
      }
      if (SongData)  {
			GlobalUnlock(SongData);
			GlobalFree(hSongData);
         SongData = NULL;
			hSongData = NULL;
      }

		wmidi_close();
		MIDIDriverInit = FALSE;
   }
}


void digi_reset()	
{
	if ( Digi_initialized )	{
		digi_stop_all_channels();
		digi_close();
		mprintf( (0, "Sound system DISABLED.\n" ));
	} else {
		digi_init();
		mprintf( (0, "Sound system ENABLED.\n" ));
	}
}


//	Channel Functions
//	----------------------------------------------------------------------------

void digi_stop_all_channels()
{
	int i;

	for (i=0; i<MAX_CHANNELS; i++ )
		digi_stop_sound(i);

	for (i=0; i<MAX_SOUNDS; i++ )	{
		Assert( digi_sound_locks[i] == 0 );
	}
}

void digi_set_max_channels(int n)
{
	digi_max_channels	= n;

	if ( digi_max_channels < 1 ) 
		digi_max_channels = 1;
	if ( digi_max_channels > 32 ) 
		digi_max_channels = 32;

	if ( !Digi_initialized ) return;
	if ( !DIGIDriverInit )	return;

	digi_stop_all_channels();
}

int digi_get_max_channels()
{
	return digi_max_channels;
}

int digi_is_channel_playing( int c )
{
	if (!Digi_initialized) return 0;
	if (!DIGIDriverInit) return 0;

	if ( channels[c].used && SSChannelPlaying((int)channels[c].handle)) 
		return 1;
	return 0;
}

void digi_set_channel_volume( int c, int volume )
{
	if (!Digi_initialized) return;
	if (!DIGIDriverInit) return;

	if ( !channels[c].used ) return;

	SSSetChannelVolume(channels[c].handle, (unsigned short)fixmuldiv(volume,digi_volume,F1_0));
}
	
void digi_set_channel_pan( int c, int pan )
{
	if (!Digi_initialized) return;
	if (!DIGIDriverInit) return;

	if ( !channels[c].used ) return;

	SSSetChannelPan(channels[c].handle, (unsigned short)pan);
}

void digi_stop_sound( int c )
{
	if (!Digi_initialized) return;
	if (!DIGIDriverInit) return;

	if (!channels[c].used) return;

	if ( digi_is_channel_playing(c)  )		{
		SSStopChannel(channels[c].handle);
	}
	channels[c].used = 0;

	channels[c].soundobj = -1;
	channels[c].persistant = 0;
}

void digi_end_sound( int c )
{
	if (!Digi_initialized) return;
	if (!DIGIDriverInit) return;

	if (!channels[c].used) return;

	channels[c].soundobj = -1;
	channels[c].persistant = 0;
}


// Returns the channel a sound number is playing on, or
// -1 if none.
int digi_find_channel(int soundno)
{
	int i, is_playing;

	if (!Digi_initialized) return -1;
	if (!DIGIDriverInit) return -1;

	if (soundno < 0 ) return -1;
	if (GameSounds[soundno].data==NULL) {
		Int3();
		return -1;
	}

	is_playing = 0;
	for (i=0; i<digi_max_channels; i++ )	{
		if ( channels[i].used && (channels[i].soundnum==soundno) )
			if ( digi_is_channel_playing(i) )
				return i;
	}	
	return -1;
}

extern void digi_end_soundobj(int channel);	
extern int SoundQ_channel;
extern void SoundQ_end();


//	DIGI Start Sample function
//	----------------------------------------------------------------------------
int digi_start_sound(short soundnum, fix volume, int pan, int looping, int loop_start, int loop_end, int soundobj)
{
	int i, starting_channel;
	int sHandle;
	SSoundBuffer ssb;

	if ( !Digi_initialized ) return -1;
	if ( !DIGIDriverInit )	return -1;

	Assert(GameSounds[soundnum].data != -1);

	memset(&ssb, 0, sizeof(SSoundBuffer));
	
	ssb.data = (char *)GameSounds[soundnum].data;
	ssb.length = GameSounds[soundnum].length;
	ssb.pan = (unsigned short)pan+1;
//	ssb.id = soundnum;
	ssb.vol = (unsigned short)fixmuldiv(volume, digi_volume, F1_0);
	ssb.vol = (ssb.vol << 2);
	ssb.rate = digi_sample_rate;
	ssb.channels = 1;
	ssb.bits_per_sample = 8;

	if (looping) {
		ssb.looping = 1;
		ssb.loop_start = ssb.loop_end = -1;
	}	
	if (loop_start != -1) {
		Assert( loop_end != -1);
		ssb.loop_start = loop_start;
		ssb.loop_end = loop_end;
		ssb.loop_length = loop_end - loop_start;
	}		

	starting_channel = next_channel;

	while(1)	{
		if ( !channels[next_channel].used ) break;
		
		if (!SSChannelPlaying((int)channels[next_channel].handle)) break;

		if ( !channels[next_channel].persistant )	{
			SSStopChannel(channels[next_channel].handle);
			break;	// use this channel!	
		}
		next_channel++;
		if ( next_channel >= digi_max_channels )
			next_channel = 0;
		if ( next_channel == starting_channel ) {
			mprintf(( 1, "OUT OF SOUND CHANNELS!!!\n" ));
			return -1;
		}
	}
	if ( channels[next_channel].used )	{
		channels[next_channel].used = 0;
		if ( channels[next_channel].soundobj > -1 )	{
			digi_end_soundobj(channels[next_channel].soundobj);	
		}
		if (SoundQ_channel==next_channel)
			SoundQ_end();
	}

	sHandle = SSInitChannel(&ssb);
	if (sHandle == -1) {
		mprintf(( 1, "NOT ENOUGH SOUND SLOTS!!!\n" ));
		return -1;
	}

	#ifndef NDEBUG
	verify_sound_channel_free(next_channel);
	#endif

	//free up any sound objects that were using this handle
	for (i=0; i<digi_max_channels; i++ )	{
		if ( channels[i].used && (channels[i].handle == sHandle)  )	{
			channels[i].used = 0;
			if ( channels[i].soundobj > -1 )	{
				digi_end_soundobj(channels[i].soundobj);	
			}
			if (SoundQ_channel==i)
				SoundQ_end();
		}
	}

	channels[next_channel].used = 1;
	channels[next_channel].soundnum = soundnum;
	channels[next_channel].soundobj = soundobj;
	channels[next_channel].handle = sHandle;
	channels[next_channel].volume = volume;
	channels[next_channel].persistant = 0;
	if ( (soundobj > -1) || (looping) || (volume>F1_0) )
		channels[next_channel].persistant = 1;

	i = next_channel;
	next_channel++;
	if ( next_channel >= digi_max_channels )
		next_channel = 0;

	return i;
}


//	Volume Functions
//	----------------------------------------------------------------------------
void digi_set_midi_volume( int mvolume )
{

	int old_volume = midi_volume;

	if ( mvolume > 127 )
		midi_volume = 127;
	else if ( mvolume < 0 )
		midi_volume = 0;
	else
		midi_volume = mvolume;

	if ( (MIDIDriverInit) )		{
		if (!Redbook_playing && (old_volume < 1) && ( midi_volume > 1 ) )	{
			if (WMidiHandle == 0 )
				digi_play_midi_song( digi_last_midi_song, NULL, NULL, 1 );
		}
		wmidi_set_volume(midi_volume);
	}

	mprintf((0, "Changing midi volume=%d.\n", midi_volume));
}

void digi_set_digi_volume( int dvolume )
{
	dvolume = fixmuldiv( dvolume, _DIGI_MAX_VOLUME, 0x7fff);
	if ( dvolume > _DIGI_MAX_VOLUME )
		digi_volume = _DIGI_MAX_VOLUME;
	else if ( dvolume < 0 )
		digi_volume = 0;
	else
		digi_volume = dvolume;

	if ( !Digi_initialized ) return;
	if ( !digi_system_initialized )	return;

	//digi_sync_sounds();
}


// 0-0x7FFF 
void digi_set_volume( int dvolume, int mvolume )	
{
	digi_set_midi_volume( mvolume );
	digi_set_digi_volume( dvolume );
	mprintf(( 1, "Volume: 0x%x and 0x%x\n", digi_volume, midi_volume ));
}

// allocate memory for file, load fil
void * digi_load_file( char * szFileName, HGLOBAL *hmem, int * length )
{
   PSTR  pDataPtr;
	CFILE * fp;
	
   // open file
   fp  =  cfopen( szFileName, "rb" );
	if ( !fp ) return NULL;

   *length  =  cfilelength(fp);

	*hmem = GlobalAlloc(GPTR, *length);
	if (!(*hmem)) return NULL;
	pDataPtr = GlobalLock(*hmem);

   // read in driver
   cfread( pDataPtr, *length, 1, fp);

   // close driver file
   cfclose( fp );

   // return 
   return( pDataPtr );
}


void digi_stop_current_song()
{
	if (!Digi_initialized) return;

	if ( MIDIDriverInit )	{
		// Stop last song...
		if (WMidiHandle > 0 )	{
		   // stop the last MIDI song from playing
			wmidi_stop();
			wmidi_close_song();
			WMidiHandle = 0;
		}
		if (SongData) 	{
			GlobalUnlock(SongData);
			GlobalFree(hSongData);
			SongData = NULL;
			hSongData = NULL;
		}
	}
}


void digi_play_midi_song( char * filename, char * melodic_bank, char * drum_bank, int loop )
{
	char fname[128];
	CFILE		*fp;
	int sl;

	if (!Digi_initialized) return;
	if ( !MIDIDriverInit )	return;

	digi_stop_current_song();

	if ( filename == NULL )	return;

	strcpy( digi_last_midi_song, filename );

	fp = NULL;

	sl = strlen( filename );
	strcpy( fname, filename );	
	fname[sl-3] = 'm';
	fname[sl-2] = 'i';
	fname[sl-1] = 'd';

	fp = cfopen( fname, "rb" );
	mprintf((0, "Loading %s\n", fname));

	if ( !fp  )	{
	 	mprintf( (1, "Error opening midi file, '%s'", filename ));
	 	return;
	}
	if ( midi_volume < 1 )		{
		cfclose(fp);
		return;				// Don't play song if volume == 0;
	}
	SongSize = cfilelength( fp );
	hSongData = GlobalAlloc(GPTR, SongSize);
	if (hSongData==NULL)	{
		cfclose(fp);
		mprintf( (1, "Error allocating %d bytes for '%s'", SongSize, filename ));
		return;
	}
	SongData = GlobalLock(hSongData);
	if ( cfread (  SongData, SongSize, 1, fp )!=1)	{
		mprintf( (1, "Error reading midi file, '%s'", filename ));
		cfclose(fp);
		GlobalUnlock(SongData);
		GlobalFree(hSongData);
		SongData=NULL;
		hSongData = NULL;
		return;
	}

	cfclose(fp);

// setup the song initialization structure
	WMidiSong.data = SongData;
	WMidiSong.length = SongSize;

	if ( loop )
		WMidiSong.looping = 1;
	else
		WMidiSong.looping = 0;

//	initialize the song
	WMidiHandle = wmidi_init_song(&WMidiSong);
	if (!WMidiHandle) {
		mprintf((1, "Unable to initialize MIDI song.\n"));		
		GlobalUnlock(SongData);
		GlobalFree(hSongData);
		SongData=NULL;
		hSongData = NULL;
		return;
	}

	Assert( WMidiHandle == 1 );
	 
// start the song playing
	mprintf((0, "Playing song %x.\n", WMidiHandle));

	if (!wmidi_play()) {
		mprintf( (1, "\nUnable to play midi song.\n"));
		wmidi_close_song();
		GlobalUnlock(SongData);
		GlobalFree(hSongData);
		SongData=NULL;
		hSongData = NULL;
		return;
   }
}


int sound_paused = 0;

void digi_pause_midi()
{
	if (!Digi_initialized) return;

	if (sound_paused==0)	{
		if ( digi_midi_type > 0 && WMidiHandle > 0)	{
			// pause here
				wmidi_pause();
		}
	}
	sound_paused++;
}



void digi_resume_midi()
{
	if (!Digi_initialized) return;

	Assert( sound_paused > 0 );

	if (sound_paused==1)	{
		// resume sound here
		if ( digi_midi_type > 0 && WMidiHandle > 0)	{
			wmidi_resume();
		}
	}
	sound_paused--;
}


#ifndef NDEBUG
void digi_debug()
{
	int i;
	int n_voices=0;

	if (!Digi_initialized) return;
	if (!DIGIDriverInit) return;

	for (i=0; i<digi_max_channels; i++ )	{
		if ( digi_is_channel_playing(i) )
			n_voices++;
	}

	mprintf_at(( 0, 2, 0, "DIGI: Active Sound Channels: %d/%d ", n_voices, digi_max_channels));
}


void digi_midi_debug()
{

}
	
#endif


extern BOOL WMidi_NewStream;

void digi_midi_wait()
{
	fix ftime;
	int tech;

	tech = wmidi_get_tech();
	if (tech) return;
	
  	ftime = timer_get_fixed_seconds() + 0x50000;
	WMidi_NewStream = FALSE;
	while ((WMidi_NewStream < 2 && WMidiHandle) || (timer_get_fixed_seconds() < ftime)) Sleep(0);
}


