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"
22 #include "../safeguards.h"
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(
const byte *&msg_start,
size_t &remaining)
78 const byte *msg_end = msg_start;
79 while (*msg_end != MIDIST_ENDSYSEX) msg_end++;
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) {
88 hdr->dwBytesRecorded = hdr->dwBufferLength;
89 midiOutLongMsg(
_midi.midi_out, hdr,
sizeof(*hdr));
95 remaining -= msg_end - msg_start;
99 static void TransmitStandardSysex(MidiSysexMessage msg)
102 const byte *data = MidiGetStandardSysexMessage(msg, length);
103 TransmitSysex(data, length);
110 void CALLBACK
TimerCallback(UINT uTimerID, UINT, DWORD_PTR dwUser, DWORD_PTR, DWORD_PTR)
113 std::unique_lock<std::mutex> mutex_lock(
_midi.lock, std::defer_lock);
114 if (!mutex_lock.try_lock())
return;
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;
126 if (
_midi.do_start != 0) {
128 if (timeGetTime() -
_midi.playback_start_time < 50) {
131 DEBUG(driver, 2,
"Win32-MIDI: timer: do_start step %d",
_midi.do_start);
133 if (
_midi.do_start == 1) {
135 midiOutReset(
_midi.midi_out);
136 _midi.playback_start_time = timeGetTime();
140 }
else if (
_midi.do_start == 2) {
142 TransmitStandardSysex(MidiSysexMessage::ResetGM);
143 _midi.playback_start_time = timeGetTime();
147 }
else if (
_midi.do_start == 3) {
149 TransmitStandardSysex(MidiSysexMessage::RolandSetReverb);
150 _midi.playback_start_time = timeGetTime();
154 }
else if (
_midi.do_start == 4) {
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;
162 _midi.current_block = 0;
166 }
else if (!
_midi.playing) {
168 DEBUG(driver, 2,
"Win32-MIDI: timer: not playing, stopping timer");
169 timeKillEvent(uTimerID);
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");
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);
191 if (
_midi.current_segment.start > 0 &&
_midi.current_block == 0 &&
_midi.current_segment.start_block == 0) {
195 size_t preload_bytes = 0;
196 for (
size_t bl = 0; bl <
_midi.current_file.blocks.size(); bl++) {
198 preload_bytes += block.
data.size();
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;
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);
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()) {
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;
231 _midi.do_stop =
true;
236 if (block.
realtime / 1000 > playback_time) {
240 const byte *data = block.
data.data();
241 size_t remaining = block.
data.size();
242 byte last_status = 0;
243 while (remaining > 0) {
246 byte status = data[0];
248 last_status = status;
252 status = last_status;
254 switch (status & 0xF0) {
256 case MIDIST_CHANPRESS:
258 TransmitChannelMsg(status, data[0]);
264 case MIDIST_POLYPRESS:
265 case MIDIST_PITCHBEND:
267 TransmitChannelMsg(status, data[0], data[1]);
271 case MIDIST_CONTROLLER:
273 if (data[0] == MIDICT_CHANVOLUME) {
275 _midi.channel_volumes[status & 0x0F] = data[1];
276 int vol = ScaleVolume(data[1],
_midi.current_volume);
277 TransmitChannelMsg(status, data[0], vol);
280 TransmitChannelMsg(status, data[0], data[1]);
289 TransmitSysex(data, remaining);
291 case MIDIST_TC_QFRAME:
296 case MIDIST_SONGPOSPTR:
307 _midi.current_block++;
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;
316 _midi.do_stop =
true;
323 DEBUG(driver, 2,
"Win32-MIDI: PlaySong: entry");
326 if (!new_song.LoadSong(song))
return;
327 DEBUG(driver, 2,
"Win32-MIDI: PlaySong: Loaded song");
329 std::lock_guard<std::mutex> mutex_lock(
_midi.lock);
331 _midi.next_file.MoveFrom(new_song);
336 DEBUG(driver, 2,
"Win32-MIDI: PlaySong: setting flag");
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);
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;
356 return _midi.playing || (
_midi.do_start != 0);
361 std::lock_guard<std::mutex> mutex_lock(
_midi.lock);
362 _midi.new_volume = vol;
367 DEBUG(driver, 2,
"Win32-MIDI: Start: initializing");
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++) {
379 if (midiOutGetDevCaps(tryport, &moc,
sizeof(moc)) == MMSYSERR_NOERROR) {
380 char tryportname[128];
385 if (portname !=
nullptr && strncmp(tryportname, portname,
lengthof(tryportname)) == 0) port = tryport;
387 DEBUG(driver, 1,
"MIDI port %2d: %s%s", tryport, tryportname, (tryport == port) ?
" [selected]" :
"");
393 if (port == UINT_MAX) {
399 resolution =
Clamp(resolution, 1, 20);
401 if (midiOutOpen(&
_midi.midi_out, devid, (DWORD_PTR)&MidiOutProc, (DWORD_PTR)
this, CALLBACK_FUNCTION) != MMSYSERR_NOERROR) {
402 return "could not open midi device";
405 midiOutReset(
_midi.midi_out);
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) {
413 DEBUG(driver, 2,
"Win32-MIDI: Start: timer resolution is %d", (
int)
_midi.time_period);
417 midiOutClose(
_midi.midi_out);
418 return "could not set timer resolution";
423 std::lock_guard<std::mutex> mutex_lock(
_midi.lock);
425 if (
_midi.timer_id) {
426 timeKillEvent(
_midi.timer_id);
430 timeEndPeriod(
_midi.time_period);
431 midiOutReset(
_midi.midi_out);
432 midiOutClose(
_midi.midi_out);