File: casio.cpp

package info (click to toggle)
scummvm 2.9.1%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 450,580 kB
  • sloc: cpp: 4,299,825; asm: 28,322; python: 12,901; sh: 11,302; java: 9,289; xml: 7,895; perl: 2,639; ansic: 2,465; yacc: 1,670; javascript: 1,020; makefile: 933; lex: 578; awk: 275; objc: 82; sed: 11; php: 1
file content (496 lines) | stat: -rw-r--r-- 17,561 bytes parent folder | download | duplicates (2)
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