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/>.
10 /** @file win32_m.cpp Music playback for Windows. */
12 #include "../stdafx.h"
13 #include "../string_func.h"
17 #include "../os/windows/win32.h"
19 #include "midifile.hpp"
22 #include "../safeguards.h"
24 struct PlaybackSegment
{
31 UINT time_period
; ///< obtained timer precision value
32 HMIDIOUT midi_out
; ///< handle to open midiOut
33 UINT timer_id
; ///< ID of active multimedia timer
34 CRITICAL_SECTION lock
; ///< synchronization for playback status fields
36 bool playing
; ///< flag indicating that playback is active
37 bool do_start
; ///< flag for starting playback of next_file at next opportunity
38 bool do_stop
; ///< flag for stopping playback at next opportunity
39 byte current_volume
; ///< current effective volume setting
40 byte new_volume
; ///< volume setting to change to
42 MidiFile current_file
; ///< file currently being played from
43 PlaybackSegment current_segment
; ///< segment info for current playback
44 DWORD playback_start_time
; ///< timestamp current file began playback
45 size_t current_block
; ///< next block index to send
46 MidiFile next_file
; ///< upcoming file to play
47 PlaybackSegment next_segment
; ///< segment info for upcoming file
49 byte channel_volumes
[16]; ///< last seen volume controller values in raw data
52 static FMusicDriver_Win32 iFMusicDriver_Win32
;
55 static byte
ScaleVolume(byte original
, byte scale
)
57 return original
* scale
/ 127;
61 void CALLBACK
MidiOutProc(HMIDIOUT hmo
, UINT wMsg
, DWORD_PTR dwInstance
, DWORD_PTR dwParam1
, DWORD_PTR dwParam2
)
63 if (wMsg
== MOM_DONE
) {
64 MIDIHDR
*hdr
= (LPMIDIHDR
)dwParam1
;
65 midiOutUnprepareHeader(hmo
, hdr
, sizeof(*hdr
));
70 static void TransmitChannelMsg(byte status
, byte p1
, byte p2
= 0)
72 midiOutShortMsg(_midi
.midi_out
, status
| (p1
<< 8) | (p2
<< 16));
75 static void TransmitSysex(byte
*&msg_start
, size_t &remaining
)
77 /* find end of message */
78 byte
*msg_end
= msg_start
;
79 while (*msg_end
!= MIDIST_ENDSYSEX
) msg_end
++;
80 msg_end
++; /* also include sysex end byte */
83 MIDIHDR
*hdr
= CallocT
<MIDIHDR
>(1);
84 hdr
->lpData
= (LPSTR
)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
));
94 /* update position in buffer */
95 remaining
-= msg_end
- msg_start
;
99 static void TransmitSysexConst(byte
*msg_start
, size_t length
)
101 TransmitSysex(msg_start
, length
);
105 * Realtime MIDI playback service routine.
106 * This is called by the multimedia timer.
108 void CALLBACK
TimerCallback(UINT uTimerID
, UINT
, DWORD_PTR dwUser
, DWORD_PTR
, DWORD_PTR
)
110 /* Try to check playback status changes.
111 * If _midi is already locked, skip checking for this cycle and try again
112 * next cycle, instead of waiting for locks in the realtime callback. */
113 if (TryEnterCriticalSection(&_midi
.lock
)) {
116 DEBUG(driver
, 2, "Win32-MIDI: timer: do_stop is set");
117 midiOutReset(_midi
.midi_out
);
118 _midi
.playing
= false;
119 _midi
.do_stop
= false;
120 LeaveCriticalSection(&_midi
.lock
);
124 /* check for start/restart/change song */
125 if (_midi
.do_start
) {
126 DEBUG(driver
, 2, "Win32-MIDI: timer: do_start is set");
128 midiOutReset(_midi
.midi_out
);
130 _midi
.current_file
.MoveFrom(_midi
.next_file
);
131 std::swap(_midi
.next_segment
, _midi
.current_segment
);
132 _midi
.current_segment
.start_block
= 0;
133 _midi
.playback_start_time
= timeGetTime();
134 _midi
.playing
= true;
135 _midi
.do_start
= false;
136 _midi
.current_block
= 0;
138 MemSetT
<byte
>(_midi
.channel_volumes
, 127, lengthof(_midi
.channel_volumes
));
139 } else if (!_midi
.playing
) {
140 /* not playing, stop the timer */
141 DEBUG(driver
, 2, "Win32-MIDI: timer: not playing, stopping timer");
142 timeKillEvent(uTimerID
);
144 LeaveCriticalSection(&_midi
.lock
);
148 /* check for volume change */
149 static int volume_throttle
= 0;
150 if (_midi
.current_volume
!= _midi
.new_volume
) {
151 if (volume_throttle
== 0) {
152 DEBUG(driver
, 2, "Win32-MIDI: timer: volume change");
153 _midi
.current_volume
= _midi
.new_volume
;
154 volume_throttle
= 20 / _midi
.time_period
;
155 for (int ch
= 0; ch
< 16; ch
++) {
156 int vol
= ScaleVolume(_midi
.channel_volumes
[ch
], _midi
.current_volume
);
157 TransmitChannelMsg(MIDIST_CONTROLLER
| ch
, MIDICT_CHANVOLUME
, vol
);
165 LeaveCriticalSection(&_midi
.lock
);
168 /* skip beginning of file? */
169 if (_midi
.current_segment
.start
> 0 && _midi
.current_block
== 0 && _midi
.current_segment
.start_block
== 0) {
170 /* find first block after start time and pretend playback started earlier
171 * this is to allow all blocks prior to the actual start to still affect playback,
172 * as they may contain important controller and program changes */
173 size_t preload_bytes
= 0;
174 for (size_t bl
= 0; bl
< _midi
.current_file
.blocks
.size(); bl
++) {
175 MidiFile::DataBlock
&block
= _midi
.current_file
.blocks
[bl
];
176 preload_bytes
+= block
.data
.Length();
177 if (block
.ticktime
>= _midi
.current_segment
.start
) {
178 if (_midi
.current_segment
.loop
) {
179 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
);
180 _midi
.current_segment
.start_block
= bl
;
183 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
);
184 _midi
.playback_start_time
-= block
.realtime
/ 1000;
192 /* play pending blocks */
193 DWORD current_time
= timeGetTime();
194 DWORD playback_time
= current_time
- _midi
.playback_start_time
;
195 while (_midi
.current_block
< _midi
.current_file
.blocks
.size()) {
196 MidiFile::DataBlock
&block
= _midi
.current_file
.blocks
[_midi
.current_block
];
198 /* check that block is not in the future */
199 if (block
.realtime
/ 1000 > playback_time
) {
202 /* check that block isn't at end-of-song override */
203 if (_midi
.current_segment
.end
> 0 && block
.ticktime
>= _midi
.current_segment
.end
) {
204 if (_midi
.current_segment
.loop
) {
205 _midi
.current_block
= _midi
.current_segment
.start_block
;
206 _midi
.playback_start_time
= timeGetTime() - _midi
.current_file
.blocks
[_midi
.current_block
].realtime
/ 1000;
208 _midi
.do_stop
= true;
213 byte
*data
= block
.data
.Begin();
214 size_t remaining
= block
.data
.Length();
215 byte last_status
= 0;
216 while (remaining
> 0) {
217 /* MidiFile ought to have converted everything out of running status,
218 * but handle it anyway just to be safe */
219 byte status
= data
[0];
221 last_status
= status
;
225 status
= last_status
;
227 switch (status
& 0xF0) {
229 case MIDIST_CHANPRESS
:
230 /* 2 byte channel messages */
231 TransmitChannelMsg(status
, data
[0]);
237 case MIDIST_POLYPRESS
:
238 case MIDIST_PITCHBEND
:
239 /* 3 byte channel messages */
240 TransmitChannelMsg(status
, data
[0], data
[1]);
244 case MIDIST_CONTROLLER
:
245 /* controller change */
246 if (data
[0] == MIDICT_CHANVOLUME
) {
247 /* volume controller, adjust for user volume */
248 _midi
.channel_volumes
[status
& 0x0F] = data
[1];
249 int vol
= ScaleVolume(data
[1], _midi
.current_volume
);
250 TransmitChannelMsg(status
, data
[0], vol
);
252 /* handle other controllers normally */
253 TransmitChannelMsg(status
, data
[0], data
[1]);
259 /* system messages */
261 case MIDIST_SYSEX
: /* system exclusive */
262 TransmitSysex(data
, remaining
);
264 case MIDIST_TC_QFRAME
: /* time code quarter frame */
265 case MIDIST_SONGSEL
: /* song select */
269 case MIDIST_SONGPOSPTR
: /* song position pointer */
273 default: /* remaining have no data bytes */
280 _midi
.current_block
++;
284 if (_midi
.current_block
== _midi
.current_file
.blocks
.size()) {
285 if (_midi
.current_segment
.loop
) {
286 _midi
.current_block
= 0;
287 _midi
.playback_start_time
= timeGetTime();
289 _midi
.do_stop
= true;
294 void MusicDriver_Win32::PlaySong(const char *filename
)
296 DEBUG(driver
, 2, "Win32-MIDI: PlaySong: entry");
297 EnterCriticalSection(&_midi
.lock
);
299 _midi
.next_file
.LoadFile(filename
);
300 _midi
.next_segment
.start
= 0;
301 _midi
.next_segment
.end
= 0;
302 _midi
.next_segment
.loop
= false;
304 DEBUG(driver
, 2, "Win32-MIDI: PlaySong: setting flag");
305 _midi
.do_stop
= _midi
.playing
;
306 _midi
.do_start
= true;
308 if (_midi
.timer_id
== 0) {
309 DEBUG(driver
, 2, "Win32-MIDI: PlaySong: starting timer");
310 _midi
.timer_id
= timeSetEvent(_midi
.time_period
, _midi
.time_period
, TimerCallback
, (DWORD_PTR
)this, TIME_PERIODIC
| TIME_CALLBACK_FUNCTION
);
313 LeaveCriticalSection(&_midi
.lock
);
316 void MusicDriver_Win32::StopSong()
318 DEBUG(driver
, 2, "Win32-MIDI: StopSong: entry");
319 EnterCriticalSection(&_midi
.lock
);
320 DEBUG(driver
, 2, "Win32-MIDI: StopSong: setting flag");
321 _midi
.do_stop
= true;
322 LeaveCriticalSection(&_midi
.lock
);
325 bool MusicDriver_Win32::IsSongPlaying()
327 return _midi
.playing
|| _midi
.do_start
;
330 void MusicDriver_Win32::SetVolume(byte vol
)
332 EnterCriticalSection(&_midi
.lock
);
333 _midi
.new_volume
= vol
;
334 LeaveCriticalSection(&_midi
.lock
);
337 const char *MusicDriver_Win32::Start(const char * const *parm
)
339 DEBUG(driver
, 2, "Win32-MIDI: Start: initializing");
341 InitializeCriticalSection(&_midi
.lock
);
343 int resolution
= GetDriverParamInt(parm
, "resolution", 5);
344 int port
= GetDriverParamInt(parm
, "port", -1);
353 resolution
= Clamp(resolution
, 1, 20);
355 if (midiOutOpen(&_midi
.midi_out
, devid
, (DWORD_PTR
)&MidiOutProc
, (DWORD_PTR
)this, CALLBACK_FUNCTION
) != MMSYSERR_NOERROR
) {
356 return "could not open midi device";
359 midiOutReset(_midi
.midi_out
);
361 /* Standard "Enable General MIDI" message */
362 static byte gm_enable_sysex
[] = { 0xF0, 0x7E, 0x00, 0x09, 0x01, 0xF7 };
363 TransmitSysexConst(&gm_enable_sysex
[0], sizeof(gm_enable_sysex
));
365 /* prepare multimedia timer */
367 if (timeGetDevCaps(&timecaps
, sizeof(timecaps
)) == MMSYSERR_NOERROR
) {
368 _midi
.time_period
= min(max((UINT
)resolution
, timecaps
.wPeriodMin
), timecaps
.wPeriodMax
);
369 if (timeBeginPeriod(_midi
.time_period
) == MMSYSERR_NOERROR
) {
371 DEBUG(driver
, 2, "Win32-MIDI: Start: timer resolution is %d", (int)_midi
.time_period
);
375 midiOutClose(_midi
.midi_out
);
376 return "could not set timer resolution";
379 void MusicDriver_Win32::Stop()
381 EnterCriticalSection(&_midi
.lock
);
383 if (_midi
.timer_id
) {
384 timeKillEvent(_midi
.timer_id
);
388 timeEndPeriod(_midi
.time_period
);
389 midiOutReset(_midi
.midi_out
);
390 midiOutClose(_midi
.midi_out
);
392 LeaveCriticalSection(&_midi
.lock
);
393 DeleteCriticalSection(&_midi
.lock
);