Initial commit
This commit is contained in:
265
engines/ultima/nuvie/sound/midiparser_m.cpp
Normal file
265
engines/ultima/nuvie/sound/midiparser_m.cpp
Normal file
@@ -0,0 +1,265 @@
|
||||
/* 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 "midiparser_m.h"
|
||||
|
||||
#include "audio/mididrv.h"
|
||||
#include "audio/midiparser.h"
|
||||
|
||||
namespace Ultima {
|
||||
namespace Nuvie {
|
||||
|
||||
MidiParser_M::MidiParser_M(int8 source) : MidiParser(source) {
|
||||
// M uses a fixed timer frequency of 60 Hz, or 16.667 ms per tick.
|
||||
_psecPerTick = 16667;
|
||||
|
||||
_trackLength = 0;
|
||||
|
||||
_loopPoint = nullptr;
|
||||
_loopStack = new Common::FixedStack<LoopData, 16>();
|
||||
}
|
||||
|
||||
MidiParser_M::~MidiParser_M() {
|
||||
delete _loopStack;
|
||||
}
|
||||
|
||||
bool MidiParser_M::loadMusic(const byte* data, uint32 size) {
|
||||
unloadMusic();
|
||||
|
||||
// M uses only 1 track.
|
||||
_tracks[0][0] = data;
|
||||
_numTracks = 1;
|
||||
_numSubtracks[0] = 1;
|
||||
_trackLength = size;
|
||||
|
||||
// The global loop defaults to the start of the M data.
|
||||
_loopPoint = data;
|
||||
|
||||
resetTracking();
|
||||
setTrack(0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MidiParser_M::unloadMusic() {
|
||||
MidiParser::unloadMusic();
|
||||
_trackLength = 0;
|
||||
|
||||
_loopPoint = nullptr;
|
||||
_loopStack->clear();
|
||||
}
|
||||
|
||||
// MidiParser::onTimer does some checks based on MIDI note on/off command bytes
|
||||
// which have a different meaning in M, so those checks are removed here.
|
||||
void MidiParser_M::onTimer() {
|
||||
uint32 endTime;
|
||||
uint32 eventTime;
|
||||
uint32 eventTick;
|
||||
|
||||
if (!_position.isTracking() || !_driver || !_doParse || _pause || !_driver->isReady(_source))
|
||||
return;
|
||||
|
||||
_abortParse = false;
|
||||
endTime = _position._playTime + _timerRate;
|
||||
|
||||
bool loopEvent = false;
|
||||
while (!_abortParse) {
|
||||
EventInfo &info = *_nextEvent;
|
||||
|
||||
eventTick = _position._subtracks[0]._lastEventTick + info.delta;
|
||||
eventTime = _position._lastEventTime + (eventTick - _position._lastEventTick) * _psecPerTick;
|
||||
if (eventTime > endTime)
|
||||
break;
|
||||
|
||||
if (!info.noop) {
|
||||
// Process the next info.
|
||||
bool ret = processEvent(info);
|
||||
if (!ret)
|
||||
return;
|
||||
}
|
||||
|
||||
loopEvent |= info.loop;
|
||||
|
||||
if (!_abortParse) {
|
||||
_position._playTime = eventTime;
|
||||
_position._lastEventTime = eventTime;
|
||||
_position._subtracks[0]._lastEventTime = eventTime;
|
||||
|
||||
_position._playTick = eventTick;
|
||||
_position._lastEventTick = eventTick;
|
||||
_position._subtracks[0]._lastEventTick = eventTick;
|
||||
|
||||
if (_position.isTracking(0)) {
|
||||
parseNextEvent(_nextSubtrackEvents[0]);
|
||||
}
|
||||
determineNextEvent();
|
||||
}
|
||||
}
|
||||
|
||||
if (!_abortParse) {
|
||||
_position._playTime = endTime;
|
||||
_position._playTick = (endTime - _position._lastEventTime) / _psecPerTick + _position._lastEventTick;
|
||||
if (loopEvent) {
|
||||
// One of the processed events has looped (part of) the MIDI data.
|
||||
// Infinite looping will cause the tracker to overflow eventually.
|
||||
// Reset the tracker positions to prevent this from happening.
|
||||
rebaseTracking();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool MidiParser_M::processEvent(const EventInfo& info, bool fireEvents) {
|
||||
const byte *playPos = _position._subtracks[0]._playPos;
|
||||
|
||||
if (info.command() == 0x8 && info.channel() == 0x1) {
|
||||
// Call subroutine
|
||||
LoopData loopData { };
|
||||
loopData.returnPos = playPos;
|
||||
loopData.numLoops = info.ext.data[0];
|
||||
uint16 startOffset = READ_LE_UINT16(info.ext.data + 1);
|
||||
assert(startOffset < _trackLength);
|
||||
loopData.startPos = _tracks[0][0] + startOffset;
|
||||
_loopStack->push(loopData);
|
||||
playPos = loopData.startPos;
|
||||
} else if (info.command() == 0xE) {
|
||||
// Set loop point
|
||||
_loopPoint = playPos;
|
||||
} else if (info.command() == 0xF) {
|
||||
// Return
|
||||
if (_loopStack->empty()) {
|
||||
// Global loop: return to the global loop point
|
||||
playPos = _loopPoint;
|
||||
} else {
|
||||
// Subroutine loop
|
||||
LoopData *loopData = &_loopStack->top();
|
||||
if (loopData->numLoops > 1) {
|
||||
// Return to the start of the subroutine data
|
||||
loopData->numLoops--;
|
||||
playPos = loopData->startPos;
|
||||
} else {
|
||||
// Return to the call position
|
||||
playPos = loopData->returnPos;
|
||||
_loopStack->pop();
|
||||
}
|
||||
}
|
||||
} else if (info.command() == 0x8 && info.channel() == 0x3) {
|
||||
// Load instrument
|
||||
if (fireEvents) {
|
||||
// Send the instrument data as a meta event
|
||||
sendMetaEventToDriver(info.ext.type, info.ext.data, (uint16)info.length);
|
||||
}
|
||||
} else if (fireEvents) {
|
||||
// Other events are handled by the driver
|
||||
sendToDriver(info.event, info.basic.param1, info.basic.param2);
|
||||
}
|
||||
|
||||
_position._subtracks[0]._playPos = playPos;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MidiParser_M::parseNextEvent(EventInfo &info) {
|
||||
const byte *playPos = _position._subtracks[0]._playPos;
|
||||
assert(playPos >= _tracks[0][0]);
|
||||
assert(playPos - _tracks[0][0] < (int)_trackLength);
|
||||
|
||||
info.start = playPos;
|
||||
info.event = *(playPos++);
|
||||
info.delta = 0;
|
||||
info.basic.param1 = 0;
|
||||
info.basic.param2 = 0;
|
||||
info.noop = false;
|
||||
info.loop = false;
|
||||
|
||||
switch (info.command()) {
|
||||
case 0x0: // Note off
|
||||
case 0x1: // Note on
|
||||
case 0x2: // Set pitch
|
||||
case 0x3: // Set level
|
||||
case 0x4: // Set modulation
|
||||
case 0x5: // Set slide
|
||||
case 0x6: // Set vibrato
|
||||
case 0x7: // Program change
|
||||
// These commands all have 1 data byte.
|
||||
info.basic.param1 = *(playPos++);
|
||||
break;
|
||||
|
||||
case 0x8: // Subcommand
|
||||
switch (info.channel()) {
|
||||
case 0x1: // Call subroutine
|
||||
// This command specifies the number of loops (1 byte) and an
|
||||
// offset in the M data to jump to (2 bytes).
|
||||
info.ext.type = info.channel();
|
||||
info.length = 3;
|
||||
info.ext.data = playPos;
|
||||
playPos += info.length;
|
||||
break;
|
||||
case 0x2: // Delay
|
||||
// This command is used to specify a delta time between the
|
||||
// previous and the next event. It does nothing otherwise.
|
||||
info.delta = *(playPos++);
|
||||
info.noop = true;
|
||||
break;
|
||||
case 0x3: // Load instrument
|
||||
// This command specifies the instrument bank slot in which the
|
||||
// instrument should be loaded (1 byte) plus an OPL instrument
|
||||
// definition (11 bytes).
|
||||
info.ext.type = info.channel();
|
||||
info.length = 12;
|
||||
info.ext.data = playPos;
|
||||
playPos += info.length;
|
||||
break;
|
||||
case 0x5: // Fade out
|
||||
case 0x6: // Fade in
|
||||
// These commands have 1 data byte.
|
||||
info.basic.param1 = *(playPos++);
|
||||
break;
|
||||
default:
|
||||
info.noop = true;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0xE: // Set loop point
|
||||
// This command does not have any data bytes.
|
||||
break;
|
||||
|
||||
case 0xF: // Return
|
||||
// This command does not have any data bytes.
|
||||
info.loop = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
info.noop = true;
|
||||
break;
|
||||
}
|
||||
|
||||
_position._subtracks[0]._playPos = playPos;
|
||||
}
|
||||
|
||||
void MidiParser_M::allNotesOff() {
|
||||
if (_driver) {
|
||||
_driver->stopAllNotes();
|
||||
}
|
||||
}
|
||||
|
||||
} // End of namespace Nuvie
|
||||
} // End of namespace Ultima
|
||||
Reference in New Issue
Block a user