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  bool 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(byte *&msg_start, size_t &remaining)
77 {
78  /* find end of message */
79  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 TransmitSysexConst(byte *msg_start, size_t length)
101 {
102  TransmitSysex(msg_start, length);
103 }
104 
109 void CALLBACK TimerCallback(UINT uTimerID, UINT, DWORD_PTR dwUser, DWORD_PTR, DWORD_PTR)
110 {
111  /* Try to check playback status changes.
112  * If _midi is already locked, skip checking for this cycle and try again
113  * next cycle, instead of waiting for locks in the realtime callback. */
114  if (TryEnterCriticalSection(&_midi.lock)) {
115  /* check for stop */
116  if (_midi.do_stop) {
117  DEBUG(driver, 2, "Win32-MIDI: timer: do_stop is set");
118  midiOutReset(_midi.midi_out);
119  _midi.playing = false;
120  _midi.do_stop = false;
121  LeaveCriticalSection(&_midi.lock);
122  return;
123  }
124 
125  /* check for start/restart/change song */
126  if (_midi.do_start) {
127  DEBUG(driver, 2, "Win32-MIDI: timer: do_start is set");
128  if (_midi.playing) {
129  midiOutReset(_midi.midi_out);
130  /* Some songs change the "Pitch bend range" registered
131  * parameter. If this doesn't get reset, everything else
132  * will start sounding wrong. */
133  for (int ch = 0; ch < 16; ch++) {
134  /* Running status, only need status for first message */
135  /* Select RPN 00.00, set value to 02.00, and unselect again */
136  TransmitChannelMsg(MIDIST_CONTROLLER | ch, MIDICT_RPN_SELECT_LO, 0x00);
137  TransmitChannelMsg(MIDICT_RPN_SELECT_HI, 0x00);
138  TransmitChannelMsg(MIDICT_DATAENTRY, 0x02);
139  TransmitChannelMsg(MIDICT_DATAENTRY_LO, 0x00);
140  TransmitChannelMsg(MIDICT_RPN_SELECT_LO, 0x7F);
141  TransmitChannelMsg(MIDICT_RPN_SELECT_HI, 0x7F);
142  }
143  }
144  _midi.current_file.MoveFrom(_midi.next_file);
145  std::swap(_midi.next_segment, _midi.current_segment);
146  _midi.current_segment.start_block = 0;
147  _midi.playback_start_time = timeGetTime();
148  _midi.playing = true;
149  _midi.do_start = false;
150  _midi.current_block = 0;
151 
152  MemSetT<byte>(_midi.channel_volumes, 127, lengthof(_midi.channel_volumes));
153  } else if (!_midi.playing) {
154  /* not playing, stop the timer */
155  DEBUG(driver, 2, "Win32-MIDI: timer: not playing, stopping timer");
156  timeKillEvent(uTimerID);
157  _midi.timer_id = 0;
158  LeaveCriticalSection(&_midi.lock);
159  return;
160  }
161 
162  /* check for volume change */
163  static int volume_throttle = 0;
164  if (_midi.current_volume != _midi.new_volume) {
165  if (volume_throttle == 0) {
166  DEBUG(driver, 2, "Win32-MIDI: timer: volume change");
167  _midi.current_volume = _midi.new_volume;
168  volume_throttle = 20 / _midi.time_period;
169  for (int ch = 0; ch < 16; ch++) {
170  int vol = ScaleVolume(_midi.channel_volumes[ch], _midi.current_volume);
171  TransmitChannelMsg(MIDIST_CONTROLLER | ch, MIDICT_CHANVOLUME, vol);
172  }
173  }
174  else {
175  volume_throttle--;
176  }
177  }
178 
179  LeaveCriticalSection(&_midi.lock);
180  }
181 
182  /* skip beginning of file? */
183  if (_midi.current_segment.start > 0 && _midi.current_block == 0 && _midi.current_segment.start_block == 0) {
184  /* find first block after start time and pretend playback started earlier
185  * this is to allow all blocks prior to the actual start to still affect playback,
186  * as they may contain important controller and program changes */
187  uint preload_bytes = 0;
188  for (size_t bl = 0; bl < _midi.current_file.blocks.size(); bl++) {
189  MidiFile::DataBlock &block = _midi.current_file.blocks[bl];
190  preload_bytes += block.data.Length();
191  if (block.ticktime >= _midi.current_segment.start) {
192  if (_midi.current_segment.loop) {
193  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);
194  _midi.current_segment.start_block = bl;
195  break;
196  } else {
197  /* Calculate offset start time for playback.
198  * The preload_bytes are used to compensate for delay in transmission over traditional serial MIDI interfaces,
199  * which have a bitrate of 31,250 bits/sec, and transmit 1+8+1 start/data/stop bits per byte.
200  * The delay compensation is needed to avoid time-compression of following messages.
201  */
202  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);
203  _midi.playback_start_time -= block.realtime / 1000 - preload_bytes * 1000 / 3125;
204  break;
205  }
206  }
207  }
208  }
209 
210 
211  /* play pending blocks */
212  DWORD current_time = timeGetTime();
213  DWORD playback_time = current_time - _midi.playback_start_time;
214  while (_midi.current_block < _midi.current_file.blocks.size()) {
215  MidiFile::DataBlock &block = _midi.current_file.blocks[_midi.current_block];
216 
217  /* check that block isn't at end-of-song override */
218  if (_midi.current_segment.end > 0 && block.ticktime >= _midi.current_segment.end) {
219  if (_midi.current_segment.loop) {
220  _midi.current_block = _midi.current_segment.start_block;
221  _midi.playback_start_time = timeGetTime() - _midi.current_file.blocks[_midi.current_block].realtime / 1000;
222  } else {
223  _midi.do_stop = true;
224  }
225  break;
226  }
227  /* check that block is not in the future */
228  if (block.realtime / 1000 > playback_time) {
229  break;
230  }
231 
232  byte *data = block.data.Begin();
233  size_t remaining = block.data.Length();
234  byte last_status = 0;
235  while (remaining > 0) {
236  /* MidiFile ought to have converted everything out of running status,
237  * but handle it anyway just to be safe */
238  byte status = data[0];
239  if (status & 0x80) {
240  last_status = status;
241  data++;
242  remaining--;
243  } else {
244  status = last_status;
245  }
246  switch (status & 0xF0) {
247  case MIDIST_PROGCHG:
248  case MIDIST_CHANPRESS:
249  /* 2 byte channel messages */
250  TransmitChannelMsg(status, data[0]);
251  data++;
252  remaining--;
253  break;
254  case MIDIST_NOTEOFF:
255  case MIDIST_NOTEON:
256  case MIDIST_POLYPRESS:
257  case MIDIST_PITCHBEND:
258  /* 3 byte channel messages */
259  TransmitChannelMsg(status, data[0], data[1]);
260  data += 2;
261  remaining -= 2;
262  break;
263  case MIDIST_CONTROLLER:
264  /* controller change */
265  if (data[0] == MIDICT_CHANVOLUME) {
266  /* volume controller, adjust for user volume */
267  _midi.channel_volumes[status & 0x0F] = data[1];
268  int vol = ScaleVolume(data[1], _midi.current_volume);
269  TransmitChannelMsg(status, data[0], vol);
270  } else {
271  /* handle other controllers normally */
272  TransmitChannelMsg(status, data[0], data[1]);
273  }
274  data += 2;
275  remaining -= 2;
276  break;
277  case 0xF0:
278  /* system messages */
279  switch (status) {
280  case MIDIST_SYSEX: /* system exclusive */
281  TransmitSysex(data, remaining);
282  break;
283  case MIDIST_TC_QFRAME: /* time code quarter frame */
284  case MIDIST_SONGSEL: /* song select */
285  data++;
286  remaining--;
287  break;
288  case MIDIST_SONGPOSPTR: /* song position pointer */
289  data += 2;
290  remaining -= 2;
291  break;
292  default: /* remaining have no data bytes */
293  break;
294  }
295  break;
296  }
297  }
298 
299  _midi.current_block++;
300  }
301 
302  /* end? */
303  if (_midi.current_block == _midi.current_file.blocks.size()) {
304  if (_midi.current_segment.loop) {
305  _midi.current_block = _midi.current_segment.start_block;
306  _midi.playback_start_time = timeGetTime() - _midi.current_file.blocks[_midi.current_block].realtime / 1000;
307  } else {
308  _midi.do_stop = true;
309  }
310  }
311 }
312 
314 {
315  DEBUG(driver, 2, "Win32-MIDI: PlaySong: entry");
316  EnterCriticalSection(&_midi.lock);
317 
318  if (!_midi.next_file.LoadSong(song)) {
319  LeaveCriticalSection(&_midi.lock);
320  return;
321  }
322 
323  _midi.next_segment.start = song.override_start;
324  _midi.next_segment.end = song.override_end;
325  _midi.next_segment.loop = song.loop;
326 
327  DEBUG(driver, 2, "Win32-MIDI: PlaySong: setting flag");
328  _midi.do_stop = _midi.playing;
329  _midi.do_start = true;
330 
331  if (_midi.timer_id == 0) {
332  DEBUG(driver, 2, "Win32-MIDI: PlaySong: starting timer");
333  _midi.timer_id = timeSetEvent(_midi.time_period, _midi.time_period, TimerCallback, (DWORD_PTR)this, TIME_PERIODIC | TIME_CALLBACK_FUNCTION);
334  }
335 
336  LeaveCriticalSection(&_midi.lock);
337 }
338 
340 {
341  DEBUG(driver, 2, "Win32-MIDI: StopSong: entry");
342  EnterCriticalSection(&_midi.lock);
343  DEBUG(driver, 2, "Win32-MIDI: StopSong: setting flag");
344  _midi.do_stop = true;
345  LeaveCriticalSection(&_midi.lock);
346 }
347 
349 {
350  return _midi.playing || _midi.do_start;
351 }
352 
354 {
355  EnterCriticalSection(&_midi.lock);
356  _midi.new_volume = vol;
357  LeaveCriticalSection(&_midi.lock);
358 }
359 
360 const char *MusicDriver_Win32::Start(const char * const *parm)
361 {
362  DEBUG(driver, 2, "Win32-MIDI: Start: initializing");
363 
364  InitializeCriticalSection(&_midi.lock);
365 
366  int resolution = GetDriverParamInt(parm, "resolution", 5);
367  int port = GetDriverParamInt(parm, "port", -1);
368 
369  UINT devid;
370  if (port < 0) {
371  devid = MIDI_MAPPER;
372  } else {
373  devid = (UINT)port;
374  }
375 
376  resolution = Clamp(resolution, 1, 20);
377 
378  if (midiOutOpen(&_midi.midi_out, devid, (DWORD_PTR)&MidiOutProc, (DWORD_PTR)this, CALLBACK_FUNCTION) != MMSYSERR_NOERROR) {
379  return "could not open midi device";
380  }
381 
382  midiOutReset(_midi.midi_out);
383 
384  /* Standard "Enable General MIDI" message */
385  static byte gm_enable_sysex[] = { 0xF0, 0x7E, 0x00, 0x09, 0x01, 0xF7 };
386  TransmitSysexConst(&gm_enable_sysex[0], sizeof(gm_enable_sysex));
387 
388  /* Roland-specific reverb room control, used by the original game */
389  static byte roland_reverb_sysex[] = { 0xF0, 0x41, 0x10, 0x42, 0x12, 0x40, 0x01, 0x30, 0x02, 0x04, 0x00, 0x40, 0x40, 0x00, 0x00, 0x09, 0xF7 };
390  TransmitSysexConst(&roland_reverb_sysex[0], sizeof(roland_reverb_sysex));
391 
392  /* prepare multimedia timer */
393  TIMECAPS timecaps;
394  if (timeGetDevCaps(&timecaps, sizeof(timecaps)) == MMSYSERR_NOERROR) {
395  _midi.time_period = min(max((UINT)resolution, timecaps.wPeriodMin), timecaps.wPeriodMax);
396  if (timeBeginPeriod(_midi.time_period) == MMSYSERR_NOERROR) {
397  /* success */
398  DEBUG(driver, 2, "Win32-MIDI: Start: timer resolution is %d", (int)_midi.time_period);
399  return NULL;
400  }
401  }
402  midiOutClose(_midi.midi_out);
403  return "could not set timer resolution";
404 }
405 
407 {
408  EnterCriticalSection(&_midi.lock);
409 
410  if (_midi.timer_id) {
411  timeKillEvent(_midi.timer_id);
412  _midi.timer_id = 0;
413  }
414 
415  timeEndPeriod(_midi.time_period);
416  midiOutReset(_midi.midi_out);
417  midiOutClose(_midi.midi_out);
418 
419  LeaveCriticalSection(&_midi.lock);
420  DeleteCriticalSection(&_midi.lock);
421 }
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:406
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:313
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:109
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:360
bool IsSongPlaying()
Are we currently playing a song?
Definition: win32_m.cpp:348
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:353
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:339
bool do_start
flag for starting playback of next_file at next opportunity
Definition: win32_m.cpp:38