Ticket #9003: midiparser_xmidi.cpp

File midiparser_xmidi.cpp, 9.4 KB (added by SF/aasmith, 15 years ago)

sound/midiparser_xmidi.cpp that explicitly names new controllers

Line 
1/* ScummVM - Graphic Adventure Engine
2 *
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.
6 *
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.
11
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.
16
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.
20 *
21 * $URL$
22 * $Id$
23 *
24 */
25
26#include "sound/midiparser.h"
27#include "sound/mididrv.h"
28#include "common/util.h"
29
30/**
31 * The XMIDI version of MidiParser.
32 *
33 * Much of this code is adapted from the XMIDI implementation from the exult
34 * project.
35 */
36class MidiParser_XMIDI : public MidiParser {
37protected:
38 NoteTimer _notes_cache[32];
39 uint32 _inserted_delta; // Track simulated deltas for note-off events
40
41 struct Loop {
42 byte *pos;
43 byte repeat;
44 };
45
46 Loop _loop[4];
47 int _loopCount;
48
49 XMidiCallbackProc _callbackProc;
50 void *_callbackData;
51
52protected:
53 uint32 readVLQ2(byte * &data);
54 void resetTracking();
55 void parseNextEvent(EventInfo &info);
56
57public:
58 MidiParser_XMIDI(XMidiCallbackProc proc, void *data) : _inserted_delta(0), _callbackProc(proc), _callbackData(data) {}
59 ~MidiParser_XMIDI() { }
60
61 bool loadMusic(byte *data, uint32 size);
62};
63
64
65// This is a special XMIDI variable length quantity
66uint32 MidiParser_XMIDI::readVLQ2(byte * &pos) {
67 uint32 value = 0;
68 while (!(pos[0] & 0x80)) {
69 value += *pos++;
70 }
71 return value;
72}
73
74void MidiParser_XMIDI::parseNextEvent(EventInfo &info) {
75 info.start = _position._play_pos;
76 info.delta = readVLQ2(_position._play_pos) - _inserted_delta;
77
78 // Process the next event.
79 _inserted_delta = 0;
80 info.event = *(_position._play_pos++);
81 switch (info.event >> 4) {
82 case 0x9: // Note On
83 info.basic.param1 = *(_position._play_pos++);
84 info.basic.param2 = *(_position._play_pos++);
85 info.length = readVLQ(_position._play_pos);
86 if (info.basic.param2 == 0) {
87 info.event = info.channel() | 0x80;
88 info.length = 0;
89 }
90 break;
91
92 case 0xC:
93 case 0xD:
94 info.basic.param1 = *(_position._play_pos++);
95 info.basic.param2 = 0;
96 break;
97
98 case 0x8:
99 case 0xA:
100 case 0xE:
101 info.basic.param1 = *(_position._play_pos++);
102 info.basic.param2 = *(_position._play_pos++);
103 break;
104
105 case 0xB:
106 info.basic.param1 = *(_position._play_pos++);
107 info.basic.param2 = *(_position._play_pos++);
108
109 // This isn't a full XMIDI implementation, but it should
110 // hopefully be "good enough" for most things.
111
112 switch (info.basic.param1) {
113 // Simplified XMIDI looping.
114 case 0x74: { // XMIDI_CONTROLLER_FOR_LOOP
115 byte *pos = _position._play_pos;
116 if (_loopCount < ARRAYSIZE(_loop) - 1)
117 _loopCount++;
118
119 _loop[_loopCount].pos = pos;
120 _loop[_loopCount].repeat = info.basic.param2;
121 break;
122 }
123
124 case 0x75: // XMIDI_CONTORLLER_NEXT_BREAK
125 if (_loopCount >= 0) {
126 if (info.basic.param2 < 64) {
127 // End the current loop.
128 _loopCount--;
129 } else {
130 _position._play_pos = _loop[_loopCount].pos;
131 // Repeat 0 means "loop forever".
132 if (_loop[_loopCount].repeat) {
133 if (--_loop[_loopCount].repeat == 0)
134 _loopCount--;
135 }
136 }
137 }
138 break;
139
140 case 0x77: // XMIDI_CONTROLLER_CALLBACK_TRIG
141 if (_callbackProc)
142 _callbackProc(info.basic.param2, _callbackData);
143 break;
144
145 case 0x6e: // XMIDI_CONTROLLER_CHAN_LOCK
146 case 0x6f: // XMIDI_CONTROLLER_CHAN_LOCK_PROT
147 case 0x70: // XMIDI_CONTROLLER_VOICE_PROT
148 case 0x71: // XMIDI_CONTROLLER_TIMBRE_PROT
149 case 0x72: // XMIDI_CONTROLLER_BANK_CHANGE
150 case 0x73: // XMIDI_CONTROLLER_IND_CTRL_PREFIX
151 case 0x76: // XMIDI_CONTROLLER_CLEAR_BB_COUNT
152 case 0x78: // XMIDI_CONTROLLER_SEQ_BRANCH_INDEX
153 default:
154 if (info.basic.param1 >= 0x6e && info.basic.param1 <= 0x78) {
155 warning("Unsupported XMIDI controller %d (0x%2x)",
156 info.basic.param1, info.basic.param1);
157 }
158 break;
159 }
160
161 // Should we really keep passing the XMIDI controller events to
162 // the MIDI driver, or should we turn them into some kind of
163 // NOP events? (Dummy meta events, perhaps?) Ah well, it has
164 // worked so far, so it shouldn't cause any damage...
165
166 break;
167
168 case 0xF: // Meta or SysEx event
169 switch (info.event & 0x0F) {
170 case 0x2: // Song Position Pointer
171 info.basic.param1 = *(_position._play_pos++);
172 info.basic.param2 = *(_position._play_pos++);
173 break;
174
175 case 0x3: // Song Select
176 info.basic.param1 = *(_position._play_pos++);
177 info.basic.param2 = 0;
178 break;
179
180 case 0x6:
181 case 0x8:
182 case 0xA:
183 case 0xB:
184 case 0xC:
185 case 0xE:
186 info.basic.param1 = info.basic.param2 = 0;
187 break;
188
189 case 0x0: // SysEx
190 info.length = readVLQ(_position._play_pos);
191 info.ext.data = _position._play_pos;
192 _position._play_pos += info.length;
193 break;
194
195 case 0xF: // META event
196 info.ext.type = *(_position._play_pos++);
197 info.length = readVLQ(_position._play_pos);
198 info.ext.data = _position._play_pos;
199 _position._play_pos += info.length;
200 if (info.ext.type == 0x51 && info.length == 3) {
201 // Tempo event. We want to make these constant 500,000.
202 info.ext.data[0] = 0x07;
203 info.ext.data[1] = 0xA1;
204 info.ext.data[2] = 0x20;
205 }
206 break;
207
208 default:
209 warning("MidiParser_XMIDI::parseNextEvent: Unsupported event code %x", info.event);
210 }
211 }
212}
213
214bool MidiParser_XMIDI::loadMusic(byte *data, uint32 size) {
215 uint32 i = 0;
216 byte *start;
217 uint32 len;
218 uint32 chunk_len;
219 char buf[32];
220
221 _loopCount = -1;
222
223 unloadMusic();
224 byte *pos = data;
225
226 if (!memcmp(pos, "FORM", 4)) {
227 pos += 4;
228
229 // Read length of
230 len = read4high(pos);
231 start = pos;
232
233 // XDIRless XMIDI, we can handle them here.
234 if (!memcmp(pos, "XMID", 4)) {
235 warning("XMIDI doesn't have XDIR");
236 pos += 4;
237 _num_tracks = 1;
238 } else if (memcmp(pos, "XDIR", 4)) {
239 // Not an XMIDI that we recognise
240 warning("Expected 'XDIR' but found '%c%c%c%c'", pos[0], pos[1], pos[2], pos[3]);
241 return false;
242 } else {
243 // Seems Valid
244 pos += 4;
245 _num_tracks = 0;
246
247 for (i = 4; i < len; i++) {
248 // Read 4 bytes of type
249 memcpy(buf, pos, 4);
250 pos += 4;
251
252 // Read length of chunk
253 chunk_len = read4high(pos);
254
255 // Add eight bytes
256 i += 8;
257
258 if (memcmp(buf, "INFO", 4)) {
259 // Must align
260 pos += (chunk_len + 1) & ~1;
261 i += (chunk_len + 1) & ~1;
262 continue;
263 }
264
265 // Must be at least 2 bytes long
266 if (chunk_len < 2) {
267 warning("Invalid chunk length %d for 'INFO' block", (int)chunk_len);
268 return false;
269 }
270
271 _num_tracks = (byte)read2low(pos);
272
273 if (chunk_len > 2) {
274 warning("Chunk length %d is greater than 2", (int)chunk_len);
275 pos += chunk_len - 2;
276 }
277 break;
278 }
279
280 // Didn't get to fill the header
281 if (_num_tracks == 0) {
282 warning("Didn't find a valid track count");
283 return false;
284 }
285
286 // Ok now to start part 2
287 // Goto the right place
288 pos = start + ((len + 1) & ~1);
289
290 if (memcmp(pos, "CAT ", 4)) {
291 // Not an XMID
292 warning("Expected 'CAT ' but found '%c%c%c%c'", pos[0], pos[1], pos[2], pos[3]);
293 return false;
294 }
295 pos += 4;
296
297 // Now read length of this track
298 len = read4high(pos);
299
300 if (memcmp(pos, "XMID", 4)) {
301 // Not an XMID
302 warning("Expected 'XMID' but found '%c%c%c%c'", pos[0], pos[1], pos[2], pos[3]);
303 return false;
304 }
305 pos += 4;
306
307 }
308
309 // Ok it's an XMIDI.
310 // We're going to identify and store the location for each track.
311 if (_num_tracks > ARRAYSIZE(_tracks)) {
312 warning("Can only handle %d tracks but was handed %d", (int)ARRAYSIZE(_tracks), (int)_num_tracks);
313 return false;
314 }
315
316 int tracks_read = 0;
317 while (tracks_read < _num_tracks) {
318 if (!memcmp(pos, "FORM", 4)) {
319 // Skip this plus the 4 bytes after it.
320 pos += 8;
321 } else if (!memcmp(pos, "XMID", 4)) {
322 // Skip this.
323 pos += 4;
324 } else if (!memcmp(pos, "TIMB", 4)) {
325 // Custom timbres?
326 // We don't support them.
327 // Read the length, skip it, and hope there was nothing there.
328 pos += 4;
329 len = read4high(pos);
330 pos += (len + 1) & ~1;
331 } else if (!memcmp(pos, "EVNT", 4)) {
332 // Ahh! What we're looking for at last.
333 _tracks[tracks_read] = pos + 8; // Skip the EVNT and length bytes
334 pos += 4;
335 len = read4high(pos);
336 pos += (len + 1) & ~1;
337 ++tracks_read;
338 } else {
339 warning("Hit invalid block '%c%c%c%c' while scanning for track locations", pos[0], pos[1], pos[2], pos[3]);
340 return false;
341 }
342 }
343
344 // If we got this far, we successfully established
345 // the locations for each of our tracks.
346 // Note that we assume the original data passed in
347 // will persist beyond this call, i.e. we do NOT
348 // copy the data to our own buffer. Take warning....
349 _ppqn = 60;
350 resetTracking();
351 setTempo(500000);
352 _inserted_delta = 0;
353 setTrack(0);
354 return true;
355 }
356
357 return false;
358}
359
360void MidiParser_XMIDI::resetTracking() {
361 MidiParser::resetTracking();
362 _inserted_delta = 0;
363}
364
365void MidiParser::defaultXMidiCallback(byte eventData, void *data) {
366 warning("MidiParser: defaultXMidiCallback(%d)", eventData);
367}
368
369MidiParser *MidiParser::createParser_XMIDI(XMidiCallbackProc proc, void *data) {
370 return new MidiParser_XMIDI(proc, data);
371}