native_midi.cpp 8.8 KB

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