1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496
|
/* 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();
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<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
|