12 #include "../stdafx.h" 13 #include "../string_func.h" 17 #include "../os/windows/win32.h" 19 #include "midifile.hpp" 21 #include "../base_media_base.h" 24 #include "../safeguards.h" 57 static byte ScaleVolume(byte original, byte scale)
59 return original * scale / 127;
63 void CALLBACK MidiOutProc(HMIDIOUT hmo, UINT wMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2)
65 if (wMsg == MOM_DONE) {
66 MIDIHDR *hdr = (LPMIDIHDR)dwParam1;
67 midiOutUnprepareHeader(hmo, hdr,
sizeof(*hdr));
72 static void TransmitChannelMsg(byte status, byte p1, byte p2 = 0)
74 midiOutShortMsg(
_midi.midi_out, status | (p1 << 8) | (p2 << 16));
77 static void TransmitSysex(
const byte *&msg_start,
size_t &remaining)
80 const byte *msg_end = msg_start;
81 while (*msg_end != MIDIST_ENDSYSEX) msg_end++;
85 MIDIHDR *hdr = CallocT<MIDIHDR>(1);
86 hdr->lpData =
reinterpret_cast<LPSTR
>(
const_cast<byte *
>(msg_start));
87 hdr->dwBufferLength = msg_end - msg_start;
88 if (midiOutPrepareHeader(
_midi.midi_out, hdr,
sizeof(*hdr)) == MMSYSERR_NOERROR) {
90 hdr->dwBytesRecorded = hdr->dwBufferLength;
91 midiOutLongMsg(
_midi.midi_out, hdr,
sizeof(*hdr));
97 remaining -= msg_end - msg_start;
101 static void TransmitStandardSysex(MidiSysexMessage msg)
104 const byte *data = MidiGetStandardSysexMessage(msg, length);
105 TransmitSysex(data, length);
112 void CALLBACK
TimerCallback(UINT uTimerID, UINT, DWORD_PTR dwUser, DWORD_PTR, DWORD_PTR)
115 std::unique_lock<std::mutex> mutex_lock(
_midi.lock, std::defer_lock);
116 if (!mutex_lock.try_lock())
return;
120 DEBUG(driver, 2,
"Win32-MIDI: timer: do_stop is set");
121 midiOutReset(
_midi.midi_out);
122 _midi.playing =
false;
123 _midi.do_stop =
false;
128 if (
_midi.do_start != 0) {
130 if (timeGetTime() -
_midi.playback_start_time < 50) {
133 DEBUG(driver, 2,
"Win32-MIDI: timer: do_start step %d",
_midi.do_start);
135 if (
_midi.do_start == 1) {
137 midiOutReset(
_midi.midi_out);
138 _midi.playback_start_time = timeGetTime();
142 }
else if (
_midi.do_start == 2) {
144 TransmitStandardSysex(MidiSysexMessage::ResetGM);
145 _midi.playback_start_time = timeGetTime();
149 }
else if (
_midi.do_start == 3) {
151 TransmitStandardSysex(MidiSysexMessage::RolandSetReverb);
152 _midi.playback_start_time = timeGetTime();
156 }
else if (
_midi.do_start == 4) {
159 std::swap(
_midi.next_segment,
_midi.current_segment);
160 _midi.current_segment.start_block = 0;
161 _midi.playback_start_time = timeGetTime();
162 _midi.playing =
true;
164 _midi.current_block = 0;
168 }
else if (!
_midi.playing) {
170 DEBUG(driver, 2,
"Win32-MIDI: timer: not playing, stopping timer");
171 timeKillEvent(uTimerID);
177 static int volume_throttle = 0;
178 if (
_midi.current_volume !=
_midi.new_volume) {
179 if (volume_throttle == 0) {
180 DEBUG(driver, 2,
"Win32-MIDI: timer: volume change");
182 volume_throttle = 20 /
_midi.time_period;
183 for (
int ch = 0; ch < 16; ch++) {
184 byte vol = ScaleVolume(
_midi.channel_volumes[ch],
_midi.current_volume);
185 TransmitChannelMsg(MIDIST_CONTROLLER | ch, MIDICT_CHANVOLUME, vol);
193 if (
_midi.current_segment.start > 0 &&
_midi.current_block == 0 &&
_midi.current_segment.start_block == 0) {
197 size_t preload_bytes = 0;
198 for (
size_t bl = 0; bl <
_midi.current_file.blocks.size(); bl++) {
200 preload_bytes += block.
data.size();
202 if (
_midi.current_segment.loop) {
203 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);
204 _midi.current_segment.start_block = bl;
212 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);
213 _midi.playback_start_time -= block.
realtime / 1000 - (DWORD)(preload_bytes * 1000 / 3125);
222 DWORD current_time = timeGetTime();
223 DWORD playback_time = current_time -
_midi.playback_start_time;
224 while (
_midi.current_block <
_midi.current_file.blocks.size()) {
229 if (
_midi.current_segment.loop) {
230 _midi.current_block =
_midi.current_segment.start_block;
231 _midi.playback_start_time = timeGetTime() -
_midi.current_file.blocks[
_midi.current_block].realtime / 1000;
233 _midi.do_stop =
true;
238 if (block.
realtime / 1000 > playback_time) {
242 const byte *data = block.
data.data();
243 size_t remaining = block.
data.size();
244 byte last_status = 0;
245 while (remaining > 0) {
248 byte status = data[0];
250 last_status = status;
254 status = last_status;
256 switch (status & 0xF0) {
258 case MIDIST_CHANPRESS:
260 TransmitChannelMsg(status, data[0]);
266 case MIDIST_POLYPRESS:
267 case MIDIST_PITCHBEND:
269 TransmitChannelMsg(status, data[0], data[1]);
273 case MIDIST_CONTROLLER:
275 if (data[0] == MIDICT_CHANVOLUME) {
277 _midi.channel_volumes[status & 0x0F] = data[1];
278 int vol = ScaleVolume(data[1],
_midi.current_volume);
279 TransmitChannelMsg(status, data[0], vol);
282 TransmitChannelMsg(status, data[0], data[1]);
291 TransmitSysex(data, remaining);
293 case MIDIST_TC_QFRAME:
298 case MIDIST_SONGPOSPTR:
309 _midi.current_block++;
313 if (
_midi.current_block ==
_midi.current_file.blocks.size()) {
314 if (
_midi.current_segment.loop) {
315 _midi.current_block =
_midi.current_segment.start_block;
316 _midi.playback_start_time = timeGetTime() -
_midi.current_file.blocks[
_midi.current_block].realtime / 1000;
318 _midi.do_stop =
true;
325 DEBUG(driver, 2,
"Win32-MIDI: PlaySong: entry");
328 if (!new_song.LoadSong(song))
return;
329 DEBUG(driver, 2,
"Win32-MIDI: PlaySong: Loaded song");
331 std::lock_guard<std::mutex> mutex_lock(
_midi.lock);
333 _midi.next_file.MoveFrom(new_song);
338 DEBUG(driver, 2,
"Win32-MIDI: PlaySong: setting flag");
342 if (
_midi.timer_id == 0) {
343 DEBUG(driver, 2,
"Win32-MIDI: PlaySong: starting timer");
344 _midi.timer_id = timeSetEvent(
_midi.time_period,
_midi.time_period,
TimerCallback, (DWORD_PTR)
this, TIME_PERIODIC | TIME_CALLBACK_FUNCTION);
350 DEBUG(driver, 2,
"Win32-MIDI: StopSong: entry");
351 std::lock_guard<std::mutex> mutex_lock(
_midi.lock);
352 DEBUG(driver, 2,
"Win32-MIDI: StopSong: setting flag");
353 _midi.do_stop =
true;
358 return _midi.playing || (
_midi.do_start != 0);
363 std::lock_guard<std::mutex> mutex_lock(
_midi.lock);
364 _midi.new_volume = vol;
369 DEBUG(driver, 2,
"Win32-MIDI: Start: initializing");
376 if (portname !=
nullptr || _debug_driver_level > 0) {
377 uint numports = midiOutGetNumDevs();
378 DEBUG(driver, 1,
"Win32-MIDI: Found %d output devices:", numports);
379 for (uint tryport = 0; tryport < numports; tryport++) {
381 if (midiOutGetDevCaps(tryport, &moc,
sizeof(moc)) == MMSYSERR_NOERROR) {
382 char tryportname[128];
387 if (portname !=
nullptr && strncmp(tryportname, portname,
lengthof(tryportname)) == 0) port = tryport;
389 DEBUG(driver, 1,
"MIDI port %2d: %s%s", tryport, tryportname, (tryport == port) ?
" [selected]" :
"");
395 if (port == UINT_MAX) {
401 resolution =
Clamp(resolution, 1, 20);
403 if (midiOutOpen(&
_midi.midi_out, devid, (DWORD_PTR)&MidiOutProc, (DWORD_PTR)
this, CALLBACK_FUNCTION) != MMSYSERR_NOERROR) {
404 return "could not open midi device";
407 midiOutReset(
_midi.midi_out);
411 if (timeGetDevCaps(&timecaps,
sizeof(timecaps)) == MMSYSERR_NOERROR) {
412 _midi.time_period =
min(
max((UINT)resolution, timecaps.wPeriodMin), timecaps.wPeriodMax);
413 if (timeBeginPeriod(
_midi.time_period) == MMSYSERR_NOERROR) {
415 DEBUG(driver, 2,
"Win32-MIDI: Start: timer resolution is %d", (
int)
_midi.time_period);
419 midiOutClose(
_midi.midi_out);
420 return "could not set timer resolution";
425 std::lock_guard<std::mutex> mutex_lock(
_midi.lock);
427 if (
_midi.timer_id) {
428 timeKillEvent(
_midi.timer_id);
432 timeEndPeriod(
_midi.time_period);
433 midiOutReset(
_midi.midi_out);
434 midiOutClose(
_midi.midi_out);
int override_end
MIDI tick to end the song at (0 if no override)
const char * GetDriverParam(const char *const *parm, const char *name)
Get a string parameter the list of parameters.
Metadata about a music track.
Factory for Windows' music player.
MidiFile current_file
file currently being played from
bool IsSongPlaying() override
Are we currently playing a song?
Base for Windows music playback.
void StopSong() override
Stop playing the current song.
size_t current_block
next block index to send
int override_start
MIDI ticks to skip over in beginning.
bool playing
flag indicating that playback is active
void PlaySong(const MusicSongInfo &song) override
Play a particular song.
byte new_volume
volume setting to change to
char * convert_from_fs(const TCHAR *name, char *utf8_buf, size_t buflen)
Convert to OpenTTD's encoding from that of the environment in UNICODE.
static T max(const T a, const T b)
Returns the maximum of two values.
void SetVolume(byte vol) override
Set the volume, if possible.
UINT timer_id
ID of active multimedia timer.
UINT time_period
obtained timer precision value
void CALLBACK TimerCallback(UINT uTimerID, UINT, DWORD_PTR dwUser, DWORD_PTR, DWORD_PTR)
Realtime MIDI playback service routine.
std::mutex lock
synchronization for playback status fields
std::vector< byte > data
raw midi data contained in block
byte channel_volumes[16]
last seen volume controller values in raw data
bool do_stop
flag for stopping playback at next opportunity
#define lengthof(x)
Return the length of an fixed size array.
static T min(const T a, const T b)
Returns the minimum of two values.
const char * Start(const char *const *param) override
Start this driver.
uint32 realtime
real-time (microseconds) since start of file this block should be triggered at
HMIDIOUT midi_out
handle to open midiOut
MidiFile next_file
upcoming file to play
static T Clamp(const T a, const T min, const T max)
Clamp a value between an interval.
#define DEBUG(name, level,...)
Output a line of debugging information.
PlaybackSegment next_segment
segment info for upcoming file
byte current_volume
current effective volume setting
uint32 ticktime
tick number since start of file this block should be triggered at
bool loop
song should play in a tight loop if possible, never ending
PlaybackSegment current_segment
segment info for current playback
static struct @24 _midi
Metadata about the midi we're playing.
DWORD playback_start_time
timestamp current file began playback
static void free(const void *ptr)
Version of the standard free that accepts const pointers.
int GetDriverParamInt(const char *const *parm, const char *name, int def)
Get an integer parameter the list of parameters.
int do_start
flag for starting playback of next_file at next opportunity
void Stop() override
Stop this driver.