1 /* Emacs style mode select -*- C++ -*-
2 *-----------------------------------------------------------------------------
3 *
4 *
5 * PrBoom: a Doom port merged with LxDoom and LSDLDoom
6 * based on BOOM, a modified and improved DOOM engine
7 *
8 * Copyright (C) 2011 by
9 * Nicholai Main
10 *
11 * This program is free software; you can redistribute it and/or
12 * modify it under the terms of the GNU General Public License
13 * as published by the Free Software Foundation; either version 2
14 * of the License, or (at your option) any later version.
15 *
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License
22 * along with this program; if not, write to the Free Software
23 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
24 * 02111-1307, USA.
25 *
26 * DESCRIPTION:
27 *
28 *---------------------------------------------------------------------
29 */
30
31 // TODO: some duplicated code with this and the fluidplayer should be
32 // split off or something
33
34 #ifdef HAVE_CONFIG_H
35 #include "config.h"
36 #endif
37
38 #include "musicplayer.h"
39
40 #ifndef HAVE_LIBPORTMIDI
41 #include <string.h>
42
pm_name(void)43 static const char *pm_name (void)
44 {
45 return "portmidi midi player (DISABLED)";
46 }
47
48
pm_init(int samplerate)49 static int pm_init (int samplerate)
50 {
51 return 0;
52 }
53
54 const music_player_t pm_player =
55 {
56 pm_name,
57 pm_init,
58 NULL,
59 NULL,
60 NULL,
61 NULL,
62 NULL,
63 NULL,
64 NULL,
65 NULL,
66 NULL
67 };
68
69 #else // HAVE_LIBPORTMIDI
70
71 #include <portmidi.h>
72 #include <porttime.h>
73 #include <stdio.h>
74 #include <stdlib.h>
75 #include <string.h>
76 #include "lprintf.h"
77 #include "midifile.h"
78 #include "i_sound.h" // for snd_mididev
79
80 static midi_event_t **events;
81 static int eventpos;
82 static midi_file_t *midifile;
83
84 static int pm_playing;
85 static int pm_paused;
86 static int pm_looping;
87 static int pm_volume;
88 static double spmc;
89 static double pm_delta;
90
91 static unsigned long trackstart;
92
93 static PortMidiStream *pm_stream;
94
95 #define SYSEX_BUFF_SIZE 1024
96 static unsigned char sysexbuff[SYSEX_BUFF_SIZE];
97 static int sysexbufflen;
98
99 // latency: we're generally writing timestamps slightly in the past (from when the last time
100 // render was called to this time. portmidi latency instruction must be larger than that window
101 // so the messages appear in the future. ~46-47ms is the nominal length if i_sound.c gets its way
102 #define DRIVER_LATENCY 80 // ms
103 // driver event buffer needs to be big enough to hold however many events occur in latency time
104 #define DRIVER_BUFFER 100 // events
105
106
107
pm_name(void)108 static const char *pm_name (void)
109 {
110 return "portmidi midi player";
111 }
112
113
114
115 #ifdef _MSC_VER
116 #define WIN32_LEAN_AND_MEAN
117 #include <windows.h>
118 #include <delayimp.h>
119 #endif
120
121
pm_init(int samplerate)122 static int pm_init (int samplerate)
123 {
124 PmDeviceID outputdevice;
125 const PmDeviceInfo *oinfo;
126 int i;
127 char devname[64];
128
129 TESTDLLLOAD ("portmidi.dll", TRUE)
130
131 if (Pm_Initialize () != pmNoError)
132 {
133 lprintf (LO_WARN, "portmidiplayer: Pm_Initialize () failed\n");
134 return 0;
135 }
136
137 outputdevice = Pm_GetDefaultOutputDeviceID ();
138
139 if (outputdevice == pmNoDevice)
140 {
141 lprintf (LO_WARN, "portmidiplayer: No output devices available\n");
142 Pm_Terminate ();
143 return 0;
144 }
145
146 // look for a device that matches the user preference
147
148 lprintf (LO_INFO, "portmidiplayer device list:\n");
149 for (i = 0; i < Pm_CountDevices (); i++)
150 {
151 oinfo = Pm_GetDeviceInfo (i);
152 if (!oinfo || !oinfo->output)
153 continue;
154 snprintf (devname, 64, "%s:%s", oinfo->interf, oinfo->name);
155 if (strlen (snd_mididev) && strstr (devname, snd_mididev))
156 {
157 outputdevice = i;
158 lprintf (LO_INFO, ">>%s\n", devname);
159 }
160 else
161 {
162 lprintf (LO_INFO, " %s\n", devname);
163 }
164 }
165
166
167 oinfo = Pm_GetDeviceInfo (outputdevice);
168
169 lprintf (LO_INFO, "portmidiplayer: Opening device %s:%s for output\n", oinfo->interf, oinfo->name);
170
171 if (Pm_OpenOutput(&pm_stream, outputdevice, NULL, DRIVER_BUFFER, NULL, NULL, DRIVER_LATENCY) != pmNoError)
172 {
173 lprintf (LO_WARN, "portmidiplayer: Pm_OpenOutput () failed\n");
174 Pm_Terminate ();
175 return 0;
176 }
177
178 return 1;
179 }
180
pm_shutdown(void)181 static void pm_shutdown (void)
182 {
183 if (pm_stream)
184 {
185 /* ugly deadlock in portmidi win32 implementation:
186
187 main thread gets stuck in Pm_Close
188 midi thread (started by windows) gets stuck in winmm_streamout_callback
189
190 winapi ref says:
191 "Applications should not call any multimedia functions from inside the callback function,
192 as doing so can cause a deadlock. Other system functions can safely be called from the callback."
193
194 winmm_streamout_callback calls midiOutUnprepareHeader. oops?
195
196
197 since timestamps are slightly in the future, it's very possible to have some messages still in
198 the windows midi queue when Pm_Close is called. this is normally no problem, but if one so happens
199 to dequeue and call winmm_streamout_callback at the exact right moment...
200
201 fix: at this point, we've stopped generating midi messages. sleep for more than DRIVER_LATENCY to ensure
202 all messages are flushed.
203
204 not a fix: calling Pm_Abort(); then midiStreamStop deadlocks instead of midiStreamClose.
205 */
206 #ifdef _WIN32
207 Pt_Sleep (DRIVER_LATENCY * 2);
208 #endif
209
210 Pm_Close (pm_stream);
211 Pm_Terminate ();
212 pm_stream = NULL;
213 }
214 }
215
216
217
218
219
pm_registersong(const void * data,unsigned len)220 static const void *pm_registersong (const void *data, unsigned len)
221 {
222 midimem_t mf;
223
224 mf.len = len;
225 mf.pos = 0;
226 mf.data = data;
227
228 midifile = MIDI_LoadFile (&mf);
229
230 if (!midifile)
231 {
232 lprintf (LO_WARN, "pm_registersong: Failed to load MIDI.\n");
233 return NULL;
234 }
235
236 events = MIDI_GenerateFlatList (midifile);
237 if (!events)
238 {
239 MIDI_FreeFile (midifile);
240 return NULL;
241 }
242 eventpos = 0;
243
244 // implicit 120BPM (this is correct to spec)
245 //spmc = compute_spmc (MIDI_GetFileTimeDivision (midifile), 500000, 1000);
246 spmc = MIDI_spmc (midifile, NULL, 1000);
247
248 // handle not used
249 return data;
250 }
251
writeevent(unsigned long when,int eve,int channel,int v1,int v2)252 static void writeevent (unsigned long when, int eve, int channel, int v1, int v2)
253 {
254 PmMessage m;
255
256 m = Pm_Message (eve | channel, v1, v2);
257 Pm_WriteShort (pm_stream, when, m);
258 }
259
260 /*
261 portmidi has no overall volume control. we have two options:
262 1. use a win32-specific hack (only if mus_extend_volume is set)
263 2. monitor the controller volume events and tweak them to serve our purpose
264 */
265
266 #ifdef _WIN32
267 extern int mus_extend_volume; // from e6y.h
268 void I_midiOutSetVolumes (int volume); // from e6y.h
269 #endif
270
271 static int channelvol[16];
272
pm_setchvolume(int ch,int v,unsigned long when)273 static void pm_setchvolume (int ch, int v, unsigned long when)
274 {
275 channelvol[ch] = v;
276 writeevent (when, MIDI_EVENT_CONTROLLER, ch, 7, channelvol[ch] * pm_volume / 15);
277 }
278
pm_refreshvolume(void)279 static void pm_refreshvolume (void)
280 {
281 int i;
282 unsigned long when = Pt_Time ();
283
284 for (i = 0; i < 16; i ++)
285 writeevent (when, MIDI_EVENT_CONTROLLER, i, 7, channelvol[i] * pm_volume / 15);
286 }
287
pm_clearchvolume(void)288 static void pm_clearchvolume (void)
289 {
290 int i;
291 for (i = 0; i < 16; i++)
292 channelvol[i] = 127; // default: max
293
294 }
295
pm_setvolume(int v)296 static void pm_setvolume (int v)
297 {
298 static int firsttime = 1;
299
300 if (pm_volume == v && !firsttime)
301 return;
302 firsttime = 0;
303
304 pm_volume = v;
305
306 // this is a bit of a hack
307 // fix: add non-win32 version
308 // fix: change win32 version to only modify the device we're using?
309 // (portmidi could know what device it's using, but the numbers
310 // don't match up with the winapi numbers...)
311
312 #ifdef _WIN32
313 if (mus_extend_volume)
314 I_midiOutSetVolumes (pm_volume);
315 else
316 #endif
317 pm_refreshvolume ();
318 }
319
320
pm_unregistersong(const void * handle)321 static void pm_unregistersong (const void *handle)
322 {
323 if (events)
324 {
325 MIDI_DestroyFlatList (events);
326 events = NULL;
327 }
328 if (midifile)
329 {
330 MIDI_FreeFile (midifile);
331 midifile = NULL;
332 }
333 }
334
pm_pause(void)335 static void pm_pause (void)
336 {
337 int i;
338 unsigned long when = Pt_Time ();
339 pm_paused = 1;
340 for (i = 0; i < 16; i++)
341 {
342 writeevent (when, MIDI_EVENT_CONTROLLER, i, 123, 0); // all notes off
343 }
344 }
pm_resume(void)345 static void pm_resume (void)
346 {
347 pm_paused = 0;
348 trackstart = Pt_Time ();
349 }
pm_play(const void * handle,int looping)350 static void pm_play (const void *handle, int looping)
351 {
352 eventpos = 0;
353 pm_looping = looping;
354 pm_playing = 1;
355 //pm_paused = 0;
356 pm_delta = 0.0;
357 pm_clearchvolume ();
358 pm_refreshvolume ();
359 trackstart = Pt_Time ();
360
361 }
362
363
writesysex(unsigned long when,int etype,unsigned char * data,int len)364 static void writesysex (unsigned long when, int etype, unsigned char *data, int len)
365 {
366 // sysex code is untested
367 // it's possible to use an auto-resizing buffer here, but a malformed
368 // midi file could make it grow arbitrarily large (since it must grow
369 // until it hits an 0xf7 terminator)
370 if (len + sysexbufflen > SYSEX_BUFF_SIZE)
371 {
372 lprintf (LO_WARN, "portmidiplayer: ignoring large or malformed sysex message\n");
373 sysexbufflen = 0;
374 return;
375 }
376 memcpy (sysexbuff + sysexbufflen, data, len);
377 sysexbufflen += len;
378 if (sysexbuff[sysexbufflen - 1] == 0xf7) // terminator
379 {
380 Pm_WriteSysEx (pm_stream, when, sysexbuff);
381 sysexbufflen = 0;
382 }
383 }
384
pm_stop(void)385 static void pm_stop (void)
386 {
387 int i;
388 unsigned long when = Pt_Time ();
389 pm_playing = 0;
390
391
392 // songs can be stopped at any time, so reset everything
393 for (i = 0; i < 16; i++)
394 {
395 writeevent (when, MIDI_EVENT_CONTROLLER, i, 123, 0); // all notes off
396 writeevent (when, MIDI_EVENT_CONTROLLER, i, 121, 0); // reset all parameters
397 }
398 // abort any partial sysex
399 sysexbufflen = 0;
400 }
401
pm_render(void * vdest,unsigned bufflen)402 static void pm_render (void *vdest, unsigned bufflen)
403 {
404 // wherever you see samples in here, think milliseconds
405
406 unsigned long newtime = Pt_Time ();
407 unsigned long length = newtime - trackstart;
408
409 //timerpos = newtime;
410 unsigned long when;
411
412 midi_event_t *currevent;
413
414 unsigned sampleswritten = 0;
415 unsigned samples;
416
417 memset (vdest, 0, bufflen * 4);
418
419
420
421 if (!pm_playing || pm_paused)
422 return;
423
424
425 while (1)
426 {
427 double eventdelta;
428 currevent = events[eventpos];
429
430 // how many samples away event is
431 eventdelta = currevent->delta_time * spmc;
432
433
434 // how many we will render (rounding down); include delta offset
435 samples = (unsigned) (eventdelta + pm_delta);
436
437
438 if (samples + sampleswritten > length)
439 { // overshoot; render some samples without processing an event
440 break;
441 }
442
443
444 sampleswritten += samples;
445 pm_delta -= samples;
446
447
448 // process event
449 when = trackstart + sampleswritten;
450 switch (currevent->event_type)
451 {
452 case MIDI_EVENT_SYSEX:
453 case MIDI_EVENT_SYSEX_SPLIT:
454 writesysex (when, currevent->event_type, currevent->data.sysex.data, currevent->data.sysex.length);
455 break;
456 case MIDI_EVENT_META: // tempo is the only meta message we're interested in
457 if (currevent->data.meta.type == MIDI_META_SET_TEMPO)
458 spmc = MIDI_spmc (midifile, currevent, 1000);
459 else if (currevent->data.meta.type == MIDI_META_END_OF_TRACK)
460 {
461 if (pm_looping)
462 {
463 int i;
464 eventpos = 0;
465 pm_delta += eventdelta;
466 // fix buggy songs that forget to terminate notes held over loop point
467 // sdl_mixer does this as well
468 for (i = 0; i < 16; i++)
469 writeevent (when, MIDI_EVENT_CONTROLLER, i, 123, 0); // all notes off
470 continue;
471 }
472 // stop
473 pm_stop ();
474 return;
475 }
476 break; // not interested in most metas
477 case MIDI_EVENT_CONTROLLER:
478 if (currevent->data.channel.param1 == 7)
479 { // volume event
480 #ifdef _WIN32
481 if (!mus_extend_volume)
482 #endif
483 {
484 pm_setchvolume (currevent->data.channel.channel, currevent->data.channel.param2, when);
485 break;
486 }
487 } // fall through
488 default:
489 writeevent (when, currevent->event_type, currevent->data.channel.channel, currevent->data.channel.param1, currevent->data.channel.param2);
490 break;
491
492 }
493 // if the event was a "reset all controllers", we need to additionally re-fix the volume (which itself was reset)
494 if (currevent->event_type == MIDI_EVENT_CONTROLLER && currevent->data.channel.param1 == 121)
495 pm_setchvolume (currevent->data.channel.channel, 127, when);
496
497 // event processed so advance midiclock
498 pm_delta += eventdelta;
499 eventpos++;
500
501 }
502
503 if (samples + sampleswritten > length)
504 { // broke due to next event being past the end of current render buffer
505 // finish buffer, return
506 samples = length - sampleswritten;
507 pm_delta -= samples; // save offset
508 }
509
510 trackstart = newtime;
511 }
512
513
514 const music_player_t pm_player =
515 {
516 pm_name,
517 pm_init,
518 pm_shutdown,
519 pm_setvolume,
520 pm_pause,
521 pm_resume,
522 pm_registersong,
523 pm_unregistersong,
524 pm_play,
525 pm_stop,
526 pm_render
527 };
528
529
530 #endif // HAVE_LIBPORTMIDI
531
532