/* 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. * */ #include "sci/sci.h" #include "sci/resource.h" #include "sci/sound/drivers/mididriver.h" #include "common/file.h" #include "common/system.h" #include "common/textconsole.h" namespace Sci { static byte volumeTable[64] = { 0x00, 0x10, 0x14, 0x18, 0x1f, 0x26, 0x2a, 0x2e, 0x2f, 0x32, 0x33, 0x33, 0x34, 0x35, 0x35, 0x36, 0x36, 0x37, 0x37, 0x38, 0x38, 0x38, 0x39, 0x39, 0x39, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f }; class MidiPlayer_Fb01 : public MidiPlayer { public: enum { kVoices = 8, kMaxSysExSize = 264 }; MidiPlayer_Fb01(SciVersion version); virtual ~MidiPlayer_Fb01(); int open(ResourceManager *resMan); void close(); void send(uint32 b); void sysEx(const byte *msg, uint16 length); bool hasRhythmChannel() const { return false; } byte getPlayId() const; int getPolyphony() const { return kVoices; } // 9 in SCI1? void setVolume(byte volume); int getVolume(); void playSwitch(bool play); private: void noteOn(int channel, int note, int velocity); void noteOff(int channel, int note); void setPatch(int channel, int patch); void controlChange(int channel, int control, int value); void setVoiceParam(byte voice, byte param, byte value); void setSystemParam(byte sysChan, byte param, byte value); void sendVoiceData(byte instrument, const byte *data); void sendBanks(const byte *data, int size); void storeVoiceData(byte instrument, byte bank, byte index); void initVoices(); void voiceOn(int voice, int note, int velocity); void voiceOff(int voice); int findVoice(int channel); void voiceMapping(int channel, int voices); void assignVoices(int channel, int voices); void releaseVoices(int channel, int voices); void donateVoices(); void sendToChannel(byte channel, byte command, byte op1, byte op2); struct Channel { uint8 patch; // Patch setting uint8 volume; // Channel volume (0-63) uint8 pan; // Pan setting (0-127, 64 is center) uint8 holdPedal; // Hold pedal setting (0 to 63 is off, 127 to 64 is on) uint8 extraVoices; // The number of additional voices this channel optimally needs uint16 pitchWheel; // Pitch wheel setting (0-16383, 8192 is center) uint8 lastVoice; // Last voice used for this MIDI channel bool enableVelocity; // Enable velocity control (SCI0) Channel() : patch(0), volume(127), pan(64), holdPedal(0), extraVoices(0), pitchWheel(8192), lastVoice(0), enableVelocity(false) { } }; struct Voice { int8 channel; // MIDI channel that this voice is assigned to or -1 int8 note; // Currently playing MIDI note or -1 int bank; // Current bank setting or -1 int patch; // Currently playing patch or -1 uint8 velocity; // Note velocity bool isSustained; // Flag indicating a note that is being sustained by the hold pedal uint16 age; // Age of the current note Voice() : channel(-1), note(-1), bank(-1), patch(-1), velocity(0), isSustained(false), age(0) { } }; bool _playSwitch; int _masterVolume; Channel _channels[16]; Voice _voices[kVoices]; Common::TimerManager::TimerProc _timerProc; void *_timerParam; static void midiTimerCallback(void *p); void setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc); byte _sysExBuf[kMaxSysExSize]; }; MidiPlayer_Fb01::MidiPlayer_Fb01(SciVersion version) : MidiPlayer(version), _playSwitch(true), _masterVolume(15), _timerParam(NULL), _timerProc(NULL) { MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_MIDI); _driver = MidiDriver::createMidi(dev); _sysExBuf[0] = 0x43; _sysExBuf[1] = 0x75; } MidiPlayer_Fb01::~MidiPlayer_Fb01() { delete _driver; } void MidiPlayer_Fb01::voiceMapping(int channel, int voices) { int curVoices = 0; for (int i = 0; i < kVoices; i++) if (_voices[i].channel == channel) curVoices++; curVoices += _channels[channel].extraVoices; if (curVoices < voices) { debug(3, "FB-01: assigning %i additional voices to channel %i", voices - curVoices, channel); assignVoices(channel, voices - curVoices); } else if (curVoices > voices) { debug(3, "FB-01: releasing %i voices from channel %i", curVoices - voices, channel); releaseVoices(channel, curVoices - voices); donateVoices(); } } void MidiPlayer_Fb01::assignVoices(int channel, int voices) { assert(voices > 0); for (int i = 0; i < kVoices; i++) { if (_voices[i].channel == -1) { _voices[i].channel = channel; if (--voices == 0) break; } } _channels[channel].extraVoices += voices; setPatch(channel, _channels[channel].patch); sendToChannel(channel, 0xe0, _channels[channel].pitchWheel & 0x7f, _channels[channel].pitchWheel >> 7); controlChange(channel, 0x07, _channels[channel].volume); controlChange(channel, 0x0a, _channels[channel].pan); controlChange(channel, 0x40, _channels[channel].holdPedal); } void MidiPlayer_Fb01::releaseVoices(int channel, int voices) { if (_channels[channel].extraVoices >= voices) { _channels[channel].extraVoices -= voices; return; } voices -= _channels[channel].extraVoices; _channels[channel].extraVoices = 0; for (int i = 0; i < kVoices; i++) { if ((_voices[i].channel == channel) && (_voices[i].note == -1)) { _voices[i].channel = -1; if (--voices == 0) return; } } for (int i = 0; i < kVoices; i++) { if (_voices[i].channel == channel) { voiceOff(i); _voices[i].channel = -1; if (--voices == 0) return; } } } void MidiPlayer_Fb01::donateVoices() { int freeVoices = 0; for (int i = 0; i < kVoices; i++) if (_voices[i].channel == -1) freeVoices++; if (freeVoices == 0) return; for (int i = 0; i < MIDI_CHANNELS; i++) { if (_channels[i].extraVoices >= freeVoices) { assignVoices(i, freeVoices); _channels[i].extraVoices -= freeVoices; return; } else if (_channels[i].extraVoices > 0) { assignVoices(i, _channels[i].extraVoices); freeVoices -= _channels[i].extraVoices; _channels[i].extraVoices = 0; } } } int MidiPlayer_Fb01::findVoice(int channel) { int voice = -1; int oldestVoice = -1; uint32 oldestAge = 0; // Try to find a voice assigned to this channel that is free (round-robin) for (int i = 0; i < kVoices; i++) { int v = (_channels[channel].lastVoice + i + 1) % kVoices; if (_voices[v].channel == channel) { if (_voices[v].note == -1) { voice = v; break; } // We also keep track of the oldest note in case the search fails // Notes started in the current time slice will not be selected if (_voices[v].age > oldestAge) { oldestAge = _voices[v].age; oldestVoice = v; } } } if (voice == -1) { if (oldestVoice >= 0) { voiceOff(oldestVoice); voice = oldestVoice; } else { return -1; } } _channels[channel].lastVoice = voice; return voice; } void MidiPlayer_Fb01::sendToChannel(byte channel, byte command, byte op1, byte op2) { for (int i = 0; i < kVoices; i++) { // Send command to all voices assigned to this channel if (_voices[i].channel == channel) _driver->send(command | i, op1, op2); } } void MidiPlayer_Fb01::setPatch(int channel, int patch) { int bank = 0; _channels[channel].patch = patch; if (patch >= 48) { patch -= 48; bank = 1; } for (int voice = 0; voice < kVoices; voice++) { if (_voices[voice].channel == channel) { if (_voices[voice].bank != bank) { _voices[voice].bank = bank; setVoiceParam(voice, 4, bank); } _driver->send(0xc0 | voice, patch, 0); } } } void MidiPlayer_Fb01::voiceOn(int voice, int note, int velocity) { if (_playSwitch) { _voices[voice].note = note; _voices[voice].age = 0; _driver->send(0x90 | voice, note, velocity); } } void MidiPlayer_Fb01::voiceOff(int voice) { _voices[voice].note = -1; _driver->send(0xb0 | voice, 0x7b, 0x00); } void MidiPlayer_Fb01::noteOff(int channel, int note) { int voice; for (voice = 0; voice < kVoices; voice++) { if ((_voices[voice].channel == channel) && (_voices[voice].note == note)) { voiceOff(voice); return; } } } void MidiPlayer_Fb01::noteOn(int channel, int note, int velocity) { if (velocity == 0) return noteOff(channel, note); if (_version > SCI_VERSION_0_LATE) velocity = volumeTable[velocity >> 1] << 1; int voice; for (voice = 0; voice < kVoices; voice++) { if ((_voices[voice].channel == channel) && (_voices[voice].note == note)) { voiceOff(voice); voiceOn(voice, note, velocity); return; } } voice = findVoice(channel); if (voice == -1) { debug(3, "FB-01: failed to find free voice assigned to channel %i", channel); return; } voiceOn(voice, note, velocity); } void MidiPlayer_Fb01::controlChange(int channel, int control, int value) { switch (control) { case 0x07: { _channels[channel].volume = value; if (_version > SCI_VERSION_0_LATE) value = volumeTable[value >> 1] << 1; byte vol = _masterVolume; if (vol > 0) vol = CLIP(vol + 3, 0, 15); sendToChannel(channel, 0xb0, control, (value * vol / 15) & 0x7f); break; } case 0x0a: _channels[channel].pan = value; sendToChannel(channel, 0xb0, control, value); break; case 0x40: _channels[channel].holdPedal = value; sendToChannel(channel, 0xb0, control, value); break; case 0x4b: // In early SCI0, voice count 15 signifies that the channel should be ignored // for this song. Assuming that there are no embedded voice count commands in // the MIDI stream, we should be able to get away with simply setting the voice // count for this channel to 0. voiceMapping(channel, (value != 15 ? value : 0)); break; case 0x7b: for (int i = 0; i < kVoices; i++) if ((_voices[i].channel == channel) && (_voices[i].note != -1)) voiceOff(i); } } void MidiPlayer_Fb01::send(uint32 b) { byte command = b & 0xf0; byte channel = b & 0xf; byte op1 = (b >> 8) & 0x7f; byte op2 = (b >> 16) & 0x7f; switch (command) { case 0x80: noteOff(channel, op1); break; case 0x90: noteOn(channel, op1, op2); break; case 0xb0: controlChange(channel, op1, op2); break; case 0xc0: setPatch(channel, op1); break; case 0xe0: _channels[channel].pitchWheel = (op1 & 0x7f) | ((op2 & 0x7f) << 7); sendToChannel(channel, command, op1, op2); break; default: warning("FB-01: Ignoring MIDI event %02x %02x %02x", command | channel, op1, op2); } } void MidiPlayer_Fb01::setVolume(byte volume) { _masterVolume = volume; for (uint i = 0; i < MIDI_CHANNELS; i++) controlChange(i, 0x07, _channels[i].volume & 0x7f); } int MidiPlayer_Fb01::getVolume() { return _masterVolume; } void MidiPlayer_Fb01::playSwitch(bool play) { } void MidiPlayer_Fb01::midiTimerCallback(void *p) { MidiPlayer_Fb01 *m = (MidiPlayer_Fb01 *)p; // Increase the age of the notes for (int i = 0; i < kVoices; i++) { if (m->_voices[i].note != -1) m->_voices[i].age++; } if (m->_timerProc) m->_timerProc(m->_timerParam); } void MidiPlayer_Fb01::setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) { _driver->setTimerCallback(NULL, NULL); _timerParam = timer_param; _timerProc = timer_proc; _driver->setTimerCallback(this, midiTimerCallback); } void MidiPlayer_Fb01::sendBanks(const byte *data, int size) { if (size < 3072) error("Failed to read FB-01 patch"); // SSCI sends bank dumps containing 48 instruments at once. We cannot do that // due to the limited maximum SysEx length. Instead we send the instruments // one by one and store them in the banks. for (int i = 0; i < 48; i++) { sendVoiceData(0, data + i * 64); storeVoiceData(0, 0, i); } // Send second bank if available if ((size >= 6146) && (READ_BE_UINT16(data + 3072) == 0xabcd)) { for (int i = 0; i < 48; i++) { sendVoiceData(0, data + 3074 + i * 64); storeVoiceData(0, 1, i); } } } int MidiPlayer_Fb01::open(ResourceManager *resMan) { assert(resMan != NULL); int retval = _driver->open(); if (retval != 0) { warning("Failed to open MIDI driver"); return retval; } // Set system channel to 0. We send this command over all 16 system channels for (int i = 0; i < 16; i++) setSystemParam(i, 0x20, 0); // Turn off memory protection setSystemParam(0, 0x21, 0); Resource *res = resMan->findResource(ResourceId(kResourceTypePatch, 2), 0); if (res) { sendBanks(res->data, res->size); } else { // Early SCI0 games have the sound bank embedded in the IMF driver. // Note that these games didn't actually support the FB-01 as a device, // but the IMF, which is the same device on an ISA card. Check: // http://wiki.vintage-computer.com/index.php/IBM_Music_feature_card warning("FB-01 patch file not found, attempting to load sound bank from IMF.DRV"); // Try to load sound bank from IMF.DRV Common::File f; if (f.open("IMF.DRV")) { int size = f.size(); byte *buf = new byte[size]; f.read(buf, size); // Search for start of sound bank int offset; for (offset = 0; offset < size; ++offset) { if (!strncmp((char *)buf + offset, "SIERRA ", 7)) break; } // Skip to voice data offset += 0x20; if (offset >= size) error("Failed to locate start of FB-01 sound bank"); sendBanks(buf + offset, size - offset); delete[] buf; } else error("Failed to open IMF.DRV"); } // Set up voices to use MIDI channels 0 - 7 for (int i = 0; i < kVoices; i++) setVoiceParam(i, 1, i); initVoices(); // Set master volume setSystemParam(0, 0x24, 0x7f); return 0; } void MidiPlayer_Fb01::close() { _driver->close(); } void MidiPlayer_Fb01::setVoiceParam(byte voice, byte param, byte value) { _sysExBuf[2] = 0x00; _sysExBuf[3] = 0x18 | voice; _sysExBuf[4] = param; _sysExBuf[5] = value; _driver->sysEx(_sysExBuf, 6); } void MidiPlayer_Fb01::setSystemParam(byte sysChan, byte param, byte value) { _sysExBuf[2] = sysChan; _sysExBuf[3] = 0x10; _sysExBuf[4] = param; _sysExBuf[5] = value; sysEx(_sysExBuf, 6); } void MidiPlayer_Fb01::sendVoiceData(byte instrument, const byte *data) { _sysExBuf[2] = 0x00; _sysExBuf[3] = 0x08 | instrument; _sysExBuf[4] = 0x00; _sysExBuf[5] = 0x00; _sysExBuf[6] = 0x01; _sysExBuf[7] = 0x00; for (int i = 0; i < 64; i++) { _sysExBuf[8 + i * 2] = data[i] & 0xf; _sysExBuf[8 + i * 2 + 1] = data[i] >> 4; } byte checksum = 0; for (int i = 8; i < 136; i++) checksum += _sysExBuf[i]; _sysExBuf[136] = (-checksum) & 0x7f; sysEx(_sysExBuf, 137); } void MidiPlayer_Fb01::storeVoiceData(byte instrument, byte bank, byte index) { _sysExBuf[2] = 0x00; _sysExBuf[3] = 0x28 | instrument; _sysExBuf[4] = 0x40; _sysExBuf[5] = (bank > 0 ? 48 : 0) + index; sysEx(_sysExBuf, 6); } void MidiPlayer_Fb01::initVoices() { int i = 2; _sysExBuf[i++] = 0x70; // Set all MIDI channels to 0 voices for (int j = 0; j < MIDI_CHANNELS; j++) { _sysExBuf[i++] = 0x70 | j; _sysExBuf[i++] = 0x00; _sysExBuf[i++] = 0x00; } // Set up the 8 MIDI channels we will be using for (int j = 0; j < 8; j++) { // One voice _sysExBuf[i++] = 0x70 | j; _sysExBuf[i++] = 0x00; _sysExBuf[i++] = 0x01; // Full range of keys _sysExBuf[i++] = 0x70 | j; _sysExBuf[i++] = 0x02; _sysExBuf[i++] = 0x7f; _sysExBuf[i++] = 0x70 | j; _sysExBuf[i++] = 0x03; _sysExBuf[i++] = 0x00; // Voice bank 0 _sysExBuf[i++] = 0x70 | j; _sysExBuf[i++] = 0x04; _sysExBuf[i++] = 0x00; // Voice 10 _sysExBuf[i++] = 0x70 | j; _sysExBuf[i++] = 0x05; _sysExBuf[i++] = 0x0a; } sysEx(_sysExBuf, i); } void MidiPlayer_Fb01::sysEx(const byte *msg, uint16 length) { _driver->sysEx(msg, length); // Wait the time it takes to send the SysEx data uint32 delay = (length + 2) * 1000 / 3125; delay += 10; g_system->delayMillis(delay); g_system->updateScreen(); } byte MidiPlayer_Fb01::getPlayId() const { switch (_version) { case SCI_VERSION_0_EARLY: return 0x01; case SCI_VERSION_0_LATE: return 0x02; default: return 0x00; } } MidiPlayer *MidiPlayer_Fb01_create(SciVersion version) { return new MidiPlayer_Fb01(version); } } // End of namespace Sci