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