Sound.c 15 KB

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