scummvm/engines/sci/sfx/midiparser.cpp

465 lines
12 KiB
C++
Raw Normal View History

/* 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.
*
* $URL$
* $Id$
*
*/
#include "sci/engine/kernel.h"
#include "sci/engine/state.h"
#include "sci/sfx/midiparser.h"
namespace Sci {
static const int nMidiParams[] = { 2, 2, 2, 2, 1, 1, 2, 0 };
enum SciSysExCommands {
kSetSignalLoop = 0x7F,
kEndOfTrack = 0xFC,
kSetReverb = 0x50,
kMidiHold = 0x52,
kUpdateCue = 0x60
};
2009-12-28 21:00:59 +00:00
// MidiParser_SCI
//
MidiParser_SCI::MidiParser_SCI() :
MidiParser() {
_mixedData = NULL;
// mididata contains delta in 1/60th second
// values of ppqn and tempo are found experimentally and may be wrong
_ppqn = 1;
setTempo(16667);
_signalSet = false;
_signalToSet = 0;
}
2009-12-28 21:00:59 +00:00
MidiParser_SCI::~MidiParser_SCI() {
unloadMusic();
}
2009-12-28 21:00:59 +00:00
bool MidiParser_SCI::loadMusic(SoundResource::Track *track, MusicEntry *psnd, int channelFilterMask, SciVersion soundVersion) {
unloadMusic();
_track = track;
_pSnd = psnd;
_soundVersion = soundVersion;
setVolume(psnd->volume);
if (channelFilterMask) {
// SCI0 only has 1 data stream, but we need to filter out channels depending on music hardware selection
midiFilterChannels(channelFilterMask);
} else {
midiMixChannels();
}
_num_tracks = 1;
_tracks[0] = _mixedData;
setTrack(0);
_loopTick = 0;
return true;
}
void MidiParser_SCI::unloadMusic() {
allNotesOff();
resetTracking();
_num_tracks = 0;
if (_mixedData) {
delete[] _mixedData;
_mixedData = NULL;
}
}
void MidiParser_SCI::parseNextEvent(EventInfo &info) {
// Set signal AFTER waiting for delta, otherwise we would set signal too soon resulting in all sorts of bugs
if (_signalSet) {
_signalSet = false;
_pSnd->signal = _signalToSet;
debugC(2, kDebugLevelSound, "signal %04x", _signalToSet);
}
info.start = _position._play_pos;
info.delta = 0;
while (*_position._play_pos == 0xF8) {
info.delta += 240;
_position._play_pos++;
}
info.delta += *(_position._play_pos++);
// Process the next info.
if ((_position._play_pos[0] & 0xF0) >= 0x80)
info.event = *(_position._play_pos++);
else
info.event = _position._running_status;
if (info.event < 0x80)
return;
_position._running_status = info.event;
switch (info.command()) {
case 0xC:
info.basic.param1 = *(_position._play_pos++);
info.basic.param2 = 0;
if (info.channel() == 0xF) {// SCI special case
if (info.basic.param1 != kSetSignalLoop) {
_signalSet = true;
_signalToSet = info.basic.param1;
} else {
_loopTick = _position._play_tick;
}
}
break;
case 0xD:
info.basic.param1 = *(_position._play_pos++);
info.basic.param2 = 0;
break;
case 0xB:
info.basic.param1 = *(_position._play_pos++);
info.basic.param2 = *(_position._play_pos++);
if (info.channel() == 0xF) {// SCI special
// Reference for some events:
// http://wiki.scummvm.org/index.php/SCI/Specifications/Sound/SCI0_Resource_Format#Status_Reference
// Also, sci/sfx/iterator/iterator.cpp, function BaseSongIterator::parseMidiCommand()
switch (info.basic.param1) {
case kSetReverb:
// TODO: Not implemented yet
break;
case kMidiHold:
// Check if the hold ID marker is the same as the hold ID marker set for that song by
// cmdSetSoundHold. If it is, set the loop position
if (info.basic.param2 == _pSnd->hold)
_loopTick = _position._play_tick;
break;
case kUpdateCue:
switch (_soundVersion) {
case SCI_VERSION_0_EARLY:
case SCI_VERSION_0_LATE:
_pSnd->dataInc += info.basic.param2;
_signalSet = true;
_signalToSet = 0x7f + _pSnd->dataInc;
break;
case SCI_VERSION_1_EARLY:
case SCI_VERSION_1_LATE:
_pSnd->dataInc++;
break;
default:
break;
}
break;
case 0x1: // unknown (example case: LB2CD)
case 0xA: // unknown (example case: LB2CD)
default:
warning("Unhandled SCI SysEx 0x%x (parameter %d)", info.basic.param1, info.basic.param2);
break;
}
}
if (info.basic.param1 == 7) // channel volume change -scale it
info.basic.param2 = info.basic.param2 * _volume / 0x7F;
info.length = 0;
break;
case 0x8:
case 0x9:
case 0xA:
case 0xE:
info.basic.param1 = *(_position._play_pos++);
info.basic.param2 = *(_position._play_pos++);
if (info.command() == 0x9 && info.basic.param2 == 0)
info.event = info.channel() | 0x80;
info.length = 0;
break;
case 0xF: // System Common, Meta or SysEx event
switch (info.event & 0x0F) {
case 0x2: // Song Position Pointer
info.basic.param1 = *(_position._play_pos++);
info.basic.param2 = *(_position._play_pos++);
break;
case 0x3: // Song Select
info.basic.param1 = *(_position._play_pos++);
info.basic.param2 = 0;
break;
case 0x6:
case 0x8:
case 0xA:
case 0xB:
case 0xC:
case 0xE:
info.basic.param1 = info.basic.param2 = 0;
break;
case 0x0: // SysEx
info.length = readVLQ(_position._play_pos);
info.ext.data = _position._play_pos;
_position._play_pos += info.length;
break;
case 0xF: // META event
info.ext.type = *(_position._play_pos++);
info.length = readVLQ(_position._play_pos);
info.ext.data = _position._play_pos;
_position._play_pos += info.length;
if (info.ext.type == 0x2F) {// end of track reached
if (_pSnd->loop)
_pSnd->loop--;
if (_pSnd->loop) {
// We need to play it again...
jumpToTick(_loopTick);
} else {
_pSnd->status = kSoundStopped;
_pSnd->signal = SIGNAL_OFFSET;
debugC(2, kDebugLevelSound, "signal EOT");
}
}
break;
default:
warning(
"MidiParser_SCI::parseNextEvent: Unsupported event code %x",
info.event);
} // // System Common, Meta or SysEx event
}// switch (info.command())
}
2009-12-28 21:00:59 +00:00
byte MidiParser_SCI::midiGetNextChannel(long ticker) {
byte curr = 0xFF;
long closest = ticker + 1000000, next = 0;
for (int i = 0; i < _track->channelCount; i++) {
if (_track->channels[i].time == -1) // channel ended
continue;
next = *_track->channels[i].data; // when the next event shoudl occur
if (next == 0xF8) // 0xF8 means 240 ticks delay
next = 240;
next += _track->channels[i].time;
if (next < closest) {
curr = i;
closest = next;
}
}
return curr;
}
2009-12-28 21:00:59 +00:00
byte *MidiParser_SCI::midiMixChannels() {
int totalSize = 0;
byte **dataPtr = new byte *[_track->channelCount];
for (int i = 0; i < _track->channelCount; i++) {
dataPtr[i] = _track->channels[i].data;
_track->channels[i].time = 0;
_track->channels[i].prev = 0;
totalSize += _track->channels[i].size;
}
byte *mixedData = new byte[totalSize * 2]; // FIXME: creates overhead and still may be not enough to hold all data
_mixedData = mixedData;
long ticker = 0;
byte curr, delta;
byte cmd, par1, global_prev = 0;
long new_delta;
SoundResource::Channel *channel;
while ((curr = midiGetNextChannel(ticker)) != 0xFF) { // there is still active channel
channel = &_track->channels[curr];
delta = *channel->data++;
channel->time += (delta == 0xF8 ? 240 : delta); // when the comamnd is supposed to occur
if (delta == 0xF8)
continue;
new_delta = channel->time - ticker;
ticker += new_delta;
cmd = *channel->data++;
if (cmd != kEndOfTrack) {
// output new delta
while (new_delta > 240) {
*mixedData++ = 0xF8;
new_delta -= 240;
}
*mixedData++ = (byte)new_delta;
}
switch (cmd) {
case 0xF0: // sysEx
*mixedData++ = cmd;
do {
par1 = *channel->data++;
*mixedData++ = par1; // out
} while (par1 != 0xF7);
break;
case kEndOfTrack: // end channel
channel->time = -1; // FIXME
break;
default: // MIDI command
if (cmd & 0x80)
par1 = *channel->data++;
else {// running status
par1 = cmd;
cmd = channel->prev;
}
if (cmd != global_prev)
*mixedData++ = cmd; // out cmd
*mixedData++ = par1;// pout par1
if (nMidiParams[(cmd >> 4) - 8] == 2)
*mixedData++ = *channel->data++; // out par2
channel->prev = cmd;
global_prev = cmd;
}// switch(cmd)
}// while (curr)
// mixing finished. inserting stop event
*mixedData++ = 0;
*mixedData++ = 0xFF;
*mixedData++ = 0x2F;
*mixedData++ = 0x00;
*mixedData++ = 0x00;
for (int channelNr = 0; channelNr < _track->channelCount; channelNr++)
_track->channels[channelNr].data = dataPtr[channelNr];
delete[] dataPtr;
return _mixedData;
}
2009-12-28 21:00:59 +00:00
// This is used for SCI0 sound-data. SCI0 only has one stream that may
// contain several channels and according to output device we remove
// certain channels from that data.
byte *MidiParser_SCI::midiFilterChannels(int channelMask) {
SoundResource::Channel *channel = &_track->channels[0];
byte *channelData = channel->data;
byte *channelDataEnd = channel->data + channel->size;
byte *filterData = new byte[channel->size + 5];
byte curChannel, curByte, curDelta;
byte command, lastCommand;
int delta = 0;
//int dataLeft = channel->size;
int midiParamCount;
_mixedData = filterData;
command = 0;
midiParamCount = 0;
lastCommand = 0;
curChannel = 15;
while (channelData < channelDataEnd) {
curDelta = *channelData++;
if (curDelta == 0xF8) {
delta += 240;
continue;
}
delta += curDelta;
curByte = *channelData++;
switch (curByte) {
case 0xF0: // sysEx
case kEndOfTrack: // end of channel
command = curByte;
curChannel = 15;
break;
default:
if (curByte & 0x80) {
command = curByte;
curChannel = command & 0x0F;
midiParamCount = nMidiParams[(command >> 4) - 8];
}
}
if ((1 << curChannel) & channelMask) {
if (command != kEndOfTrack) {
debugC(2, kDebugLevelSound, "\nDELTA ");
// Write delta
while (delta > 240) {
*filterData++ = 0xF8;
debugC(2, kDebugLevelSound, "F8 ");
delta -= 240;
}
*filterData++ = (byte)delta;
debugC(2, kDebugLevelSound, "%02X ", delta);
delta = 0;
}
// Write command
switch (command) {
case 0xF0: // sysEx
*filterData++ = command;
debugC(2, kDebugLevelSound, "%02X ", command);
do {
curByte = *channelData++;
*filterData++ = curByte; // out
} while (curByte != 0xF7);
lastCommand = command;
break;
case kEndOfTrack: // end of channel
break;
default: // MIDI command
if (lastCommand != command) {
*filterData++ = command;
debugC(2, kDebugLevelSound, "%02X ", command);
lastCommand = command;
}
if (midiParamCount > 0) {
if (curByte & 0x80) {
debugC(2, kDebugLevelSound, "%02X ", *channelData);
*filterData++ = *channelData++;
} else {
debugC(2, kDebugLevelSound, "%02X ", curByte);
*filterData++ = curByte;
}
}
if (midiParamCount > 1) {
debugC(2, kDebugLevelSound, "%02X ", *channelData);
*filterData++ = *channelData++;
}
}
} else {
if (curByte & 0x80) {
channelData += midiParamCount;
} else {
channelData += midiParamCount - 1;
}
}
}
// Stop event
*filterData++ = 0; // delta
*filterData++ = 0xFF; // Meta-Event
*filterData++ = 0x2F; // End-Of-Track
*filterData++ = 0x00;
*filterData++ = 0x00;
return _mixedData;
}
void MidiParser_SCI::setVolume(byte bVolume) {
if (bVolume > 0x7F)
bVolume = 0x7F;
if (_volume != bVolume) {
_volume = bVolume;
// sending volume change to all active channels
for (int i = 0; i < _track->channelCount; i++)
if (_track->channels[i].number <= 0xF)
_driver->send(0xB0 + _track->channels[i].number, 7, _volume);
}
}
2009-12-28 21:00:59 +00:00
} // End of namespace Sci