native_midi_alsa.c 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758
  1. #include "native_midi/native_midi.h"
  2. #include "native_midi/native_midi_common.h"
  3. #include <SDL_thread.h>
  4. #include <alsa/asoundlib.h>
  5. typedef struct {
  6. uint32_t tick;
  7. uint8_t type, channel, data1, data2;
  8. union {
  9. int32_t pitch;
  10. int32_t tempo;
  11. uint32_t len;
  12. };
  13. uint8_t *sysex;
  14. uint64_t time;
  15. } midi_event_info;
  16. struct _NativeMidiSong {
  17. snd_seq_t *sequencer;
  18. int src_client_id;
  19. int src_port_id;
  20. int dst_client_id;
  21. int dst_port_id;
  22. int queue;
  23. midi_event_info *events;
  24. unsigned int timediv;
  25. volatile int playing;
  26. int current_volume;
  27. volatile int new_volume;
  28. int looping;
  29. SDL_Thread *thread;
  30. };
  31. static const uint8_t alsa_event_types[8] = {
  32. SND_SEQ_EVENT_NOTEOFF,
  33. SND_SEQ_EVENT_NOTEON,
  34. SND_SEQ_EVENT_KEYPRESS,
  35. SND_SEQ_EVENT_CONTROLLER,
  36. SND_SEQ_EVENT_PGMCHANGE,
  37. SND_SEQ_EVENT_CHANPRESS,
  38. SND_SEQ_EVENT_PITCHBEND,
  39. SND_SEQ_EVENT_NONE
  40. };
  41. static int native_midi_available = -1;
  42. static int dst_client_id = 0;
  43. static int dst_port_id = 0;
  44. static void error_handler(const char *file, int line, const char *function, int err, const char *fmt, ...)
  45. {
  46. // do nothing
  47. }
  48. static int create_src_port(NativeMidiSong *song)
  49. {
  50. snd_seq_port_info_t *pinfo;
  51. snd_seq_port_info_alloca(&pinfo);
  52. snd_seq_port_info_set_name(pinfo, "PAL-midi");
  53. snd_seq_port_info_set_capability(pinfo, 0);
  54. snd_seq_port_info_set_type(pinfo, SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION);
  55. if (snd_seq_create_port(song->sequencer, pinfo) < 0) return -1;
  56. song->src_port_id = snd_seq_port_info_get_port(pinfo);
  57. return 0;
  58. }
  59. static int find_dst_port(NativeMidiSong *song, const char *midi_client)
  60. {
  61. snd_seq_client_info_t *cinfo;
  62. snd_seq_port_info_t *pinfo;
  63. int client_id, port_id;
  64. if (midi_client != NULL)
  65. {
  66. snd_seq_addr_t addr;
  67. if (snd_seq_parse_address(song->sequencer, &addr, midi_client) < 0) return -1;
  68. song->dst_client_id = addr.client;
  69. song->dst_port_id = addr.port;
  70. snd_seq_port_info_alloca(&pinfo);
  71. if (snd_seq_get_any_port_info(song->sequencer, song->dst_client_id, song->dst_port_id, pinfo) < 0) return -2;
  72. if ((snd_seq_port_info_get_capability(pinfo) & (SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE | SND_SEQ_PORT_CAP_NO_EXPORT)) != (SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE))
  73. {
  74. return -3;
  75. }
  76. else
  77. {
  78. return 0;
  79. }
  80. }
  81. else
  82. {
  83. client_id = -1;
  84. snd_seq_client_info_alloca(&cinfo);
  85. snd_seq_port_info_alloca(&pinfo);
  86. snd_seq_client_info_set_client(cinfo, -1);
  87. while (snd_seq_query_next_client(song->sequencer, cinfo) >= 0)
  88. {
  89. snd_seq_port_info_set_client(pinfo, snd_seq_client_info_get_client(cinfo));
  90. snd_seq_port_info_set_port(pinfo, -1);
  91. while (snd_seq_query_next_port(song->sequencer, pinfo) >= 0)
  92. {
  93. if ( ((snd_seq_port_info_get_capability(pinfo) & (SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE | SND_SEQ_PORT_CAP_NO_EXPORT)) == (SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE)) &&
  94. (snd_seq_port_info_get_type(pinfo) & (SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_MIDI_GM))
  95. )
  96. {
  97. if (snd_seq_port_info_get_midi_channels(pinfo))
  98. {
  99. song->dst_client_id = snd_seq_client_info_get_client(cinfo);
  100. song->dst_port_id = snd_seq_port_info_get_port(pinfo);
  101. return 0;
  102. }
  103. else if (client_id == -1)
  104. {
  105. client_id = snd_seq_client_info_get_client(cinfo);
  106. port_id = snd_seq_port_info_get_port(pinfo);
  107. }
  108. }
  109. }
  110. }
  111. if (client_id != -1)
  112. {
  113. song->dst_client_id = client_id;
  114. song->dst_port_id = port_id;
  115. return 0;
  116. }
  117. else
  118. {
  119. return -4;
  120. }
  121. }
  122. }
  123. static int open_alsa_port(NativeMidiSong *song)
  124. {
  125. if (song == NULL) return -1;
  126. if (song->sequencer != NULL) return -2;
  127. if (snd_seq_open(&(song->sequencer), "default", SND_SEQ_OPEN_DUPLEX, 0) < 0)
  128. {
  129. song->sequencer = NULL;
  130. return -3;
  131. }
  132. if (snd_seq_set_client_name(song->sequencer, "PAL-midi") < 0)
  133. {
  134. snd_seq_close(song->sequencer);
  135. song->sequencer = NULL;
  136. return -4;
  137. }
  138. song->src_client_id = snd_seq_client_id(song->sequencer);
  139. if (song->src_client_id < 0)
  140. {
  141. snd_seq_close(song->sequencer);
  142. song->sequencer = NULL;
  143. return -5;
  144. }
  145. if (dst_client_id == 0)
  146. {
  147. if (find_dst_port(song, getenv("MIDI_CLIENT")) < 0)
  148. {
  149. snd_seq_close(song->sequencer);
  150. song->sequencer = NULL;
  151. return -6;
  152. }
  153. }
  154. else
  155. {
  156. song->dst_client_id = dst_client_id;
  157. song->dst_port_id = dst_port_id;
  158. }
  159. if (create_src_port(song) < 0)
  160. {
  161. snd_seq_close(song->sequencer);
  162. song->sequencer = NULL;
  163. return -7;
  164. }
  165. song->queue = snd_seq_alloc_named_queue(song->sequencer, "PAL-midi");
  166. if (song->queue < 0)
  167. {
  168. snd_seq_delete_port(song->sequencer, song->src_port_id);
  169. snd_seq_close(song->sequencer);
  170. song->sequencer = NULL;
  171. return -8;
  172. }
  173. if (snd_seq_connect_to(song->sequencer, song->src_port_id, song->dst_client_id, song->dst_port_id) < 0)
  174. {
  175. snd_seq_free_queue(song->sequencer, song->queue);
  176. snd_seq_delete_port(song->sequencer, song->src_port_id);
  177. snd_seq_close(song->sequencer);
  178. song->sequencer = NULL;
  179. return -9;
  180. }
  181. return 0;
  182. }
  183. static void close_alsa_port(NativeMidiSong *song)
  184. {
  185. if (song->sequencer != NULL)
  186. {
  187. snd_seq_disconnect_to(song->sequencer, song->src_port_id, song->dst_client_id, song->dst_port_id);
  188. snd_seq_free_queue(song->sequencer, song->queue);
  189. snd_seq_delete_port(song->sequencer, song->src_port_id);
  190. snd_seq_close(song->sequencer);
  191. song->sequencer = NULL;
  192. }
  193. }
  194. static int convert_event_list(NativeMidiSong *song, MIDIEvent *eventlist)
  195. {
  196. int eventcount = 0;
  197. MIDIEvent* eventlist1;
  198. for (eventlist1 = eventlist; eventlist1 != NULL; eventlist1 = eventlist1->next)
  199. {
  200. eventcount++;
  201. }
  202. midi_event_info *events = calloc(eventcount + 1, sizeof(midi_event_info));
  203. if (events == NULL) return -1;
  204. events[0].tick = 0;
  205. events[0].type = SND_SEQ_EVENT_NONE;
  206. events[0].len = 0;
  207. events[0].sysex = NULL;
  208. events[0].time = 0;
  209. unsigned int tempo, tempo_tick;
  210. uint64_t tempo_time;
  211. tempo = 500000; // 500000 MPQN = 120 BPM
  212. tempo_tick = 0;
  213. tempo_time = 0;
  214. unsigned int time_division = song->timediv;
  215. eventcount = 0;
  216. for (; eventlist != NULL; eventlist = eventlist->next)
  217. {
  218. midi_event_info event;
  219. event.tick = eventlist->time;
  220. event.sysex = NULL;
  221. event.type = SND_SEQ_EVENT_NONE;
  222. // calculate event time in nanoseconds
  223. {
  224. div_t divres;
  225. divres = div(event.tick - tempo_tick, time_division);
  226. event.time = ( ((1000 * divres.rem) * (uint64_t)tempo) / time_division )
  227. + ( (divres.quot * (uint64_t)tempo) * 1000 )
  228. + tempo_time
  229. ;
  230. //event.time = ( (((event.tick - tempo_tick) * (uint64_t) 1000) * tempo) / time_division ) + tempo_time;
  231. }
  232. int status = (eventlist->status & 0xf0) >> 4;
  233. switch (status)
  234. {
  235. case MIDI_STATUS_NOTE_OFF:
  236. case MIDI_STATUS_NOTE_ON:
  237. case MIDI_STATUS_AFTERTOUCH:
  238. case MIDI_STATUS_CONTROLLER:
  239. case MIDI_STATUS_PROG_CHANGE:
  240. case MIDI_STATUS_PRESSURE:
  241. case MIDI_STATUS_PITCH_WHEEL:
  242. event.type = alsa_event_types[status - 8];
  243. event.channel = eventlist->status & 0x0f;
  244. event.data1 = eventlist->data[0];
  245. event.data2 = eventlist->data[1];
  246. if (status == MIDI_STATUS_PITCH_WHEEL)
  247. {
  248. event.pitch = ( ((int32_t)event.data1) | (((int32_t)event.data2) << 7) ) - 0x2000;
  249. }
  250. break;
  251. case MIDI_STATUS_SYSEX:
  252. if (eventlist->status == 0xff) // meta events
  253. {
  254. if (eventlist->data[0] == 0x51) // set tempo
  255. {
  256. event.type = SND_SEQ_EVENT_TEMPO;
  257. event.channel = eventlist->extraData[0];
  258. event.data1 = eventlist->extraData[1];
  259. event.data2 = eventlist->extraData[2];
  260. event.tempo = (((uint32_t)event.channel) << 16) | (((uint32_t)event.data1) << 8) | ((uint32_t)event.data2);
  261. tempo = event.tempo;
  262. tempo_tick = event.tick;
  263. tempo_time = event.time;
  264. }
  265. }
  266. else if ((eventlist->status == 0xf0) || (eventlist->status == 0xf7)) // sysex
  267. {
  268. event.type = SND_SEQ_EVENT_SYSEX;
  269. event.len = eventlist->extraLen + (eventlist->status == 0xf0)?1:0;
  270. if (event.len)
  271. {
  272. event.sysex = (uint8_t *) malloc(event.len);
  273. if (event.sysex != NULL)
  274. {
  275. if (eventlist->status == 0xf0)
  276. {
  277. event.sysex[0] = 0xf0;
  278. memcpy(event.sysex + 1, eventlist->extraData, eventlist->extraLen);
  279. }
  280. else
  281. {
  282. memcpy(event.sysex, eventlist->extraData, eventlist->extraLen);
  283. }
  284. }
  285. }
  286. }
  287. break;
  288. }
  289. if (event.type != SND_SEQ_EVENT_NONE)
  290. {
  291. eventcount++;
  292. events[eventcount] = event;
  293. }
  294. }
  295. events[0].len = eventcount;
  296. song->events = events;
  297. return 0;
  298. }
  299. static void free_midi_events(NativeMidiSong *song)
  300. {
  301. unsigned int index;
  302. if (song->events != NULL)
  303. {
  304. for (index = song->events[0].len; index != 0; index--)
  305. {
  306. if (song->events[index].sysex != NULL)
  307. {
  308. free(song->events[index].sysex);
  309. song->events[index].sysex = NULL;
  310. }
  311. }
  312. free(song->events);
  313. song->events = NULL;
  314. }
  315. }
  316. static int play_midi(void *_song)
  317. {
  318. NativeMidiSong *song = (NativeMidiSong *)_song;
  319. midi_event_info *events;
  320. unsigned int current_event, base_tick, last_tick, num_events;
  321. uint64_t base_time;
  322. // set queue tempo
  323. {
  324. snd_seq_queue_tempo_t *queue_tempo;
  325. snd_seq_queue_tempo_alloca(&queue_tempo);
  326. snd_seq_queue_tempo_set_tempo(queue_tempo, 500000); // 120 BPM
  327. snd_seq_queue_tempo_set_ppq(queue_tempo, song->timediv);
  328. if (0 > snd_seq_set_queue_tempo(song->sequencer, song->queue, queue_tempo))
  329. {
  330. return 2;
  331. }
  332. }
  333. // start play
  334. if (0 > snd_seq_start_queue(song->sequencer, song->queue, NULL))
  335. {
  336. return 3;
  337. }
  338. if (0 > snd_seq_drain_output(song->sequencer))
  339. {
  340. return 4;
  341. }
  342. events = song->events;
  343. num_events = events[0].len;
  344. current_event = 1;
  345. base_tick = 0;
  346. base_time = 0;
  347. last_tick = 0;
  348. snd_seq_sync_output_queue(song->sequencer);
  349. int do_sleep;
  350. snd_seq_queue_status_t *queue_status;
  351. const snd_seq_real_time_t *real_time;
  352. int64_t time_diff, base_diff;
  353. snd_seq_event_t event;
  354. snd_seq_queue_status_alloca(&queue_status);
  355. snd_seq_ev_clear(&event);
  356. event.queue = song->queue;
  357. event.source.port = song->src_port_id;
  358. event.flags = SND_SEQ_TIME_STAMP_TICK | SND_SEQ_TIME_MODE_ABS;
  359. do_sleep = 0;
  360. while (1)
  361. {
  362. if (do_sleep)
  363. {
  364. do_sleep = 0;
  365. SDL_Delay(10);
  366. }
  367. if (!song->playing) break;
  368. if (current_event > num_events)
  369. {
  370. if (!song->looping)
  371. {
  372. song->playing = 0;
  373. break;
  374. }
  375. // looping
  376. base_tick += events[num_events].tick;
  377. base_time += events[num_events].time;
  378. current_event = 1;
  379. }
  380. if ((song->new_volume != song->current_volume) && (events[current_event].tick != 0))
  381. {
  382. int chan;
  383. song->current_volume = song->new_volume;
  384. snd_seq_ev_set_fixed(&event);
  385. event.type = SND_SEQ_EVENT_CONTROLLER;
  386. event.time.tick = base_tick + events[current_event - 1].tick;
  387. event.dest.client = song->dst_client_id;
  388. event.dest.port = song->dst_port_id;
  389. event.data.control.param = MIDI_CTL_MSB_MAIN_VOLUME;
  390. event.data.control.value = song->current_volume;
  391. for (chan = 0; chan < 16; chan++)
  392. {
  393. event.data.control.channel = chan;
  394. snd_seq_event_output(song->sequencer, &event);
  395. }
  396. snd_seq_drain_output(song->sequencer);
  397. }
  398. if (0 > snd_seq_get_queue_status(song->sequencer, song->queue, queue_status))
  399. {
  400. do_sleep = 1;
  401. continue;
  402. }
  403. real_time = snd_seq_queue_status_get_real_time(queue_status);
  404. base_diff = ((real_time->tv_sec * (uint64_t)1000000000) + real_time->tv_nsec) - base_time;
  405. time_diff = events[current_event].time - base_diff;
  406. if (time_diff >= 100000000) // 100ms
  407. {
  408. do_sleep = 1;
  409. continue;
  410. }
  411. do
  412. {
  413. // add events to queue
  414. event.type = events[current_event].type;
  415. event.time.tick = base_tick + events[current_event].tick;
  416. event.dest.client = song->dst_client_id;
  417. event.dest.port = song->dst_port_id;
  418. switch (event.type)
  419. {
  420. case SND_SEQ_EVENT_NOTEON:
  421. case SND_SEQ_EVENT_NOTEOFF:
  422. case SND_SEQ_EVENT_KEYPRESS:
  423. snd_seq_ev_set_fixed(&event);
  424. event.data.note.channel = events[current_event].channel;
  425. event.data.note.note = events[current_event].data1;
  426. event.data.note.velocity = events[current_event].data2;
  427. break;
  428. case SND_SEQ_EVENT_CONTROLLER:
  429. snd_seq_ev_set_fixed(&event);
  430. event.data.control.channel = events[current_event].channel;
  431. event.data.control.param = events[current_event].data1;
  432. event.data.control.value = events[current_event].data2;
  433. break;
  434. case SND_SEQ_EVENT_PGMCHANGE:
  435. case SND_SEQ_EVENT_CHANPRESS:
  436. snd_seq_ev_set_fixed(&event);
  437. event.data.control.channel = events[current_event].channel;
  438. event.data.control.value = events[current_event].data1;
  439. break;
  440. case SND_SEQ_EVENT_PITCHBEND:
  441. snd_seq_ev_set_fixed(&event);
  442. event.data.control.channel = events[current_event].channel;
  443. event.data.control.value = events[current_event].pitch;
  444. break;
  445. case SND_SEQ_EVENT_SYSEX:
  446. snd_seq_ev_set_variable(&event, events[current_event].len, events[current_event].sysex);
  447. break;
  448. case SND_SEQ_EVENT_TEMPO:
  449. snd_seq_ev_set_fixed(&event);
  450. event.dest.client = SND_SEQ_CLIENT_SYSTEM;
  451. event.dest.port = SND_SEQ_PORT_SYSTEM_TIMER;
  452. event.data.queue.queue = song->queue;
  453. event.data.queue.param.value = events[current_event].tempo;
  454. break;
  455. }
  456. snd_seq_event_output(song->sequencer, &event);
  457. current_event++;
  458. if (current_event > num_events) break;
  459. time_diff = events[current_event].time - base_diff;
  460. } while (time_diff < 100000000); // 100ms
  461. snd_seq_drain_output(song->sequencer);
  462. last_tick = event.time.tick;
  463. }
  464. // stop playing
  465. event.time.tick = last_tick;
  466. snd_seq_sync_output_queue(song->sequencer);
  467. event.type = SND_SEQ_EVENT_CONTROLLER;
  468. event.dest.client = song->dst_client_id;
  469. event.dest.port = song->dst_port_id;
  470. int chan;
  471. for (chan = 0; chan < 16; chan++)
  472. {
  473. snd_seq_ev_set_fixed(&event);
  474. event.data.control.channel = chan;
  475. event.data.control.param = MIDI_CTL_ALL_NOTES_OFF; // All notes off (this message stops all the notes that are currently playing)
  476. event.data.control.value = 0;
  477. snd_seq_event_output(song->sequencer, &event);
  478. snd_seq_ev_set_fixed(&event);
  479. event.data.control.channel = chan;
  480. event.data.control.param = MIDI_CTL_RESET_CONTROLLERS; // All controllers off (this message clears all the controller values for this channel, back to their default values)
  481. event.data.control.value = 0;
  482. snd_seq_event_output(song->sequencer, &event);
  483. }
  484. snd_seq_ev_set_fixed(&event);
  485. event.type = SND_SEQ_EVENT_STOP;
  486. event.dest.client = SND_SEQ_CLIENT_SYSTEM;
  487. event.dest.port = SND_SEQ_PORT_SYSTEM_TIMER;
  488. event.data.queue.queue = song->queue;
  489. snd_seq_event_output(song->sequencer, &event);
  490. snd_seq_drain_output(song->sequencer);
  491. snd_seq_sync_output_queue(song->sequencer);
  492. return 0;
  493. }
  494. #ifdef __cplusplus
  495. extern "C"
  496. {
  497. #endif
  498. int native_midi_detect()
  499. {
  500. if (native_midi_available != -1) return native_midi_available;
  501. NativeMidiSong *song = calloc(1, sizeof(NativeMidiSong));
  502. if (song == NULL) return 0;
  503. snd_lib_error_set_handler(error_handler);
  504. if (open_alsa_port(song) < 0)
  505. {
  506. free(song);
  507. native_midi_available = 0;
  508. return 0;
  509. }
  510. native_midi_available = 1;
  511. dst_client_id = song->dst_client_id;
  512. dst_port_id = song->dst_port_id;
  513. close_alsa_port(song);
  514. free(song);
  515. return 1;
  516. }
  517. NativeMidiSong *native_midi_loadsong(const char *midifile)
  518. {
  519. // Attempt to load the midi file
  520. SDL_RWops *rw = SDL_RWFromFile(midifile, "rb");
  521. if (rw == NULL) return NULL;
  522. NativeMidiSong *song = native_midi_loadsong_RW(rw);
  523. SDL_RWclose(rw);
  524. return song;
  525. }
  526. NativeMidiSong *native_midi_loadsong_RW(SDL_RWops *rw)
  527. {
  528. NativeMidiSong *song = calloc(1, sizeof(NativeMidiSong));
  529. if (song == NULL) return NULL;
  530. Uint16 division;
  531. MIDIEvent *eventlist = CreateMIDIEventList(rw, &division);
  532. if (eventlist == NULL)
  533. {
  534. free(song);
  535. return NULL;
  536. }
  537. song->current_volume = 128;
  538. song->new_volume = 128;
  539. song->timediv = division;
  540. if (convert_event_list(song, eventlist) < 0)
  541. {
  542. FreeMIDIEventList(eventlist);
  543. free(song);
  544. return NULL;
  545. }
  546. FreeMIDIEventList(eventlist);
  547. if (open_alsa_port(song) < 0)
  548. {
  549. free_midi_events(song);
  550. free(song);
  551. return NULL;
  552. }
  553. return song;
  554. }
  555. void native_midi_freesong(NativeMidiSong *song)
  556. {
  557. if (song != NULL)
  558. {
  559. native_midi_stop(song);
  560. close_alsa_port(song);
  561. free_midi_events(song);
  562. free(song);
  563. }
  564. }
  565. void native_midi_start(NativeMidiSong *song, int looping)
  566. {
  567. if (song != NULL)
  568. {
  569. native_midi_stop(song);
  570. song->playing = 1;
  571. song->looping = looping;
  572. #if SDL_VERSION_ATLEAST(2,0,0)
  573. song->thread = SDL_CreateThread(&play_midi, "PAL-midi", (void *)song);
  574. #else
  575. song->thread = SDL_CreateThread(&play_midi, (void *)song);
  576. #endif
  577. }
  578. }
  579. void native_midi_stop(NativeMidiSong *song)
  580. {
  581. if (song != NULL)
  582. {
  583. song->playing = 0;
  584. if (song->thread != NULL)
  585. {
  586. SDL_WaitThread(song->thread, NULL);
  587. song->thread = NULL;
  588. }
  589. }
  590. }
  591. int native_midi_active(NativeMidiSong *song)
  592. {
  593. return (song && song->playing) ? 1 : 0;
  594. }
  595. void native_midi_setvolume(NativeMidiSong *song, int volume)
  596. {
  597. if (song != NULL)
  598. {
  599. if (volume > 127) volume = 127;
  600. if (volume < 0) volume = 0;
  601. song->new_volume = volume;
  602. }
  603. }
  604. const char *native_midi_error(NativeMidiSong *song)
  605. {
  606. return "";
  607. }
  608. #ifdef __cplusplus
  609. }
  610. #endif