/*****************************************************************************/
/* MODSND.C 							Stereo Digital Mixing System											 */
/*												Written by John W. Ratclif, Copyright (c) 1992,1995*/
/*																																					 */
/* Mixes up to 4 sounds at once, into output digital audio stream.					 */
/*****************************************************************************/
#include "modsnd.h" // Common C header for MODSND code.
#include "mod.h"    // Assembly prototypes for mixer code.
#include "digplay.h"    // Include DIGPLAY header.
#include "doscalls.h"

// Application provided memory allocation functions.
unsigned char far * far memalloc(long int siz);
void far memfree(char far *memory);

void ServiceSound(void); // Application callback, for servicing sound!

#define BASEFREQ 11000	 // Modify this for your base-line playback frequency

/******* Private Data region used by the MIXER	*******/
static short far *InMixer;
static char far *soundbase;
static short Active[PHYSICAL] = { 0,0,0,0 };
static short HaveDigPak=0;
static short ActiveCount=0; // Current number of channels active.
static MODSPEC MOD[PHYSICAL]; // Array of all logical channel spec's.
static char far *buildbuf1;  // Output buffer BUFFSIZE*CHANNELS
static char far *buildbuf2;  // Output buffer BUFFSIZE*CHANNELS
static char far *buff1; 		 // Double buffered built sound buffer.
static char far *buff2; 		 // Second of double buffered sound.
static char far *buffat;		 // Which of the double buffers currently at.
static short alive=0,build;
static SNDSTRUC snd;
static short far *PendingFlag;
static unsigned short dmasize;
static unsigned short DMACOUNTL,DMACOUNTH,DMALOAD;
static unsigned short BSIZE=BUFFSIZE; // Buffer build size.
static short AUTOINITDMA=0; // True if driver supports autoinit DMA
static short far *InDigPak;

// Returns the number of bytes needed by the mixer code for work buffers.
short ModSizeNeeded(void)
{
	short count;
	short size;

	size = STREAMSIZE*2; // Room for build buffers 8 bit stereo * #channels
	dmasize = BUFFSIZE*2;  // Room for double buffered audot.
	if (dmasize < MINIMUMDMA )
	{
		// compute the closest DMA size that is an integer multiple of the
		// buffsize to be used.
		count = ((MINIMUMDMA-1)/dmasize)+1; // Found out how many fit.
		dmasize = count*dmasize;
	}
	return(size+dmasize); // Total memory needed ...
}

short StartMod(short *InMix)
{
	short i,j,cap,null,size;
	short *temp;
	char far *sb;

	InMixer = InMix; // assign mixer callback semaphore.

	alive = 0;
	if ( !CheckIn() ) return(-1); // return -1 if sound driver not available.

	size = ModSizeNeeded(); // How much memory required?
	soundbase = memalloc(size);
	if ( soundbase )
	{
		if ( !VerifyDMA(soundbase,size) ) // if crossed 64k boundary.
		{
			sb = memalloc(size);
			memfree(soundbase);
			soundbase = sb;
		}
	}

	if ( !soundbase ) return(-2); // insufficient memory for work space.

	InDigPak = ReportSemaphoreAddress();

	cap = AudioCapabilities();

	BSIZE = BUFFSIZE;

	if ( cap&DMABACKFILL && !InWindows() )
		AUTOINITDMA = 1;
	else
    AUTOINITDMA = 0;

	PendingFlag = PendingAddress(); // Report pending address.
	ResetMod();
	HaveDigPak = 1;
	StartDMA();
	HaveDigPak = alive;

	return(alive); // Mod player installed.
}

// ** WARNING!!!!*** This routine is invoked in a hardware interrupt!!!!!!!
// ** DON'T DO ANYTHING THAT WILL CAUSE THE MACHINE TO CRASH!! Don't
// ** MOVE ANY SOUND EFFECTS AROUND IN MEMORY THAT YOU ARE MIXING
// ** DON'T CALL ANY DOS FUNCTIONS, OR NUTHIN.  HARDWARE INTERRUPT CALLBACKS
// ** A DANGEROUS CRITTERS!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// ** If you are writing you own interrupt handler, rather than using the
// ** one provided, you better be sure to save and restore all registers,
// ** set up a stack frame, and establish data segment addressability!!!!!
void PollMod(void)
{
	short nextframe=0,count;
	short old,new;
	short a1,j,i,map;

	if ( alive && !*InDigPak && !*InMixer)
	{
		if ( AUTOINITDMA )
		{
			count = dmasize-ReportDMAC();
			if ( count >= DMACOUNTL && count < DMACOUNTH )
			{
				nextframe = 1;
				buffat = buff1+DMALOAD;
				DMALOAD+=BSIZE;
				if ( DMALOAD == dmasize ) DMALOAD = 0;
				DMACOUNTL+=BSIZE;
				if ( DMACOUNTL == dmasize ) DMACOUNTL = 0;
				DMACOUNTH=DMACOUNTL+BSIZE;
			}
		}
		else
		{
			if ( !*PendingFlag ) nextframe = 1;
		}

		if ( nextframe )
		{

			ServiceSound();  // Call application's ServiceSound routine.

			for (j=0,i=0; j<PHYSICAL; j++)
			{
				a1 = Active[j]; // If this physical channel is active.
				if ( a1 )
				{
					a1--; 	// Less one to get actual channel number
					if ( MOD[a1].trigger == 0 )
					{
						Active[j] = 0;
						ActiveCount--;	// one less active channel.
					}
					else
					{
						MOD[a1].buff = buildbuf1+i*2;
						BuildModStream(&MOD[a1]);
						i++;
					}
				}
			}

			snd.sound = buffat; // Destination.
			snd.sndlen = BSIZE; // EXACT length of output sound frame.
			snd.frequency = BASEFREQ;

			switch ( ActiveCount )
			{
				case 0:
					ModSilence(&MOD[0],buffat); // Merge into output buffer.
					break;
				case 1:
					MergeMod1(&MOD[0],buffat); // Merge into output buffer.
					break;
				case 2:
					MergeMod2(&MOD[0],buffat); // Merge into output buffer.
					break;
				case 3:
					MergeMod3(&MOD[0],buffat); // Merge into output buffer.
					break;
				case 4:
					MergeMod4(&MOD[0],buffat); // Merge into output buffer.
					break;
			}
			if ( !AUTOINITDMA ) // If not in auto-init dma mode.
			{
				if ( buffat == buff1 )
					buffat = buff2;
				else
					buffat = buff1;  // Flip/flop buffer.
				PostAudioPending(&snd);
			}
		}
	}
}

// Set active channel.
void SetActive(short channel)
{
	if ( !Active[channel] ) ActiveCount++; // Increase total active.
	Active[channel] = channel+1;	// Set physical channel to this logical channel.
}

void InActive(short channel)
{
	if ( Active[channel] ) ActiveCount--;
	Active[channel] = 0;
}

// Set this audio channel up.
short SetChannel(char *sound,unsigned short length,short channel,short mode)
{
	char *temp;

	MOD[channel].sound = sound;
	MOD[channel].sndloc = sound;
	MOD[channel].sndlen = length;
	MOD[channel].bufflen = BUFFSIZE;
	MOD[channel].oneshot = mode;
	MOD[channel].trigger = 1; 	// Sample is active, trigger it.

	return(channel);
}

void ChannelOn(short logical)
{
	MOD[logical].trigger = 1;
}

short ChannelState(short logical)
{
	return(MOD[logical].trigger);
}

short ActiveState(short logical)
{
	return(Active[logical]);
}

void ChannelOff(short logical)
{
	MOD[logical].trigger = 0;
}

void ChannelVolume(short logical,short volume)
{
	MOD[logical].volume = volume;
}

void ChannelFreq(short logical,short freq)
{
	MOD[logical].freq = freq;
}

void StopMod(void)
{
	*InMixer = 1;
	if ( HaveDigPak )
	{
		SetBackFillMode(0);
		SetPlayMode(PCM_8_MONO);	// Reset sound driver to mono PCM.
		StopSound();
		alive = 0;
		memfree(soundbase); // free the memory used.
	}
	*InMixer = 0;
}

void StartDMA(void)
{
	short null;

	*InMixer = 1;
	if ( HaveDigPak )
	{
		if ( !alive )
		{
			null = 0x80;
			SetPlayMode(PCM_8_MONO);

			if ( AUTOINITDMA ) // If auto init dma, then get tha sucker rolling...
			{
				if ( VerifyDMA(buff1,dmasize) )
				{
					DMACOUNTL = 0;		 // If greater than or equal to this low value
					DMACOUNTH = BSIZE;	// or less than this high value, then it's time
					DMALOAD = BSIZE; // Next buffer load location.
					// to load the next DMA buffer.
					snd.sound = buff1;
					snd.sndlen = dmasize;  // total length of DMA transfer buffer.
					snd.frequency = BASEFREQ;
					NullSound(buff1,snd.sndlen,null);
					SetBackFillMode(1);
					DigPlay(&snd);
				}
				else
				{
					AUTOINITDMA = 0;
				}
			}
		}
		alive = 1;
	}
	*InMixer = 0;
}


void SetChannelMode(short logical,short mode)
{
	MOD[logical].oneshot = mode;
}


short GetaliveCount()
{
	return(ActiveCount);
}

void StopDMA(void)
{
	*InMixer = 1;
	if ( HaveDigPak )
	{
		if ( alive ) // If currently alive.
		{
			SetBackFillMode(0); // back fill OFF
			SetPlayMode(PCM_8_MONO);	// Reset sound driver to mono PCM.
			StopSound(); // Turn audio off.
			alive = 0; // not alive.
		}
	}
	*InMixer = 0;
}

// cause mixer reset!!!!
void ResetMod(void)
{
	short i,j;
	short *temp;

	*InMixer = 1;
	buildbuf1 = soundbase;	// Build buffer where left 8 bit builds happens.
	buildbuf2 = soundbase+STREAMSIZE; // Second build stream.
	// holds 4 channels of built data for stereo.
	buff1 = buildbuf2+STREAMSIZE;
	buff2 = buff1+BSIZE;
	buffat = buff1;
	build = 1;
	for (i=0; i<PHYSICAL; i++)
	{
		MOD[i].buff = buildbuf1; // Left channel build buffer.
		MOD[i].volume = 0;
		MOD[i].bufflen = BUFFSIZE; // number of 8 bit bytes built each time.
		MOD[i].sound = 0;
		MOD[i].sndlen = 0;
		MOD[i].freq = 256;
		MOD[i].silence = 0;
		MOD[i].silout = 0;
		MOD[i].volume = 256;
		MOD[i].lastvol = 0;
		MOD[i].oneshot = 0;
		MOD[i].trigger = 0; 	// Sample is inactive.
		temp = MOD[i].voltab;
		for (j=0; j<256; j++) *temp++ = 0; // Initialize volume table.
	}
	for (i=0; i<PHYSICAL; i++) Active[i] = 0;
	ActiveCount = 0;
	*InMixer = 0;
}

