/* 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