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