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