1 /** EMULib Emulation Library *********************************/
2 /**                                                         **/
3 /**                          Sound.c                        **/
4 /**                                                         **/
5 /** This file file implements core part of the sound API    **/
6 /** and functions needed to log soundtrack into a MIDI      **/
7 /** file. See Sound.h for declarations.                     **/
8 /**                                                         **/
9 /** Copyright (C) Marat Fayzullin 1996-2014                 **/
10 /**     You are not allowed to distribute this software     **/
11 /**     commercially. Please, notify me, if you make any    **/
12 /**     changes to this file.                               **/
13 /*************************************************************/
14 #include "Sound.h"
15 
16 #include <stdio.h>
17 #include <string.h>
18 
19 #if defined(_WIN32)
20 #include <direct.h>
21 #else
22 #include <unistd.h>
23 #endif
24 
25 typedef unsigned char byte;
26 typedef unsigned short word;
27 
28 struct SndDriverStruct SndDriver =
29 {
30   (void (*)(int,int))0,
31   (void (*)(int,int))0,
32   (void (*)(int,int))0,
33   (void (*)(int,int,int))0,
34   (void (*)(int,const signed char *,int,int))0,
35   (const signed char *(*)(int))0
36 };
37 
38 static const struct { byte Note;word Wheel; } Freqs[4096] =
39 {
40 #include "MIDIFreq.h"
41 };
42 
43 static const int Programs[5] =
44 {
45   80,  /* SND_MELODIC/SND_RECTANGLE */
46   80,  /* SND_TRIANGLE */
47   122, /* SND_NOISE */
48   122, /* SND_PERIODIC */
49   80   /* SND_WAVE */
50 };
51 
52 static struct
53 {
54   int Type;
55   int Note;
56   int Pitch;
57   int Level;
58 } MidiCH[MIDI_CHANNELS] =
59 {
60   { -1,-1,-1,-1 },
61   { -1,-1,-1,-1 },
62   { -1,-1,-1,-1 },
63   { -1,-1,-1,-1 },
64   { -1,-1,-1,-1 },
65   { -1,-1,-1,-1 },
66   { -1,-1,-1,-1 },
67   { -1,-1,-1,-1 },
68   { -1,-1,-1,-1 },
69   { -1,-1,-1,-1 },
70   { -1,-1,-1,-1 },
71   { -1,-1,-1,-1 },
72   { -1,-1,-1,-1 },
73   { -1,-1,-1,-1 },
74   { -1,-1,-1,-1 },
75   { -1,-1,-1,-1 }
76 };
77 
78 static struct
79 {
80   int Type;                       /* Channel type (SND_*)             */
81   int Freq;                       /* Channel frequency (Hz)           */
82   int Volume;                     /* Channel volume (0..255)          */
83 
84   const signed char *Data;        /* Wave data (-128..127 each)       */
85   int Length;                     /* Wave length in Data              */
86   int Rate;                       /* Wave playback rate (or 0Hz)      */
87   int Pos;                        /* Wave current position in Data    */
88 
89   int Count;                      /* Phase counter                    */
90 } WaveCH[SND_CHANNELS] =
91 {
92   { SND_MELODIC,0,0,0,0,0,0,0 },
93   { SND_MELODIC,0,0,0,0,0,0,0 },
94   { SND_MELODIC,0,0,0,0,0,0,0 },
95   { SND_MELODIC,0,0,0,0,0,0,0 },
96   { SND_MELODIC,0,0,0,0,0,0,0 },
97   { SND_MELODIC,0,0,0,0,0,0,0 },
98   { SND_MELODIC,0,0,0,0,0,0,0 },
99   { SND_MELODIC,0,0,0,0,0,0,0 },
100   { SND_MELODIC,0,0,0,0,0,0,0 },
101   { SND_MELODIC,0,0,0,0,0,0,0 },
102   { SND_MELODIC,0,0,0,0,0,0,0 },
103   { SND_MELODIC,0,0,0,0,0,0,0 },
104   { SND_MELODIC,0,0,0,0,0,0,0 },
105   { SND_MELODIC,0,0,0,0,0,0,0 },
106   { SND_MELODIC,0,0,0,0,0,0,0 },
107   { SND_MELODIC,0,0,0,0,0,0,0 }
108 };
109 
110 /** RenderAudio() Variables *******************************************/
111 static int SndRate    = 0;        /* Sound rate (0=Off)               */
112 static int NoiseGen   = 1;        /* Noise generator seed             */
113 int MasterSwitch      = 0xFFFF;   /* Switches to turn channels on/off */
114 int MasterVolume      = 192;      /* Master volume                    */
115 
116 /** MIDI Logging Variables ********************************************/
117 static const char *LogName = 0;   /* MIDI logging file name           */
118 static int  Logging   = MIDI_OFF; /* MIDI logging state (MIDI_*)      */
119 static int  TickCount = 0;        /* MIDI ticks since WriteDelta()    */
120 static int  LastMsg   = -1;       /* Last MIDI message                */
121 static int  DrumOn    = 0;        /* 1: MIDI drums are ON             */
122 static FILE *MIDIOut  = 0;        /* MIDI logging file handle         */
123 
124 static void MIDISound(int Channel,int Freq,int Volume);
125 static void MIDISetSound(int Channel,int Type);
126 static void MIDIDrum(int Type,int Force);
127 static void MIDIMessage(byte D0,byte D1,byte D2);
128 static void NoteOn(byte Channel,byte Note,byte Level);
129 static void NoteOff(byte Channel);
130 static void WriteDelta(void);
131 static void WriteTempo(int Freq);
132 
133 /** SHIFT() **************************************************/
134 /** Make MIDI channel#10 last, as it is normally used for   **/
135 /** percussion instruments only and doesn't sound nice.     **/
136 /*************************************************************/
137 #define SHIFT(Ch) (Ch==15? 9:Ch>8? Ch+1:Ch)
138 
139 /** GetSndRate() *********************************************/
140 /** Get current sampling rate used for synthesis.           **/
141 /*************************************************************/
GetSndRate(void)142 unsigned int GetSndRate(void) { return(SndRate); }
143 
144 /** Sound() **************************************************/
145 /** Generate sound of given frequency (Hz) and volume       **/
146 /** (0..255) via given channel. Setting Freq=0 or Volume=0  **/
147 /** turns sound off.                                        **/
148 /*************************************************************/
Sound(int Channel,int Freq,int Volume)149 void Sound(int Channel,int Freq,int Volume)
150 {
151   /* All parameters have to be valid */
152   if((Channel<0)||(Channel>=SND_CHANNELS)) return;
153   Freq   = Freq<0? 0:Freq>20000? 0:Freq;
154   Volume = Volume<0? 0:Volume>255? 255:Volume;
155 
156   /* Modify wave channel */
157   WaveCH[Channel].Volume = Volume;
158   WaveCH[Channel].Freq   = Freq;
159 
160   /* Call sound driver if present */
161   if(SndDriver.Sound) (*SndDriver.Sound)(Channel,Freq,Volume);
162 
163   /* Log sound to MIDI file */
164   MIDISound(Channel,Freq,Volume);
165 }
166 
167 /** Drum() ***************************************************/
168 /** Hit a drum of given type with given force (0..255).     **/
169 /** MIDI drums can be used by ORing their numbers with      **/
170 /** SND_MIDI.                                               **/
171 /*************************************************************/
Drum(int Type,int Force)172 void Drum(int Type,int Force)
173 {
174   /* Drum force has to be valid */
175   Force = Force<0? 0:Force>255? 255:Force;
176 
177   /* Call sound driver if present */
178   if(SndDriver.Drum) (*SndDriver.Drum)(Type,Force);
179 
180   /* Log drum to MIDI file */
181   MIDIDrum(Type,Force);
182 }
183 
184 /** SetSound() ***********************************************/
185 /** Set sound type at a given channel. MIDI instruments can **/
186 /** be set directly by ORing their numbers with SND_MIDI.   **/
187 /*************************************************************/
SetSound(int Channel,int Type)188 void SetSound(int Channel,int Type)
189 {
190   /* Channel has to be valid */
191   if((Channel<0)||(Channel>=SND_CHANNELS)) return;
192 
193   /* Set wave channel type */
194   WaveCH[Channel].Type = Type;
195 
196   /* Call sound driver if present */
197   if(SndDriver.SetSound) (*SndDriver.SetSound)(Channel,Type);
198 
199   /* Log instrument change to MIDI file */
200   MIDISetSound(Channel,Type);
201 }
202 
203 /** SetChannels() ********************************************/
204 /** Set master volume (0..255) and switch channels on/off.  **/
205 /** Each channel N has corresponding bit 2^N in Switch. Set **/
206 /** or reset this bit to turn the channel on or off.        **/
207 /*************************************************************/
SetChannels(int Volume,int Switch)208 void SetChannels(int Volume,int Switch)
209 {
210   /* Volume has to be valid */
211   Volume = Volume<0? 0:Volume>255? 255:Volume;
212 
213   /* Call sound driver if present */
214   if(SndDriver.SetChannels) (*SndDriver.SetChannels)(Volume,Switch);
215 
216   /* Modify wave master settings */
217   MasterVolume = Volume;
218   MasterSwitch = Switch&((1<<SND_CHANNELS)-1);
219 }
220 
221 /** SetWave() ************************************************/
222 /** Set waveform for a given channel. The channel will be   **/
223 /** marked with sound type SND_WAVE. Set Rate=0 if you want **/
224 /** waveform to be an instrument or set it to the waveform  **/
225 /** own playback rate.                                      **/
226 /*************************************************************/
SetWave(int Channel,const signed char * Data,int Length,int Rate)227 void SetWave(int Channel,const signed char *Data,int Length,int Rate)
228 {
229   /* Channel and waveform length have to be valid */
230   if((Channel<0)||(Channel>=SND_CHANNELS)||(Length<=0)) return;
231 
232   /* Set wave channel parameters */
233   WaveCH[Channel].Type   = SND_WAVE;
234   WaveCH[Channel].Length = Length;
235   WaveCH[Channel].Rate   = Rate;
236   WaveCH[Channel].Pos    = Length? WaveCH[Channel].Pos%Length:0;
237   WaveCH[Channel].Count  = 0;
238   WaveCH[Channel].Data   = Data;
239 
240   /* Call sound driver if present */
241   if(SndDriver.SetWave) (*SndDriver.SetWave)(Channel,Data,Length,Rate);
242 
243   /* Log instrument change to MIDI file */
244   MIDISetSound(Channel,Rate? -1:SND_MELODIC);
245 }
246 
247 /** GetWave() ************************************************/
248 /** Get current read position for the buffer set with the   **/
249 /** SetWave() call. Returns 0 if no buffer has been set, or **/
250 /** if there is no playrate set (i.e. wave is instrument).  **/
251 /*************************************************************/
GetWave(int Channel)252 const signed char *GetWave(int Channel)
253 {
254   /* Channel has to be valid */
255   if((Channel<0)||(Channel>=SND_CHANNELS)) return(0);
256 
257   /* If driver present, call it */
258   if(SndDriver.GetWave) return((*SndDriver.GetWave)(Channel));
259 
260   /* Return current read position */
261   return(
262     WaveCH[Channel].Rate&&(WaveCH[Channel].Type==SND_WAVE)?
263     WaveCH[Channel].Data+WaveCH[Channel].Pos:0
264   );
265 }
266 
267 /** InitMIDI() ***********************************************/
268 /** Initialize soundtrack logging into MIDI file FileName.  **/
269 /** Repeated calls to InitMIDI() will close current MIDI    **/
270 /** file and continue logging into a new one.               **/
271 /*************************************************************/
InitMIDI(const char * FileName)272 void InitMIDI(const char *FileName)
273 {
274   int WasLogging;
275 
276   /* Must pass a name! */
277   if(!FileName) return;
278 
279   /* Memorize logging status */
280   WasLogging=Logging;
281 
282   /* If MIDI logging in progress, close current file */
283   if(MIDIOut) TrashMIDI();
284 
285   /* Set log file name and ticks/second parameter, no logging yet */
286   LogName   = FileName;
287   Logging   = MIDI_OFF;
288   LastMsg   = -1;
289   TickCount = 0;
290   MIDIOut   = 0;
291   DrumOn    = 0;
292 
293   /* If was logging, restart */
294   if(WasLogging) MIDILogging(MIDI_ON);
295 }
296 
297 /** TrashMIDI() **********************************************/
298 /** Finish logging soundtrack and close the MIDI file.      **/
299 /*************************************************************/
TrashMIDI(void)300 void TrashMIDI(void)
301 {
302   long Length;
303   int J;
304 
305   /* If not logging, drop out */
306   if(!MIDIOut) return;
307   /* Turn sound off */
308   for(J=0;J<MIDI_CHANNELS;J++) NoteOff(J);
309   /* End of track */
310   MIDIMessage(0xFF,0x2F,0x00);
311   /* Put track length in file */
312   fseek(MIDIOut,0,SEEK_END);
313   Length=ftell(MIDIOut)-22;
314   fseek(MIDIOut,18,SEEK_SET);
315   fputc((Length>>24)&0xFF,MIDIOut);
316   fputc((Length>>16)&0xFF,MIDIOut);
317   fputc((Length>>8)&0xFF,MIDIOut);
318   fputc(Length&0xFF,MIDIOut);
319 
320   /* Done logging */
321   fclose(MIDIOut);
322   Logging   = MIDI_OFF;
323   LastMsg   = -1;
324   TickCount = 0;
325   MIDIOut   = 0;
326 }
327 
328 /** MIDILogging() ********************************************/
329 /** Turn soundtrack logging on/off and return its current   **/
330 /** status. Possible values of Switch are MIDI_OFF (turn    **/
331 /** logging off), MIDI_ON (turn logging on), MIDI_TOGGLE    **/
332 /** (toggle logging), and MIDI_QUERY (just return current   **/
333 /** state of logging).                                      **/
334 /*************************************************************/
MIDILogging(int Switch)335 int MIDILogging(int Switch)
336 {
337   static const char MThd[] = "MThd\0\0\0\006\0\0\0\1";
338                            /* ID  DataLen   Fmt Trks */
339   static const char MTrk[] = "MTrk\0\0\0\0";
340                            /* ID  TrkLen   */
341   int J,I;
342 
343   /* Toggle logging if requested */
344   if(Switch==MIDI_TOGGLE) Switch=!Logging;
345 
346   if((Switch==MIDI_ON)||(Switch==MIDI_OFF))
347     if(Switch^Logging)
348     {
349       /* When turning logging off, silence all channels */
350       if(!Switch&&MIDIOut)
351         for(J=0;J<MIDI_CHANNELS;J++) NoteOff(J);
352 
353       /* When turning logging on, open MIDI file */
354       if(Switch&&!MIDIOut&&LogName)
355       {
356         /* No messages have been sent yet */
357         LastMsg=-1;
358 
359         /* Clear all storage */
360         for(J=0;J<MIDI_CHANNELS;J++)
361           MidiCH[J].Note=MidiCH[J].Pitch=MidiCH[J].Level=-1;
362 
363         /* Open new file and write out the header */
364         MIDIOut=fopen(LogName,"wb");
365         if(!MIDIOut) return(MIDI_OFF);
366         if(fwrite(MThd,1,12,MIDIOut)!=12)
367         { fclose(MIDIOut);MIDIOut=0;return(MIDI_OFF); }
368         fputc((MIDI_DIVISIONS>>8)&0xFF,MIDIOut);
369         fputc(MIDI_DIVISIONS&0xFF,MIDIOut);
370         if(fwrite(MTrk,1,8,MIDIOut)!=8)
371         { fclose(MIDIOut);MIDIOut=0;return(MIDI_OFF); }
372 
373         /* Write out the tempo */
374         WriteTempo(MIDI_DIVISIONS);
375       }
376 
377       /* Turn logging off on failure to open MIDIOut */
378       if(!MIDIOut) Switch=MIDI_OFF;
379 
380       /* Assign new switch value */
381       Logging=Switch;
382 
383       /* If switching logging on... */
384       if(Switch)
385       {
386         /* Start logging without a pause */
387         TickCount=0;
388 
389         /* Write instrument changes */
390         for(J=0;J<MIDI_CHANNELS;J++)
391           if((MidiCH[J].Type>=0)&&(MidiCH[J].Type&0x10000))
392           {
393             I=MidiCH[J].Type&~0x10000;
394             MidiCH[J].Type=-1;
395             MIDISetSound(J,I);
396           }
397       }
398     }
399 
400   /* Return current logging status */
401   return(Logging);
402 }
403 
404 /** MIDITicks() **********************************************/
405 /** Log N 1ms MIDI ticks.                                   **/
406 /*************************************************************/
MIDITicks(int N)407 void MIDITicks(int N)
408 {
409   if(Logging&&MIDIOut&&(N>0)) TickCount+=N;
410 }
411 
412 /** MIDISound() **********************************************/
413 /** Set sound frequency (Hz) and volume (0..255) for a      **/
414 /** given channel.                                          **/
415 /*************************************************************/
MIDISound(int Channel,int Freq,int Volume)416 void MIDISound(int Channel,int Freq,int Volume)
417 {
418   int MIDIVolume,MIDINote,MIDIWheel;
419 
420   /* If logging off, file closed, or invalid channel, drop out */
421   if(!Logging||!MIDIOut||(Channel>=MIDI_CHANNELS-1)||(Channel<0)) return;
422   /* Frequency must be in range */
423   if((Freq<MIDI_MINFREQ)||(Freq>MIDI_MAXFREQ)) Freq=0;
424   /* Volume must be in range */
425   if(Volume<0) Volume=0; else if(Volume>255) Volume=255;
426   /* Instrument number must be valid */
427   if(MidiCH[Channel].Type<0) Freq=0;
428 
429   if(!Volume||!Freq) NoteOff(Channel);
430   else
431   {
432     /* SND_TRIANGLE is twice quieter than SND_MELODIC */
433     if(MidiCH[Channel].Type==SND_TRIANGLE) Volume=(Volume+1)/2;
434     /* Compute MIDI note parameters */
435     MIDIVolume = (127*Volume+128)/255;
436     MIDINote   = Freqs[Freq/3].Note;
437     MIDIWheel  = Freqs[Freq/3].Wheel;
438 
439     /* Play new note */
440     NoteOn(Channel,MIDINote,MIDIVolume);
441 
442     /* Change pitch */
443     if(MidiCH[Channel].Pitch!=MIDIWheel)
444     {
445       MIDIMessage(0xE0+SHIFT(Channel),MIDIWheel&0x7F,(MIDIWheel>>7)&0x7F);
446       MidiCH[Channel].Pitch=MIDIWheel;
447     }
448   }
449 }
450 
451 /** MIDISetSound() *******************************************/
452 /** Set sound type for a given channel.                     **/
453 /*************************************************************/
MIDISetSound(int Channel,int Type)454 void MIDISetSound(int Channel,int Type)
455 {
456   /* Channel must be valid */
457   if((Channel>=MIDI_CHANNELS-1)||(Channel<0)) return;
458 
459   /* If instrument changed... */
460   if(MidiCH[Channel].Type!=Type)
461   {
462     /* If logging off or file closed, drop out */
463     if(!Logging||!MIDIOut) MidiCH[Channel].Type=Type|0x10000;
464     else
465     {
466       MidiCH[Channel].Type=Type;
467       if(Type<0) NoteOff(Channel);
468       else
469       {
470         Type=Type&SND_MIDI? (Type&0x7F):Programs[Type%5];
471         MIDIMessage(0xC0+SHIFT(Channel),Type,255);
472       }
473     }
474   }
475 }
476 
477 /** MIDIDrum() ***********************************************/
478 /** Hit a drum of a given type with given force.            **/
479 /*************************************************************/
MIDIDrum(int Type,int Force)480 void MIDIDrum(int Type,int Force)
481 {
482   /* If logging off or invalid channel, drop out */
483   if(!Logging||!MIDIOut) return;
484   /* The only non-MIDI drum is a click ("Low Wood Block") */
485   Type=Type&DRM_MIDI? (Type&0x7F):77;
486   /* Release previous drum */
487   if(DrumOn) MIDIMessage(0x89,DrumOn,127);
488   /* Hit next drum */
489   if(Type) MIDIMessage(0x99,Type,(Force&0xFF)/2);
490   DrumOn=Type;
491 }
492 
493 /** MIDIMessage() ********************************************/
494 /** Write out a MIDI message.                               **/
495 /*************************************************************/
MIDIMessage(byte D0,byte D1,byte D2)496 void MIDIMessage(byte D0,byte D1,byte D2)
497 {
498   /* Write number of ticks that passed */
499   WriteDelta();
500 
501   /* Write out the command */
502   if(D0!=LastMsg) { LastMsg=D0;fputc(D0,MIDIOut); }
503 
504   /* Write out the arguments */
505   if(D1<128)
506   {
507     fputc(D1,MIDIOut);
508     if(D2<128) fputc(D2,MIDIOut);
509   }
510 }
511 
512 /** NoteOn() *************************************************/
513 /** Turn on a note on a given channel.                      **/
514 /*************************************************************/
NoteOn(byte Channel,byte Note,byte Level)515 void NoteOn(byte Channel,byte Note,byte Level)
516 {
517   Note  = Note>0x7F? 0x7F:Note;
518   Level = Level>0x7F? 0x7F:Level;
519 
520   if((MidiCH[Channel].Note!=Note)||(MidiCH[Channel].Level!=Level))
521   {
522     if(MidiCH[Channel].Note>=0) NoteOff(Channel);
523     MIDIMessage(0x90+SHIFT(Channel),Note,Level);
524     MidiCH[Channel].Note=Note;
525     MidiCH[Channel].Level=Level;
526   }
527 }
528 
529 /** NoteOff() ************************************************/
530 /** Turn off a note on a given channel.                     **/
531 /*************************************************************/
NoteOff(byte Channel)532 void NoteOff(byte Channel)
533 {
534   if(MidiCH[Channel].Note>=0)
535   {
536     MIDIMessage(0x80+SHIFT(Channel),MidiCH[Channel].Note,127);
537     MidiCH[Channel].Note=-1;
538   }
539 }
540 
541 /** WriteDelta() *********************************************/
542 /** Write number of ticks since the last MIDI command and   **/
543 /** reset the counter.                                      **/
544 /*************************************************************/
WriteDelta(void)545 void WriteDelta(void)
546 {
547   if(TickCount<128) fputc(TickCount,MIDIOut);
548   else
549   {
550     if(TickCount<128*128)
551     {
552       fputc((TickCount>>7)|0x80,MIDIOut);
553       fputc(TickCount&0x7F,MIDIOut);
554     }
555     else
556     {
557       fputc(((TickCount>>14)&0x7F)|0x80,MIDIOut);
558       fputc(((TickCount>>7)&0x7F)|0x80,MIDIOut);
559       fputc(TickCount&0x7F,MIDIOut);
560     }
561   }
562 
563   TickCount=0;
564 }
565 
566 /** WriteTempo() *********************************************/
567 /** Write out soundtrack tempo (Hz).                        **/
568 /*************************************************************/
WriteTempo(int Freq)569 void WriteTempo(int Freq)
570 {
571   int J;
572 
573   J=500000*MIDI_DIVISIONS*2/Freq;
574   WriteDelta();
575   fputc(0xFF,MIDIOut);
576   fputc(0x51,MIDIOut);
577   fputc(0x03,MIDIOut);
578   fputc((J>>16)&0xFF,MIDIOut);
579   fputc((J>>8)&0xFF,MIDIOut);
580   fputc(J&0xFF,MIDIOut);
581 }
582 
583 /** InitSound() **********************************************/
584 /** Initialize RenderSound() with given parameters.         **/
585 /*************************************************************/
InitSound(unsigned int Rate,unsigned int Latency)586 unsigned int InitSound(unsigned int Rate,unsigned int Latency)
587 {
588   int I;
589 
590   /* Shut down current sound */
591   TrashSound();
592 
593   /* Initialize internal variables (keeping MasterVolume/MasterSwitch!) */
594   SndRate  = 0;
595   NoiseGen = 1;
596 
597   /* Reset sound parameters */
598   for(I=0;I<SND_CHANNELS;I++)
599   {
600     /* NOTICE: Preserving Type value! */
601     WaveCH[I].Count  = 0;
602     WaveCH[I].Volume = 0;
603     WaveCH[I].Freq   = 0;
604   }
605 
606   /* Rate=0 means silence */
607   if(!Rate) { SndRate=0;return(0); }
608 
609   /* Done */
610   SetChannels(MasterVolume,MasterSwitch);
611   return(SndRate=Rate);
612 }
613 
614 /** TrashSound() *********************************************/
615 /** Shut down RenderSound() driver.                         **/
616 /*************************************************************/
TrashSound(void)617 void TrashSound(void)
618 {
619   /* Sound is now off */
620   SndRate = 0;
621 }
622 
623 #if !defined(NO_AUDIO_PLAYBACK)
624 /** RenderAudio() ********************************************/
625 /** Render given number of melodic sound samples into an    **/
626 /** integer buffer for mixing.                              **/
627 /*************************************************************/
RenderAudio(int * Wave,unsigned int Samples)628 void RenderAudio(int *Wave,unsigned int Samples)
629 {
630   register int N,J,K,I,L,L1,L2,V,A1,A2;
631 
632   /* Exit if wave sound not initialized */
633   if(SndRate<8192) return;
634 
635   /* Keep GCC happy about variable initialization */
636   N=L=A2=0;
637 
638   /* Waveform generator */
639   for(J=0;J<SND_CHANNELS;J++)
640     if(WaveCH[J].Freq&&(V=WaveCH[J].Volume)&&(MasterSwitch&(1<<J)))
641       switch(WaveCH[J].Type)
642       {
643         case SND_WAVE: /* Custom Waveform */
644           /* Waveform data must have correct length! */
645           if(WaveCH[J].Length<=0) break;
646           /* Start counting */
647           K  = WaveCH[J].Rate>0?
648                (SndRate<<15)/WaveCH[J].Freq/WaveCH[J].Rate
649              : (SndRate<<15)/WaveCH[J].Freq/WaveCH[J].Length;
650           L1 = WaveCH[J].Pos%WaveCH[J].Length;
651           L2 = WaveCH[J].Count;
652           A1 = WaveCH[J].Data[L1]*V;
653 #ifdef NO_WAVE_INTERPOLATION
654           /* Add waveform to the buffer */
655           for(I=0;I<Samples;I++)
656           {
657             /* If next step... */
658             if(L2>=K)
659             {
660               L1 = (L1+L2/K)%WaveCH[J].Length;
661               A1 = WaveCH[J].Data[L1]*V;
662               L2 = L2%K;
663             }
664             /* Output waveform */
665             Wave[I]+=A1;
666             /* Next waveform step */
667             L2+=0x8000;
668           }
669 #else
670           /* If expecting interpolation... */
671           if(L2<K)
672           {
673             /* Compute interpolation parameters */
674             A2 = WaveCH[J].Data[(L1+1)%WaveCH[J].Length]*V;
675             L  = (L2>>15)+1;
676             N  = ((K-(L2&0x7FFF))>>15)+1;
677           }
678           /* Add waveform to the buffer */
679           for(I=0;I<Samples;I++)
680             if(L2<K)
681             {
682               /* Interpolate linearly */
683               Wave[I]+=A1+L*(A2-A1)/N;
684               /* Next waveform step */
685               L2+=0x8000;
686               /* Next interpolation step */
687               L++;
688             }
689             else
690             {
691               L1 = (L1+L2/K)%WaveCH[J].Length;
692               L2 = (L2%K)+0x8000;
693               A1 = WaveCH[J].Data[L1]*V;
694               Wave[I]+=A1;
695               /* If expecting interpolation... */
696               if(L2<K)
697               {
698                 /* Compute interpolation parameters */
699                 A2 = WaveCH[J].Data[(L1+1)%WaveCH[J].Length]*V;
700                 L  = 1;
701                 N  = ((K-L2)>>15)+1;
702               }
703             }
704 #endif /* !NO_WAVE_INTERPOLATION */
705           /* End counting */
706           WaveCH[J].Pos   = L1;
707           WaveCH[J].Count = L2;
708           break;
709 
710         case SND_NOISE: /* White Noise */
711           /* For high frequencies, recompute volume */
712           if(WaveCH[J].Freq<=SndRate) K=0x10000*WaveCH[J].Freq/SndRate;
713           else { V=V*SndRate/WaveCH[J].Freq;K=0x10000; }
714           L1=WaveCH[J].Count;
715           for(I=0;I<Samples;I++)
716           {
717             L1+=K;
718             if(L1&0xFFFF0000)
719             {
720               if((NoiseGen<<=1)&0x80000000) NoiseGen^=0x08000001;
721               L1&=0xFFFF;
722             }
723             Wave[I]+=(NoiseGen&1? 127:-128)*V;
724           }
725           WaveCH[J].Count=L1;
726           break;
727 
728         case SND_MELODIC:  /* Melodic Sound   */
729         case SND_TRIANGLE: /* Triangular Wave */
730         default:           /* Default Sound   */
731           /* Do not allow frequencies that are too high */
732           if(WaveCH[J].Freq>=SndRate/2) break;
733           K=0x10000*WaveCH[J].Freq/SndRate;
734           L1=WaveCH[J].Count;
735 #ifndef SLOW_MELODIC_AUDIO
736           for(I=0;I<Samples;I++,L1+=K)
737             Wave[I]+=((L1-K)^(L1+K))&0x8000? 0:(L1&0x8000? 127:-128)*V;
738 #else
739           for(I=0;I<Samples;I++,L1+=K)
740           {
741             L2 = L1+K;
742             A1 = L1&0x8000? 127:-128;
743             if((L1^L2)&0x8000)
744               A1=A1*(0x8000-(L1&0x7FFF)-(L2&0x7FFF))/K;
745             Wave[I]+=A1*V;
746           }
747 #endif
748           WaveCH[J].Count=L1&0xFFFF;
749           break;
750       }
751 }
752 
753 /** PlayAudio() **********************************************/
754 /** Normalize and play given number of samples from the mix **/
755 /** buffer. Returns the number of samples actually played.  **/
756 /*************************************************************/
PlayAudio(int * Wave,unsigned int Samples)757 unsigned int PlayAudio(int *Wave,unsigned int Samples)
758 {
759   sample Buf[256];
760   unsigned int I,J,K;
761   int D;
762 
763   /* Exit if wave sound not initialized */
764   if(SndRate<8192) return(0);
765 
766   /* Check if the buffer contains enough free space */
767   J = GetFreeAudio();
768   if(J<Samples) Samples=J;
769 
770   /* Spin until all samples played or WriteAudio() fails */
771   for(K=I=J=0;(K<Samples)&&(I==J);K+=I)
772   {
773     /* Compute number of samples to convert */
774     J = sizeof(Buf)/sizeof(sample);
775     J = Samples<=J? Samples:J;
776 
777     /* Convert samples */
778     for(I=0;I<J;++I)
779     {
780       D      = ((*Wave++)*MasterVolume)/255;
781       D      = D>32767? 32767:D<-32768? -32768:D;
782 #if defined(BPU16)
783       Buf[I] = D+32768;
784 #elif defined(BPS16)
785       Buf[I] = D;
786 #elif defined(BPU8)
787       Buf[I] = (D>>8)+128;
788 #else
789       Buf[I] = D>>8;
790 #endif
791     }
792 
793     /* Play samples */
794     I = WriteAudio(Buf,J);
795   }
796 
797   /* Return number of samples played */
798   return(K);
799 }
800 
801 /** RenderAndPlayAudio() *************************************/
802 /** Render and play a given number of samples. Returns the  **/
803 /** number of samples actually played.                      **/
804 /*************************************************************/
RenderAndPlayAudio(unsigned int Samples)805 unsigned int RenderAndPlayAudio(unsigned int Samples)
806 {
807   int Buf[256];
808   unsigned int J,I;
809 
810   /* Exit if wave sound not initialized */
811   if(SndRate<8192) return(0);
812 
813   J       = GetFreeAudio();
814   Samples = Samples<J? Samples:J;
815 
816   /* Render and play sound */
817   for(I=0;I<Samples;I+=J)
818   {
819     J = Samples-I;
820     J = J<sizeof(Buf)/sizeof(Buf[0])? J:sizeof(Buf)/sizeof(Buf[0]);
821     memset(Buf,0,J*sizeof(Buf[0]));
822     RenderAudio(Buf,J);
823     if(PlayAudio(Buf,J)<J) { I+=J;break; }
824   }
825 
826   /* Return number of samples rendered */
827   return(I);
828 }
829 #endif /* !NO_AUDIO_PLAYBACK */
830