OpenTTD
win32_m.cpp
Go to the documentation of this file.
1 /* $Id$ */
2 
3 /*
4  * This file is part of OpenTTD.
5  * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
6  * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
7  * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
8  */
9 
12 #include "../stdafx.h"
13 #include "../string_func.h"
14 #include "win32_m.h"
15 #include <windows.h>
16 #include <mmsystem.h>
17 #include "../os/windows/win32.h"
18 #include "../debug.h"
19 #include "midifile.hpp"
20 #include "midi.h"
21 #include "../base_media_base.h"
22 #include <mutex>
23 
24 #include "../safeguards.h"
25 
27  uint32 start, end;
28  size_t start_block;
29  bool loop;
30 };
31 
32 static struct {
33  UINT time_period;
34  HMIDIOUT midi_out;
35  UINT timer_id;
36  std::mutex lock;
37 
38  bool playing;
39  int do_start;
40  bool do_stop;
42  byte new_volume;
43 
47  size_t current_block;
50 
51  byte channel_volumes[16];
52 } _midi;
53 
54 static FMusicDriver_Win32 iFMusicDriver_Win32;
55 
56 
57 static byte ScaleVolume(byte original, byte scale)
58 {
59  return original * scale / 127;
60 }
61 
62 
63 void CALLBACK MidiOutProc(HMIDIOUT hmo, UINT wMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2)
64 {
65  if (wMsg == MOM_DONE) {
66  MIDIHDR *hdr = (LPMIDIHDR)dwParam1;
67  midiOutUnprepareHeader(hmo, hdr, sizeof(*hdr));
68  free(hdr);
69  }
70 }
71 
72 static void TransmitChannelMsg(byte status, byte p1, byte p2 = 0)
73 {
74  midiOutShortMsg(_midi.midi_out, status | (p1 << 8) | (p2 << 16));
75 }
76 
77 static void TransmitSysex(const byte *&msg_start, size_t &remaining)
78 {
79  /* find end of message */
80  const byte *msg_end = msg_start;
81  while (*msg_end != MIDIST_ENDSYSEX) msg_end++;
82  msg_end++; /* also include sysex end byte */
83 
84  /* prepare header */
85  MIDIHDR *hdr = CallocT<MIDIHDR>(1);
86  hdr->lpData = reinterpret_cast<LPSTR>(const_cast<byte *>(msg_start));
87  hdr->dwBufferLength = msg_end - msg_start;
88  if (midiOutPrepareHeader(_midi.midi_out, hdr, sizeof(*hdr)) == MMSYSERR_NOERROR) {
89  /* transmit - just point directly into the data buffer */
90  hdr->dwBytesRecorded = hdr->dwBufferLength;
91  midiOutLongMsg(_midi.midi_out, hdr, sizeof(*hdr));
92  } else {
93  free(hdr);
94  }
95 
96  /* update position in buffer */
97  remaining -= msg_end - msg_start;
98  msg_start = msg_end;
99 }
100 
101 static void TransmitStandardSysex(MidiSysexMessage msg)
102 {
103  size_t length = 0;
104  const byte *data = MidiGetStandardSysexMessage(msg, length);
105  TransmitSysex(data, length);
106 }
107 
112 void CALLBACK TimerCallback(UINT uTimerID, UINT, DWORD_PTR dwUser, DWORD_PTR, DWORD_PTR)
113 {
114  /* Ensure only one timer callback is running at once, and prevent races on status flags */
115  std::unique_lock<std::mutex> mutex_lock(_midi.lock, std::defer_lock);
116  if (!mutex_lock.try_lock()) return;
117 
118  /* check for stop */
119  if (_midi.do_stop) {
120  DEBUG(driver, 2, "Win32-MIDI: timer: do_stop is set");
121  midiOutReset(_midi.midi_out);
122  _midi.playing = false;
123  _midi.do_stop = false;
124  return;
125  }
126 
127  /* check for start/restart/change song */
128  if (_midi.do_start != 0) {
129  /* Have a delay between playback start steps, prevents jumbled-together notes at the start of song */
130  if (timeGetTime() - _midi.playback_start_time < 50) {
131  return;
132  }
133  DEBUG(driver, 2, "Win32-MIDI: timer: do_start step %d", _midi.do_start);
134 
135  if (_midi.do_start == 1) {
136  /* Send "all notes off" */
137  midiOutReset(_midi.midi_out);
138  _midi.playback_start_time = timeGetTime();
139  _midi.do_start = 2;
140 
141  return;
142  } else if (_midi.do_start == 2) {
143  /* Reset the device to General MIDI defaults */
144  TransmitStandardSysex(MidiSysexMessage::ResetGM);
145  _midi.playback_start_time = timeGetTime();
146  _midi.do_start = 3;
147 
148  return;
149  } else if (_midi.do_start == 3) {
150  /* Set up device-specific effects */
151  TransmitStandardSysex(MidiSysexMessage::RolandSetReverb);
152  _midi.playback_start_time = timeGetTime();
153  _midi.do_start = 4;
154 
155  return;
156  } else if (_midi.do_start == 4) {
157  /* Load the new file */
158  _midi.current_file.MoveFrom(_midi.next_file);
159  std::swap(_midi.next_segment, _midi.current_segment);
160  _midi.current_segment.start_block = 0;
161  _midi.playback_start_time = timeGetTime();
162  _midi.playing = true;
163  _midi.do_start = 0;
164  _midi.current_block = 0;
165 
166  MemSetT<byte>(_midi.channel_volumes, 127, lengthof(_midi.channel_volumes));
167  }
168  } else if (!_midi.playing) {
169  /* not playing, stop the timer */
170  DEBUG(driver, 2, "Win32-MIDI: timer: not playing, stopping timer");
171  timeKillEvent(uTimerID);
172  _midi.timer_id = 0;
173  return;
174  }
175 
176  /* check for volume change */
177  static int volume_throttle = 0;
178  if (_midi.current_volume != _midi.new_volume) {
179  if (volume_throttle == 0) {
180  DEBUG(driver, 2, "Win32-MIDI: timer: volume change");
181  _midi.current_volume = _midi.new_volume;
182  volume_throttle = 20 / _midi.time_period;
183  for (int ch = 0; ch < 16; ch++) {
184  byte vol = ScaleVolume(_midi.channel_volumes[ch], _midi.current_volume);
185  TransmitChannelMsg(MIDIST_CONTROLLER | ch, MIDICT_CHANVOLUME, vol);
186  }
187  } else {
188  volume_throttle--;
189  }
190  }
191 
192  /* skip beginning of file? */
193  if (_midi.current_segment.start > 0 && _midi.current_block == 0 && _midi.current_segment.start_block == 0) {
194  /* find first block after start time and pretend playback started earlier
195  * this is to allow all blocks prior to the actual start to still affect playback,
196  * as they may contain important controller and program changes */
197  size_t preload_bytes = 0;
198  for (size_t bl = 0; bl < _midi.current_file.blocks.size(); bl++) {
199  MidiFile::DataBlock &block = _midi.current_file.blocks[bl];
200  preload_bytes += block.data.size();
201  if (block.ticktime >= _midi.current_segment.start) {
202  if (_midi.current_segment.loop) {
203  DEBUG(driver, 2, "Win32-MIDI: timer: loop from block %d (ticktime %d, realtime %.3f, bytes %d)", (int)bl, (int)block.ticktime, ((int)block.realtime)/1000.0, (int)preload_bytes);
204  _midi.current_segment.start_block = bl;
205  break;
206  } else {
207  /* Calculate offset start time for playback.
208  * The preload_bytes are used to compensate for delay in transmission over traditional serial MIDI interfaces,
209  * which have a bitrate of 31,250 bits/sec, and transmit 1+8+1 start/data/stop bits per byte.
210  * The delay compensation is needed to avoid time-compression of following messages.
211  */
212  DEBUG(driver, 2, "Win32-MIDI: timer: start from block %d (ticktime %d, realtime %.3f, bytes %d)", (int)bl, (int)block.ticktime, ((int)block.realtime) / 1000.0, (int)preload_bytes);
213  _midi.playback_start_time -= block.realtime / 1000 - (DWORD)(preload_bytes * 1000 / 3125);
214  break;
215  }
216  }
217  }
218  }
219 
220 
221  /* play pending blocks */
222  DWORD current_time = timeGetTime();
223  DWORD playback_time = current_time - _midi.playback_start_time;
224  while (_midi.current_block < _midi.current_file.blocks.size()) {
225  MidiFile::DataBlock &block = _midi.current_file.blocks[_midi.current_block];
226 
227  /* check that block isn't at end-of-song override */
228  if (_midi.current_segment.end > 0 && block.ticktime >= _midi.current_segment.end) {
229  if (_midi.current_segment.loop) {
230  _midi.current_block = _midi.current_segment.start_block;
231  _midi.playback_start_time = timeGetTime() - _midi.current_file.blocks[_midi.current_block].realtime / 1000;
232  } else {
233  _midi.do_stop = true;
234  }
235  break;
236  }
237  /* check that block is not in the future */
238  if (block.realtime / 1000 > playback_time) {
239  break;
240  }
241 
242  const byte *data = block.data.data();
243  size_t remaining = block.data.size();
244  byte last_status = 0;
245  while (remaining > 0) {
246  /* MidiFile ought to have converted everything out of running status,
247  * but handle it anyway just to be safe */
248  byte status = data[0];
249  if (status & 0x80) {
250  last_status = status;
251  data++;
252  remaining--;
253  } else {
254  status = last_status;
255  }
256  switch (status & 0xF0) {
257  case MIDIST_PROGCHG:
258  case MIDIST_CHANPRESS:
259  /* 2 byte channel messages */
260  TransmitChannelMsg(status, data[0]);
261  data++;
262  remaining--;
263  break;
264  case MIDIST_NOTEOFF:
265  case MIDIST_NOTEON:
266  case MIDIST_POLYPRESS:
267  case MIDIST_PITCHBEND:
268  /* 3 byte channel messages */
269  TransmitChannelMsg(status, data[0], data[1]);
270  data += 2;
271  remaining -= 2;
272  break;
273  case MIDIST_CONTROLLER:
274  /* controller change */
275  if (data[0] == MIDICT_CHANVOLUME) {
276  /* volume controller, adjust for user volume */
277  _midi.channel_volumes[status & 0x0F] = data[1];
278  int vol = ScaleVolume(data[1], _midi.current_volume);
279  TransmitChannelMsg(status, data[0], vol);
280  } else {
281  /* handle other controllers normally */
282  TransmitChannelMsg(status, data[0], data[1]);
283  }
284  data += 2;
285  remaining -= 2;
286  break;
287  case 0xF0:
288  /* system messages */
289  switch (status) {
290  case MIDIST_SYSEX: /* system exclusive */
291  TransmitSysex(data, remaining);
292  break;
293  case MIDIST_TC_QFRAME: /* time code quarter frame */
294  case MIDIST_SONGSEL: /* song select */
295  data++;
296  remaining--;
297  break;
298  case MIDIST_SONGPOSPTR: /* song position pointer */
299  data += 2;
300  remaining -= 2;
301  break;
302  default: /* remaining have no data bytes */
303  break;
304  }
305  break;
306  }
307  }
308 
309  _midi.current_block++;
310  }
311 
312  /* end? */
313  if (_midi.current_block == _midi.current_file.blocks.size()) {
314  if (_midi.current_segment.loop) {
315  _midi.current_block = _midi.current_segment.start_block;
316  _midi.playback_start_time = timeGetTime() - _midi.current_file.blocks[_midi.current_block].realtime / 1000;
317  } else {
318  _midi.do_stop = true;
319  }
320  }
321 }
322 
324 {
325  DEBUG(driver, 2, "Win32-MIDI: PlaySong: entry");
326 
327  MidiFile new_song;
328  if (!new_song.LoadSong(song)) return;
329  DEBUG(driver, 2, "Win32-MIDI: PlaySong: Loaded song");
330 
331  std::lock_guard<std::mutex> mutex_lock(_midi.lock);
332 
333  _midi.next_file.MoveFrom(new_song);
334  _midi.next_segment.start = song.override_start;
335  _midi.next_segment.end = song.override_end;
336  _midi.next_segment.loop = song.loop;
337 
338  DEBUG(driver, 2, "Win32-MIDI: PlaySong: setting flag");
339  _midi.do_stop = _midi.playing;
340  _midi.do_start = 1;
341 
342  if (_midi.timer_id == 0) {
343  DEBUG(driver, 2, "Win32-MIDI: PlaySong: starting timer");
344  _midi.timer_id = timeSetEvent(_midi.time_period, _midi.time_period, TimerCallback, (DWORD_PTR)this, TIME_PERIODIC | TIME_CALLBACK_FUNCTION);
345  }
346 }
347 
349 {
350  DEBUG(driver, 2, "Win32-MIDI: StopSong: entry");
351  std::lock_guard<std::mutex> mutex_lock(_midi.lock);
352  DEBUG(driver, 2, "Win32-MIDI: StopSong: setting flag");
353  _midi.do_stop = true;
354 }
355 
357 {
358  return _midi.playing || (_midi.do_start != 0);
359 }
360 
362 {
363  std::lock_guard<std::mutex> mutex_lock(_midi.lock);
364  _midi.new_volume = vol;
365 }
366 
367 const char *MusicDriver_Win32::Start(const char * const *parm)
368 {
369  DEBUG(driver, 2, "Win32-MIDI: Start: initializing");
370 
371  int resolution = GetDriverParamInt(parm, "resolution", 5);
372  uint port = (uint)GetDriverParamInt(parm, "port", UINT_MAX);
373  const char *portname = GetDriverParam(parm, "portname");
374 
375  /* Enumerate ports either for selecting port by name, or for debug output */
376  if (portname != nullptr || _debug_driver_level > 0) {
377  uint numports = midiOutGetNumDevs();
378  DEBUG(driver, 1, "Win32-MIDI: Found %d output devices:", numports);
379  for (uint tryport = 0; tryport < numports; tryport++) {
380  MIDIOUTCAPS moc{};
381  if (midiOutGetDevCaps(tryport, &moc, sizeof(moc)) == MMSYSERR_NOERROR) {
382  char tryportname[128];
383  convert_from_fs(moc.szPname, tryportname, lengthof(tryportname));
384 
385  /* Compare requested and detected port name.
386  * If multiple ports have the same name, this will select the last matching port, and the debug output will be confusing. */
387  if (portname != nullptr && strncmp(tryportname, portname, lengthof(tryportname)) == 0) port = tryport;
388 
389  DEBUG(driver, 1, "MIDI port %2d: %s%s", tryport, tryportname, (tryport == port) ? " [selected]" : "");
390  }
391  }
392  }
393 
394  UINT devid;
395  if (port == UINT_MAX) {
396  devid = MIDI_MAPPER;
397  } else {
398  devid = (UINT)port;
399  }
400 
401  resolution = Clamp(resolution, 1, 20);
402 
403  if (midiOutOpen(&_midi.midi_out, devid, (DWORD_PTR)&MidiOutProc, (DWORD_PTR)this, CALLBACK_FUNCTION) != MMSYSERR_NOERROR) {
404  return "could not open midi device";
405  }
406 
407  midiOutReset(_midi.midi_out);
408 
409  /* prepare multimedia timer */
410  TIMECAPS timecaps;
411  if (timeGetDevCaps(&timecaps, sizeof(timecaps)) == MMSYSERR_NOERROR) {
412  _midi.time_period = min(max((UINT)resolution, timecaps.wPeriodMin), timecaps.wPeriodMax);
413  if (timeBeginPeriod(_midi.time_period) == MMSYSERR_NOERROR) {
414  /* success */
415  DEBUG(driver, 2, "Win32-MIDI: Start: timer resolution is %d", (int)_midi.time_period);
416  return nullptr;
417  }
418  }
419  midiOutClose(_midi.midi_out);
420  return "could not set timer resolution";
421 }
422 
424 {
425  std::lock_guard<std::mutex> mutex_lock(_midi.lock);
426 
427  if (_midi.timer_id) {
428  timeKillEvent(_midi.timer_id);
429  _midi.timer_id = 0;
430  }
431 
432  timeEndPeriod(_midi.time_period);
433  midiOutReset(_midi.midi_out);
434  midiOutClose(_midi.midi_out);
435 }
int override_end
MIDI tick to end the song at (0 if no override)
const char * GetDriverParam(const char *const *parm, const char *name)
Get a string parameter the list of parameters.
Definition: driver.cpp:39
Metadata about a music track.
Factory for Windows&#39; music player.
Definition: win32_m.h:35
MidiFile current_file
file currently being played from
Definition: win32_m.cpp:44
bool IsSongPlaying() override
Are we currently playing a song?
Definition: win32_m.cpp:356
Base for Windows music playback.
void StopSong() override
Stop playing the current song.
Definition: win32_m.cpp:348
size_t current_block
next block index to send
Definition: win32_m.cpp:47
int override_start
MIDI ticks to skip over in beginning.
bool playing
flag indicating that playback is active
Definition: win32_m.cpp:38
void PlaySong(const MusicSongInfo &song) override
Play a particular song.
Definition: win32_m.cpp:323
byte new_volume
volume setting to change to
Definition: win32_m.cpp:42
char * convert_from_fs(const TCHAR *name, char *utf8_buf, size_t buflen)
Convert to OpenTTD&#39;s encoding from that of the environment in UNICODE.
Definition: win32.cpp:593
static T max(const T a, const T b)
Returns the maximum of two values.
Definition: math_func.hpp:26
void SetVolume(byte vol) override
Set the volume, if possible.
Definition: win32_m.cpp:361
UINT timer_id
ID of active multimedia timer.
Definition: win32_m.cpp:35
UINT time_period
obtained timer precision value
Definition: win32_m.cpp:33
void CALLBACK TimerCallback(UINT uTimerID, UINT, DWORD_PTR dwUser, DWORD_PTR, DWORD_PTR)
Realtime MIDI playback service routine.
Definition: win32_m.cpp:112
std::mutex lock
synchronization for playback status fields
Definition: win32_m.cpp:36
std::vector< byte > data
raw midi data contained in block
Definition: midifile.hpp:27
byte channel_volumes[16]
last seen volume controller values in raw data
Definition: win32_m.cpp:51
bool do_stop
flag for stopping playback at next opportunity
Definition: win32_m.cpp:40
#define lengthof(x)
Return the length of an fixed size array.
Definition: depend.cpp:42
static T min(const T a, const T b)
Returns the minimum of two values.
Definition: math_func.hpp:42
const char * Start(const char *const *param) override
Start this driver.
Definition: win32_m.cpp:367
uint32 realtime
real-time (microseconds) since start of file this block should be triggered at
Definition: midifile.hpp:26
HMIDIOUT midi_out
handle to open midiOut
Definition: win32_m.cpp:34
MidiFile next_file
upcoming file to play
Definition: win32_m.cpp:48
static T Clamp(const T a, const T min, const T max)
Clamp a value between an interval.
Definition: math_func.hpp:139
#define DEBUG(name, level,...)
Output a line of debugging information.
Definition: debug.h:37
PlaybackSegment next_segment
segment info for upcoming file
Definition: win32_m.cpp:49
byte current_volume
current effective volume setting
Definition: win32_m.cpp:41
uint32 ticktime
tick number since start of file this block should be triggered at
Definition: midifile.hpp:25
bool loop
song should play in a tight loop if possible, never ending
PlaybackSegment current_segment
segment info for current playback
Definition: win32_m.cpp:45
static struct @24 _midi
Metadata about the midi we&#39;re playing.
DWORD playback_start_time
timestamp current file began playback
Definition: win32_m.cpp:46
static void free(const void *ptr)
Version of the standard free that accepts const pointers.
Definition: depend.cpp:131
int GetDriverParamInt(const char *const *parm, const char *name, int def)
Get an integer parameter the list of parameters.
Definition: driver.cpp:75
int do_start
flag for starting playback of next_file at next opportunity
Definition: win32_m.cpp:39
void Stop() override
Stop this driver.
Definition: win32_m.cpp:423