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/>.
8 /** @file win32_m.cpp Music playback for Windows. */
10 #include "../stdafx.h"
11 #include "../string_func.h"
15 #include "../os/windows/win32.h"
17 #include "midifile.hpp"
19 #include "../base_media_base.h"
20 #include "../core/mem_func.hpp"
23 #include "../safeguards.h"
25 struct PlaybackSegment
{
32 UINT time_period
; ///< obtained timer precision value
33 HMIDIOUT midi_out
; ///< handle to open midiOut
34 UINT timer_id
; ///< ID of active multimedia timer
35 std::mutex lock
; ///< synchronization for playback status fields
37 bool playing
; ///< flag indicating that playback is active
38 int do_start
; ///< flag for starting playback of next_file at next opportunity
39 bool do_stop
; ///< flag for stopping playback at next opportunity
40 uint8_t current_volume
; ///< current effective volume setting
41 uint8_t new_volume
; ///< volume setting to change to
43 MidiFile current_file
; ///< file currently being played from
44 PlaybackSegment current_segment
; ///< segment info for current playback
45 DWORD playback_start_time
; ///< timestamp current file began playback
46 size_t current_block
; ///< next block index to send
47 MidiFile next_file
; ///< upcoming file to play
48 PlaybackSegment next_segment
; ///< segment info for upcoming file
50 uint8_t channel_volumes
[16]; ///< last seen volume controller values in raw data
53 static FMusicDriver_Win32 iFMusicDriver_Win32
;
56 static uint8_t ScaleVolume(uint8_t original
, uint8_t scale
)
58 return original
* scale
/ 127;
62 void CALLBACK
MidiOutProc(HMIDIOUT hmo
, UINT wMsg
, DWORD_PTR
, DWORD_PTR dwParam1
, DWORD_PTR
)
64 if (wMsg
== MOM_DONE
) {
65 MIDIHDR
*hdr
= (LPMIDIHDR
)dwParam1
;
66 midiOutUnprepareHeader(hmo
, hdr
, sizeof(*hdr
));
71 static void TransmitChannelMsg(uint8_t status
, uint8_t p1
, uint8_t p2
= 0)
73 midiOutShortMsg(_midi
.midi_out
, status
| (p1
<< 8) | (p2
<< 16));
76 static void TransmitSysex(const uint8_t *&msg_start
, size_t &remaining
)
78 /* find end of message */
79 const uint8_t *msg_end
= msg_start
;
80 while (*msg_end
!= MIDIST_ENDSYSEX
) msg_end
++;
81 msg_end
++; /* also include sysex end byte */
84 MIDIHDR
*hdr
= CallocT
<MIDIHDR
>(1);
85 hdr
->lpData
= reinterpret_cast<LPSTR
>(const_cast<uint8_t *>(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
));
95 /* update position in buffer */
96 remaining
-= msg_end
- msg_start
;
100 static void TransmitStandardSysex(MidiSysexMessage msg
)
103 const uint8_t *data
= MidiGetStandardSysexMessage(msg
, length
);
104 TransmitSysex(data
, length
);
108 * Realtime MIDI playback service routine.
109 * This is called by the multimedia timer.
111 void CALLBACK
TimerCallback(UINT uTimerID
, UINT
, DWORD_PTR
, DWORD_PTR
, DWORD_PTR
)
113 static int volume_throttle
= 0;
115 /* Ensure only one timer callback is running at once, and prevent races on status flags */
116 std::unique_lock
<std::mutex
> mutex_lock(_midi
.lock
, std::defer_lock
);
117 if (!mutex_lock
.try_lock()) return;
121 Debug(driver
, 2, "Win32-MIDI: timer: do_stop is set");
122 midiOutReset(_midi
.midi_out
);
123 _midi
.playing
= false;
124 _midi
.do_stop
= false;
128 /* check for start/restart/change song */
129 if (_midi
.do_start
!= 0) {
130 /* Have a delay between playback start steps, prevents jumbled-together notes at the start of song */
131 if (timeGetTime() - _midi
.playback_start_time
< 50) {
134 Debug(driver
, 2, "Win32-MIDI: timer: do_start step {}", _midi
.do_start
);
136 if (_midi
.do_start
== 1) {
137 /* Send "all notes off" */
138 midiOutReset(_midi
.midi_out
);
139 _midi
.playback_start_time
= timeGetTime();
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();
150 } else if (_midi
.do_start
== 3) {
151 /* Set up device-specific effects */
152 TransmitStandardSysex(MidiSysexMessage::RolandSetReverb
);
153 _midi
.playback_start_time
= timeGetTime();
157 } else if (_midi
.do_start
== 4) {
158 /* Load the new file */
159 _midi
.current_file
.MoveFrom(_midi
.next_file
);
160 std::swap(_midi
.next_segment
, _midi
.current_segment
);
161 _midi
.current_segment
.start_block
= 0;
162 _midi
.playback_start_time
= timeGetTime();
163 _midi
.playing
= true;
165 _midi
.current_block
= 0;
167 MemSetT
<uint8_t>(_midi
.channel_volumes
, 127, lengthof(_midi
.channel_volumes
));
168 /* Invalidate current volume. */
169 _midi
.current_volume
= UINT8_MAX
;
172 } else if (!_midi
.playing
) {
173 /* not playing, stop the timer */
174 Debug(driver
, 2, "Win32-MIDI: timer: not playing, stopping timer");
175 timeKillEvent(uTimerID
);
180 /* check for volume change */
181 if (_midi
.current_volume
!= _midi
.new_volume
) {
182 if (volume_throttle
== 0) {
183 Debug(driver
, 2, "Win32-MIDI: timer: volume change");
184 _midi
.current_volume
= _midi
.new_volume
;
185 volume_throttle
= 20 / _midi
.time_period
;
186 for (int ch
= 0; ch
< 16; ch
++) {
187 uint8_t vol
= ScaleVolume(_midi
.channel_volumes
[ch
], _midi
.current_volume
);
188 TransmitChannelMsg(MIDIST_CONTROLLER
| ch
, MIDICT_CHANVOLUME
, vol
);
195 /* skip beginning of file? */
196 if (_midi
.current_segment
.start
> 0 && _midi
.current_block
== 0 && _midi
.current_segment
.start_block
== 0) {
197 /* find first block after start time and pretend playback started earlier
198 * this is to allow all blocks prior to the actual start to still affect playback,
199 * as they may contain important controller and program changes */
200 size_t preload_bytes
= 0;
201 for (size_t bl
= 0; bl
< _midi
.current_file
.blocks
.size(); bl
++) {
202 MidiFile::DataBlock
&block
= _midi
.current_file
.blocks
[bl
];
203 preload_bytes
+= block
.data
.size();
204 if (block
.ticktime
>= _midi
.current_segment
.start
) {
205 if (_midi
.current_segment
.loop
) {
206 Debug(driver
, 2, "Win32-MIDI: timer: loop from block {} (ticktime {}, realtime {:.3f}, bytes {})", bl
, block
.ticktime
, ((int)block
.realtime
)/1000.0, preload_bytes
);
207 _midi
.current_segment
.start_block
= bl
;
210 /* Calculate offset start time for playback.
211 * The preload_bytes are used to compensate for delay in transmission over traditional serial MIDI interfaces,
212 * which have a bitrate of 31,250 bits/sec, and transmit 1+8+1 start/data/stop bits per byte.
213 * The delay compensation is needed to avoid time-compression of following messages.
215 Debug(driver
, 2, "Win32-MIDI: timer: start from block {} (ticktime {}, realtime {:.3f}, bytes {})", bl
, block
.ticktime
, ((int)block
.realtime
) / 1000.0, preload_bytes
);
216 _midi
.playback_start_time
-= block
.realtime
/ 1000 - (DWORD
)(preload_bytes
* 1000 / 3125);
224 /* play pending blocks */
225 DWORD current_time
= timeGetTime();
226 DWORD playback_time
= current_time
- _midi
.playback_start_time
;
227 while (_midi
.current_block
< _midi
.current_file
.blocks
.size()) {
228 MidiFile::DataBlock
&block
= _midi
.current_file
.blocks
[_midi
.current_block
];
230 /* check that block isn't at end-of-song override */
231 if (_midi
.current_segment
.end
> 0 && block
.ticktime
>= _midi
.current_segment
.end
) {
232 if (_midi
.current_segment
.loop
) {
233 _midi
.current_block
= _midi
.current_segment
.start_block
;
234 _midi
.playback_start_time
= timeGetTime() - _midi
.current_file
.blocks
[_midi
.current_block
].realtime
/ 1000;
236 _midi
.do_stop
= true;
240 /* check that block is not in the future */
241 if (block
.realtime
/ 1000 > playback_time
) {
245 const uint8_t *data
= block
.data
.data();
246 size_t remaining
= block
.data
.size();
247 uint8_t last_status
= 0;
248 while (remaining
> 0) {
249 /* MidiFile ought to have converted everything out of running status,
250 * but handle it anyway just to be safe */
251 uint8_t status
= data
[0];
253 last_status
= status
;
257 status
= last_status
;
259 switch (status
& 0xF0) {
261 case MIDIST_CHANPRESS
:
262 /* 2 byte channel messages */
263 TransmitChannelMsg(status
, data
[0]);
269 case MIDIST_POLYPRESS
:
270 case MIDIST_PITCHBEND
:
271 /* 3 byte channel messages */
272 TransmitChannelMsg(status
, data
[0], data
[1]);
276 case MIDIST_CONTROLLER
:
277 /* controller change */
278 if (data
[0] == MIDICT_CHANVOLUME
) {
279 /* volume controller, adjust for user volume */
280 _midi
.channel_volumes
[status
& 0x0F] = data
[1];
281 int vol
= ScaleVolume(data
[1], _midi
.current_volume
);
282 TransmitChannelMsg(status
, data
[0], vol
);
284 /* handle other controllers normally */
285 TransmitChannelMsg(status
, data
[0], data
[1]);
291 /* system messages */
293 case MIDIST_SYSEX
: /* system exclusive */
294 TransmitSysex(data
, remaining
);
296 case MIDIST_TC_QFRAME
: /* time code quarter frame */
297 case MIDIST_SONGSEL
: /* song select */
301 case MIDIST_SONGPOSPTR
: /* song position pointer */
305 default: /* remaining have no data bytes */
312 _midi
.current_block
++;
316 if (_midi
.current_block
== _midi
.current_file
.blocks
.size()) {
317 if (_midi
.current_segment
.loop
) {
318 _midi
.current_block
= _midi
.current_segment
.start_block
;
319 _midi
.playback_start_time
= timeGetTime() - _midi
.current_file
.blocks
[_midi
.current_block
].realtime
/ 1000;
321 _midi
.do_stop
= true;
326 void MusicDriver_Win32::PlaySong(const MusicSongInfo
&song
)
328 Debug(driver
, 2, "Win32-MIDI: PlaySong: entry");
331 if (!new_song
.LoadSong(song
)) return;
332 Debug(driver
, 2, "Win32-MIDI: PlaySong: Loaded song");
334 std::lock_guard
<std::mutex
> mutex_lock(_midi
.lock
);
336 _midi
.next_file
.MoveFrom(new_song
);
337 _midi
.next_segment
.start
= song
.override_start
;
338 _midi
.next_segment
.end
= song
.override_end
;
339 _midi
.next_segment
.loop
= song
.loop
;
341 Debug(driver
, 2, "Win32-MIDI: PlaySong: setting flag");
342 _midi
.do_stop
= _midi
.playing
;
345 if (_midi
.timer_id
== 0) {
346 Debug(driver
, 2, "Win32-MIDI: PlaySong: starting timer");
347 _midi
.timer_id
= timeSetEvent(_midi
.time_period
, _midi
.time_period
, TimerCallback
, (DWORD_PTR
)this, TIME_PERIODIC
| TIME_CALLBACK_FUNCTION
);
351 void MusicDriver_Win32::StopSong()
353 Debug(driver
, 2, "Win32-MIDI: StopSong: entry");
354 std::lock_guard
<std::mutex
> mutex_lock(_midi
.lock
);
355 Debug(driver
, 2, "Win32-MIDI: StopSong: setting flag");
356 _midi
.do_stop
= true;
359 bool MusicDriver_Win32::IsSongPlaying()
361 return _midi
.playing
|| (_midi
.do_start
!= 0);
364 void MusicDriver_Win32::SetVolume(uint8_t vol
)
366 std::lock_guard
<std::mutex
> mutex_lock(_midi
.lock
);
367 _midi
.new_volume
= vol
;
370 std::optional
<std::string_view
> MusicDriver_Win32::Start(const StringList
&parm
)
372 Debug(driver
, 2, "Win32-MIDI: Start: initializing");
374 int resolution
= GetDriverParamInt(parm
, "resolution", 5);
375 uint port
= (uint
)GetDriverParamInt(parm
, "port", UINT_MAX
);
376 const char *portname
= GetDriverParam(parm
, "portname");
378 /* Enumerate ports either for selecting port by name, or for debug output */
379 if (portname
!= nullptr || _debug_driver_level
> 0) {
380 uint numports
= midiOutGetNumDevs();
381 Debug(driver
, 1, "Win32-MIDI: Found {} output devices:", numports
);
382 for (uint tryport
= 0; tryport
< numports
; tryport
++) {
384 if (midiOutGetDevCaps(tryport
, &moc
, sizeof(moc
)) == MMSYSERR_NOERROR
) {
385 char tryportname
[128];
386 convert_from_fs(moc
.szPname
, tryportname
);
388 /* Compare requested and detected port name.
389 * If multiple ports have the same name, this will select the last matching port, and the debug output will be confusing. */
390 if (portname
!= nullptr && strncmp(tryportname
, portname
, lengthof(tryportname
)) == 0) port
= tryport
;
392 Debug(driver
, 1, "MIDI port {:2d}: {}{}", tryport
, tryportname
, (tryport
== port
) ? " [selected]" : "");
398 if (port
== UINT_MAX
) {
404 resolution
= Clamp(resolution
, 1, 20);
406 if (midiOutOpen(&_midi
.midi_out
, devid
, (DWORD_PTR
)&MidiOutProc
, (DWORD_PTR
)this, CALLBACK_FUNCTION
) != MMSYSERR_NOERROR
) {
407 return "could not open midi device";
410 midiOutReset(_midi
.midi_out
);
412 /* prepare multimedia timer */
414 if (timeGetDevCaps(&timecaps
, sizeof(timecaps
)) == MMSYSERR_NOERROR
) {
415 _midi
.time_period
= std::min(std::max((UINT
)resolution
, timecaps
.wPeriodMin
), timecaps
.wPeriodMax
);
416 if (timeBeginPeriod(_midi
.time_period
) == MMSYSERR_NOERROR
) {
418 Debug(driver
, 2, "Win32-MIDI: Start: timer resolution is {}", _midi
.time_period
);
422 midiOutClose(_midi
.midi_out
);
423 return "could not set timer resolution";
426 void MusicDriver_Win32::Stop()
428 std::lock_guard
<std::mutex
> mutex_lock(_midi
.lock
);
430 if (_midi
.timer_id
) {
431 timeKillEvent(_midi
.timer_id
);
435 timeEndPeriod(_midi
.time_period
);
436 midiOutReset(_midi
.midi_out
);
437 midiOutClose(_midi
.midi_out
);