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