scummvm/engines/agos/midiparser_simonwin.cpp
Coen Rampen 97745050c2 AGOS: Add option for Simon 1 DOS tempos to parsers
The DOS version of Simon 1 has different music tempos compared to the Windows
version. This commit adds the option to both the DOS and Windows MIDI parsers
to use either the DOS or Windows tempos.
2022-05-09 17:19:43 +02:00

202 lines
6.2 KiB
C++

/* 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 <http://www.gnu.org/licenses/>.
*
*/
#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.
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