[Scummvm-git-logs] scummvm master -> 332a428191a3bd4e5bfa675570c2b7768413c133
NMIError
noreply at scummvm.org
Wed Jan 18 20:43:44 UTC 2023
This automated email contains information about 3 new commits which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .
Summary:
abdc5b0fb7 AUDIO: Casio MIDI driver enhancements and fixes
f376d56d19 SCI: Add support for Casio MT-540, CT-460 and CSM-1
332a428191 NEWS: Add SCI Casio MIDI support
Commit: abdc5b0fb73910bda1bdadd9d589636d368101b1
https://github.com/scummvm/scummvm/commit/abdc5b0fb73910bda1bdadd9d589636d368101b1
Author: Coen Rampen (crampen at gmail.com)
Date: 2023-01-18T21:39:29+01:00
Commit Message:
AUDIO: Casio MIDI driver enhancements and fixes
- Add support for sustain controller
- Correct MT-540 <> CT-460/CSM-1 instrument remapping
- Simplify volume control (Casio devices do not support note velocity)
Changed paths:
audio/casio.cpp
audio/casio.h
diff --git a/audio/casio.cpp b/audio/casio.cpp
index b084d2c5195..178eb0cd002 100644
--- a/audio/casio.cpp
+++ b/audio/casio.cpp
@@ -31,29 +31,36 @@ void MidiDriver_Casio::ActiveNote::clear() {
source = 0x7F;
channel = 0xFF;
note = 0xFF;
+ sustained = false;
}
-const uint8 MidiDriver_Casio::INSTRUMENT_REMAPPING_CT460_TO_MT540[30] {
- 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x0E, 0x0A, 0x07,
- 0x09, 0x1B, 0x0F, 0x10, 0x11, 0x14, 0x08, 0x15, 0x0B, 0x0C,
- 0x0D, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1C, 0x1D, 0x12, 0x13
+const int MidiDriver_Casio::CASIO_CHANNEL_POLYPHONY[] = { 6, 4, 2, 4 };
+
+const uint8 MidiDriver_Casio::INSTRUMENT_REMAPPING_CT460_TO_MT540[] {
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x10, 0x0C, 0x07,
+ 0x0B, 0x1B, 0x11, 0x08, 0x09, 0x14, 0x0A, 0x15, 0x0D, 0x0E,
+ 0x0F, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1C, 0x1D, 0x12, 0x13
};
-const uint8 MidiDriver_Casio::INSTRUMENT_REMAPPING_MT540_TO_CT460[30] {
- 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x09, 0x10, 0x0A,
- 0x08, 0x12, 0x13, 0x14, 0x07, 0x0C, 0x0D, 0x0E, 0x1C, 0x1D,
+const uint8 MidiDriver_Casio::INSTRUMENT_REMAPPING_MT540_TO_CT460[] {
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x09, 0x0D, 0x0E,
+ 0x10, 0x0A, 0x08, 0x12, 0x13, 0x14, 0x07, 0x0C, 0x1C, 0x1D,
0x0F, 0x11, 0x15, 0x16, 0x17, 0x18, 0x19, 0x0B, 0x1A, 0x1B
};
-const uint8 MidiDriver_Casio::RHYTHM_INSTRUMENT_MT540 = 0x10;
+const uint8 MidiDriver_Casio::RHYTHM_INSTRUMENT_MT540 = 0x08;
const uint8 MidiDriver_Casio::RHYTHM_INSTRUMENT_CT460 = 0x0D;
+const uint8 MidiDriver_Casio::BASS_INSTRUMENT_MT540 = 0x12;
+const uint8 MidiDriver_Casio::BASS_INSTRUMENT_CT460 = 0x1C;
MidiDriver_Casio::MidiDriver_Casio(MusicType midiType) : _midiType(midiType), _driver(nullptr),
- _deviceType(MT_CT460), _isOpen(false), _rhythmNoteRemapping(nullptr) {
+ _deviceType(MT_CT460), _isOpen(false), _rhythmNoteRemapping(nullptr), _sendUntrackedNoteOff(true) {
if (!(_midiType == MT_MT540 || _midiType == MT_CT460)) {
- error("MidiDriver_Casio - Unsupported music data type %i", midiType);
+ error("MidiDriver_Casio - Unsupported music data type %i", _midiType);
}
Common::fill(_instruments, _instruments + ARRAYSIZE(_instruments), 0);
+ Common::fill(_rhythmChannel, _rhythmChannel + ARRAYSIZE(_rhythmChannel), false);
+ Common::fill(_sustain, _sustain + ARRAYSIZE(_sustain), false);
}
MidiDriver_Casio::~MidiDriver_Casio() {
@@ -128,6 +135,7 @@ void MidiDriver_Casio::close() {
if (_driver && _isOpen) {
stopAllNotes();
+ _driver->setTimerCallback(nullptr, nullptr);
_driver->close();
_isOpen = false;
}
@@ -181,6 +189,9 @@ void MidiDriver_Casio::processEvent(int8 source, uint32 b, uint8 outputChannel)
case MIDI_COMMAND_PROGRAM_CHANGE:
programChange(outputChannel, op1, source);
break;
+ case MIDI_COMMAND_CONTROL_CHANGE:
+ controlChange(outputChannel, op1, op2, source);
+ break;
default:
warning("MidiDriver_Casio::processEvent - Received unsupported event %02x", command);
break;
@@ -197,16 +208,25 @@ void MidiDriver_Casio::noteOff(byte outputChannel, byte command, byte note, byte
_mutex.lock();
// Remove this note from the active note registry.
+ bool foundActiveNote = false;
for (int i = 0; i < ARRAYSIZE(_activeNotes); i++) {
if (_activeNotes[i].channel == outputChannel && _activeNotes[i].note == mappedNote &&
- _activeNotes[i].source == source) {
- _activeNotes[i].clear();
+ _activeNotes[i].source == source && !_activeNotes[i].sustained) {
+ foundActiveNote = true;
+ if (outputChannel < 4 && _sustain[outputChannel]) {
+ _activeNotes[i].sustained = true;
+ } else {
+ _activeNotes[i].clear();
+ }
break;
}
}
_mutex.unlock();
+ if (!foundActiveNote && !_sendUntrackedNoteOff)
+ return;
+
_driver->send(command | outputChannel, mappedNote, velocity);
}
@@ -257,9 +277,12 @@ int8 MidiDriver_Casio::mapNote(byte outputChannel, byte note) {
byte MidiDriver_Casio::calculateVelocity(int8 source, byte velocity) {
byte calculatedVelocity = velocity;
// Apply volume settings to velocity.
+ // The Casio devices do not apply note velocity, so the only volume control
+ // possible is setting the velocity to 0 if one of the volume settings is 0.
if (source >= 0) {
// Scale to source volume.
- calculatedVelocity = (velocity * _sources[source].volume) / _sources[source].neutralVolume;
+ if (_sources[source].volume == 0)
+ calculatedVelocity = 0;
}
if (_userVolumeScaling) {
if (_userMute) {
@@ -267,28 +290,34 @@ byte MidiDriver_Casio::calculateVelocity(int8 source, byte velocity) {
} else {
// Scale to user volume.
uint16 userVolume = _sources[source].type == SOURCE_TYPE_SFX ? _userSfxVolume : _userMusicVolume;
- calculatedVelocity = (calculatedVelocity * userVolume) >> 8;
+ if (userVolume == 0)
+ calculatedVelocity = 0;
}
}
- // Source volume scaling might clip volume, so reduce to maximum,
- return MIN(calculatedVelocity, static_cast<byte>(0x7F));
+
+ return calculatedVelocity;
}
-void MidiDriver_Casio::programChange(byte outputChannel, byte patchId, int8 source) {
+void MidiDriver_Casio::programChange(byte outputChannel, byte patchId, int8 source, bool applyRemapping) {
if (outputChannel < 4)
// Register the new instrument.
_instruments[outputChannel] = patchId;
// Apply instrument mapping.
- byte mappedInstrument = mapInstrument(patchId);
+ byte mappedInstrument = mapInstrument(patchId, applyRemapping);
+
+ if (outputChannel < 4) {
+ _rhythmChannel[outputChannel] =
+ mappedInstrument == (_deviceType == MT_MT540 ? RHYTHM_INSTRUMENT_MT540 : RHYTHM_INSTRUMENT_CT460);
+ }
_driver->send(MIDI_COMMAND_PROGRAM_CHANGE | outputChannel | (mappedInstrument << 8));
}
-byte MidiDriver_Casio::mapInstrument(byte program) {
+byte MidiDriver_Casio::mapInstrument(byte program, bool applyRemapping) {
byte mappedInstrument = program;
- if (_instrumentRemapping)
+ if (applyRemapping && _instrumentRemapping)
// Apply custom instrument mapping.
mappedInstrument = _instrumentRemapping[program];
@@ -304,15 +333,39 @@ byte MidiDriver_Casio::mapInstrument(byte program) {
return mappedInstrument;
}
+void MidiDriver_Casio::controlChange(byte outputChannel, byte controllerNumber, byte controllerValue, int8 source) {
+ if (outputChannel >= 4)
+ return;
+
+ if (controllerNumber == MIDI_CONTROLLER_SUSTAIN) {
+ _sustain[outputChannel] = controllerValue >= 0x40;
+
+ if (!_sustain[outputChannel]) {
+ // Remove sustained notes if sustain is turned off.
+
+ _mutex.lock();
+
+ for (int i = 0; i < ARRAYSIZE(_activeNotes); i++) {
+ if (_activeNotes[i].channel == outputChannel && _activeNotes[i].sustained) {
+ _activeNotes[i].clear();
+ }
+ }
+
+ _mutex.unlock();
+ }
+
+ _driver->send(MIDI_COMMAND_CONTROL_CHANGE | outputChannel | (controllerNumber << 8) | (controllerValue << 16));
+ } else {
+ // No other controllers are supported.
+ //warning("MidiDriver_Casio::controlChange - Received event for unsupported controller %02x", controllerNumber);
+ }
+}
+
bool MidiDriver_Casio::isRhythmChannel(uint8 outputChannel) {
if (outputChannel >= 4)
return false;
- // Check if the current instrument on the channel is the rhythm instrument.
- byte currentInstrument = mapInstrument(_instruments[outputChannel]);
- byte rhythmInstrument = _deviceType == MT_MT540 ? RHYTHM_INSTRUMENT_MT540 : RHYTHM_INSTRUMENT_CT460;
-
- return currentInstrument == rhythmInstrument;
+ return _rhythmChannel[outputChannel];
}
void MidiDriver_Casio::stopAllNotes(bool stopSustainedNotes) {
@@ -322,12 +375,20 @@ void MidiDriver_Casio::stopAllNotes(bool stopSustainedNotes) {
void MidiDriver_Casio::stopAllNotes(uint8 source, uint8 channel) {
_mutex.lock();
+ // Turn off sustain.
+ for (int i = 0; i < 4; i++) {
+ if (channel == 0xFF || i == channel) {
+ controlChange(i, MIDI_CONTROLLER_SUSTAIN, 0, source > 127 ? -1 : source);
+ }
+ }
+
// Send note off events for all notes in the active note registry for this
// source and channel.
for (int i = 0; i < ARRAYSIZE(_activeNotes); i++) {
if (_activeNotes[i].note != 0xFF && (source == 0xFF || _activeNotes[i].source == source) &&
(channel == 0xFF || _activeNotes[i].channel == channel)) {
- noteOff(_activeNotes[i].channel, MIDI_COMMAND_NOTE_OFF, _activeNotes[i].note, 0, _activeNotes[i].source);
+ _driver->send(MIDI_COMMAND_NOTE_OFF | _activeNotes[i].channel, _activeNotes[i].note, 0);
+ _activeNotes[i].clear();
}
}
diff --git a/audio/casio.h b/audio/casio.h
index 510ae07e6b4..674016c9956 100644
--- a/audio/casio.h
+++ b/audio/casio.h
@@ -65,6 +65,9 @@ protected:
// The MIDI note number of the playing note
// (0xFF if no note is tracked).
uint8 note;
+ // True if this note is sustained (turned off but held due to the
+ // sustain controller).
+ bool sustained;
ActiveNote();
@@ -73,6 +76,9 @@ protected:
};
public:
+ // The maximum polyphony for each output channel.
+ static const int CASIO_CHANNEL_POLYPHONY[4];
+
// Array for remapping instrument numbers from CT-460/CSM-1 to MT-540.
static const uint8 INSTRUMENT_REMAPPING_CT460_TO_MT540[30];
// Array for remapping instrument numbers from MT-540 to CT-460/CSM-1.
@@ -83,6 +89,12 @@ public:
// The instrument number used for rhythm sounds on the CT-460 and CSM-1.
static const uint8 RHYTHM_INSTRUMENT_CT460;
+ // The instrument number used for the bass instruments on the MT-540.
+ static const uint8 BASS_INSTRUMENT_MT540;
+ // The instrument number used for the bass instruments on the CT-460 and
+ // CSM-1.
+ static const uint8 BASS_INSTRUMENT_CT460;
+
/**
* Constructs a new Casio MidiDriver instance.
*
@@ -162,8 +174,19 @@ protected:
* @param outputChannel The MIDI channel on which the event should be sent.
* @param patchId The instrument that should be set.
* @param source The source sending the MIDI event.
+ * @param applyRemapping True if the instrument remapping
+ * (_instrumentRemapping) should be applied.
*/
- virtual void programChange(byte outputChannel, byte patchId, int8 source);
+ virtual void programChange(byte outputChannel, byte patchId, int8 source, bool applyRemapping = true);
+ /**
+ * Processes a MIDI control change event.
+ *
+ * @param outputChannel The MIDI channel on which the event should be sent.
+ * @param controllerNumber The controller for which the value should be set.
+ * @param controllerValue The controller value that should be set.
+ * @param source The source sending the MIDI event.
+ */
+ virtual void controlChange(byte outputChannel, byte controllerNumber, byte controllerValue, int8 source);
/**
* Maps the specified note to a different note according to the rhythm note
@@ -188,14 +211,17 @@ protected:
/**
* Maps the specified instrument to the instrument value that should be
* sent to the MIDI device. This applies the current instrument remapping
- * (if present) and maps MT-540 instruments to CT-460/CSM-1 instruments
- * (or the other way around) if necessary.
+ * (if present and if applyRemapping is specified) and maps MT-540
+ * instruments to CT-460/CSM-1 instruments (or the other way around) if
+ * necessary.
*
* @param program The instrument that should be mapped.
+ * @param applyRemapping True if the instrument remapping
+ * (_instrumentRemapping) should be applied.
* @return The mapped instrument, or the specified instrument if no mapping
* was necessary.
*/
- virtual byte mapInstrument(byte program);
+ virtual byte mapInstrument(byte program, bool applyRemapping = true);
/**
* Returns whether the specified MIDI channel is a rhythm channel. On the
* Casio devices, the rhythm channel is not fixed but is created by setting
@@ -205,7 +231,7 @@ protected:
* @return True if the specified channel is a rhythm channel, false
* otherwise.
*/
- bool isRhythmChannel(uint8 outputChannel);
+ virtual bool isRhythmChannel(uint8 outputChannel);
// This implementation does nothing, because source volume is applied to
// note velocity and cannot be applied immediately.
void applySourceVolume(uint8 source) override;
@@ -225,14 +251,27 @@ protected:
// instrument number as specified in the program change events (before
// remapping is applied).
byte _instruments[4];
+ // Indicates if each output channel is currently a rhythm channel (i.e. it
+ // has the rhythm instrument set).
+ bool _rhythmChannel[4];
// Tracks the notes currently active on the MIDI device.
ActiveNote _activeNotes[32];
+ // Tracks the sustain controller status of the output channels.
+ bool _sustain[4];
// Optional remapping for rhythm notes. Should point to a 128 byte array
// which maps the rhythm note numbers in the input MIDI data to the rhythm
// note numbers used by the output device.
byte *_rhythmNoteRemapping;
+ // If true the driver will send note off events for notes which are not in
+ // the active note registry. Typically this should be true to prevent
+ // hanging notes in case there are more active notes than can be stored in
+ // the active note registry. Can be set to false if these note offs are not
+ // desirable, f.e. because the driver will be receiving note off events
+ // without corresponding note on events.
+ bool _sendUntrackedNoteOff;
+
// Mutex for operations on active notes.
Common::Mutex _mutex;
Commit: f376d56d198d00dd4dffa8f9a839f7eeef91cc7a
https://github.com/scummvm/scummvm/commit/f376d56d198d00dd4dffa8f9a839f7eeef91cc7a
Author: Coen Rampen (crampen at gmail.com)
Date: 2023-01-18T21:39:29+01:00
Commit Message:
SCI: Add support for Casio MT-540, CT-460 and CSM-1
This adds support for the Casio MIDI devices originally supported by several
SCI0 games:
- Space Quest III
- Hoyle's Book Of Games I
- Quest For Glory I
- Leisure Suit Larry III
- The Colonel's Bequest
- Codename: Iceman
- Conquests Of Camelot
Changed paths:
A engines/sci/sound/drivers/casio.cpp
engines/sci/detection.h
engines/sci/detection_options.h
engines/sci/module.mk
engines/sci/sound/drivers/mididriver.h
engines/sci/sound/music.cpp
diff --git a/engines/sci/detection.h b/engines/sci/detection.h
index 14efa8f6638..e6dc9ead709 100644
--- a/engines/sci/detection.h
+++ b/engines/sci/detection.h
@@ -150,7 +150,9 @@ enum SciVersion {
enum kMidiMode {
kMidiModeStandard,
kMidiModeFB01,
- kMidiModeD110
+ kMidiModeD110,
+ kMidiModeMT540,
+ kMidiModeCT460
};
} // End of namespace Sci
diff --git a/engines/sci/detection_options.h b/engines/sci/detection_options.h
index 01d4a50fa36..d03dff624b7 100644
--- a/engines/sci/detection_options.h
+++ b/engines/sci/detection_options.h
@@ -251,6 +251,14 @@ const PopUpOptionsMap popUpOptionsList[] = {
_s("Yamaha FB-01"),
kMidiModeFB01
},
+ {
+ _s("Casio MT-540"),
+ kMidiModeMT540
+ },
+ {
+ _s("Casio CT-460 / CSM-1"),
+ kMidiModeCT460
+ },
POPUP_OPTIONS_ITEMS_TERMINATOR
}
},
diff --git a/engines/sci/module.mk b/engines/sci/module.mk
index d72f06bee67..9898f653b64 100644
--- a/engines/sci/module.mk
+++ b/engines/sci/module.mk
@@ -79,6 +79,7 @@ MODULE_OBJS := \
sound/drivers/adlib.o \
sound/drivers/amigamac0.o \
sound/drivers/amigamac1.o \
+ sound/drivers/casio.o \
sound/drivers/cms.o \
sound/drivers/fb01.o \
sound/drivers/fmtowns.o \
diff --git a/engines/sci/sound/drivers/casio.cpp b/engines/sci/sound/drivers/casio.cpp
new file mode 100644
index 00000000000..b350bdd7968
--- /dev/null
+++ b/engines/sci/sound/drivers/casio.cpp
@@ -0,0 +1,492 @@
+/* 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#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<const byte> &data, MusicType midiType = MT_AUTO);
+ void initTrack(SciSpan<const byte> &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<const byte> &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<const byte> &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<const byte> &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();
+
+ // 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);
+
+ 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;
+ }
+ }
+ _channelMap[currentDataChannel] = 2;
+ _channelMap[currentTargetDataChannel] = outputChannel;
+
+ programChange(outputChannel, _instruments[2], source, applyRemapping, false);
+
+ _mutex.unlock();
+
+ outputChannel = 2;
+ }
+
+ // 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<const byte> &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
diff --git a/engines/sci/sound/drivers/mididriver.h b/engines/sci/sound/drivers/mididriver.h
index 0009398f2d6..5f97852b16d 100644
--- a/engines/sci/sound/drivers/mididriver.h
+++ b/engines/sci/sound/drivers/mididriver.h
@@ -147,6 +147,7 @@ extern MidiPlayer *MidiPlayer_CMS_create(SciVersion version);
extern MidiPlayer *MidiPlayer_MacSci0_create(SciVersion version);
extern MidiPlayer *MidiPlayer_Midi_create(SciVersion version);
extern MidiPlayer *MidiPlayer_Fb01_create(SciVersion version);
+extern MidiPlayer *MidiPlayer_Casio_create(SciVersion version, MusicType midiType);
extern MidiPlayer *MidiPlayer_FMTowns_create(SciVersion version);
extern MidiPlayer *MidiPlayer_PC9801_create(SciVersion version);
diff --git a/engines/sci/sound/music.cpp b/engines/sci/sound/music.cpp
index 9caeb4888d2..1b703d1ef9f 100644
--- a/engines/sci/sound/music.cpp
+++ b/engines/sci/sound/music.cpp
@@ -137,9 +137,15 @@ void SciMusic::init() {
_pMidiDrv = MidiPlayer_PC9801_create(_soundVersion);
break;
default:
- if (ConfMan.getInt("midi_mode") == kMidiModeFB01
+ int midiMode;
+ midiMode = ConfMan.getInt("midi_mode");
+ if (midiMode == kMidiModeFB01
|| (ConfMan.hasKey("native_fb01") && ConfMan.getBool("native_fb01")))
_pMidiDrv = MidiPlayer_Fb01_create(_soundVersion);
+ else if (midiMode == kMidiModeMT540)
+ _pMidiDrv = MidiPlayer_Casio_create(_soundVersion, MusicType::MT_MT540);
+ else if (midiMode == kMidiModeCT460)
+ _pMidiDrv = MidiPlayer_Casio_create(_soundVersion, MusicType::MT_CT460);
else
_pMidiDrv = MidiPlayer_Midi_create(_soundVersion);
}
Commit: 332a428191a3bd4e5bfa675570c2b7768413c133
https://github.com/scummvm/scummvm/commit/332a428191a3bd4e5bfa675570c2b7768413c133
Author: Coen Rampen (crampen at gmail.com)
Date: 2023-01-18T21:39:29+01:00
Commit Message:
NEWS: Add SCI Casio MIDI support
Changed paths:
NEWS.md
diff --git a/NEWS.md b/NEWS.md
index f036e79f328..b807cf614b4 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -92,6 +92,8 @@ For a more comprehensive changelog of the latest experimental code, see:
SCI:
- Improved text rendering for Macintosh titles.
+ - Added support for Casio MT-540, CT-460 and CSM-1 MIDI devices for the SCI0
+ games that originally supported it.
SCUMM:
- Added support for CGA, CGA Composite, CGA black & white and Hercules modes
More information about the Scummvm-git-logs
mailing list