/* 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_simonwin.h" #include "audio/mididrv.h" namespace AGOS { MidiParser_SimonWin::MidiParser_SimonWin(int8 source, bool useDosTempos) : MidiParser_SMF(source), _trackData(), _useDosTempos(useDosTempos) { } MidiParser_SimonWin::~MidiParser_SimonWin() { // Call unloadMusic to make sure any _trackData contents are deallocated. unloadMusic(); } void MidiParser_SimonWin::parseNextEvent(EventInfo &info) { byte *parsePos = _position._playPos; uint8 *start = parsePos; uint32 delta = readVLQ(parsePos); 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._playPos = parsePos; } else { // Processing of the other events is the same as the SMF format. info.noop = false; MidiParser_SMF::parseNextEvent(info); } } void MidiParser_SimonWin::setTempo(uint32 tempo) { uint32 newTempo = tempo; if (_useDosTempos && tempo < 750000) { // WORKAROUND The tempos set in the SMF data of Simon 1 Windows are // faster than the DOS version for the faster tempos. These are // corrected here to match the DOS version. The correct tempos have // been determined by measuring the tempos generated by the DOS version // running in DOSBox. newTempo = 330000 + (((tempo / 125000) - 2) * 105000); } MidiParser::setTempo(newTempo); } int32 MidiParser_SimonWin::determineDataSize(Common::SeekableReadStream *stream) { int64 startPos = stream->pos(); // Read the number of tracks. byte numSongs = stream->readByte(); if (numSongs > MAXIMUM_TRACKS) { warning("MidiParser_SimonWin::determineDataSize - Can only handle %d tracks but was handed %d", MAXIMUM_TRACKS, numSongs); return -1; } // Add up the sizes of the individual SMF tracks. int32 totalSize = 1; for (int i = 0; i < numSongs; ++i) { int size = MidiParser_SMF::determineDataSize(stream); if (size < 0) return -1; totalSize += size; stream->seek(startPos + totalSize, SEEK_SET); } return totalSize; } bool MidiParser_SimonWin::loadMusic(byte *data, uint32 size) { assert(size > 7); unloadMusic(); // The first byte indicates the number of tracks in the MIDI data. byte *pos = data; _numTracks = *(pos++); if (_numTracks > 16) { warning("MidiParser_SimonWin::loadMusic - Can only handle %d tracks but was handed %d", MAXIMUM_TRACKS, _numTracks); return false; } // Read the tracks. byte *trackDataStart; for (int i = 0; i < _numTracks; ++i) { trackDataStart = pos; // Read the track header. // Make sure there's a MThd. if (memcmp(pos, "MThd", 4) != 0) { warning("MidiParser_SimonWin::loadMusic - Expected MThd but found '%c%c%c%c' instead", pos[0], pos[1], pos[2], pos[3]); return false; } pos += 4; // Verify correct header length. uint32 len = read4high(pos); if (len != 6) { warning("MidiParser_SimonWin::loadMusic - MThd length 6 expected but found %d", len); return false; } // Verify that this MIDI is type 0 or 1 (it is expected to be type 1). uint16 numSubtracks = pos[2] << 8 | pos[3]; assert(numSubtracks >= 1 && numSubtracks <= 20); uint8 subtrackMidiType = pos[1]; if (subtrackMidiType >= 2) { warning("MidiParser_SimonWin::loadMusic - MIDI track contained a type %d subtrack", subtrackMidiType); return false; } // Each track could potentially have a different PPQN, but for Simon 1 // and 2 all tracks have PPQN 192, so this is not a problem. _ppqn = pos[4] << 8 | pos[5]; pos += len; // Now determine all the MTrk (sub)track start offsets. byte *subtrackStarts[20]; for (int j = 0; j < numSubtracks; j++) { if (memcmp(pos, "MTrk", 4) != 0) { warning("MidiParser_SimonWin::loadMusic - Could not find subtrack header at expected location"); return false; } pos += 4; uint32 subtrackLength = READ_BE_UINT32(pos); pos += 4; subtrackStarts[j] = pos; pos += subtrackLength; } // We are now at the end of the track, so we can determine the total // track length. uint32 trackDataLength = pos - trackDataStart; if (subtrackMidiType == 1) { // Compress the type 1 data to type 0. byte *buffer = new byte[trackDataLength * 2]; uint32 compressedDataLength = compressToType0(subtrackStarts, numSubtracks, buffer, true); // Copy the compressed data to the _trackData array. _trackData[i] = new byte[compressedDataLength]; memcpy(_trackData[i], buffer, compressedDataLength); delete[] buffer; _tracks[i] = _trackData[i]; } else { // 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.... _tracks[i] = subtrackStarts[0]; } } _disableAutoStartPlayback = true; resetTracking(); setTempo(500000); setTrack(0); return true; } void MidiParser_SimonWin::unloadMusic() { MidiParser_SMF::unloadMusic(); // Deallocate the compressed type 0 track data. for (int i = 0; i < MAXIMUM_TRACKS; i++) { if (_trackData[i]) { delete[] _trackData[i]; _trackData[i] = nullptr; } } } } // End of namespace AGOS