/* 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 . * */ #include "sci/sound/drivers/mididriver.h" #include "audio/casio.h" #include "sci/resource/resource.h" namespace Sci { class MidiDriver_Casio : public ::MidiDriver_Casio { protected: // The instrument number used for the slap bass instrument on MT-540. static const uint8 SLAP_BASS_INSTRUMENT_MT540; // The instrument number used for the slap bass instrument on CT-460 and // CSM-1. static const uint8 SLAP_BASS_INSTRUMENT_CT460; static const uint8 PATCH_RESOURCE_SIZE; public: MidiDriver_Casio(MusicType midiType) : ::MidiDriver_Casio(midiType), _highSplitInstOutputChannel(-1), _rhythmChannelMapped(false), _playSwitch(true) { Common::fill(_instrumentRemapping, _instrumentRemapping + ARRAYSIZE(_instrumentRemapping), 0); setInstrumentRemapping(_instrumentRemapping); _rhythmNoteRemapping = new byte[128]; Common::fill(_instrumentFixedNotes, _instrumentFixedNotes + ARRAYSIZE(_instrumentFixedNotes), 0); Common::fill(_channelMap, _channelMap + ARRAYSIZE(_channelMap), 0); Common::fill(_channelFixedNotes, _channelFixedNotes + ARRAYSIZE(_channelFixedNotes), 0); _sendUntrackedNoteOff = false; } ~MidiDriver_Casio() { delete[] _rhythmNoteRemapping; } bool loadResource(const SciSpan &data, MusicType midiType = MT_AUTO); void initTrack(SciSpan &header); void playSwitch(bool play); protected: void noteOn(byte outputChannel, byte note, byte velocity, int8 source) override; void programChange(byte outputChannel, byte patchId, int8 source, bool applyRemapping = true) override; void programChange(byte outputChannel, byte patchId, int8 source, bool applyRemapping, bool applyBassSwap); int8 mapSourceChannel(uint8 source, uint8 dataChannel) override; byte mapInstrument(byte program, bool applyRemapping) override; int8 mapNote(byte outputChannel, byte note) override; bool isRhythmChannel(uint8 outputChannel) override; byte calculateVelocity(int8 source, byte velocity) override; byte _instrumentRemapping[128]; // If > 0, a fixed note value should be played for the corresponding // instrument instead of the MIDI event note value. byte _instrumentFixedNotes[0x60]; // Tracks the output channel which is currently being used by the "high" // split bass instrument (if any). Will be either -1 or 2. int8 _highSplitInstOutputChannel; int8 _channelMap[16]; // True if the rhythm channel has been mapped to output channel 3. bool _rhythmChannelMapped; // The fixed note that needs to be played on each output channel instead of // the MIDI event note value (or 0 if there is no fixed note). byte _channelFixedNotes[4]; bool _playSwitch; }; class MidiPlayer_Casio : public MidiPlayer { public: static const uint8 RESOURCE_HEADER_FLAG; protected: static const byte PATCH_RESOURCE_MT540; static const byte PATCH_RESOURCE_CT460; public: MidiPlayer_Casio(SciVersion soundVersion, MusicType midiType); ~MidiPlayer_Casio() override; int open(ResourceManager *resMan) override; void close() override; byte getPlayId() const override; int getPolyphony() const override; bool hasRhythmChannel() const override; void setVolume(byte volume) override; void playSwitch(bool play) override; void initTrack(SciSpan &header) override; int getLastChannel() const override; void send(uint32 b) override; protected: MidiDriver_Casio *_casioDriver; MusicType _midiType; }; const uint8 MidiDriver_Casio::SLAP_BASS_INSTRUMENT_MT540 = 0x14; const uint8 MidiDriver_Casio::SLAP_BASS_INSTRUMENT_CT460 = 0x1E; const uint8 MidiDriver_Casio::PATCH_RESOURCE_SIZE = 0xE9; bool MidiDriver_Casio::loadResource(const SciSpan &data, MusicType midiType) { if (midiType != MT_AUTO) { if (!(midiType == MT_MT540 || midiType == MT_CT460)) { error("CASIO: Unsupported music data type %i", midiType); } _midiType = midiType; } const uint32 size = data.size(); if (size != PATCH_RESOURCE_SIZE) { error("CASIO: Unsupported patch format (%u bytes)", size); return false; } uint32 dataIndex = 0; for (int i = 0; i < 0x60; i++) { _instrumentRemapping[i] = data.getUint8At(dataIndex++); _instrumentFixedNotes[i] = data.getUint8At(dataIndex++); } for (int i = 0; i < 0x29; i++) { _rhythmNoteRemapping[0x23 + i] = data.getUint8At(dataIndex++); } return true; } void MidiDriver_Casio::initTrack(SciSpan &header) { if (!_isOpen) return; Common::fill(_channelMap, _channelMap + ARRAYSIZE(_channelMap), -1); Common::fill(_rhythmChannel, _rhythmChannel + ARRAYSIZE(_rhythmChannel), false); Common::fill(_channelFixedNotes, _channelFixedNotes + ARRAYSIZE(_channelFixedNotes), 0); _rhythmChannelMapped = false; uint8 readPos = 0; uint8 caps = header.getInt8At(readPos++); if (caps != 0 && caps != 2) // Not a supported sound resource type. return; uint8 numChannels = 16; if (caps == 2) // Digital sound data on channel 15; don't use this channel. numChannels--; byte outputChannel = 0; for (int i = 0; i < numChannels; i++) { bool rhythmChannel = ((header.getInt8At(readPos++) & 0x80) > 0); bool deviceFlag = ((header.getInt8At(readPos++) & MidiPlayer_Casio::RESOURCE_HEADER_FLAG) > 0); if (!deviceFlag) // Data channel is not used for Casio devices. continue; if (rhythmChannel) { if (!_rhythmChannelMapped) { if (outputChannel == 4) { // The rhythm channel has already been assigned to a melodic // instrument. This means that more than 4 channels have // been flagged for Casio, which should not happen, but // clear the existing channel mapping just in case. for (int j = 0; j < numChannels; j++) { if (_channelMap[j] == 3) _channelMap[j] = -1; } } _channelMap[i] = 3; programChange(3, _midiType == MusicType::MT_MT540 ? RHYTHM_INSTRUMENT_MT540 : RHYTHM_INSTRUMENT_CT460, 0, false); _rhythmChannelMapped = true; } } else if (outputChannel < (_rhythmChannelMapped ? 3 : 4)) { _channelMap[i] = outputChannel++; } } } void MidiDriver_Casio::playSwitch(bool play) { _playSwitch = play; if (!_playSwitch) stopAllNotes(0xFF, 0xFF); } void MidiDriver_Casio::noteOn(byte outputChannel, byte note, byte velocity, int8 source) { if (velocity == 0) { // Note on with velocity 0 is a note off. noteOff(outputChannel, MIDI_COMMAND_NOTE_ON, note, velocity, source); return; } _mutex.lock(); // Check if there is an available voice for this note. int polyphonyCount = 0; for (int i = 0; i < ARRAYSIZE(_activeNotes); i++) { // Note that this check ignores sustained notes; original driver does // this too. if (_activeNotes[i].channel == outputChannel && !_activeNotes[i].sustained) { polyphonyCount++; } } if (polyphonyCount >= CASIO_CHANNEL_POLYPHONY[outputChannel]) { // Maximum channel polyphony has been reached. Don't play this note. _mutex.unlock(); return; } ::MidiDriver_Casio::noteOn(outputChannel, note, velocity, source); _mutex.unlock(); } void MidiDriver_Casio::programChange(byte outputChannel, byte patchId, int8 source, bool applyRemapping) { programChange(outputChannel, patchId, source, applyRemapping, true); } void MidiDriver_Casio::programChange(byte outputChannel, byte patchId, int8 source, bool applyRemapping, bool applyBassSwap) { if ((_rhythmChannelMapped && outputChannel == 3) || outputChannel >= 4) // Ignore program change on the rhythm channel or on unused channels. return; // Apply instrument mapping. byte mappedInstrument = mapInstrument(patchId, applyRemapping); // The Casio devices have an instrument (at 0x12 / 0x1C) which combines two // different bass instruments with a split note range. SCI assigns a // separate number to the slap bass instrument, which is on the "high" note // range (0x14 / 0x1E). Check for this number. if (mappedInstrument == (_deviceType == MT_MT540 ? SLAP_BASS_INSTRUMENT_MT540 : SLAP_BASS_INSTRUMENT_CT460)) { // The "high" split instrument (slap bass) has been selected. // Set the channel using this instrument so notes can be remapped to // the correct range. _highSplitInstOutputChannel = 2; // Output channel is set to 2 below. // Set the actual instrument number used by the Casio devices. mappedInstrument = (_deviceType == MT_MT540 ? BASS_INSTRUMENT_MT540 : BASS_INSTRUMENT_CT460); } else if (_highSplitInstOutputChannel == outputChannel) { // The instrument on this channel is changed from the "high" split // instrument to a different instrument. Reset the output channel // variable. _highSplitInstOutputChannel = -1; } // If the bass instrument is set on any channel, SCI always moves this // instrument to output channel 2. This is probably because the Casio // devices have a limited fixed polyphony on each channel: 6, 4, // 2 and 4 respectively. Moving the bass to channel 2 overcomes this // limitation somewhat, because this channel has the lowest polyphony and // the bass doesn't tend to play chords. // Check if the bass instrument is set to a channel other than 2, and // move it to channel 2 if necessary. if (applyBassSwap && mappedInstrument == (_deviceType == MT_MT540 ? BASS_INSTRUMENT_MT540 : BASS_INSTRUMENT_CT460) && outputChannel != 2) { _mutex.lock(); int currentDataChannel = -1; int currentTargetDataChannel = -1; for (int i = 0; i < MIDI_CHANNEL_COUNT; i++) { if (_channelMap[i] == outputChannel) { currentDataChannel = i; } else if (_channelMap[i] == 2) { currentTargetDataChannel = i; } } // These data channels should always be mapped, but check it just in case. if (currentDataChannel >= 0 && currentTargetDataChannel >= 0) { // The original drivers do not stop all notes before swapping channels. // This could potentially cause hanging notes, so this is done here to // be safe. Instead, the original drivers swap out the registered // active notes between the channels. This does not accomplish anything // other than putting the driver state out of sync with the device // state. stopAllNotes(source, outputChannel); stopAllNotes(source, 2); _channelMap[currentDataChannel] = 2; _channelMap[currentTargetDataChannel] = outputChannel; programChange(outputChannel, _instruments[2], source, applyRemapping, false); outputChannel = 2; } _mutex.unlock(); } // Register the new instrument. _instruments[outputChannel] = patchId; _channelFixedNotes[outputChannel] = (patchId < ARRAYSIZE(_instrumentFixedNotes) ? _instrumentFixedNotes[patchId] : 0); _rhythmChannel[outputChannel] = mappedInstrument == (_deviceType == MT_MT540 ? RHYTHM_INSTRUMENT_MT540 : RHYTHM_INSTRUMENT_CT460); _driver->send(MIDI_COMMAND_PROGRAM_CHANGE | outputChannel | (mappedInstrument << 8)); } int8 MidiDriver_Casio::mapSourceChannel(uint8 source, uint8 dataChannel) { // Only source 0 is used by this driver. return _channelMap[dataChannel]; } byte MidiDriver_Casio::mapInstrument(byte program, bool applyRemapping) { byte mappedInstrument = ::MidiDriver_Casio::mapInstrument(program, applyRemapping); if (applyRemapping) { // Correct remapping of the extra SCI slap bass instrument. if (_midiType == MT_MT540 && _deviceType == MT_CT460 && mappedInstrument == INSTRUMENT_REMAPPING_MT540_TO_CT460[SLAP_BASS_INSTRUMENT_MT540]) { // For the MT-540, SCI uses 0x14 as the slap bass instrument, which // actually is the honky-tonk piano. If the instrument has been mapped // to the CT-460 honky-tonk piano, correct it to the CT-460 SCI slap // bass. mappedInstrument = SLAP_BASS_INSTRUMENT_CT460; } else if (_midiType == MT_CT460 && _deviceType == MT_MT540 && mappedInstrument == SLAP_BASS_INSTRUMENT_CT460) { // For the CT-460, SCI uses 0x1E as the slap bass instrument, which // is unused, so it will not be remapped. Manually remap it here to // the MT-540 SCI slap bass instrument. mappedInstrument = SLAP_BASS_INSTRUMENT_MT540; } } return mappedInstrument; } int8 MidiDriver_Casio::mapNote(byte outputChannel, byte note) { if (!isRhythmChannel(outputChannel) && outputChannel < 4) { if (_highSplitInstOutputChannel == outputChannel) { // The slap bass instrument has been set on this output channel. // Transpose the note up to the range used by this instrument. byte transposedNote = note + 0x18; if (transposedNote < 0x3C) transposedNote += 0xC; return transposedNote; } // Check if the MIDI event note should be replaced by a fixed note. byte fixedNote = _channelFixedNotes[outputChannel]; return fixedNote > 0 ? fixedNote : note; } // Apply rhythm note mapping. return ::MidiDriver_Casio::mapNote(outputChannel, note); } bool MidiDriver_Casio::isRhythmChannel(uint8 outputChannel) { // SCI only uses channel 3 as the rhythm channel. If the drum instrument is // set on a different channel, it is for a sound effect and rhythm note // remapping should not be applied. return _rhythmChannelMapped && outputChannel == 3; } byte MidiDriver_Casio::calculateVelocity(int8 source, byte velocity) { if (!_playSwitch) return 0; return ::MidiDriver_Casio::calculateVelocity(source, velocity); } const uint8 MidiPlayer_Casio::RESOURCE_HEADER_FLAG = 0x08; const byte MidiPlayer_Casio::PATCH_RESOURCE_MT540 = 4; const byte MidiPlayer_Casio::PATCH_RESOURCE_CT460 = 7; MidiPlayer_Casio::MidiPlayer_Casio(SciVersion soundVersion, MusicType midiType) : MidiPlayer(soundVersion) { _casioDriver = new MidiDriver_Casio(midiType); _driver = _casioDriver; _midiType = midiType; } MidiPlayer_Casio::~MidiPlayer_Casio() { delete _casioDriver; _casioDriver = nullptr; _driver = nullptr; } int MidiPlayer_Casio::open(ResourceManager* resMan) { if (_version < SCI_VERSION_0_LATE || _version > SCI_VERSION_01) { warning("CASIO: Unsupported SCI version"); return -1; } assert(resMan != nullptr); // WORKAROUND The Casio devices have a bass instrument which combines two // instruments on different note ranges. To make the second instrument // (slap bass) selectable, SCI assigns a new instrument number to this // instrument. On the MT-540 Sierra used instrument 0x14 (20), probably // because they thought this was the first unused instrument number. // However, besides the 20 instruments selectable on the keyboard, the // device has 10 more instruments which can only be selected via MIDI. // The result of this is that the instrument which actually uses number // 0x14 on the MT-540 (honky-tonk piano) cannot be used by the instrument // mapping. Sierra worked around this by using the normal piano instead of // the honky-tonk piano for the MT-540. This affects at least Hoyle 1. // The CT-460 and CSM-1 are not affected by this issue because Sierra used // instrument 0x1E (30) as the slap bass instrument. To fix this problem, // the CT-460 instrument mapping is also used for the MT-540, with the // output instruments remapped to their MT-540 equivalents. // Load the CT-460 patch resource. int patchResource = PATCH_RESOURCE_CT460; _midiType = MusicType::MT_CT460; Resource *res = resMan->findResource(ResourceId(kResourceTypePatch, patchResource), false); bool ok = false; if (res) { ok = _casioDriver->loadResource(*res, _midiType); } if (!ok) { // CT-460 patch resource not present. Fall back to the MT-540 resource. MusicType altMidiType = MT_MT540; int altPatchResource = PATCH_RESOURCE_CT460; warning("CASIO: Failed to load patch.00%i - falling back to patch.00%i", patchResource, altPatchResource); res = resMan->findResource(ResourceId(kResourceTypePatch, altPatchResource), false); if (res) { ok = _casioDriver->loadResource(*res, altMidiType); } if (!ok) { warning("CASIO: Failed to load fallback patch.00%i", altPatchResource); return -1; } _midiType = altMidiType; } return _casioDriver->open(); } void MidiPlayer_Casio::close() { _driver->close(); } byte MidiPlayer_Casio::getPlayId() const { return RESOURCE_HEADER_FLAG; } int MidiPlayer_Casio::getPolyphony() const { return 16; } bool MidiPlayer_Casio::hasRhythmChannel() const { // Only use the rhythm channel if it has the Casio flag set. return false; } void MidiPlayer_Casio::setVolume(byte volume) { _casioDriver->setSourceVolume(0, volume); } void MidiPlayer_Casio::playSwitch(bool play) { _casioDriver->playSwitch(play); } void MidiPlayer_Casio::initTrack(SciSpan &header) { _casioDriver->initTrack(header); } int MidiPlayer_Casio::getLastChannel() const { // Not relevant for SCI0. return 15; } void MidiPlayer_Casio::send(uint32 b) { _driver->send(0, b); } MidiPlayer *MidiPlayer_Casio_create(SciVersion version, MusicType midiType) { return new MidiPlayer_Casio(version, midiType); } } // End of namespace Sci