1 /* ScummVM - Graphic Adventure Engine
3 * ScummVM is the legal property of its developers, whose names
4 * are too numerous to list here. Please refer to the COPYRIGHT
5 * file distributed with this source distribution.
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
26 #include "sound/midiparser.h"
27 #include "sound/mididrv.h"
28 #include "common/util.h"
30 //////////////////////////////////////////////////
32 // MidiParser implementation
34 //////////////////////////////////////////////////
36 MidiParser::MidiParser() :
37 _hanging_notes_count(0),
39 _timer_rate(0x4A0000),
42 _psec_per_tick(5208), // 500000 / 96
45 _centerPitchWheelOnUnload(false),
49 memset(_active_notes
, 0, sizeof(_active_notes
));
52 void MidiParser::property(int prop
, int value
) {
55 _autoLoop
= (value
!= 0);
58 _smartJump
= (value
!= 0);
60 case mpCenterPitchWheelOnUnload
:
61 _centerPitchWheelOnUnload
= (value
!= 0);
66 void MidiParser::setTempo(uint32 tempo
) {
69 _psec_per_tick
= (tempo
+ (_ppqn
>> 2)) / _ppqn
;
72 // This is the conventional (i.e. SMF) variable length quantity
73 uint32
MidiParser::readVLQ(byte
* &data
) {
78 for (i
= 0; i
< 4; ++i
) {
81 value
= (value
<< 7) | (str
& 0x7F);
88 void MidiParser::activeNote(byte channel
, byte note
, bool active
) {
89 if (note
>= 128 || channel
>= 16)
93 _active_notes
[note
] |= (1 << channel
);
95 _active_notes
[note
] &= ~(1 << channel
);
97 // See if there are hanging notes that we can cancel
98 NoteTimer
*ptr
= _hanging_notes
;
100 for (i
= ARRAYSIZE(_hanging_notes
); i
; --i
, ++ptr
) {
101 if (ptr
->channel
== channel
&& ptr
->note
== note
&& ptr
->time_left
) {
103 --_hanging_notes_count
;
109 void MidiParser::hangingNote(byte channel
, byte note
, uint32 time_left
, bool recycle
) {
111 NoteTimer
*ptr
= _hanging_notes
;
114 if (_hanging_notes_count
>= ARRAYSIZE(_hanging_notes
)) {
115 warning("MidiParser::hangingNote(): Exceeded polyphony");
119 for (i
= ARRAYSIZE(_hanging_notes
); i
; --i
, ++ptr
) {
120 if (ptr
->channel
== channel
&& ptr
->note
== note
) {
121 if (ptr
->time_left
&& ptr
->time_left
< time_left
&& recycle
)
124 if (ptr
->time_left
) {
126 _driver
->send(0x80 | channel
, note
, 0);
127 --_hanging_notes_count
;
130 } else if (!best
&& ptr
->time_left
== 0) {
135 // Occassionally we might get a zero or negative note
136 // length, if the note should be turned on and off in
137 // the same iteration. For now just set it to 1 and
138 // we'll turn it off in the next cycle.
139 if (!time_left
|| time_left
& 0x80000000)
143 best
->channel
= channel
;
145 best
->time_left
= time_left
;
146 ++_hanging_notes_count
;
148 // We checked this up top. We should never get here!
149 warning("MidiParser::hangingNote(): Internal error");
153 void MidiParser::onTimer() {
157 if (!_position
._play_pos
|| !_driver
)
160 _abort_parse
= false;
161 end_time
= _position
._play_time
+ _timer_rate
;
163 // Scan our hanging notes for any
164 // that should be turned off.
165 if (_hanging_notes_count
) {
166 NoteTimer
*ptr
= &_hanging_notes
[0];
168 for (i
= ARRAYSIZE(_hanging_notes
); i
; --i
, ++ptr
) {
169 if (ptr
->time_left
) {
170 if (ptr
->time_left
<= _timer_rate
) {
171 _driver
->send(0x80 | ptr
->channel
, ptr
->note
, 0);
173 --_hanging_notes_count
;
175 ptr
->time_left
-= _timer_rate
;
181 while (!_abort_parse
) {
182 EventInfo
&info
= _next_event
;
184 event_time
= _position
._last_event_time
+ info
.delta
* _psec_per_tick
;
185 if (event_time
> end_time
)
188 // Process the next info.
189 _position
._last_event_tick
+= info
.delta
;
190 if (info
.event
< 0x80) {
191 warning("Bad command or running status %02X", info
.event
);
192 _position
._play_pos
= 0;
196 if (info
.event
== 0xF0) {
198 // Check for trailing 0xF7 -- if present, remove it.
199 if (info
.ext
.data
[info
.length
-1] == 0xF7)
200 _driver
->sysEx(info
.ext
.data
, (uint16
)info
.length
-1);
202 _driver
->sysEx(info
.ext
.data
, (uint16
)info
.length
);
203 } else if (info
.event
== 0xFF) {
205 if (info
.ext
.type
== 0x2F) {
206 // End of Track must be processed by us,
207 // as well as sending it to the output device.
210 parseNextEvent(_next_event
);
213 _driver
->metaEvent(info
.ext
.type
, info
.ext
.data
, (uint16
)info
.length
);
216 } else if (info
.ext
.type
== 0x51) {
217 if (info
.length
>= 3) {
218 setTempo(info
.ext
.data
[0] << 16 | info
.ext
.data
[1] << 8 | info
.ext
.data
[2]);
221 _driver
->metaEvent(info
.ext
.type
, info
.ext
.data
, (uint16
)info
.length
);
223 if (info
.command() == 0x8) {
224 activeNote(info
.channel(), info
.basic
.param1
, false);
225 } else if (info
.command() == 0x9) {
227 hangingNote(info
.channel(), info
.basic
.param1
, info
.length
* _psec_per_tick
- (end_time
- event_time
));
229 activeNote(info
.channel(), info
.basic
.param1
, true);
231 _driver
->send(info
.event
, info
.basic
.param1
, info
.basic
.param2
);
236 _position
._last_event_time
= event_time
;
237 parseNextEvent(_next_event
);
242 _position
._play_time
= end_time
;
243 _position
._play_tick
= (_position
._play_time
- _position
._last_event_time
) / _psec_per_tick
+ _position
._last_event_tick
;
247 void MidiParser::allNotesOff() {
253 // Turn off all active notes
254 for (i
= 0; i
< 128; ++i
) {
255 for (j
= 0; j
< 16; ++j
) {
256 if (_active_notes
[i
] & (1 << j
)) {
257 _driver
->send(0x80 | j
, i
, 0);
262 // Turn off all hanging notes
263 for (i
= 0; i
< ARRAYSIZE(_hanging_notes
); i
++) {
264 if (_hanging_notes
[i
].time_left
) {
265 _driver
->send(0x80 | _hanging_notes
[i
].channel
, _hanging_notes
[i
].note
, 0);
266 _hanging_notes
[i
].time_left
= 0;
269 _hanging_notes_count
= 0;
271 // To be sure, send an "All Note Off" event (but not all MIDI devices
274 for (i
= 0; i
< 16; ++i
) {
275 _driver
->send(0xB0 | i
, 0x7b, 0); // All notes off
278 memset(_active_notes
, 0, sizeof(_active_notes
));
281 void MidiParser::resetTracking() {
285 bool MidiParser::setTrack(int track
) {
286 if (track
< 0 || track
>= _num_tracks
)
288 // We allow restarting the track via setTrack when
289 // it isn't playing anymore. This allows us to reuse
290 // a MidiParser when a track has finished and will
291 // be restarted via setTrack by the client again.
292 // This isn't exactly how setTrack behaved before though,
293 // the old MidiParser code did not allow setTrack to be
294 // used to restart a track, which was already finished.
296 // TODO: Check if any engine has problem with this
297 // handling, if so we need to find a better way to handle
298 // track restarts. (KYRA relies on this working)
299 else if (track
== _active_track
&& isPlaying())
303 hangAllActiveNotes();
308 memset(_active_notes
, 0, sizeof(_active_notes
));
309 _active_track
= track
;
310 _position
._play_pos
= _tracks
[track
];
311 parseNextEvent(_next_event
);
315 void MidiParser::stopPlaying() {
320 void MidiParser::hangAllActiveNotes() {
321 // Search for note off events until we have
322 // accounted for every active note.
323 uint16 temp_active
[128];
324 memcpy(temp_active
, _active_notes
, sizeof (temp_active
));
326 uint32 advance_tick
= _position
._last_event_tick
;
329 for (i
= 0; i
< 128; ++i
)
330 if (temp_active
[i
] != 0)
334 parseNextEvent(_next_event
);
335 advance_tick
+= _next_event
.delta
;
336 if (_next_event
.command() == 0x8) {
337 if (temp_active
[_next_event
.basic
.param1
] & (1 << _next_event
.channel())) {
338 hangingNote(_next_event
.channel(), _next_event
.basic
.param1
, (advance_tick
- _position
._last_event_tick
) * _psec_per_tick
, false);
339 temp_active
[_next_event
.basic
.param1
] &= ~ (1 << _next_event
.channel());
341 } else if (_next_event
.event
== 0xFF && _next_event
.ext
.type
== 0x2F) {
342 // warning("MidiParser::hangAllActiveNotes(): Hit End of Track with active notes left");
343 for (i
= 0; i
< 128; ++i
) {
344 for (j
= 0; j
< 16; ++j
) {
345 if (temp_active
[i
] & (1 << j
)) {
346 activeNote(j
, i
, false);
347 _driver
->send(0x80 | j
, i
, 0);
356 bool MidiParser::jumpToTick(uint32 tick
, bool fireEvents
) {
357 if (_active_track
>= _num_tracks
)
360 Tracker
currentPos(_position
);
361 EventInfo
currentEvent(_next_event
);
364 _position
._play_pos
= _tracks
[_active_track
];
365 parseNextEvent(_next_event
);
368 EventInfo
&info
= _next_event
;
369 if (_position
._last_event_tick
+ info
.delta
>= tick
) {
370 _position
._play_time
+= (tick
- _position
._last_event_tick
) * _psec_per_tick
;
371 _position
._play_tick
= tick
;
375 _position
._last_event_tick
+= info
.delta
;
376 _position
._last_event_time
+= info
.delta
* _psec_per_tick
;
377 _position
._play_tick
= _position
._last_event_tick
;
378 _position
._play_time
= _position
._last_event_time
;
380 if (info
.event
== 0xFF) {
381 if (info
.ext
.type
== 0x2F) { // End of track
382 _position
= currentPos
;
383 _next_event
= currentEvent
;
386 if (info
.ext
.type
== 0x51 && info
.length
>= 3) // Tempo
387 setTempo(info
.ext
.data
[0] << 16 | info
.ext
.data
[1] << 8 | info
.ext
.data
[2]);
389 _driver
->metaEvent(info
.ext
.type
, info
.ext
.data
, (uint16
) info
.length
);
391 } else if (fireEvents
) {
392 if (info
.event
== 0xF0) {
393 if (info
.ext
.data
[info
.length
-1] == 0xF7)
394 _driver
->sysEx(info
.ext
.data
, (uint16
)info
.length
-1);
396 _driver
->sysEx(info
.ext
.data
, (uint16
)info
.length
);
398 _driver
->send(info
.event
, info
.basic
.param1
, info
.basic
.param2
);
401 parseNextEvent(_next_event
);
405 if (!_smartJump
|| !currentPos
._play_pos
) {
408 EventInfo
targetEvent(_next_event
);
409 Tracker
targetPosition(_position
);
411 _position
= currentPos
;
412 _next_event
= currentEvent
;
413 hangAllActiveNotes();
415 _next_event
= targetEvent
;
416 _position
= targetPosition
;
423 void MidiParser::unloadMusic() {
430 if (_centerPitchWheelOnUnload
) {
431 // Center the pitch wheels in preparation for the next piece of
432 // music. It's not safe to do this from within allNotesOff(),
433 // and might not even be safe here, so we only do it if the
434 // client has explicitly asked for it.
437 for (int i
= 0; i
< 16; ++i
) {
438 _driver
->send(0xE0 | i
, 0, 0x40);