Sound.c 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497
  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-2007 **/
  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. #include <stdio.h>
  16. #include <string.h>
  17. #ifdef UNIX
  18. #include <unistd.h>
  19. #endif
  20. typedef unsigned char byte;
  21. typedef unsigned short word;
  22. struct SndDriverStruct SndDriver =
  23. {
  24. (void (*)(int,int))0,
  25. (void (*)(int,int))0,
  26. (void (*)(int,int))0,
  27. (void (*)(int,int,int))0,
  28. (void (*)(int,const signed char *,int,int))0,
  29. (const signed char *(*)(int))0
  30. };
  31. static const struct { byte Note;word Wheel; } Freqs[4096] =
  32. {
  33. #include "MIDIFreq.h"
  34. };
  35. static const int Programs[5] =
  36. {
  37. 80, /* SND_MELODIC/SND_RECTANGLE */
  38. 80, /* SND_TRIANGLE */
  39. 122, /* SND_NOISE */
  40. 122, /* SND_PERIODIC */
  41. 80 /* SND_WAVE */
  42. };
  43. static struct
  44. {
  45. int Type;
  46. int Note;
  47. int Pitch;
  48. int Level;
  49. } CH[MIDI_CHANNELS] =
  50. {
  51. { -1,-1,-1,-1 },
  52. { -1,-1,-1,-1 },
  53. { -1,-1,-1,-1 },
  54. { -1,-1,-1,-1 },
  55. { -1,-1,-1,-1 },
  56. { -1,-1,-1,-1 },
  57. { -1,-1,-1,-1 },
  58. { -1,-1,-1,-1 },
  59. { -1,-1,-1,-1 },
  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. };
  68. static const char *LogName = 0;
  69. static int Logging = MIDI_OFF;
  70. static int TickCount = 0;
  71. static int LastMsg = -1;
  72. static int DrumOn = 0;
  73. static FILE *MIDIOut = 0;
  74. static void MIDISound(int Channel,int Freq,int Volume);
  75. static void MIDISetSound(int Channel,int Type);
  76. static void MIDIDrum(int Type,int Force);
  77. static void MIDIMessage(byte D0,byte D1,byte D2);
  78. static void NoteOn(byte Channel,byte Note,byte Level);
  79. static void NoteOff(byte Channel);
  80. static void WriteDelta(void);
  81. static void WriteTempo(int Freq);
  82. /** SHIFT() **************************************************/
  83. /** Make MIDI channel#10 last, as it is normally used for **/
  84. /** percussion instruments only and doesn't sound nice. **/
  85. /*************************************************************/
  86. #define SHIFT(Ch) (Ch==15? 9:Ch>8? Ch+1:Ch)
  87. /** Sound() **************************************************/
  88. /** Generate sound of given frequency (Hz) and volume **/
  89. /** (0..255) via given channel. Setting Freq=0 or Volume=0 **/
  90. /** turns sound off. **/
  91. /*************************************************************/
  92. void Sound(int Channel,int Freq,int Volume)
  93. {
  94. if(Channel<0) return;
  95. Freq = Freq<0? 0:Freq;
  96. Volume = Volume<0? 0:Volume>255? 255:Volume;
  97. /* Call sound driver if present */
  98. if(SndDriver.Sound) (*SndDriver.Sound)(Channel,Freq,Volume);
  99. /* Log sound to MIDI file */
  100. MIDISound(Channel,Freq,Volume);
  101. }
  102. /** Drum() ***************************************************/
  103. /** Hit a drum of given type with given force (0..255). **/
  104. /** MIDI drums can be used by ORing their numbers with **/
  105. /** SND_MIDI. **/
  106. /*************************************************************/
  107. void Drum(int Type,int Force)
  108. {
  109. Force = Force<0? 0:Force>255? 255:Force;
  110. if(SndDriver.Drum) (*SndDriver.Drum)(Type,Force);
  111. /* Log drum to MIDI file */
  112. MIDIDrum(Type,Force);
  113. }
  114. /** SetSound() ***********************************************/
  115. /** Set sound type at a given channel. MIDI instruments can **/
  116. /** be set directly by ORing their numbers with SND_MIDI. **/
  117. /*************************************************************/
  118. void SetSound(int Channel,int Type)
  119. {
  120. if(Channel<0) return;
  121. if(SndDriver.SetSound) (*SndDriver.SetSound)(Channel,Type);
  122. /* Log instrument change to MIDI file */
  123. MIDISetSound(Channel,Type);
  124. }
  125. /** SetChannels() ********************************************/
  126. /** Set master volume (0..255) and switch channels on/off. **/
  127. /** Each channel N has corresponding bit 2^N in Switch. Set **/
  128. /** or reset this bit to turn the channel on or off. **/
  129. /*************************************************************/
  130. void SetChannels(int Volume,int Switch)
  131. {
  132. Volume = Volume<0? 0:Volume>255? 255:Volume;
  133. if(SndDriver.SetChannels) (*SndDriver.SetChannels)(Volume,Switch);
  134. }
  135. /** SetWave() ************************************************/
  136. /** Set waveform for a given channel. The channel will be **/
  137. /** marked with sound type SND_WAVE. Set Rate=0 if you want **/
  138. /** waveform to be an instrument or set it to the waveform **/
  139. /** own playback rate. **/
  140. /*************************************************************/
  141. void SetWave(int Channel,const signed char *Data,int Length,int Rate)
  142. {
  143. if((Channel<0)||(Length<=0)) return;
  144. if(SndDriver.SetWave) (*SndDriver.SetWave)(Channel,Data,Length,Rate);
  145. /* Log instrument change to MIDI file */
  146. MIDISetSound(Channel,Rate? -1:SND_MELODIC);
  147. }
  148. /** GetWave() ************************************************/
  149. /** Get current read position for the buffer set with the **/
  150. /** SetWave() call. Returns 0 if no buffer has been set, or **/
  151. /** if there is no playrate set (i.e. wave is instrument). **/
  152. /*************************************************************/
  153. const signed char *GetWave(int Channel)
  154. {
  155. return(SndDriver.GetWave? (*SndDriver.GetWave)(Channel):0);
  156. }
  157. /** InitMIDI() ***********************************************/
  158. /** Initialize soundtrack logging into MIDI file FileName. **/
  159. /** Repeated calls to InitMIDI() will close current MIDI **/
  160. /** file and continue logging into a new one. **/
  161. /*************************************************************/
  162. void InitMIDI(const char *FileName)
  163. {
  164. int WasLogging;
  165. /* Must pass a name! */
  166. if(!FileName) return;
  167. /* Memorize logging status */
  168. WasLogging=Logging;
  169. /* If MIDI logging in progress, close current file */
  170. if(MIDIOut) TrashMIDI();
  171. /* Set log file name and ticks/second parameter, no logging yet */
  172. LogName = FileName;
  173. Logging = MIDI_OFF;
  174. LastMsg = -1;
  175. TickCount = 0;
  176. MIDIOut = 0;
  177. DrumOn = 0;
  178. /* If was logging, restart */
  179. if(WasLogging) MIDILogging(MIDI_ON);
  180. }
  181. /** TrashMIDI() **********************************************/
  182. /** Finish logging soundtrack and close the MIDI file. **/
  183. /*************************************************************/
  184. void TrashMIDI(void)
  185. {
  186. long Length;
  187. int J;
  188. /* If not logging, drop out */
  189. if(!MIDIOut) return;
  190. /* Turn sound off */
  191. for(J=0;J<MIDI_CHANNELS;J++) NoteOff(J);
  192. /* End of track */
  193. MIDIMessage(0xFF,0x2F,0x00);
  194. /* Put track length in file */
  195. fseek(MIDIOut,0,SEEK_END);
  196. Length=ftell(MIDIOut)-22;
  197. fseek(MIDIOut,18,SEEK_SET);
  198. fputc((Length>>24)&0xFF,MIDIOut);
  199. fputc((Length>>16)&0xFF,MIDIOut);
  200. fputc((Length>>8)&0xFF,MIDIOut);
  201. fputc(Length&0xFF,MIDIOut);
  202. /* Done logging */
  203. fclose(MIDIOut);
  204. Logging = MIDI_OFF;
  205. LastMsg = -1;
  206. TickCount = 0;
  207. MIDIOut = 0;
  208. }
  209. /** MIDILogging() ********************************************/
  210. /** Turn soundtrack logging on/off and return its current **/
  211. /** status. Possible values of Switch are MIDI_OFF (turn **/
  212. /** logging off), MIDI_ON (turn logging on), MIDI_TOGGLE **/
  213. /** (toggle logging), and MIDI_QUERY (just return current **/
  214. /** state of logging). **/
  215. /*************************************************************/
  216. int MIDILogging(int Switch)
  217. {
  218. static const char MThd[] = "MThd\0\0\0\006\0\0\0\1";
  219. /* ID DataLen Fmt Trks */
  220. static const char MTrk[] = "MTrk\0\0\0\0";
  221. /* ID TrkLen */
  222. int J,I;
  223. /* Toggle logging if requested */
  224. if(Switch==MIDI_TOGGLE) Switch=!Logging;
  225. if((Switch==MIDI_ON)||(Switch==MIDI_OFF))
  226. if(Switch^Logging)
  227. {
  228. /* When turning logging off, silence all channels */
  229. if(!Switch&&MIDIOut)
  230. for(J=0;J<MIDI_CHANNELS;J++) NoteOff(J);
  231. /* When turning logging on, open MIDI file */
  232. if(Switch&&!MIDIOut&&LogName)
  233. {
  234. /* No messages have been sent yet */
  235. LastMsg=-1;
  236. /* Clear all storage */
  237. for(J=0;J<MIDI_CHANNELS;J++)
  238. CH[J].Note=CH[J].Pitch=CH[J].Level=-1;
  239. /* Open new file and write out the header */
  240. MIDIOut=fopen(LogName,"wb");
  241. if(!MIDIOut) return(MIDI_OFF);
  242. if(fwrite(MThd,1,12,MIDIOut)!=12)
  243. { fclose(MIDIOut);MIDIOut=0;return(MIDI_OFF); }
  244. fputc((MIDI_DIVISIONS>>8)&0xFF,MIDIOut);
  245. fputc(MIDI_DIVISIONS&0xFF,MIDIOut);
  246. if(fwrite(MTrk,1,8,MIDIOut)!=8)
  247. { fclose(MIDIOut);MIDIOut=0;return(MIDI_OFF); }
  248. /* Write out the tempo */
  249. WriteTempo(MIDI_DIVISIONS);
  250. }
  251. /* Turn logging off on failure to open MIDIOut */
  252. if(!MIDIOut) Switch=MIDI_OFF;
  253. /* Assign new switch value */
  254. Logging=Switch;
  255. /* If switching logging on... */
  256. if(Switch)
  257. {
  258. /* Start logging without a pause */
  259. TickCount=0;
  260. /* Write instrument changes */
  261. for(J=0;J<MIDI_CHANNELS;J++)
  262. if((CH[J].Type>=0)&&(CH[J].Type&0x10000))
  263. {
  264. I=CH[J].Type&~0x10000;
  265. CH[J].Type=-1;
  266. MIDISetSound(J,I);
  267. }
  268. }
  269. }
  270. /* Return current logging status */
  271. return(Logging);
  272. }
  273. /** MIDITicks() **********************************************/
  274. /** Log N 1ms MIDI ticks. **/
  275. /*************************************************************/
  276. void MIDITicks(int N)
  277. {
  278. if(Logging&&MIDIOut&&(N>0)) TickCount+=N;
  279. }
  280. /** MIDISound() **********************************************/
  281. /** Set sound frequency (Hz) and volume (0..255) for a **/
  282. /** given channel. **/
  283. /*************************************************************/
  284. void MIDISound(int Channel,int Freq,int Volume)
  285. {
  286. int MIDIVolume,MIDINote,MIDIWheel;
  287. /* If logging off, file closed, or invalid channel, drop out */
  288. if(!Logging||!MIDIOut||(Channel>=MIDI_CHANNELS-1)||(Channel<0)) return;
  289. /* Frequency must be in range */
  290. if((Freq<MIDI_MINFREQ)||(Freq>MIDI_MAXFREQ)) Freq=0;
  291. /* Volume must be in range */
  292. if(Volume<0) Volume=0; else if(Volume>255) Volume=255;
  293. /* Instrument number must be valid */
  294. if(CH[Channel].Type<0) Freq=0;
  295. if(!Volume||!Freq) NoteOff(Channel);
  296. else
  297. {
  298. /* SND_TRIANGLE is twice quieter than SND_MELODIC */
  299. if(CH[Channel].Type==SND_TRIANGLE) Volume=(Volume+1)/2;
  300. /* Compute MIDI note parameters */
  301. MIDIVolume = (127*Volume+128)/255;
  302. MIDINote = Freqs[Freq/3].Note;
  303. MIDIWheel = Freqs[Freq/3].Wheel;
  304. /* Play new note */
  305. NoteOn(Channel,MIDINote,MIDIVolume);
  306. /* Change pitch */
  307. if(CH[Channel].Pitch!=MIDIWheel)
  308. {
  309. MIDIMessage(0xE0+SHIFT(Channel),MIDIWheel&0x7F,(MIDIWheel>>7)&0x7F);
  310. CH[Channel].Pitch=MIDIWheel;
  311. }
  312. }
  313. }
  314. /** MIDISetSound() *******************************************/
  315. /** Set sound type for a given channel. **/
  316. /*************************************************************/
  317. void MIDISetSound(int Channel,int Type)
  318. {
  319. /* Channel must be valid */
  320. if((Channel>=MIDI_CHANNELS-1)||(Channel<0)) return;
  321. /* If instrument changed... */
  322. if(CH[Channel].Type!=Type)
  323. {
  324. /* If logging off or file closed, drop out */
  325. if(!Logging||!MIDIOut) CH[Channel].Type=Type|0x10000;
  326. else
  327. {
  328. CH[Channel].Type=Type;
  329. if(Type<0) NoteOff(Channel);
  330. else
  331. {
  332. Type=Type&SND_MIDI? (Type&0x7F):Programs[Type%5];
  333. MIDIMessage(0xC0+SHIFT(Channel),Type,255);
  334. }
  335. }
  336. }
  337. }
  338. /** MIDIDrum() ***********************************************/
  339. /** Hit a drum of a given type with given force. **/
  340. /*************************************************************/
  341. void MIDIDrum(int Type,int Force)
  342. {
  343. /* If logging off or invalid channel, drop out */
  344. if(!Logging||!MIDIOut) return;
  345. /* The only non-MIDI drum is a click ("Low Wood Block") */
  346. Type=Type&DRM_MIDI? (Type&0x7F):77;
  347. /* Release previous drum */
  348. if(DrumOn) MIDIMessage(0x89,DrumOn,127);
  349. /* Hit next drum */
  350. if(Type) MIDIMessage(0x99,Type,(Force&0xFF)/2);
  351. DrumOn=Type;
  352. }
  353. /** MIDIMessage() ********************************************/
  354. /** Write out a MIDI message. **/
  355. /*************************************************************/
  356. void MIDIMessage(byte D0,byte D1,byte D2)
  357. {
  358. /* Write number of ticks that passed */
  359. WriteDelta();
  360. /* Write out the command */
  361. if(D0!=LastMsg) { LastMsg=D0;fputc(D0,MIDIOut); }
  362. /* Write out the arguments */
  363. if(D1<128)
  364. {
  365. fputc(D1,MIDIOut);
  366. if(D2<128) fputc(D2,MIDIOut);
  367. }
  368. }
  369. /** NoteOn() *************************************************/
  370. /** Turn on a note on a given channel. **/
  371. /*************************************************************/
  372. void NoteOn(byte Channel,byte Note,byte Level)
  373. {
  374. Note = Note>0x7F? 0x7F:Note;
  375. Level = Level>0x7F? 0x7F:Level;
  376. if((CH[Channel].Note!=Note)||(CH[Channel].Level!=Level))
  377. {
  378. if(CH[Channel].Note>=0) NoteOff(Channel);
  379. MIDIMessage(0x90+SHIFT(Channel),Note,Level);
  380. CH[Channel].Note=Note;
  381. CH[Channel].Level=Level;
  382. }
  383. }
  384. /** NoteOff() ************************************************/
  385. /** Turn off a note on a given channel. **/
  386. /*************************************************************/
  387. void NoteOff(byte Channel)
  388. {
  389. if(CH[Channel].Note>=0)
  390. {
  391. MIDIMessage(0x80+SHIFT(Channel),CH[Channel].Note,127);
  392. CH[Channel].Note=-1;
  393. }
  394. }
  395. /** WriteDelta() *********************************************/
  396. /** Write number of ticks since the last MIDI command and **/
  397. /** reset the counter. **/
  398. /*************************************************************/
  399. void WriteDelta(void)
  400. {
  401. if(TickCount<128) fputc(TickCount,MIDIOut);
  402. else
  403. {
  404. if(TickCount<128*128)
  405. {
  406. fputc((TickCount>>7)|0x80,MIDIOut);
  407. fputc(TickCount&0x7F,MIDIOut);
  408. }
  409. else
  410. {
  411. fputc(((TickCount>>14)&0x7F)|0x80,MIDIOut);
  412. fputc(((TickCount>>7)&0x7F)|0x80,MIDIOut);
  413. fputc(TickCount&0x7F,MIDIOut);
  414. }
  415. }
  416. TickCount=0;
  417. }
  418. /** WriteTempo() *********************************************/
  419. /** Write out soundtrack tempo (Hz). **/
  420. /*************************************************************/
  421. void WriteTempo(int Freq)
  422. {
  423. int J;
  424. J=500000*MIDI_DIVISIONS*2/Freq;
  425. WriteDelta();
  426. fputc(0xFF,MIDIOut);
  427. fputc(0x51,MIDIOut);
  428. fputc(0x03,MIDIOut);
  429. fputc((J>>16)&0xFF,MIDIOut);
  430. fputc((J>>8)&0xFF,MIDIOut);
  431. fputc(J&0xFF,MIDIOut);
  432. }