/* 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 "darkseed/midiparser_sbr.h" #include "audio/mididrv.h" namespace Darkseed { MidiParser_SBR::MidiParser_SBR(int8 source, bool sfx) : MidiParser_SMF(source), _sfx(sfx) { Common::fill(_trackDeltas, _trackDeltas + ARRAYSIZE(_trackDeltas), 0); Common::fill(_trackInstruments, _trackInstruments + ARRAYSIZE(_trackInstruments), 0xFF); Common::fill(_trackNoteActive, _trackNoteActive + ARRAYSIZE(_trackNoteActive), 0xFF); Common::fill(_trackLoopCounter, _trackLoopCounter + ARRAYSIZE(_trackLoopCounter), 0); // SBR uses a fixed tempo. _ppqn = 96; setTempo(1318214); } void MidiParser_SBR::parseNextEvent(EventInfo &info) { uint8 subtrack = info.subtrack; byte *parsePos = _position._subtracks[subtrack]._playPos; uint8 *start = parsePos; /** * SBR uses 6 byte structures to represent events: * - Event type / note * - Note velocity * - (unused) * - Delta (word, little-endian) * - Instrument * Delta is ticks to the next event, not preceding this event like SMF. * * The following event types are used: * - 0x00: End of track. This is the only byte in this event. * - 0x01: Subtrack list. See loadMusic. * - 0x02: Rest. Effectively a note off and a delta to the next event. * - 0x03: Sample. Indicates the sample filename shoud be read from the * corresponding DIG file entry. See Sound class. * - 0x05: Restart playback from the beginning of the subtrack. * - 0x06: Loop. Velocity specifies the number of events to jump back. * Delta specifies the number of times to repeat this section. * - 0x24+: Note on. */ info.start = start; info.length = 0; info.noop = false; info.loop = false; info.delta = _trackDeltas[subtrack]; _trackDeltas[subtrack] = 0; // Any event will turn off the active note on this subtrack. if (_trackNoteActive[subtrack] != 0xFF) { info.event = 0x80 | subtrack; info.basic.param1 = _trackNoteActive[subtrack]; info.basic.param2 = 0x00; _trackNoteActive[subtrack] = 0xFF; return; } if (parsePos == nullptr || parsePos[0] == 0) { // Reached the end of the track. Generate an end-of-track event. info.event = 0xFF; info.ext.type = MidiDriver::MIDI_META_END_OF_TRACK; info.ext.data = parsePos; return; } // There are more MIDI events in this track. uint8 noteType = parsePos[0]; if (noteType == 5) { // Subtrack needs to be restarted. Generate a custom meta event. info.event = 0xFF; info.ext.type = 5; info.loop = true; return; } if (noteType == 6) { // Subtrack needs to be looped. Generate a custom meta event. info.event = 0xFF; info.ext.type = 6; info.ext.data = parsePos; info.loop = true; // Delta needs to be one more than specified due to differences in the // way this event is processed compared to the original code. info.delta++; return; } // Check if the instrument specified in this event is different from the // current instrument for this track. Typically, all events in a subtrack // have the same instrument. uint8 instrument = parsePos[5]; if (_trackInstruments[subtrack] != instrument) { if (_trackInstruments[subtrack] <= 0xF && (_trackInstruments[subtrack] / 3) != (instrument / 3)) { // WORKAROUND The current instrument for this subtrack is a rhythm // instrument, and the instrument specified in this event is for a // different rhythm instrument type. This occurs a few times in // the Dark Seed SBR files. The instrument for the offending events // is 0, probably by mistake. The original code will allocate a // subtrack to an OPL rhythm instrument based on the first event // and then never change it, even when an event with an instrument // with a different rhythm type is encountered. Instead, it will // write the new instrument definition to the originally allocated // OPL rhythm instrument, even if it has the wrong rhythm type. // The ScummVM code will change the allocation to the rhythm // instrument indicated by the instrument on the new event, which // causes incorrect playback, because typically there already is // a subtrack allocated to this instrument. // To fix this, the incorrect instrument on this event is simply // ignored. } else { // The instrument on this event is different from the current // instrument. Generate a program change event. _trackInstruments[subtrack] = instrument; info.event = 0xC0 | subtrack; info.basic.param1 = instrument; info.basic.param2 = 0; return; } } if (noteType >= 24) { // Note on. info.event = 0x90 | subtrack; info.basic.param1 = noteType; info.basic.param2 = parsePos[1]; _trackNoteActive[subtrack] = noteType; } else { // For rest events, nothing needs to be done other than turning off // the current note (already done above) and process the delta. // Subtrack list and sample events are not processed here. info.noop = true; } if (noteType == 2 || noteType >= 24) { // Delta to the next event is only processed for // note on and rest events. _trackDeltas[subtrack] = READ_LE_UINT16(parsePos + 3); } // Set play position to the start of the next event. _position._subtracks[subtrack]._playPos += 6; } bool MidiParser_SBR::processEvent(const EventInfo &info, bool fireEvents) { // Handle custom meta events here. if (info.event == 0xFF) { uint8 subtrack = info.subtrack; if (info.ext.type == 5) { // Restart. Set play position to the beginning of the subtrack. _position._subtracks[subtrack]._playPos = _tracks[_activeTrack][subtrack]; return true; } else if (info.ext.type == 6) { // Loop. bool loop = false; if (_trackLoopCounter[subtrack] > 0) { // A loop iteration has completed. _trackLoopCounter[subtrack]--; if (_trackLoopCounter[subtrack] > 0) { // There are more iterations remaining. loop = true; } else { // Loop has finished. Playback will resume at the event // after the loop event. _position._subtracks[subtrack]._playPos += 6; } } else { // Initialize the loop. Read number of iterations from the // event delta. _trackLoopCounter[subtrack] = READ_LE_UINT16(info.ext.data + 3); loop = true; } if (loop) { // Set the play position back by the number of events indicated // by the event velocity. _position._subtracks[subtrack]._playPos -= ((info.ext.data[1]) * 6); } return true; } } // All other events are handled like SMF events. return MidiParser::processEvent(info, fireEvents); } void MidiParser_SBR::onTrackStart(uint8 track) { Common::fill(_trackDeltas, _trackDeltas + ARRAYSIZE(_trackDeltas), 0); Common::fill(_trackInstruments, _trackInstruments + ARRAYSIZE(_trackInstruments), 0xFF); Common::fill(_trackNoteActive, _trackNoteActive + ARRAYSIZE(_trackNoteActive), 0xFF); Common::fill(_trackLoopCounter, _trackLoopCounter + ARRAYSIZE(_trackLoopCounter), 0); } bool MidiParser_SBR::loadMusic(byte *data, uint32 size) { assert(size > 0); unloadMusic(); // SBR files typically contain 120 tracks; some have 100. // Music files only use the first 10. SFX files use the remaining 110. uint8 startTrack = _sfx ? 10 : 0; uint8 endTrack = _sfx ? 120 : 10; // Read all the tracks. These consist of 0 or more 6 byte events, // terminated by a 00 byte. uint16 bytesRead = 0; for (int i = 0; i < endTrack; i++) { byte *startOfTrack = data; uint16 trackSize = 0; bool foundEndOfTrack = false; while (bytesRead < size) { uint8 eventType = data[0]; if (eventType == 0) { foundEndOfTrack = true; data++; trackSize++; bytesRead++; break; } else { data += 6; trackSize += 6; bytesRead += 6; } } if (!foundEndOfTrack) { // Some files have less than 120 tracks endTrack = i; break; } else if (i < startTrack) { _tracks[i][0] = nullptr; } else { _tracks[i][0] = startOfTrack; } } _numTracks = endTrack; // Look for tracks starting with a subtrack list event (type 01). // These are tracks consisting of multiple parallel subtracks. // Music files typically have a subtrack list in track 0. Some SFX use // multiple subtracks as well. for (int i = 0; i < _numTracks; i++) { if (_tracks[i][0] != nullptr && _tracks[i][0][0] == 0x01) { // Read the subtrack list. This is a variable-length list of // subtrack indices, terminated by a 00 byte. // (The track is padded with garbage and terminated by another // 00 byte to match the x * 6 byte event format used by all tracks.) uint8 *tracklist = _tracks[i][0] + 1; uint8 subtrackIndex = 0; while (*tracklist != 0) { _tracks[i][subtrackIndex++] = _tracks[*tracklist][0]; tracklist++; } _numSubtracks[i] = subtrackIndex; } else { // This is a track containing just a single subtrack. _numSubtracks[i] = 1; } } _disableAutoStartPlayback = true; resetTracking(); setTrack(0); return true; } } // End of namespace Darkseed