0x47 stub
[scummvm-innocent.git] / sound / midiparser.cpp
blob5aa458a79241430d947ddf892c90ec173cfd8b22
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.
21 * $URL$
22 * $Id$
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),
38 _driver(0),
39 _timer_rate(0x4A0000),
40 _ppqn(96),
41 _tempo(500000),
42 _psec_per_tick(5208), // 500000 / 96
43 _autoLoop(false),
44 _smartJump(false),
45 _centerPitchWheelOnUnload(false),
46 _num_tracks(0),
47 _active_track(255),
48 _abort_parse(0) {
49 memset(_active_notes, 0, sizeof(_active_notes));
52 void MidiParser::property(int prop, int value) {
53 switch (prop) {
54 case mpAutoLoop:
55 _autoLoop = (value != 0);
56 break;
57 case mpSmartJump:
58 _smartJump = (value != 0);
59 break;
60 case mpCenterPitchWheelOnUnload:
61 _centerPitchWheelOnUnload = (value != 0);
62 break;
66 void MidiParser::setTempo(uint32 tempo) {
67 _tempo = tempo;
68 if (_ppqn)
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) {
74 byte str;
75 uint32 value = 0;
76 int i;
78 for (i = 0; i < 4; ++i) {
79 str = data[0];
80 ++data;
81 value = (value << 7) | (str & 0x7F);
82 if (!(str & 0x80))
83 break;
85 return value;
88 void MidiParser::activeNote(byte channel, byte note, bool active) {
89 if (note >= 128 || channel >= 16)
90 return;
92 if (active)
93 _active_notes[note] |= (1 << channel);
94 else
95 _active_notes[note] &= ~(1 << channel);
97 // See if there are hanging notes that we can cancel
98 NoteTimer *ptr = _hanging_notes;
99 int i;
100 for (i = ARRAYSIZE(_hanging_notes); i; --i, ++ptr) {
101 if (ptr->channel == channel && ptr->note == note && ptr->time_left) {
102 ptr->time_left = 0;
103 --_hanging_notes_count;
104 break;
109 void MidiParser::hangingNote(byte channel, byte note, uint32 time_left, bool recycle) {
110 NoteTimer *best = 0;
111 NoteTimer *ptr = _hanging_notes;
112 int i;
114 if (_hanging_notes_count >= ARRAYSIZE(_hanging_notes)) {
115 warning("MidiParser::hangingNote(): Exceeded polyphony");
116 return;
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)
122 return;
123 best = ptr;
124 if (ptr->time_left) {
125 if (recycle)
126 _driver->send(0x80 | channel, note, 0);
127 --_hanging_notes_count;
129 break;
130 } else if (!best && ptr->time_left == 0) {
131 best = ptr;
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)
140 time_left = 1;
142 if (best) {
143 best->channel = channel;
144 best->note = note;
145 best->time_left = time_left;
146 ++_hanging_notes_count;
147 } else {
148 // We checked this up top. We should never get here!
149 warning("MidiParser::hangingNote(): Internal error");
153 void MidiParser::onTimer() {
154 uint32 end_time;
155 uint32 event_time;
157 if (!_position._play_pos || !_driver)
158 return;
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];
167 int i;
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);
172 ptr->time_left = 0;
173 --_hanging_notes_count;
174 } else {
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)
186 break;
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;
193 return;
196 if (info.event == 0xF0) {
197 // SysEx event
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);
201 else
202 _driver->sysEx(info.ext.data, (uint16)info.length);
203 } else if (info.event == 0xFF) {
204 // META event
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.
208 if (_autoLoop) {
209 jumpToTick(0);
210 parseNextEvent(_next_event);
211 } else {
212 stopPlaying();
213 _driver->metaEvent(info.ext.type, info.ext.data, (uint16)info.length);
215 return;
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);
222 } else {
223 if (info.command() == 0x8) {
224 activeNote(info.channel(), info.basic.param1, false);
225 } else if (info.command() == 0x9) {
226 if (info.length > 0)
227 hangingNote(info.channel(), info.basic.param1, info.length * _psec_per_tick - (end_time - event_time));
228 else
229 activeNote(info.channel(), info.basic.param1, true);
231 _driver->send(info.event, info.basic.param1, info.basic.param2);
235 if (!_abort_parse) {
236 _position._last_event_time = event_time;
237 parseNextEvent(_next_event);
241 if (!_abort_parse) {
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() {
248 if (!_driver)
249 return;
251 int i, j;
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
272 // support this...).
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() {
282 _position.clear();
285 bool MidiParser::setTrack(int track) {
286 if (track < 0 || track >= _num_tracks)
287 return false;
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())
300 return true;
302 if (_smartJump)
303 hangAllActiveNotes();
304 else
305 allNotesOff();
307 resetTracking();
308 memset(_active_notes, 0, sizeof(_active_notes));
309 _active_track = track;
310 _position._play_pos = _tracks[track];
311 parseNextEvent(_next_event);
312 return true;
315 void MidiParser::stopPlaying() {
316 allNotesOff();
317 resetTracking();
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;
327 while (true) {
328 int i, j;
329 for (i = 0; i < 128; ++i)
330 if (temp_active[i] != 0)
331 break;
332 if (i == 128)
333 break;
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);
351 break;
356 bool MidiParser::jumpToTick(uint32 tick, bool fireEvents) {
357 if (_active_track >= _num_tracks)
358 return false;
360 Tracker currentPos(_position);
361 EventInfo currentEvent(_next_event);
363 resetTracking();
364 _position._play_pos = _tracks[_active_track];
365 parseNextEvent(_next_event);
366 if (tick > 0) {
367 while (true) {
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;
372 break;
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;
384 return false;
385 } else {
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]);
388 if (fireEvents)
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);
395 else
396 _driver->sysEx(info.ext.data, (uint16)info.length);
397 } else
398 _driver->send(info.event, info.basic.param1, info.basic.param2);
401 parseNextEvent(_next_event);
405 if (!_smartJump || !currentPos._play_pos) {
406 allNotesOff();
407 } else {
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;
419 _abort_parse = true;
420 return true;
423 void MidiParser::unloadMusic() {
424 resetTracking();
425 allNotesOff();
426 _num_tracks = 0;
427 _active_track = 255;
428 _abort_parse = true;
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.
436 if (_driver) {
437 for (int i = 0; i < 16; ++i) {
438 _driver->send(0xE0 | i, 0, 0x40);