mirror of
https://github.com/scummvm/scummvm.git
synced 2025-04-02 10:52:32 -04:00
297 lines
9.6 KiB
C++
297 lines
9.6 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 "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
|