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" 23 #include "../safeguards.h" 56 static byte ScaleVolume(byte original, byte scale)
58 return original * scale / 127;
62 void CALLBACK MidiOutProc(HMIDIOUT hmo, UINT wMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2)
64 if (wMsg == MOM_DONE) {
65 MIDIHDR *hdr = (LPMIDIHDR)dwParam1;
66 midiOutUnprepareHeader(hmo, hdr,
sizeof(*hdr));
71 static void TransmitChannelMsg(byte status, byte p1, byte p2 = 0)
73 midiOutShortMsg(_midi.midi_out, status | (p1 << 8) | (p2 << 16));
76 static void TransmitSysex(byte *&msg_start,
size_t &remaining)
79 byte *msg_end = msg_start;
80 while (*msg_end != MIDIST_ENDSYSEX) msg_end++;
84 MIDIHDR *hdr = CallocT<MIDIHDR>(1);
85 hdr->lpData = (LPSTR)msg_start;
86 hdr->dwBufferLength = msg_end - msg_start;
87 if (midiOutPrepareHeader(_midi.midi_out, hdr,
sizeof(*hdr)) == MMSYSERR_NOERROR) {
89 hdr->dwBytesRecorded = hdr->dwBufferLength;
90 midiOutLongMsg(_midi.midi_out, hdr,
sizeof(*hdr));
96 remaining -= msg_end - msg_start;
100 static void TransmitSysexConst(byte *msg_start,
size_t length)
102 TransmitSysex(msg_start, length);
109 void CALLBACK
TimerCallback(UINT uTimerID, UINT, DWORD_PTR dwUser, DWORD_PTR, DWORD_PTR)
114 if (TryEnterCriticalSection(&_midi.lock)) {
117 DEBUG(driver, 2,
"Win32-MIDI: timer: do_stop is set");
118 midiOutReset(_midi.midi_out);
119 _midi.playing =
false;
120 _midi.do_stop =
false;
121 LeaveCriticalSection(&_midi.lock);
126 if (_midi.do_start) {
127 DEBUG(driver, 2,
"Win32-MIDI: timer: do_start is set");
129 midiOutReset(_midi.midi_out);
133 for (
int ch = 0; ch < 16; ch++) {
136 TransmitChannelMsg(MIDIST_CONTROLLER | ch, MIDICT_RPN_SELECT_LO, 0x00);
137 TransmitChannelMsg(MIDICT_RPN_SELECT_HI, 0x00);
138 TransmitChannelMsg(MIDICT_DATAENTRY, 0x02);
139 TransmitChannelMsg(MIDICT_DATAENTRY_LO, 0x00);
140 TransmitChannelMsg(MIDICT_RPN_SELECT_LO, 0x7F);
141 TransmitChannelMsg(MIDICT_RPN_SELECT_HI, 0x7F);
144 _midi.current_file.MoveFrom(_midi.next_file);
145 std::swap(_midi.next_segment, _midi.current_segment);
146 _midi.current_segment.start_block = 0;
147 _midi.playback_start_time = timeGetTime();
148 _midi.playing =
true;
149 _midi.do_start =
false;
150 _midi.current_block = 0;
152 MemSetT<byte>(_midi.channel_volumes, 127,
lengthof(_midi.channel_volumes));
153 }
else if (!_midi.playing) {
155 DEBUG(driver, 2,
"Win32-MIDI: timer: not playing, stopping timer");
156 timeKillEvent(uTimerID);
158 LeaveCriticalSection(&_midi.lock);
163 static int volume_throttle = 0;
164 if (_midi.current_volume != _midi.new_volume) {
165 if (volume_throttle == 0) {
166 DEBUG(driver, 2,
"Win32-MIDI: timer: volume change");
167 _midi.current_volume = _midi.new_volume;
168 volume_throttle = 20 / _midi.time_period;
169 for (
int ch = 0; ch < 16; ch++) {
170 int vol = ScaleVolume(_midi.channel_volumes[ch], _midi.current_volume);
171 TransmitChannelMsg(MIDIST_CONTROLLER | ch, MIDICT_CHANVOLUME, vol);
179 LeaveCriticalSection(&_midi.lock);
183 if (_midi.current_segment.start > 0 && _midi.current_block == 0 && _midi.current_segment.start_block == 0) {
187 uint preload_bytes = 0;
188 for (
size_t bl = 0; bl < _midi.current_file.blocks.size(); bl++) {
191 if (block.
ticktime >= _midi.current_segment.start) {
192 if (_midi.current_segment.loop) {
193 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);
194 _midi.current_segment.start_block = bl;
202 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);
203 _midi.playback_start_time -= block.
realtime / 1000 - preload_bytes * 1000 / 3125;
212 DWORD current_time = timeGetTime();
213 DWORD playback_time = current_time - _midi.playback_start_time;
214 while (_midi.current_block < _midi.current_file.blocks.size()) {
218 if (_midi.current_segment.end > 0 && block.
ticktime >= _midi.current_segment.end) {
219 if (_midi.current_segment.loop) {
220 _midi.current_block = _midi.current_segment.start_block;
221 _midi.playback_start_time = timeGetTime() - _midi.current_file.blocks[_midi.current_block].realtime / 1000;
223 _midi.do_stop =
true;
228 if (block.
realtime / 1000 > playback_time) {
234 byte last_status = 0;
235 while (remaining > 0) {
238 byte status = data[0];
240 last_status = status;
244 status = last_status;
246 switch (status & 0xF0) {
248 case MIDIST_CHANPRESS:
250 TransmitChannelMsg(status, data[0]);
256 case MIDIST_POLYPRESS:
257 case MIDIST_PITCHBEND:
259 TransmitChannelMsg(status, data[0], data[1]);
263 case MIDIST_CONTROLLER:
265 if (data[0] == MIDICT_CHANVOLUME) {
267 _midi.channel_volumes[status & 0x0F] = data[1];
268 int vol = ScaleVolume(data[1], _midi.current_volume);
269 TransmitChannelMsg(status, data[0], vol);
272 TransmitChannelMsg(status, data[0], data[1]);
281 TransmitSysex(data, remaining);
283 case MIDIST_TC_QFRAME:
288 case MIDIST_SONGPOSPTR:
299 _midi.current_block++;
303 if (_midi.current_block == _midi.current_file.blocks.size()) {
304 if (_midi.current_segment.loop) {
305 _midi.current_block = _midi.current_segment.start_block;
306 _midi.playback_start_time = timeGetTime() - _midi.current_file.blocks[_midi.current_block].realtime / 1000;
308 _midi.do_stop =
true;
315 DEBUG(driver, 2,
"Win32-MIDI: PlaySong: entry");
316 EnterCriticalSection(&_midi.lock);
318 if (!_midi.next_file.LoadSong(song)) {
319 LeaveCriticalSection(&_midi.lock);
325 _midi.next_segment.loop = song.
loop;
327 DEBUG(driver, 2,
"Win32-MIDI: PlaySong: setting flag");
328 _midi.do_stop = _midi.playing;
329 _midi.do_start =
true;
331 if (_midi.timer_id == 0) {
332 DEBUG(driver, 2,
"Win32-MIDI: PlaySong: starting timer");
333 _midi.timer_id = timeSetEvent(_midi.time_period, _midi.time_period,
TimerCallback, (DWORD_PTR)
this, TIME_PERIODIC | TIME_CALLBACK_FUNCTION);
336 LeaveCriticalSection(&_midi.lock);
341 DEBUG(driver, 2,
"Win32-MIDI: StopSong: entry");
342 EnterCriticalSection(&_midi.lock);
343 DEBUG(driver, 2,
"Win32-MIDI: StopSong: setting flag");
344 _midi.do_stop =
true;
345 LeaveCriticalSection(&_midi.lock);
350 return _midi.playing || _midi.do_start;
355 EnterCriticalSection(&_midi.lock);
356 _midi.new_volume = vol;
357 LeaveCriticalSection(&_midi.lock);
362 DEBUG(driver, 2,
"Win32-MIDI: Start: initializing");
364 InitializeCriticalSection(&_midi.lock);
376 resolution =
Clamp(resolution, 1, 20);
378 if (midiOutOpen(&_midi.midi_out, devid, (DWORD_PTR)&MidiOutProc, (DWORD_PTR)
this, CALLBACK_FUNCTION) != MMSYSERR_NOERROR) {
379 return "could not open midi device";
382 midiOutReset(_midi.midi_out);
385 static byte gm_enable_sysex[] = { 0xF0, 0x7E, 0x00, 0x09, 0x01, 0xF7 };
386 TransmitSysexConst(&gm_enable_sysex[0],
sizeof(gm_enable_sysex));
389 static byte roland_reverb_sysex[] = { 0xF0, 0x41, 0x10, 0x42, 0x12, 0x40, 0x01, 0x30, 0x02, 0x04, 0x00, 0x40, 0x40, 0x00, 0x00, 0x09, 0xF7 };
390 TransmitSysexConst(&roland_reverb_sysex[0],
sizeof(roland_reverb_sysex));
394 if (timeGetDevCaps(&timecaps,
sizeof(timecaps)) == MMSYSERR_NOERROR) {
395 _midi.time_period =
min(
max((UINT)resolution, timecaps.wPeriodMin), timecaps.wPeriodMax);
396 if (timeBeginPeriod(_midi.time_period) == MMSYSERR_NOERROR) {
398 DEBUG(driver, 2,
"Win32-MIDI: Start: timer resolution is %d", (
int)_midi.time_period);
402 midiOutClose(_midi.midi_out);
403 return "could not set timer resolution";
408 EnterCriticalSection(&_midi.lock);
410 if (_midi.timer_id) {
411 timeKillEvent(_midi.timer_id);
415 timeEndPeriod(_midi.time_period);
416 midiOutReset(_midi.midi_out);
417 midiOutClose(_midi.midi_out);
419 LeaveCriticalSection(&_midi.lock);
420 DeleteCriticalSection(&_midi.lock);
int override_end
MIDI tick to end the song at (0 if no override)
Metadata about a music track.
void Stop()
Stop this driver.
Factory for Windows' music player.
MidiFile current_file
file currently being played from
CRITICAL_SECTION lock
synchronization for playback status fields
Base for Windows music playback.
SmallVector< byte, 8 > data
raw midi data contained in block
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
const T * Begin() const
Get the pointer to the first item (const)
byte new_volume
volume setting to change to
static T max(const T a, const T b)
Returns the maximum of two values.
void PlaySong(const MusicSongInfo &song)
Play a particular song.
UINT timer_id
ID of active multimedia timer.
uint Length() const
Get the number of items in the list.
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.
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)
Start this driver.
bool IsSongPlaying()
Are we currently playing a song?
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
void SetVolume(byte vol)
Set the volume, if possible.
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.
void StopSong()
Stop playing the current song.
bool do_start
flag for starting playback of next_file at next opportunity