diff options
Diffstat (limited to 'engines/sci/sound/midiparser_sci.cpp')
-rw-r--r-- | engines/sci/sound/midiparser_sci.cpp | 239 |
1 files changed, 178 insertions, 61 deletions
diff --git a/engines/sci/sound/midiparser_sci.cpp b/engines/sci/sound/midiparser_sci.cpp index 186fc18a5c..c0b4f3122e 100644 --- a/engines/sci/sound/midiparser_sci.cpp +++ b/engines/sci/sound/midiparser_sci.cpp @@ -8,12 +8,12 @@ * 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. @@ -58,6 +58,10 @@ MidiParser_SCI::MidiParser_SCI(SciVersion soundVersion, SciMusic *music) : _resetOnPause = false; _pSnd = 0; + + _mainThreadCalled = false; + + resetStateTracking(); } MidiParser_SCI::~MidiParser_SCI() { @@ -68,10 +72,12 @@ MidiParser_SCI::~MidiParser_SCI() { } void MidiParser_SCI::mainThreadBegin() { + assert(!_mainThreadCalled); _mainThreadCalled = true; } void MidiParser_SCI::mainThreadEnd() { + assert(_mainThreadCalled); _mainThreadCalled = false; } @@ -83,12 +89,21 @@ bool MidiParser_SCI::loadMusic(SoundResource::Track *track, MusicEntry *psnd, in for (int i = 0; i < 16; i++) { _channelUsed[i] = false; - _channelRemap[i] = -1; _channelMuted[i] = false; _channelVolume[i] = 127; + + if (_soundVersion <= SCI_VERSION_0_LATE) + _channelRemap[i] = i; + else + _channelRemap[i] = -1; } - _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 + + // FIXME: SSCI does not always start playing a track at the first byte. + // By default it skips 10 (or 13?) bytes containing prio/voices, patch, + // volume, pan commands in fixed locations, and possibly a signal + // in channel 15. We should initialize state tracking to those values + // so that they automatically get set up properly when the channels get + // mapped. See also the related FIXME in MidiParser_SCI::processEvent. if (channelFilterMask) { // SCI0 only has 1 data stream, but we need to filter out channels depending on music hardware selection @@ -314,31 +329,26 @@ byte *MidiParser_SCI::midiFilterChannels(int channelMask) { 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::resetStateTracking() { + for (int i = 0; i < 16; ++i) { + ChannelState &s = _channelState[i]; + s._modWheel = 0; + s._pan = 64; + s._patch = 0; // TODO: Initialize properly (from data in LoadMusic?) + s._note = -1; + s._sustain = false; + s._pitchWheel = 0x2000; + s._voices = 0; -void MidiParser_SCI::lostChannels() { - for (int curChannel = 0; curChannel < 15; curChannel++) - if ((_channelUsed[curChannel]) && (curChannel != 9)) - _channelRemap[curChannel] = -1; + _channelVolume[i] = 127; + } } void MidiParser_SCI::sendInitCommands() { - // reset our "global" volume and channel volumes + resetStateTracking(); + + // reset our "global" volume _volume = 127; - for (int i = 0; i < 16; i++) - _channelVolume[i] = 127; // Set initial voice count if (_pSnd) { @@ -390,53 +400,119 @@ void MidiParser_SCI::sendFromScriptToDriver(uint32 midi) { // 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 - } - } + // State tracking + trackState(midi); - // Is channel muted? if so, don't send command - if (_channelMuted[midiChannel]) + if ((midi & 0xFFF0) == 0x4EB0 && _soundVersion >= SCI_VERSION_1_EARLY) { + // Mute. Handled in trackState(). + // CHECKME: Should we send this on to the driver? 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 local volume channelVolume = channelVolume * _volume / 127; - midi = (midi & 0xFFF0) | ((channelVolume & 0xFF) << 16); + midi = (midi & 0xFFFF) | ((channelVolume & 0xFF) << 16); } + // Channel remapping + byte midiChannel = midi & 0xf; int16 realChannel = _channelRemap[midiChannel]; if (realChannel == -1) return; midi = (midi & 0xFFFFFFF0) | realChannel; + sendToDriver_raw(midi); +} + +void MidiParser_SCI::sendToDriver_raw(uint32 midi) { if (_mainThreadCalled) _music->putMidiCommandInQueue(midi); else _driver->send(midi); } +void MidiParser_SCI::trackState(uint32 b) { + // We keep track of most of the state of a midi channel, so we can + // at any time reset the device to the current state, even if the + // channel has been temporarily disabled due to remapping. + + byte command = b & 0xf0; + byte channel = b & 0xf; + byte op1 = (b >> 8) & 0x7f; + byte op2 = (b >> 16) & 0x7f; + + ChannelState &s = _channelState[channel]; + + switch (command) { + case 0x90: + if (op2 != 0) { + // note on + s._note = op1; + break; + } + // else, fall-through + case 0x80: + // note off + if (s._note == op1) + s._note = -1; + break; + case 0xB0: + // control change + switch (op1) { + case 0x01: // mod wheel + s._modWheel = op2; + break; + case 0x07: // channel volume + _channelVolume[channel] = op2; + break; + case 0x0A: // pan + s._pan = op2; + break; + case 0x40: // sustain + s._sustain = (op2 != 0); + break; + case 0x4B: // voices + s._voices = op2; + _pSnd->_chan[channel]._voices = op2; // Also sync our MusicEntry + break; + case 0x4E: // mute + // This is channel mute only for sci1. + // (It's velocity control for sci0, but we don't need state in sci0) + if (_soundVersion >= SCI_VERSION_1_EARLY) { + // FIXME: mute is a level, not a bool, in some SCI versions + bool m = op2; + if (_pSnd->_chan[channel]._mute != m) { + _pSnd->_chan[channel]._mute = m; + // TODO: If muting/unmuting a channel, remap channels. + warning("Mute change without immediate remapping (mainThread = %d)", _mainThreadCalled); + } + } + break; + default: + break; + } + break; + case 0xC0: + // program change + s._patch = op1; + break; + case 0xE0: + // pitchwheel + s._pitchWheel = (op2 << 7) | op1; + break; + default: + break; + } +} + void MidiParser_SCI::parseNextEvent(EventInfo &info) { info.start = _position._playPos; info.delta = 0; @@ -477,8 +553,10 @@ void MidiParser_SCI::parseNextEvent(EventInfo &info) { case 0xE: info.basic.param1 = *(_position._playPos++); info.basic.param2 = *(_position._playPos++); - if (info.command() == 0x9 && info.basic.param2 == 0) + if (info.command() == 0x9 && info.basic.param2 == 0) { + // NoteOn with param2==0 is a NoteOff info.event = info.channel() | 0x80; + } info.length = 0; break; @@ -523,11 +601,10 @@ void MidiParser_SCI::parseNextEvent(EventInfo &info) { }// switch (info.command()) } -void MidiParser_SCI::processEvent(const EventInfo &info, bool fireEvents) { +bool MidiParser_SCI::processEvent(const EventInfo &info, bool fireEvents) { if (!fireEvents) { // We don't do any processing that should be done while skipping events - MidiParser::processEvent(info, fireEvents); - return; + return MidiParser::processEvent(info, fireEvents); } switch (info.command()) { @@ -579,7 +656,7 @@ void MidiParser_SCI::processEvent(const EventInfo &info, bool fireEvents) { } // Done with this event. - return; + return true; } // Break to let parent handle the rest. @@ -606,7 +683,7 @@ void MidiParser_SCI::processEvent(const EventInfo &info, bool fireEvents) { switch (info.basic.param1) { case kSetReverb: // Already handled above - return; + return true; case kMidiHold: // Check if the hold ID marker is the same as the hold ID // marker set for that song by cmdSetSoundHold. @@ -614,9 +691,9 @@ void MidiParser_SCI::processEvent(const EventInfo &info, bool fireEvents) { if (info.basic.param2 == _pSnd->hold) { jumpToTick(_loopTick, false, false); // Done with this event. - return; + return true; } - return; + return true; case kUpdateCue: if (!_jumpingToTick) { int inc; @@ -637,17 +714,17 @@ void MidiParser_SCI::processEvent(const EventInfo &info, bool fireEvents) { debugC(4, kDebugLevelSound, "datainc %04x", inc); } - return; + return true; case kResetOnPause: _resetOnPause = info.basic.param2; - return; + return true; // 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 - return; + return true; // Standard MIDI commands case 0x01: // mod wheel case 0x04: // foot controller @@ -662,10 +739,10 @@ void MidiParser_SCI::processEvent(const EventInfo &info, bool fireEvents) { 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); - return; + return true; default: warning("Unhandled SCI MIDI command 0x%x (parameter %d)", info.basic.param1, info.basic.param2); - return; + return true; } } @@ -684,7 +761,7 @@ void MidiParser_SCI::processEvent(const EventInfo &info, bool fireEvents) { jumpToTick(_loopTick); // Done with this event. - return; + return true; } else { _pSnd->status = kSoundStopped; @@ -704,7 +781,7 @@ void MidiParser_SCI::processEvent(const EventInfo &info, bool fireEvents) { // Let parent handle the rest - MidiParser::processEvent(info, fireEvents); + return MidiParser::processEvent(info, fireEvents); } byte MidiParser_SCI::getSongReverb() { @@ -809,4 +886,44 @@ void MidiParser_SCI::setVolume(byte volume) { } } +void MidiParser_SCI::remapChannel(int channel, int devChannel) { + if (_channelRemap[channel] == devChannel) + return; + + _channelRemap[channel] = devChannel; + + if (devChannel == -1) + return; + +// debug(" restoring state: channel %d on devChannel %d", channel, devChannel); + + // restore state + ChannelState &s = _channelState[channel]; + + int channelVolume = _channelVolume[channel]; + channelVolume = (channelVolume * _volume / 127) & 0xFF; + byte pitch1 = s._pitchWheel & 0x7F; + byte pitch2 = (s._pitchWheel >> 7) & 0x7F; + + sendToDriver_raw(0x0040B0 | devChannel); // sustain off + sendToDriver_raw(0x004BB0 | devChannel | (s._voices << 16)); + sendToDriver_raw(0x0000C0 | devChannel | (s._patch << 8)); + sendToDriver_raw(0x0007B0 | devChannel | (channelVolume << 16)); + sendToDriver_raw(0x000AB0 | devChannel | (s._pan << 16)); + sendToDriver_raw(0x0001B0 | devChannel | (s._modWheel << 16)); + sendToDriver_raw(0x0040B0 | devChannel | (s._sustain ? 0x7F0000 : 0)); + sendToDriver_raw(0x0000E0 | devChannel | (pitch1 << 8) | (pitch2 << 16)); + + // CHECKME: Some SSCI version send a control change 0x4E with s._note as + // parameter. + // We need to investigate how (and if) drivers should act on this. + // Related: controller 0x4E is used for 'mute' in the midiparser. + // This could be a bug in SSCI that went unnoticed because few (or no?) + // drivers implement controller 0x4E + + // NB: The line below is _not_ valid since s._note can be 0xFF. + // SSCI handles this out of band in the driver interface. + // sendToDriver_raw(0x004EB0 | devChannel | (s._note << 16); +} + } // End of namespace Sci |