/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "agos/midiparser_gmf.h" #include "audio/mididrv.h" namespace AGOS { MidiParser_GMF::MidiParser_GMF(int8 source, bool useDosTempos) : MidiParser_SMF(source), _useDosTempos(useDosTempos) { memset(_tracksEndPos, 0, sizeof(_tracksEndPos)); } void MidiParser_GMF::parseNextEvent(EventInfo &info) { const byte *parsePos = _position._subtracks[0]._playPos; const uint8 *start = parsePos; uint32 delta = readVLQ(parsePos); // GMF does not use end of track events, so we have to use the size of the // MIDI data to determine if we're at the end of the track. // Simon 1 CD version data has several zero bytes at the end. // Check for these and ignore them. bool containsMoreData = true; if (parsePos > _tracksEndPos[_activeTrack] - 5) { containsMoreData = false; const byte *checkPos = parsePos; while (checkPos < _tracksEndPos[_activeTrack]) { if (*checkPos != 0) { containsMoreData = true; break; } checkPos++; } // If we're already past the end of the track, the while loop will not // execute and containsMoreData remains false. } if (!containsMoreData) { // Reached the end of the track. Generate an end-of-track event. info.start = start; info.delta = delta; info.event = 0xFF; info.ext.type = MidiDriver::MIDI_META_END_OF_TRACK; info.length = 0; info.ext.data = parsePos; info.noop = false; _position._subtracks[0]._playPos = parsePos; return; } // There are more MIDI events in this track. uint8 event = *(parsePos++); // Pitch bend events in the Simon 1 data are broken. They contain just the // command byte; no data bytes follow. We generate an empty event and set // the noop flag to have MidiParser process only the delta and otherwise // ignore the event. if ((event & 0xF0) == MidiDriver::MIDI_COMMAND_PITCH_BEND) { info.start = start; info.delta = delta; info.event = event; info.basic.param1 = 0; info.basic.param2 = 0; info.length = 0; info.noop = true; _position._subtracks[0]._playPos = parsePos; } else { // Processing of the other events is the same as the SMF format. info.noop = false; MidiParser_SMF::parseNextEvent(info); } } bool MidiParser_GMF::loadMusic(const byte *data, uint32 size) { assert(size > 7); unloadMusic(); // Determine start and end of the MIDI track(s) in the data, as well as // tempo and loop flag. uint8 headerTempo; bool headerLoop; // Simon 1 uses two GMF variants: a single track music file and a multiple // track SFX file. These are processed as a MIDI type 0 and type 2 track, // respectively. if (!memcmp(data, "GMF", 3)) { // Single track file. _numTracks = 1; // GMF header is GMF. // Version is always 1.0 for Simon 1. headerTempo = data[5]; headerLoop = data[6] == 1; // MIDI track data starts immediately after the GMF header. _tracks[0][0] = data + 7; _tracksEndPos[0] = data + size; } else { // Assume multi-track file. // Each track has its own GMF header, but tempo and loop flags are all // the same for Simon 1. headerTempo = 2; headerLoop = false; // A multi-track file starts with a list of 2-byte offsets which // identify the starting position of each track, as well as the end of // the last track. const byte *pos = data; // Read the start offset of the first track. const byte *trackStart = data + READ_LE_UINT16(pos); pos += 2; // The number of offsets before the first track indicates the number of // tracks plus 1 because the end offset of the last track is included // as well. _numTracks = (*trackStart / 2) - 1; if (_numTracks > ARRAYSIZE(_tracks)) { warning("MidiParser_GMF::loadMusic - Can only handle %d tracks but was handed %d", (int)ARRAYSIZE(_tracks), (int)_numTracks); return false; } // Read all the track start offsets. int tracksRead = 0; while (tracksRead < _numTracks) { _tracks[tracksRead][0] = trackStart + 7; // Skip 7-byte GMF header trackStart = data + READ_LE_UINT16(pos); pos += 2; // Start of the next track is the end of this track. _tracksEndPos[tracksRead] = trackStart; _numSubtracks[tracksRead] = 1; tracksRead++; } } // Note that we assume the original data passed in // will persist beyond this call, i.e. we do NOT // copy the data to our own buffer. Take warning.... _disableAutoStartPlayback = true; resetTracking(); _autoLoop = headerLoop; _ppqn = 192; uint32 tempo; if (_useDosTempos) { // These translations from the GMF header tempo (2-8) to the SMF tempo have // been determined by measuring the tempos generated by the DOS version of // Simon 1 in DOSBox. if (headerTempo < 6) { tempo = 330000 + ((headerTempo - 2) * 105000); } else { tempo = 750000 + ((headerTempo - 6) * 125000); } } else { // These are the tempos as specified in the Windows version SMF data. tempo = headerTempo * 125000; } setTempo(tempo); setTrack(0); return true; } } // End of namespace AGOS