scummvm/engines/sci/sound/midiparser_sci.cpp
Martin Kiewitz 7487b51e87 SCI: not error()ing out on no free channels
instead we just ignore such channels. I'm not sure how sierra sci behaved in that case, they ignored channels as well, but maybe they removed them from earlier music

svn-id: r51715
2010-08-03 21:38:26 +00:00

689 lines
18 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 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/sound/midiparser_sci.h"
#include "sci/sound/drivers/mididriver.h"
namespace Sci {
static const int nMidiParams[] = { 2, 2, 2, 2, 1, 1, 2, 0 };
enum SciMidiCommands {
kSetSignalLoop = 0x7F,
kEndOfTrack = 0xFC,
kSetReverb = 0x50,
kMidiHold = 0x52,
kUpdateCue = 0x60,
kResetOnPause = 0x4C
};
// MidiParser_SCI
//
MidiParser_SCI::MidiParser_SCI(SciVersion soundVersion, SciMusic *music) :
MidiParser() {
_soundVersion = soundVersion;
_music = music;
_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);
_volume = 127;
_signalSet = false;
_signalToSet = 0;
_dataincAdd = false;
_dataincToAdd = 0;
_resetOnPause = false;
_pSnd = 0;
}
MidiParser_SCI::~MidiParser_SCI() {
unloadMusic();
// we do this, so that MidiParser won't be able to call his own ::allNotesOff()
// this one would affect all channels and we can't let that happen
_driver = 0;
}
void MidiParser_SCI::mainThreadBegin() {
_mainThreadCalled = true;
}
void MidiParser_SCI::mainThreadEnd() {
_mainThreadCalled = false;
}
bool MidiParser_SCI::loadMusic(SoundResource::Track *track, MusicEntry *psnd, int channelFilterMask, SciVersion soundVersion) {
unloadMusic();
_track = track;
_pSnd = psnd;
_soundVersion = soundVersion;
for (int i = 0; i < 16; i++) {
_channelUsed[i] = false;
_channelRemap[i] = -1;
_channelMuted[i] = false;
_channelVolume[i] = 127;
}
_channelRemap[9] = 9; // never map channel 9, because that's used for percussion
_channelRemap[15] = 15; // never map channel 15, because thats used by sierra internally
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;
if (_pSnd)
setTrack(0);
_loopTick = 0;
return true;
}
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;
SoundResource::Channel *curChannel = &_track->channels[i];
if (curChannel->curPos >= curChannel->size)
continue;
next = curChannel->data[curChannel->curPos]; // when the next event should 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;
}
byte *MidiParser_SCI::midiMixChannels() {
int totalSize = 0;
for (int i = 0; i < _track->channelCount; i++) {
_track->channels[i].time = 0;
_track->channels[i].prev = 0;
_track->channels[i].curPos = 0;
totalSize += _track->channels[i].size;
}
byte *outData = new byte[totalSize * 2]; // FIXME: creates overhead and still may be not enough to hold all data
_mixedData = outData;
long ticker = 0;
byte channelNr, curDelta;
byte midiCommand = 0, midiParam, global_prev = 0;
long newDelta;
SoundResource::Channel *channel;
while ((channelNr = midiGetNextChannel(ticker)) != 0xFF) { // there is still an active channel
channel = &_track->channels[channelNr];
curDelta = channel->data[channel->curPos++];
channel->time += (curDelta == 0xF8 ? 240 : curDelta); // when the command is supposed to occur
if (curDelta == 0xF8)
continue;
newDelta = channel->time - ticker;
ticker += newDelta;
midiCommand = channel->data[channel->curPos++];
if (midiCommand != kEndOfTrack) {
// Write delta
while (newDelta > 240) {
*outData++ = 0xF8;
newDelta -= 240;
}
*outData++ = (byte)newDelta;
}
// Write command
switch (midiCommand) {
case 0xF0: // sysEx
*outData++ = midiCommand;
do {
midiParam = channel->data[channel->curPos++];
*outData++ = midiParam;
} while (midiParam != 0xF7);
break;
case kEndOfTrack: // end of channel
channel->time = -1;
break;
default: // MIDI command
if (midiCommand & 0x80) {
midiParam = channel->data[channel->curPos++];
} else {// running status
midiParam = midiCommand;
midiCommand = channel->prev;
}
// remember which channel got used for channel remapping
byte midiChannel = midiCommand & 0xF;
_channelUsed[midiChannel] = true;
if (midiCommand != global_prev)
*outData++ = midiCommand;
*outData++ = midiParam;
if (nMidiParams[(midiCommand >> 4) - 8] == 2)
*outData++ = channel->data[channel->curPos++];
channel->prev = midiCommand;
global_prev = midiCommand;
}
}
// Insert stop event
*outData++ = 0; // Delta
*outData++ = 0xFF; // Meta event
*outData++ = 0x2F; // End of track (EOT)
*outData++ = 0x00;
*outData++ = 0x00;
return _mixedData;
}
// 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 *outData = new byte[channel->size + 5];
byte curChannel = 15, curByte, curDelta;
byte command = 0, lastCommand = 0;
int delta = 0;
int midiParamCount = 0;
_mixedData = outData;
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) {
// Write delta
while (delta > 240) {
*outData++ = 0xF8;
delta -= 240;
}
*outData++ = (byte)delta;
delta = 0;
}
// Write command
switch (command) {
case 0xF0: // sysEx
*outData++ = command;
do {
curByte = *channelData++;
*outData++ = curByte; // out
} while (curByte != 0xF7);
lastCommand = command;
break;
case kEndOfTrack: // end of channel
break;
default: // MIDI command
// remember which channel got used for channel remapping
byte midiChannel = command & 0xF;
_channelUsed[midiChannel] = true;
if (lastCommand != command) {
*outData++ = command;
lastCommand = command;
}
if (midiParamCount > 0) {
if (curByte & 0x80)
*outData++ = *channelData++;
else
*outData++ = curByte;
}
if (midiParamCount > 1) {
*outData++ = *channelData++;
}
}
} else {
if (curByte & 0x80)
channelData += midiParamCount;
else
channelData += midiParamCount - 1;
}
}
// Insert stop event
*outData++ = 0; // Delta
*outData++ = 0xFF; // Meta event
*outData++ = 0x2F; // End of track (EOT)
*outData++ = 0x00;
*outData++ = 0x00;
return _mixedData;
}
// This will get called right before actual playing and will try to own the used channels
void MidiParser_SCI::tryToOwnChannels() {
// We don't have SciMusic in case debug command show_instruments is used
if (!_music)
return;
for (int curChannel = 0; curChannel < 15; curChannel++) {
if (_channelUsed[curChannel]) {
if (_channelRemap[curChannel] == -1) {
_channelRemap[curChannel] = _music->tryToOwnChannel(_pSnd, curChannel);
}
}
}
}
void MidiParser_SCI::lostChannels() {
for (int curChannel = 0; curChannel < 15; curChannel++)
if ((_channelUsed[curChannel]) && (curChannel != 9))
_channelRemap[curChannel] = -1;
}
void MidiParser_SCI::sendInitCommands() {
// reset our "global" volume and channel volumes
_volume = 127;
for (int i = 0; i < 16; i++)
_channelVolume[i] = 127;
// Set initial voice count
if (_pSnd) {
if (_soundVersion <= SCI_VERSION_0_LATE) {
for (int i = 0; i < 15; ++i) {
byte voiceCount = 0;
if (_channelUsed[i]) {
voiceCount = _pSnd->soundRes->getInitialVoiceCount(i);
sendToDriver(0xB0 | i, 0x4B, voiceCount);
}
}
}
}
// Send a velocity off signal to all channels
for (int i = 0; i < 15; ++i) {
if (_channelUsed[i])
sendToDriver(0xB0 | i, 0x4E, 0); // Reset velocity
}
// Center the pitch wheels and hold pedal in preparation for the next piece of music
for (int i = 0; i < 16; ++i) {
if (_channelUsed[i]) {
sendToDriver(0xE0 | i, 0, 0x40); // Reset pitch wheel
sendToDriver(0xB0 | i, 0x40, 0); // Reset hold pedal
}
}
}
void MidiParser_SCI::unloadMusic() {
if (_pSnd) {
resetTracking();
allNotesOff();
}
_num_tracks = 0;
_active_track = 255;
_resetOnPause = false;
if (_mixedData) {
delete[] _mixedData;
_mixedData = NULL;
}
}
// this is used for scripts sending midi commands to us. we verify in that case that the channel is actually
// used, so that channel remapping will work as well and then send them on
void MidiParser_SCI::sendFromScriptToDriver(uint32 midi) {
byte midiChannel = midi & 0xf;
if (!_channelUsed[midiChannel]) {
// trying to send to an unused channel
// this happens for cmdSendMidi at least in sq1vga right at the start, it's a script issue
return;
}
if (_channelRemap[midiChannel] == -1) {
// trying to send to an unmapped channel
// this happens for cmdSendMidi at least in sq1vga right at the start, scripts are pausing the sound
// and then sending manually. it's a script issue
return;
}
sendToDriver(midi);
}
void MidiParser_SCI::sendToDriver(uint32 midi) {
byte midiChannel = midi & 0xf;
if ((midi & 0xFFF0) == 0x4EB0) {
// this is channel mute only for sci1
// it's velocity control for sci0
if (_soundVersion >= SCI_VERSION_1_EARLY) {
_channelMuted[midiChannel] = midi & 0xFF0000 ? true : false;
return; // don't send this to driver at all
}
}
// Is channel muted? if so, don't send command
if (_channelMuted[midiChannel])
return;
if ((midi & 0xFFF0) == 0x07B0) {
// someone trying to set channel volume?
int channelVolume = (midi >> 16) & 0xFF;
// Remember, if we need to set it ourselves
_channelVolume[midiChannel] = channelVolume;
// Adjust volume accordingly to current "global" volume
channelVolume = channelVolume * _volume / 127;
midi = (midi & 0xFFF0) | ((channelVolume & 0xFF) << 16);
}
// Channel remapping
int16 realChannel = _channelRemap[midiChannel];
if (realChannel == -1)
return;
midi = (midi & 0xFFFFFFF0) | realChannel;
if (_mainThreadCalled)
_music->putMidiCommandInQueue(midi);
else
_driver->send(midi);
}
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 (_dataincAdd) {
_dataincAdd = false;
_pSnd->dataInc += _dataincToAdd;
_pSnd->signal = 0x7f + _pSnd->dataInc;
debugC(4, kDebugLevelSound, "datainc %04x", _dataincToAdd);
}
if (_signalSet) {
_signalSet = false;
_pSnd->signal = _signalToSet;
debugC(4, 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) {
// at least in kq5/french&mac the first scene in the intro has a song that sets signal to 4 immediately
// on tick 0. Signal isn't set at that point by sierra sci and it would cause the castle daventry text to
// get immediately removed, so we currently filter it.
// Sierra SCI ignores them as well at that time
if ((_position._play_tick) || (info.delta)) {
_signalSet = true;
_signalToSet = info.basic.param1;
}
} else {
_loopTick = _position._play_tick + info.delta;
}
}
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/sound/iterator/iterator.cpp, function BaseSongIterator::parseMidiCommand()
switch (info.basic.param1) {
case kSetReverb:
((MidiPlayer *)_driver)->setReverb(info.basic.param2);
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, loop back, but don't stop notes when jumping.
if (info.basic.param2 == _pSnd->hold)
jumpToTick(_loopTick, false, false);
break;
case kUpdateCue:
_dataincAdd = true;
switch (_soundVersion) {
case SCI_VERSION_0_EARLY:
case SCI_VERSION_0_LATE:
_dataincToAdd = info.basic.param2;
break;
case SCI_VERSION_1_EARLY:
case SCI_VERSION_1_LATE:
case SCI_VERSION_2_1:
_dataincToAdd = 1;
break;
default:
error("unsupported _soundVersion");
}
break;
case kResetOnPause:
_resetOnPause = info.basic.param2;
break;
// Unhandled SCI commands
case 0x46: // LSL3 - binoculars
case 0x61: // Iceman (AdLib?)
case 0x73: // Hoyle
case 0xD1: // KQ4, when riding the unicorn
// Obscure SCI commands - ignored
break;
// Standard MIDI commands
case 0x01: // mod wheel
case 0x04: // foot controller
case 0x07: // channel volume
case 0x0A: // pan
case 0x0B: // expression
case 0x40: // sustain
case 0x79: // reset all
case 0x7B: // notes off
// These are all handled by the music driver, so ignore them
break;
case 0x4B: // voice mapping
// TODO: is any support for this needed at the MIDI parser level?
warning("Unhanded SCI MIDI command 0x%x - voice mapping (parameter %d)", info.basic.param1, info.basic.param2);
break;
default:
warning("Unhandled SCI MIDI command 0x%x (parameter %d)", info.basic.param1, info.basic.param2);
break;
}
}
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(4, kDebugLevelSound, "signal EOT");
}
}
break;
default:
warning(
"MidiParser_SCI::parseNextEvent: Unsupported event code %x",
info.event);
} // // System Common, Meta or SysEx event
}// switch (info.command())
}
void MidiParser_SCI::allNotesOff() {
if (!_driver)
return;
int i, j;
// Turn off all active notes
for (i = 0; i < 128; ++i) {
for (j = 0; j < 16; ++j) {
if ((_active_notes[i] & (1 << j)) && (_channelRemap[j] != -1)){
sendToDriver(0x80 | j, i, 0);
}
}
}
// Turn off all hanging notes
for (i = 0; i < ARRAYSIZE(_hanging_notes); i++) {
byte midiChannel = _hanging_notes[i].channel;
if ((_hanging_notes[i].time_left) && (_channelRemap[midiChannel] != -1)) {
sendToDriver(0x80 | midiChannel, _hanging_notes[i].note, 0);
_hanging_notes[i].time_left = 0;
}
}
_hanging_notes_count = 0;
// To be sure, send an "All Note Off" event (but not all MIDI devices
// support this...).
for (i = 0; i < 16; ++i) {
if (_channelRemap[i] != -1)
sendToDriver(0xB0 | i, 0x7b, 0); // All notes off
}
memset(_active_notes, 0, sizeof(_active_notes));
}
void MidiParser_SCI::setVolume(byte volume) {
assert(volume <= MUSIC_VOLUME_MAX);
_volume = volume;
switch (_soundVersion) {
case SCI_VERSION_0_EARLY:
case SCI_VERSION_0_LATE: {
// SCI0 adlib driver doesn't support channel volumes, so we need to go this way
// TODO: this should take the actual master volume into account
int16 globalVolume = _volume * 15 / 127;
((MidiPlayer *)_driver)->setVolume(globalVolume);
break;
}
case SCI_VERSION_1_EARLY:
case SCI_VERSION_1_LATE:
case SCI_VERSION_2_1:
// Send previous channel volumes again to actually update the volume
for (int i = 0; i < 15; i++)
if (_channelRemap[i] != -1)
sendToDriver(0xB0 + i, 7, _channelVolume[i]);
break;
default:
error("MidiParser_SCI::setVolume: Unsupported soundVersion");
}
}
} // End of namespace Sci