MCUME/MCUME_pico2/picomsx/Sound.c

906 wiersze
28 KiB
C

2025-02-18 19:49:00 +00:00
/** EMULib Emulation Library *********************************/
/** **/
/** Sound.c **/
/** **/
/** This file file implements core part of the sound API **/
/** and functions needed to log soundtrack into a MIDI **/
/** file. See Sound.h for declarations. **/
/** **/
/** Copyright (C) Marat Fayzullin 1996-2021 **/
/** You are not allowed to distribute this software **/
/** commercially. Please, notify me, if you make any **/
/** changes to this file. **/
/*************************************************************/
#include "Sound.h"
#include <stdio.h>
#include <string.h>
#if defined(UNIX) || defined(MAEMO) || defined(STMP3700) || defined(NXC2600) || defined(ANDROID)
#include <unistd.h>
#endif
#if defined(ANDROID)
/* On Android, may need to open files for writing at an */
/* alternative location, if the requested location is */
/* not available. OpenRealFile() WILL NOT USE ZLIB. */
#define fopen OpenRealFile
#endif
typedef unsigned char byte;
typedef unsigned short word;
struct SndDriverStruct SndDriver =
{
(void (*)(int,int))0,
(void (*)(int,int))0,
(void (*)(int,int))0,
(void (*)(int,int,int))0,
(void (*)(int,const signed char *,int,int))0,
(const signed char *(*)(int))0
};
static const struct { byte Note;word Wheel; } Freqs[4096] =
{
#include "MIDIFreq.h"
};
static const int Programs[] =
{
80, /* SND_MELODIC/SND_RECTANGLE */
80, /* SND_TRIANGLE */
122, /* SND_NOISE */
122, /* SND_PERIODIC */
80, /* SND_WAVE */
};
static struct
{
int Type;
int Note;
int Pitch;
int Level;
int Power;
} MidiCH[MIDI_CHANNELS] =
{
{ -1,-1,-1,-1,256 },
{ -1,-1,-1,-1,256 },
{ -1,-1,-1,-1,256 },
{ -1,-1,-1,-1,256 },
{ -1,-1,-1,-1,256 },
{ -1,-1,-1,-1,256 },
{ -1,-1,-1,-1,256 },
{ -1,-1,-1,-1,256 },
{ -1,-1,-1,-1,256 },
{ -1,-1,-1,-1,256 },
{ -1,-1,-1,-1,256 },
{ -1,-1,-1,-1,256 },
{ -1,-1,-1,-1,256 },
{ -1,-1,-1,-1,256 },
{ -1,-1,-1,-1,256 },
{ -1,-1,-1,-1,256 }
};
static struct
{
int Type; /* Channel type (SND_*) */
int Freq; /* Channel frequency (Hz) */
int Volume; /* Channel volume (0..255) */
const signed char *Data; /* Wave data (-128..127 each) */
int Length; /* Wave length in Data */
int Rate; /* Wave playback rate (or 0Hz) */
int Pos; /* Wave current position in Data */
int Count; /* Phase counter */
} WaveCH[SND_CHANNELS] =
{
{ SND_MELODIC,0,0,0,0,0,0,0 },
{ SND_MELODIC,0,0,0,0,0,0,0 },
{ SND_MELODIC,0,0,0,0,0,0,0 },
{ SND_MELODIC,0,0,0,0,0,0,0 },
{ SND_MELODIC,0,0,0,0,0,0,0 },
{ SND_MELODIC,0,0,0,0,0,0,0 },
{ SND_MELODIC,0,0,0,0,0,0,0 },
{ SND_MELODIC,0,0,0,0,0,0,0 },
{ SND_MELODIC,0,0,0,0,0,0,0 },
{ SND_MELODIC,0,0,0,0,0,0,0 },
{ SND_MELODIC,0,0,0,0,0,0,0 },
{ SND_MELODIC,0,0,0,0,0,0,0 },
{ SND_MELODIC,0,0,0,0,0,0,0 },
{ SND_MELODIC,0,0,0,0,0,0,0 },
{ SND_MELODIC,0,0,0,0,0,0,0 },
{ SND_MELODIC,0,0,0,0,0,0,0 }
};
/** RenderAudio() Variables *******************************************/
static int SndRate = 0; /* Sound rate (0=Off) */
static int NoiseGen = 0x10000; /* Noise generator seed */
static int NoiseOut = 16; /* NoiseGen bit used for output */
static int NoiseXor = 14; /* NoiseGen bit used for XORing */
int MasterSwitch = 0xFFFF; /* Switches to turn channels on/off */
int MasterVolume = 192; /* Master volume */
/** MIDI Logging Variables ********************************************/
static const char *LogName = 0; /* MIDI logging file name */
static int Logging = MIDI_OFF; /* MIDI logging state (MIDI_*) */
static int TickCount = 0; /* MIDI ticks since WriteDelta() */
static int LastMsg = -1; /* Last MIDI message */
static int DrumOn = 0; /* 1: MIDI drums are ON */
static FILE *MIDIOut = 0; /* MIDI logging file handle */
static void MIDISound(int Channel,int Freq,int Volume);
static void MIDISetSound(int Channel,int Type);
static void MIDIDrum(int Type,int Force);
static void MIDIMessage(byte D0,byte D1,byte D2);
static void NoteOn(byte Channel,byte Note,byte Level);
static void NoteOff(byte Channel);
static void WriteDelta(void);
static void WriteTempo(int Freq);
/** SHIFT() **************************************************/
/** Make MIDI channel#10 last, as it is normally used for **/
/** percussion instruments only and doesn't sound nice. **/
/*************************************************************/
#define SHIFT(Ch) (Ch==15? 9:Ch>8? Ch+1:Ch)
/** GetSndRate() *********************************************/
/** Get current sampling rate used for synthesis. **/
/*************************************************************/
unsigned int GetSndRate(void) { return(SndRate); }
/** Sound() **************************************************/
/** Generate sound of given frequency (Hz) and volume **/
/** (0..255) via given channel. Setting Freq=0 or Volume=0 **/
/** turns sound off. **/
/*************************************************************/
void Sound(int Channel,int Freq,int Volume)
{
/* All parameters have to be valid */
if((Channel<0)||(Channel>=SND_CHANNELS)) return;
Freq = Freq<0? 0:Freq;
Volume = Volume<0? 0:Volume>255? 255:Volume;
/* Modify channel parameters */
WaveCH[Channel].Volume = Volume;
WaveCH[Channel].Freq = Freq;
/* When disabling sound, reset waveform */
if(!Freq||!Volume)
{
WaveCH[Channel].Pos = 0;
WaveCH[Channel].Count = 0;
}
/* Call sound driver if present */
if(SndDriver.Sound) (*SndDriver.Sound)(Channel,Freq,Volume);
/* Log sound to MIDI file */
MIDISound(Channel,Freq,Volume);
}
/** Drum() ***************************************************/
/** Hit a drum of given type with given force (0..255). **/
/** MIDI drums can be used by ORing their numbers with **/
/** SND_MIDI. **/
/*************************************************************/
void Drum(int Type,int Force)
{
/* Drum force has to be valid */
Force = Force<0? 0:Force>255? 255:Force;
/* Call sound driver if present */
if(SndDriver.Drum) (*SndDriver.Drum)(Type,Force);
/* Log drum to MIDI file */
MIDIDrum(Type,Force);
}
/** SetSound() ***********************************************/
/** Set sound type at a given channel. MIDI instruments can **/
/** be set directly by ORing their numbers with SND_MIDI. **/
/*************************************************************/
void SetSound(int Channel,int Type)
{
/* Channel has to be valid */
if((Channel<0)||(Channel>=SND_CHANNELS)) return;
/* Set wave channel type */
WaveCH[Channel].Type = Type;
/* Call sound driver if present */
if(SndDriver.SetSound) (*SndDriver.SetSound)(Channel,Type);
/* Log instrument change to MIDI file */
MIDISetSound(Channel,Type);
}
/** SetChannels() ********************************************/
/** Set master volume (0..255) and switch channels on/off. **/
/** Each channel N has corresponding bit 2^N in Switch. Set **/
/** or reset this bit to turn the channel on or off. **/
/*************************************************************/
void SetChannels(int Volume,int Switch)
{
/* Volume has to be valid */
Volume = Volume<0? 0:Volume>255? 255:Volume;
/* Call sound driver if present */
if(SndDriver.SetChannels) (*SndDriver.SetChannels)(Volume,Switch);
/* Modify wave master settings */
MasterVolume = Volume;
MasterSwitch = Switch&((1<<SND_CHANNELS)-1);
}
/** SetNoise() ***********************************************/
/** Initialize random noise generator to the given Seed and **/
/** then take random output from OUTBit and XOR it with **/
/** XORBit. **/
/*************************************************************/
void SetNoise(int Seed,int OUTBit,int XORBit)
{
NoiseGen = Seed;
NoiseOut = OUTBit;
NoiseXor = XORBit;
}
/** SetWave() ************************************************/
/** Set waveform for a given channel. The channel will be **/
/** marked with sound type SND_WAVE. Set Rate=0 if you want **/
/** waveform to be an instrument or set it to the waveform **/
/** own playback rate. **/
/*************************************************************/
void SetWave(int Channel,const signed char *Data,int Length,int Rate)
{
unsigned int I,J;
/* Channel and waveform length have to be valid */
if((Channel<0)||(Channel>=SND_CHANNELS)||(Length<=0)) return;
/* Set wave channel parameters */
WaveCH[Channel].Type = SND_WAVE;
WaveCH[Channel].Length = Length;
WaveCH[Channel].Rate = Rate;
WaveCH[Channel].Pos = Length? WaveCH[Channel].Pos%Length:0;
WaveCH[Channel].Count = 0;
WaveCH[Channel].Data = Data;
/* Call sound driver if present */
if(SndDriver.SetWave) (*SndDriver.SetWave)(Channel,Data,Length,Rate);
/* Log instrument change to MIDI file */
MIDISetSound(Channel,Rate? -1:SND_WAVE);
/* Compute overall waveform power for MIDI */
if(Rate) I=0;
else
{
for(J=I=0;J<Length;++J) I+=Data[J]>0? Data[J]:-Data[J];
I = (I<<1)/Length;
I = I>256? 256:I;
}
/* Will use power value when computing MIDI volume */
MidiCH[Channel].Power = I;
}
/** GetWave() ************************************************/
/** Get current read position for the buffer set with the **/
/** SetWave() call. Returns 0 if no buffer has been set, or **/
/** if there is no playrate set (i.e. wave is instrument). **/
/*************************************************************/
const signed char *GetWave(int Channel)
{
/* Channel has to be valid */
if((Channel<0)||(Channel>=SND_CHANNELS)) return(0);
/* If driver present, call it */
if(SndDriver.GetWave) return((*SndDriver.GetWave)(Channel));
/* Return current read position */
return(
WaveCH[Channel].Rate&&(WaveCH[Channel].Type==SND_WAVE)?
WaveCH[Channel].Data+WaveCH[Channel].Pos:0
);
}
/** InitMIDI() ***********************************************/
/** Initialize soundtrack logging into MIDI file FileName. **/
/** Repeated calls to InitMIDI() will close current MIDI **/
/** file and continue logging into a new one. **/
/*************************************************************/
void InitMIDI(const char *FileName)
{
int WasLogging;
/* Must pass a name! */
if(!FileName) return;
/* Memorize logging status */
WasLogging=Logging;
/* If MIDI logging in progress, close current file */
if(MIDIOut) TrashMIDI();
/* Set log file name and ticks/second parameter, no logging yet */
LogName = FileName;
Logging = MIDI_OFF;
LastMsg = -1;
TickCount = 0;
MIDIOut = 0;
DrumOn = 0;
/* If was logging, restart */
if(WasLogging) MIDILogging(MIDI_ON);
}
/** TrashMIDI() **********************************************/
/** Finish logging soundtrack and close the MIDI file. **/
/*************************************************************/
void TrashMIDI(void)
{
long Length;
int J;
/* If not logging, drop out */
if(!MIDIOut) return;
/* Turn sound off */
for(J=0;J<MIDI_CHANNELS;J++) NoteOff(J);
/* End of track */
MIDIMessage(0xFF,0x2F,0x00);
/* Put track length in file */
fseek(MIDIOut,0,SEEK_END);
Length=ftell(MIDIOut)-22;
fseek(MIDIOut,18,SEEK_SET);
fputc((Length>>24)&0xFF,MIDIOut);
fputc((Length>>16)&0xFF,MIDIOut);
fputc((Length>>8)&0xFF,MIDIOut);
fputc(Length&0xFF,MIDIOut);
/* Done logging */
fclose(MIDIOut);
Logging = MIDI_OFF;
LastMsg = -1;
TickCount = 0;
MIDIOut = 0;
}
/** MIDILogging() ********************************************/
/** Turn soundtrack logging on/off and return its current **/
/** status. Possible values of Switch are MIDI_OFF (turn **/
/** logging off), MIDI_ON (turn logging on), MIDI_TOGGLE **/
/** (toggle logging), and MIDI_QUERY (just return current **/
/** state of logging). **/
/*************************************************************/
int MIDILogging(int Switch)
{
static const char MThd[] = "MThd\0\0\0\006\0\0\0\1";
/* ID DataLen Fmt Trks */
static const char MTrk[] = "MTrk\0\0\0\0";
/* ID TrkLen */
int J,I;
/* Toggle logging if requested */
if(Switch==MIDI_TOGGLE) Switch=!Logging;
if((Switch==MIDI_ON)||(Switch==MIDI_OFF))
if(Switch^Logging)
{
/* When turning logging off, silence all channels */
if(!Switch&&MIDIOut)
for(J=0;J<MIDI_CHANNELS;J++) NoteOff(J);
/* When turning logging on, open MIDI file */
if(Switch&&!MIDIOut&&LogName)
{
/* No messages have been sent yet */
LastMsg=-1;
/* Clear all storage */
for(J=0;J<MIDI_CHANNELS;J++)
MidiCH[J].Note=MidiCH[J].Pitch=MidiCH[J].Level=-1;
/* Open new file and write out the header */
MIDIOut=fopen(LogName,"wb");
if(!MIDIOut) return(MIDI_OFF);
if(fwrite(MThd,1,12,MIDIOut)!=12)
{ fclose(MIDIOut);MIDIOut=0;return(MIDI_OFF); }
fputc((MIDI_DIVISIONS>>8)&0xFF,MIDIOut);
fputc(MIDI_DIVISIONS&0xFF,MIDIOut);
if(fwrite(MTrk,1,8,MIDIOut)!=8)
{ fclose(MIDIOut);MIDIOut=0;return(MIDI_OFF); }
/* Write out the tempo */
WriteTempo(MIDI_DIVISIONS);
}
/* Turn logging off on failure to open MIDIOut */
if(!MIDIOut) Switch=MIDI_OFF;
/* Assign new switch value */
Logging=Switch;
/* If switching logging on... */
if(Switch)
{
/* Start logging without a pause */
TickCount=0;
/* Write instrument changes */
for(J=0;J<MIDI_CHANNELS;J++)
if((MidiCH[J].Type>=0)&&(MidiCH[J].Type&0x10000))
{
I=MidiCH[J].Type&~0x10000;
MidiCH[J].Type=-1;
MIDISetSound(J,I);
}
}
}
/* Return current logging status */
return(Logging);
}
/** MIDITicks() **********************************************/
/** Log N 1ms MIDI ticks. **/
/*************************************************************/
void MIDITicks(int N)
{
if(Logging&&MIDIOut&&(N>0)) TickCount+=N;
}
/** MIDISound() **********************************************/
/** Set sound frequency (Hz) and volume (0..255) for a **/
/** given channel. **/
/*************************************************************/
void MIDISound(int Channel,int Freq,int Volume)
{
int MIDIVolume,MIDINote,MIDIWheel;
/* If logging off, file closed, or invalid channel, drop out */
if(!Logging||!MIDIOut||(Channel>=MIDI_CHANNELS-1)||(Channel<0)) return;
/* Frequency must be in range */
if((Freq<MIDI_MINFREQ)||(Freq>MIDI_MAXFREQ)) Freq=0;
/* Instrument number must be valid */
if(MidiCH[Channel].Type<0) Freq=0;
/* MIDI sound volume values are in 0..127 range */
/* SND_TRIANGLE has 1/2 volume of SND_MELODIC */
/* SND_WAVE may have different effective volume */
Volume =
MidiCH[Channel].Type==SND_TRIANGLE? ((Volume+3)>>2)
: MidiCH[Channel].Type==SND_WAVE? (((Volume*MidiCH[Channel].Power)+511)>>9)
: ((Volume+1)>>1);
/* Volume must be in range */
if(Volume<0) Volume=0; else if(Volume>127) Volume=127;
if(!Volume||!Freq) NoteOff(Channel);
else
{
/* Compute MIDI note parameters */
MIDIVolume = Volume;
MIDINote = Freqs[Freq/3].Note;
MIDIWheel = Freqs[Freq/3].Wheel;
/* Play new note */
NoteOn(Channel,MIDINote,MIDIVolume);
/* Change pitch */
if(MidiCH[Channel].Pitch!=MIDIWheel)
{
MIDIMessage(0xE0+SHIFT(Channel),MIDIWheel&0x7F,(MIDIWheel>>7)&0x7F);
MidiCH[Channel].Pitch=MIDIWheel;
}
}
}
/** MIDISetSound() *******************************************/
/** Set sound type for a given channel. **/
/*************************************************************/
void MIDISetSound(int Channel,int Type)
{
/* Channel must be valid */
if((Channel>=MIDI_CHANNELS-1)||(Channel<0)) return;
/* If instrument changed... */
if(MidiCH[Channel].Type!=Type)
{
/* If logging off or file closed, drop out */
if(!Logging||!MIDIOut) MidiCH[Channel].Type=Type|0x10000;
else
{
MidiCH[Channel].Type=Type;
if(Type<0) NoteOff(Channel);
else
{
Type=Type&SND_MIDI? (Type&0x7F):Programs[Type%5];
MIDIMessage(0xC0+SHIFT(Channel),Type,255);
}
}
}
}
/** MIDIDrum() ***********************************************/
/** Hit a drum of a given type with given force. **/
/*************************************************************/
void MIDIDrum(int Type,int Force)
{
/* If logging off or invalid channel, drop out */
if(!Logging||!MIDIOut) return;
/* The only non-MIDI drum is a click ("Low Wood Block") */
Type=Type&DRM_MIDI? (Type&0x7F):77;
/* Release previous drum */
if(DrumOn) MIDIMessage(0x89,DrumOn,127);
/* Hit next drum */
if(Type) MIDIMessage(0x99,Type,(Force&0xFF)>>1);
DrumOn=Type;
}
/** MIDIMessage() ********************************************/
/** Write out a MIDI message. **/
/*************************************************************/
void MIDIMessage(byte D0,byte D1,byte D2)
{
/* Write number of ticks that passed */
WriteDelta();
/* Write out the command */
if(D0!=LastMsg) { LastMsg=D0;fputc(D0,MIDIOut); }
/* Write out the arguments */
if(D1<128)
{
fputc(D1,MIDIOut);
if(D2<128) fputc(D2,MIDIOut);
}
}
/** NoteOn() *************************************************/
/** Turn on a note on a given channel. **/
/*************************************************************/
void NoteOn(byte Channel,byte Note,byte Level)
{
Note = Note>0x7F? 0x7F:Note;
Level = Level>0x7F? 0x7F:Level;
if((MidiCH[Channel].Note!=Note)||(MidiCH[Channel].Level!=Level))
{
if(MidiCH[Channel].Note>=0) NoteOff(Channel);
MIDIMessage(0x90+SHIFT(Channel),Note,Level);
MidiCH[Channel].Note=Note;
MidiCH[Channel].Level=Level;
}
}
/** NoteOff() ************************************************/
/** Turn off a note on a given channel. **/
/*************************************************************/
void NoteOff(byte Channel)
{
if(MidiCH[Channel].Note>=0)
{
MIDIMessage(0x80+SHIFT(Channel),MidiCH[Channel].Note,127);
MidiCH[Channel].Note=-1;
}
}
/** WriteDelta() *********************************************/
/** Write number of ticks since the last MIDI command and **/
/** reset the counter. **/
/*************************************************************/
void WriteDelta(void)
{
if(TickCount<128) fputc(TickCount,MIDIOut);
else
{
if(TickCount<128*128)
{
fputc((TickCount>>7)|0x80,MIDIOut);
fputc(TickCount&0x7F,MIDIOut);
}
else
{
fputc(((TickCount>>14)&0x7F)|0x80,MIDIOut);
fputc(((TickCount>>7)&0x7F)|0x80,MIDIOut);
fputc(TickCount&0x7F,MIDIOut);
}
}
TickCount=0;
}
/** WriteTempo() *********************************************/
/** Write out soundtrack tempo (Hz). **/
/*************************************************************/
void WriteTempo(int Freq)
{
int J;
J=500000*MIDI_DIVISIONS*2/Freq;
WriteDelta();
fputc(0xFF,MIDIOut);
fputc(0x51,MIDIOut);
fputc(0x03,MIDIOut);
fputc((J>>16)&0xFF,MIDIOut);
fputc((J>>8)&0xFF,MIDIOut);
fputc(J&0xFF,MIDIOut);
}
/** InitSound() **********************************************/
/** Initialize RenderSound() with given parameters. **/
/*************************************************************/
unsigned int InitSound(unsigned int Rate,unsigned int Latency)
{
int I;
/* Shut down current sound */
TrashSound();
/* Initialize internal variables (keeping MasterVolume/MasterSwitch!) */
SndRate = 0;
/* Reset sound parameters */
for(I=0;I<SND_CHANNELS;I++)
{
/* NOTICE: Preserving Type value! */
WaveCH[I].Count = 0;
WaveCH[I].Volume = 0;
WaveCH[I].Freq = 0;
}
/* Initialize platform-dependent audio */
#if defined(WINDOWS)
Rate = WinInitSound(Rate,Latency);
#else
Rate = 22050; //InitAudio(Rate,Latency);
#endif
/* Rate=0 means silence */
if(!Rate) { SndRate=0;return(0); }
/* Done */
SetChannels(MasterVolume,MasterSwitch);
return(SndRate=Rate);
}
/** TrashSound() *********************************************/
/** Shut down RenderSound() driver. **/
/*************************************************************/
void TrashSound(void)
{
/* Sound is now off */
SndRate = 0;
/* Shut down platform-dependent audio */
#if !defined(NO_AUDIO_PLAYBACK)
#if defined(WINDOWS)
WinTrashSound();
#else
//TrashAudio();
#endif
#endif
}
#if !defined(NO_AUDIO_PLAYBACK)
/** RenderAudio() ********************************************/
/** Render given number of melodic sound samples into an **/
/** integer buffer for mixing. **/
/*************************************************************/
void RenderAudio(int *Wave,unsigned int Samples)
{
register int J,K,I,L1,L2,V,A1;
#ifdef WAVE_INTERPOLATION
/* Keep GCC happy about variable initialization */
register int A2 = 0;
register int L = 0;
register int N = 0;
#endif
/* Exit if wave sound not initialized */
if(SndRate<8192) return;
/* Waveform generator */
for(J=0;J<SND_CHANNELS;J++)
if(WaveCH[J].Freq&&(V=WaveCH[J].Volume)&&(MasterSwitch&(1<<J)))
switch(WaveCH[J].Type)
{
case SND_WAVE: /* Custom Waveform */
/* Waveform data must have correct length! */
if(WaveCH[J].Length<=0) break;
/* Start counting */
K = WaveCH[J].Rate>0?
(SndRate<<15)/WaveCH[J].Freq/WaveCH[J].Rate
: (SndRate<<15)/WaveCH[J].Freq/WaveCH[J].Length;
/* Do not allow high frequencies (GBC Frogger) */
if(K<0x8000) break;
L1 = WaveCH[J].Pos%WaveCH[J].Length;
L2 = WaveCH[J].Count;
A1 = WaveCH[J].Data[L1]*V;
#if !defined(WAVE_INTERPOLATION)
/* Add waveform to the buffer */
for(I=0;I<Samples;I++)
{
/* If next step... */
if(L2>=K)
{
L1 = (L1+L2/K)%WaveCH[J].Length;
A1 = WaveCH[J].Data[L1]*V;
L2 = L2%K;
}
/* Output waveform */
Wave[I]+=A1;
/* Next waveform step */
L2+=0x8000;
}
#else /* WAVE_INTERPOLATION */
/* If expecting interpolation... */
if(L2<K)
{
/* Compute interpolation parameters */
A2 = WaveCH[J].Data[(L1+1)%WaveCH[J].Length]*V;
L = (L2>>15)+1;
N = ((K-(L2&0x7FFF))>>15)+1;
}
/* Add waveform to the buffer */
for(I=0;I<Samples;I++)
if(L2<K)
{
/* Interpolate linearly */
Wave[I]+=A1+L*(A2-A1)/N;
/* Next waveform step */
L2+=0x8000;
/* Next interpolation step */
L++;
}
else
{
L1 = (L1+L2/K)%WaveCH[J].Length;
L2 = (L2%K)+0x8000;
A1 = WaveCH[J].Data[L1]*V;
Wave[I]+=A1;
/* If expecting interpolation... */
if(L2<K)
{
/* Compute interpolation parameters */
A2 = WaveCH[J].Data[(L1+1)%WaveCH[J].Length]*V;
L = 1;
N = ((K-L2)>>15)+1;
}
}
#endif /* WAVE_INTERPOLATION */
/* End counting */
WaveCH[J].Pos = L1;
WaveCH[J].Count = L2;
break;
case SND_NOISE: /* White Noise */
/* For high frequencies, recompute volume */
if(WaveCH[J].Freq<SndRate)
K=((unsigned int)WaveCH[J].Freq<<16)/SndRate;
else
{
V = V*SndRate/WaveCH[J].Freq;
K = 0x10000;
}
L1=WaveCH[J].Count;
for(I=0;I<Samples;I++)
{
/* Use NoiseOut bit for output */
Wave[I]+=((NoiseGen>>NoiseOut)&1? 127:-128)*V;
L1+=K;
if(L1&0xFFFF0000)
{
/* XOR NoiseOut and NoiseXOR bits and feed them back */
NoiseGen=
(((NoiseGen>>NoiseOut)^(NoiseGen>>NoiseXor))&1)
| ((NoiseGen<<1)&((2<<NoiseOut)-1));
L1&=0xFFFF;
}
}
WaveCH[J].Count=L1;
break;
case SND_MELODIC: /* Melodic Sound */
case SND_TRIANGLE: /* Triangular Wave */
default: /* Default Sound */
/* Do not allow frequencies that are too high */
if(WaveCH[J].Freq>=SndRate/2) break;
K=0x10000*WaveCH[J].Freq/SndRate;
L1=WaveCH[J].Count;
#if !defined(SLOW_MELODIC_AUDIO)
for(I=0;I<Samples;I++,L1+=K)
Wave[I]+=((L1-K)^(L1+K))&0x8000? 0:(L1&0x8000? 127:-128)*V;
#else /* SLOW_MELODIC_AUDIO */
for(I=0;I<Samples;I++,L1+=K)
{
L2 = L1+K;
A1 = L1&0x8000? 127:-128;
if((L1^L2)&0x8000)
A1=A1*(0x8000-(L1&0x7FFF)-(L2&0x7FFF))/K;
Wave[I]+=A1*V;
}
#endif /* SLOW_MELODIC_AUDIO */
WaveCH[J].Count=L1&0xFFFF;
break;
}
}
/** PlayAudio() **********************************************/
/** Normalize and play given number of samples from the mix **/
/** buffer. Returns the number of samples actually played. **/
/*************************************************************/
unsigned int PlayAudio(int *Wave,unsigned int Samples)
{
sample Buf[256];
unsigned int I,J,K;
int D;
/* Exit if wave sound not initialized */
if(SndRate<8192) return(0);
/* Check if the buffer contains enough free space */
J = GetFreeAudio();
if(J<Samples) Samples=J;
/* Spin until all samples played or WriteAudio() fails */
for(K=I=J=0;(K<Samples)&&(I==J);K+=I)
{
/* Compute number of samples to convert */
J = sizeof(Buf)/sizeof(sample);
J = Samples-K>J? J:Samples-K;
/* Convert samples */
for(I=0;I<J;++I)
{
D = ((*Wave++)*MasterVolume)>>8;
D = D>32767? 32767:D<-32768? -32768:D;
#if defined(BPU16)
Buf[I] = D+32768;
#elif defined(BPS16)
Buf[I] = D;
#elif defined(BPU8)
Buf[I] = (D>>8)+128;
#else
Buf[I] = D>>8;
#endif
}
/* Play samples */
I = WriteAudio(Buf,J);
}
/* Return number of samples played */
return(K);
}
/** RenderAndPlayAudio() *************************************/
/** Render and play a given number of samples. Returns the **/
/** number of samples actually played. **/
/*************************************************************/
unsigned int RenderAndPlayAudio(unsigned int Samples)
{
int Buf[256];
unsigned int J,I;
/* Exit if wave sound not initialized */
if(SndRate<8192) return(0);
J = GetFreeAudio();
Samples = Samples<J? Samples:J;
/* Render and play sound */
for(I=0;I<Samples;I+=J)
{
J = Samples-I;
J = J<sizeof(Buf)/sizeof(Buf[0])? J:sizeof(Buf)/sizeof(Buf[0]);
memset(Buf,0,J*sizeof(Buf[0]));
RenderAudio(Buf,J);
if(PlayAudio(Buf,J)<J) { I+=J;break; }
}
/* Return number of samples rendered */
return(I);
}
#endif /* !NO_AUDIO_PLAYBACK */