scummvm/engines/agos/midiparser_s1d.cpp
Johannes Schickel f705a9b286 AGOS: Proper implementation of the Simon 1 demo aka Accolade MIDI parser.
This is based on the Elvira 1 demo.
2011-08-21 04:49:48 +02:00

204 lines
5.3 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 2
* 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
#include "common/debug.h"
#include "common/util.h"
#include "common/textconsole.h"
#include "audio/mididrv.h"
#include "audio/midiparser.h"
namespace AGOS {
/**
* Simon 1 Demo version of MidiParser.
*/
class MidiParser_S1D : public MidiParser {
private:
byte *_data;
bool _no_delta;
struct Loop {
uint16 timer;
byte *start, *end;
} _loops[16];
uint32 readVLQ2(byte *&data);
void chainEvent(EventInfo &info);
protected:
void parseNextEvent(EventInfo &info);
void resetTracking();
public:
MidiParser_S1D() : _data(0), _no_delta(false) {}
bool loadMusic(byte *data, uint32 size);
};
uint32 MidiParser_S1D::readVLQ2(byte *&data) {
uint32 delta = 0;
// LE format VLQ, which is 2 bytes long at max.
delta = *data++;
if (delta & 0x80) {
delta &= 0x7F;
delta |= *data++ << 7;
}
return delta;
}
void MidiParser_S1D::chainEvent(EventInfo &info) {
// When we chain an event, we add up the old delta.
uint32 delta = info.delta;
parseNextEvent(info);
info.delta += delta;
}
void MidiParser_S1D::parseNextEvent(EventInfo &info) {
info.start = _position._play_pos;
info.length = 0;
info.delta = _no_delta ? 0 : readVLQ2(_position._play_pos);
_no_delta = false;
info.event = *_position._play_pos++;
if (!(info.event & 0x80)) {
_no_delta = true;
info.event |= 0x80;
}
if (info.event == 0xFC) {
// This means End of Track.
// Rewrite in SMF (MIDI transmission) form.
info.event = 0xFF;
info.ext.type = 0x2F;
} else {
switch (info.command()) {
case 0x8: // note off
info.basic.param1 = *_position._play_pos++;
info.basic.param2 = 0;
break;
case 0x9: // note on
info.basic.param1 = *_position._play_pos++;
info.basic.param2 = *_position._play_pos++;
break;
case 0xA: { // loop control
// In case the stop mode(?) is set to 0x80 this will stop the
// track over here.
const int16 loopIterations = int8(*_position._play_pos++);
if (!loopIterations) {
_loops[info.channel()].start = _position._play_pos;
} else {
if (!_loops[info.channel()].timer) {
if (_loops[info.channel()].start) {
_loops[info.channel()].timer = uint16(loopIterations);
_loops[info.channel()].end = _position._play_pos;
// Go to the start of the loop
_position._play_pos = _loops[info.channel()].start;
}
} else {
if (_loops[info.channel()].timer)
_position._play_pos = _loops[info.channel()].start;
--_loops[info.channel()].timer;
}
}
// We need to read the next midi event here. Since we can not
// safely pass this event to the MIDI event processing.
chainEvent(info);
} break;
case 0xB: // auto stop marker(?)
// In case the stop mode(?) is set to 0x80 this will stop the
// track.
// We need to read the next midi event here. Since we can not
// safely pass this event to the MIDI event processing.
chainEvent(info);
break;
case 0xC: // program change
info.basic.param1 = *_position._play_pos++;
info.basic.param2 = 0;
break;
case 0xD: // jump to loop end
if (_loops[info.channel()].end)
_position._play_pos = _loops[info.channel()].end;
// We need to read the next midi event here. Since we can not
// safely pass this event to the MIDI event processing.
chainEvent(info);
break;
default:
// The original called some other function from here, which seems
// not to be MIDI related.
warning("MidiParser_S1D: default case %d", info.channel());
// We need to read the next midi event here. Since we can not
// safely pass this event to the MIDI event processing.
chainEvent(info);
break;
}
}
}
bool MidiParser_S1D::loadMusic(byte *data, uint32 size) {
unloadMusic();
// The original actually just ignores the first two bytes.
byte *pos = data;
if (*(pos++) != 0xFC)
debug(1, "Expected 0xFC header but found 0x%02X instead", (int) *pos);
pos += 1;
// And now we're at the actual data. Only one track.
_num_tracks = 1;
_data = pos;
_tracks[0] = pos;
// 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....
resetTracking();
setTempo(666667);
setTrack(0);
return true;
}
void MidiParser_S1D::resetTracking() {
MidiParser::resetTracking();
// The first event never contains any delta.
_no_delta = true;
memset(_loops, 0, sizeof(_loops));
}
MidiParser *MidiParser_createS1D() { return new MidiParser_S1D; }
} // End of namespace AGOS