native_midi.cpp 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  1. /*
  2. native_midi: Hardware Midi support for the SDL_mixer library
  3. Copyright (C) 2000,2001 Florian 'Proff' Schulze
  4. This library is free software; you can redistribute it and/or
  5. modify it under the terms of the GNU Library General Public
  6. License as published by the Free Software Foundation; either
  7. version 2 of the License, or (at your option) any later version.
  8. This library is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  11. Library General Public License for more details.
  12. You should have received a copy of the GNU Library General Public
  13. License along with this library; if not, write to the Free
  14. Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  15. Florian 'Proff' Schulze
  16. florian.proff.schulze@gmx.net
  17. */
  18. #include "SDL.h"
  19. /* everything below is currently one very big bad hack ;) Proff */
  20. #define WIN32_LEAN_AND_MEAN
  21. #include <windows.h>
  22. #include <mmsystem.h>
  23. #include <stdlib.h>
  24. #include <memory>
  25. #include <future>
  26. #include <thread>
  27. #include <condition_variable>
  28. #include <mutex>
  29. #include <atomic>
  30. #include <vector>
  31. #include "native_midi/native_midi.h"
  32. #include "native_midi/native_midi_common.h"
  33. static int native_midi_available = -1;
  34. enum class MidiSystemMessage {
  35. Exclusive = 0,
  36. TimeCode = 1,
  37. SongPositionPointer = 2,
  38. SongSelect = 3,
  39. TuneRequest = 6,
  40. EndOfExclusive = 7,
  41. TimingClock = 8,
  42. Start = 10,
  43. Continue = 11,
  44. Stop = 12,
  45. ActiveSensing = 14,
  46. SystemReset = 15
  47. };
  48. struct MidiMessage
  49. {
  50. virtual MMRESULT Send(HMIDIOUT hmo) = 0;
  51. };
  52. struct MidiShortMessage
  53. : public MidiMessage
  54. {
  55. uint32_t data;
  56. MidiShortMessage(uint32_t data) : data(data) {}
  57. virtual MMRESULT Send(HMIDIOUT hmo) { return midiOutShortMsg(hmo, data); }
  58. };
  59. struct MidiLongMessage
  60. : public MidiMessage
  61. {
  62. MIDIHDR hdr;
  63. MidiLongMessage(uint8_t* data, int length)
  64. {
  65. memset(&hdr, 0, sizeof(MIDIHDR));
  66. hdr.lpData = (LPSTR)malloc(length);
  67. hdr.dwBufferLength = hdr.dwBytesRecorded = length;
  68. memcpy(hdr.lpData, data, length);
  69. }
  70. virtual MMRESULT Send(HMIDIOUT hmo)
  71. {
  72. MMRESULT retval;
  73. if (MMSYSERR_NOERROR == (retval = midiOutPrepareHeader(hmo, &hdr, sizeof(MIDIHDR))))
  74. {
  75. retval = midiOutLongMsg(hmo, &hdr, sizeof(MIDIHDR));
  76. midiOutUnprepareHeader(hmo, &hdr, sizeof(MIDIHDR));
  77. }
  78. return retval;
  79. }
  80. };
  81. struct MidiResetMessage
  82. : public MidiMessage
  83. {
  84. virtual MMRESULT Send(HMIDIOUT hmo) { return midiOutReset(hmo); }
  85. };
  86. struct MidiCustomMessage
  87. : public MidiMessage
  88. {
  89. uint32_t message;
  90. uint32_t data1;
  91. uint32_t data2;
  92. MidiCustomMessage(uint8_t status, uint8_t data1, uint8_t data2)
  93. : message(status), data1(data1), data2(data2)
  94. {}
  95. virtual MMRESULT SendEvent(HMIDIOUT hmo) { return midiOutMessage(hmo, message, data1, data2); }
  96. };
  97. struct MidiEvent
  98. {
  99. std::unique_ptr<MidiMessage> message;
  100. uint32_t deltaTime; // time in ticks
  101. uint32_t tempo; // microseconds per quarter note
  102. std::chrono::system_clock::duration DeltaTimeAsTick(uint16_t ppq)
  103. {
  104. return std::chrono::microseconds((int64_t)deltaTime * tempo / ppq);
  105. }
  106. MMRESULT Send(HMIDIOUT hmo) { return message->Send(hmo); }
  107. };
  108. struct _NativeMidiSong {
  109. std::vector<MidiEvent> Events;
  110. std::thread Thread;
  111. std::mutex Mutex;
  112. std::condition_variable Stop;
  113. HMIDIOUT Synthesizer;
  114. int Size;
  115. int Position;
  116. Uint16 ppq; // parts (ticks) per quarter note
  117. volatile bool Playing;
  118. bool Loaded;
  119. bool Looping;
  120. _NativeMidiSong()
  121. : Synthesizer(nullptr), Size(0), Position(0)
  122. , ppq(0), Playing(false), Loaded(false), Looping(false)
  123. { }
  124. };
  125. static void MIDItoStream(NativeMidiSong *song, MIDIEvent *eventlist)
  126. {
  127. int eventcount = 0, prevtime = 0, tempo = 500000;
  128. for (MIDIEvent* event = eventlist; event; event = event->next)
  129. {
  130. if (event->status != 0xFF)
  131. eventcount++;
  132. }
  133. song->Events.resize(song->Size = eventcount);
  134. song->Position = 0;
  135. song->Loaded = true;
  136. eventcount = 0;
  137. for (MIDIEvent* event = eventlist; event; event = event->next)
  138. {
  139. MidiMessage* message = nullptr;
  140. int status = (event->status & 0xF0) >> 4;
  141. switch (status)
  142. {
  143. case MIDI_STATUS_NOTE_OFF:
  144. case MIDI_STATUS_NOTE_ON:
  145. case MIDI_STATUS_AFTERTOUCH:
  146. case MIDI_STATUS_CONTROLLER:
  147. case MIDI_STATUS_PROG_CHANGE:
  148. case MIDI_STATUS_PRESSURE:
  149. case MIDI_STATUS_PITCH_WHEEL:
  150. message = new MidiShortMessage(event->status | (event->data[0] << 8) | (event->data[1] << 16));
  151. break;
  152. case MIDI_STATUS_SYSEX:
  153. switch ((MidiSystemMessage)(event->status & 0xF))
  154. {
  155. case MidiSystemMessage::Exclusive:
  156. message = new MidiLongMessage(event->extraData, event->extraLen);
  157. break;
  158. case MidiSystemMessage::TimeCode:
  159. case MidiSystemMessage::SongSelect:
  160. message = new MidiShortMessage(event->status | (event->extraData[0] << 8));
  161. break;
  162. case MidiSystemMessage::SongPositionPointer:
  163. message = new MidiShortMessage(event->status | (event->extraData[0] << 8) | (event->extraData[1] << 16));
  164. break;
  165. case MidiSystemMessage::TuneRequest:
  166. case MidiSystemMessage::TimingClock:
  167. case MidiSystemMessage::Start:
  168. case MidiSystemMessage::Continue:
  169. case MidiSystemMessage::Stop:
  170. case MidiSystemMessage::ActiveSensing:
  171. message = new MidiShortMessage(event->status);
  172. break;
  173. case MidiSystemMessage::SystemReset:
  174. // This message is only used as meta-event in MIDI files
  175. if (event->data[0] == 0x51)
  176. tempo = (event->extraData[0] << 16) | (event->extraData[1] << 8) | event->extraData[2];
  177. break;
  178. }
  179. break;
  180. }
  181. if (message)
  182. {
  183. auto evt = &song->Events[eventcount++];
  184. evt->message.reset(message);
  185. evt->deltaTime = event->time - prevtime;
  186. evt->tempo = tempo;
  187. prevtime = event->time;
  188. }
  189. }
  190. }
  191. int native_midi_detect()
  192. {
  193. if (-1 == native_midi_available)
  194. {
  195. HMIDIOUT out;
  196. if (MMSYSERR_NOERROR == midiOutOpen(&out, MIDI_MAPPER, 0, 0, CALLBACK_NULL))
  197. {
  198. midiOutClose(out);
  199. native_midi_available = 1;
  200. }
  201. else
  202. native_midi_available = 0;
  203. }
  204. return native_midi_available;
  205. }
  206. NativeMidiSong *native_midi_loadsong(const char *midifile)
  207. {
  208. /* Attempt to load the midi file */
  209. auto rw = SDL_RWFromFile(midifile, "rb");
  210. if (rw)
  211. {
  212. auto song = native_midi_loadsong_RW(rw);
  213. SDL_RWclose(rw);
  214. return song;
  215. }
  216. return nullptr;
  217. }
  218. NativeMidiSong *native_midi_loadsong_RW(SDL_RWops *rw)
  219. {
  220. std::unique_ptr<NativeMidiSong> newsong(new NativeMidiSong);
  221. if (newsong)
  222. {
  223. auto eventlist = CreateMIDIEventList(rw, &newsong->ppq);
  224. if (eventlist)
  225. {
  226. MIDItoStream(newsong.get(), eventlist);
  227. FreeMIDIEventList(eventlist);
  228. if (midiOutOpen(&newsong->Synthesizer, MIDI_MAPPER, NULL, 0, CALLBACK_NULL) == MMSYSERR_NOERROR)
  229. return newsong.release();
  230. }
  231. }
  232. return nullptr;
  233. }
  234. void native_midi_freesong(NativeMidiSong *song)
  235. {
  236. if (song)
  237. {
  238. native_midi_stop(song);
  239. if (song->Synthesizer)
  240. midiOutClose(song->Synthesizer);
  241. delete song;
  242. }
  243. }
  244. void native_midi_start(NativeMidiSong *song, int looping)
  245. {
  246. if (!song) return;
  247. native_midi_stop(song);
  248. song->Playing = true;
  249. song->Looping = looping ? true : false;
  250. song->Thread = std::move(std::thread([](NativeMidiSong *song)->void {
  251. auto time = std::chrono::system_clock::now();
  252. while (song->Playing)
  253. {
  254. do
  255. {
  256. song->Events[song->Position++].Send(song->Synthesizer);
  257. } while (song->Position < song->Size && song->Events[song->Position].deltaTime == 0);
  258. if (song->Position < song->Size)
  259. {
  260. auto mutex = std::unique_lock<std::mutex>(song->Mutex);
  261. time += std::chrono::system_clock::duration(song->Events[song->Position].DeltaTimeAsTick(song->ppq));
  262. while (song->Playing)
  263. {
  264. if (song->Stop.wait_until(mutex, time) == std::cv_status::timeout)
  265. break;
  266. }
  267. }
  268. else if (song->Playing = song->Looping)
  269. {
  270. song->Position = 0;
  271. midiOutReset(song->Synthesizer);
  272. }
  273. }
  274. }, song));
  275. }
  276. void native_midi_stop(NativeMidiSong *song)
  277. {
  278. if (song)
  279. {
  280. song->Playing = false;
  281. song->Stop.notify_all();
  282. if (song->Thread.joinable())
  283. song->Thread.join();
  284. song->Thread = std::move(std::thread());
  285. if (song->Synthesizer)
  286. midiOutReset(song->Synthesizer);
  287. }
  288. }
  289. int native_midi_active(NativeMidiSong *song)
  290. {
  291. return (song && song->Playing) ? 1 : 0;
  292. }
  293. void native_midi_setvolume(NativeMidiSong *song, int volume)
  294. {
  295. if (song && song->Synthesizer)
  296. {
  297. uint16_t calcVolume;
  298. if (volume > 127)
  299. volume = 127;
  300. if (volume < 0)
  301. volume = 0;
  302. calcVolume = volume << 9;
  303. midiOutSetVolume(song->Synthesizer, MAKELONG(calcVolume, calcVolume));
  304. }
  305. }
  306. const char *native_midi_error(NativeMidiSong *song)
  307. {
  308. return "";
  309. }