scummvm/audio/miles.h
NMIError 2f4937bd6d MIDI: Fix XMIDI SysEx Final Data controller
XMIDI defines controllers which allow the MIDI data to send controller changes
that build up SysEx messages. The last SysEx data byte is specified using the
Final Data controller, which should append the final byte to the SysEx message
and send it to the MIDI device. The old implementation did not append the last
byte. Additionally, when increasing the memory address for the SysEx, it did
not take into account that the MIDI bytes are 7 bit.

This commit fixes these issues. This restores a missing data byte in a SySex in
the MIDI initialization of The 7th Guest.
2020-07-25 00:35:47 +02:00

426 lines
14 KiB
C++

/* 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.
*
*/
#ifndef AUDIO_MILES_MIDIDRIVER_H
#define AUDIO_MILES_MIDIDRIVER_H
#include "audio/mididrv.h"
#include "common/error.h"
#include "common/mutex.h"
#include "common/stream.h"
namespace Audio {
#define MILES_MIDI_CHANNEL_COUNT 16
#define MILES_RHYTHM_CHANNEL 9
// Miles Audio supported controllers for control change messages
#define MILES_CONTROLLER_SELECT_PATCH_BANK 114
#define MILES_CONTROLLER_PROTECT_VOICE 112
#define MILES_CONTROLLER_PROTECT_TIMBRE 113
#define MILES_CONTROLLER_LOCK_CHANNEL 110
#define MILES_CONTROLLER_PROTECT_CHANNEL 111
#define MILES_CONTROLLER_BANK_SELECT_MSB 0
#define MILES_CONTROLLER_BANK_SELECT_LSB 32
#define MILES_CONTROLLER_MODULATION 1
#define MILES_CONTROLLER_VOLUME 7
#define MILES_CONTROLLER_EXPRESSION 11
#define MILES_CONTROLLER_PANNING 10
#define MILES_CONTROLLER_SUSTAIN 64
#define MILES_CONTROLLER_PITCH_RANGE 6
#define MILES_CONTROLLER_RESET_ALL 121
#define MILES_CONTROLLER_ALL_NOTES_OFF 123
#define MILES_CONTROLLER_OMNI_ON 124
#define MILES_CONTROLLER_OMNI_OFF 125
#define MILES_CONTROLLER_MONO_ON 126
#define MILES_CONTROLLER_POLY_ON 127
#define MILES_CONTROLLER_PATCH_REVERB 59
#define MILES_CONTROLLER_PATCH_BENDER 60
#define MILES_CONTROLLER_REVERB_MODE 61
#define MILES_CONTROLLER_REVERB_TIME 62
#define MILES_CONTROLLER_REVERB_LEVEL 63
#define MILES_CONTROLLER_RHYTHM_KEY_TIMBRE 58
// 3 SysEx controllers, each range 5
// 32-36 for 1st queue
// 37-41 for 2nd queue
// 42-46 for 3rd queue
#define MILES_CONTROLLER_SYSEX_RANGE_BEGIN 32
#define MILES_CONTROLLER_SYSEX_RANGE_END 46
#define MILES_CONTROLLER_SYSEX_QUEUE_COUNT 3
#define MILES_CONTROLLER_SYSEX_QUEUE_SIZE 32
#define MILES_CONTROLLER_SYSEX_COMMAND_ADDRESS1 0
#define MILES_CONTROLLER_SYSEX_COMMAND_ADDRESS2 1
#define MILES_CONTROLLER_SYSEX_COMMAND_ADDRESS3 2
#define MILES_CONTROLLER_SYSEX_COMMAND_DATA 3
#define MILES_CONTROLLER_SYSEX_COMMAND_FINAL_DATA 4
#define MILES_CONTROLLER_XMIDI_RANGE_BEGIN 110
#define MILES_CONTROLLER_XMIDI_RANGE_END 120
// Miles Audio actually used 0x4000, because they didn't shift the 2 bytes properly
#define MILES_PITCHBENDER_DEFAULT 0x2000
// The maximum number of sources sending MIDI data to this driver.
// This is based on the requirements of the KYRA engine, but can be increased if
// necessary.
#define MILES_MAXIMUM_SOURCES 4
// Maximum number of tracked active notes for the MT-32
// This is the maximum polyphony of the MT-32 plus some overhead (MIDI data may send
// more notes than the MT-32 can handle simultaneously).
#define MILES_MT32_ACTIVE_NOTES 48
// Maximum number of tracked active notes for GM
// This is the maximum polyphony of the SC-88 and AWE64 plus some overhead (MIDI data
// may send more notes than the GM device can handle simultaneously).
#define MILES_GM_ACTIVE_NOTES 96
#define MILES_MT32_PATCHES_COUNT 128
#define MILES_MT32_CUSTOMTIMBRE_COUNT 64
#define MILES_MT32_PATCHDATA_COMMONPARAMETER_SIZE 14
#define MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE 58
#define MILES_MT32_PATCHDATA_PARTIALPARAMETERS_COUNT 4
#define MILES_MT32_PATCHDATA_TOTAL_SIZE (MILES_MT32_PATCHDATA_COMMONPARAMETER_SIZE + (MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE * MILES_MT32_PATCHDATA_PARTIALPARAMETERS_COUNT))
struct MilesMT32InstrumentEntry {
byte bankId;
byte patchId;
byte commonParameter[MILES_MT32_PATCHDATA_COMMONPARAMETER_SIZE + 1];
byte partialParameters[MILES_MT32_PATCHDATA_PARTIALPARAMETERS_COUNT][MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE + 1];
};
class MidiDriver_Miles_Midi : public MidiDriver {
public:
MidiDriver_Miles_Midi(MusicType midiType, MilesMT32InstrumentEntry *instrumentTablePtr, uint16 instrumentTableCount);
virtual ~MidiDriver_Miles_Midi();
public:
// MidiDriver
int open() override;
// Open the Miles driver using the specified MidiDriver instance.
int open(MidiDriver *driver, bool nativeMT32);
void close() override;
bool isOpen() const override { return _isOpen; }
using MidiDriver_BASE::send;
void send(uint32 b) override;
void send(int8 source, uint32 b) override;
void sysEx(const byte *msg, uint16 length) override;
uint16 sysExNoDelay(const byte *msg, uint16 length) override;
void metaEvent(int8 source, byte type, byte *data, uint16 length) override;
/**
* De-initialize a source. Call this after playing a track or sound effect using this source.
* This will unlock and unprotect channels used by this source and stop any active notes
* from this source.
* Automatically executed when an End Of Track meta event is received.
*/
void deinitSource(uint8 source);
/**
* Set the volume for this source. This will be used to scale the volume values in the MIDI
* data from this source. Expected volume values are 0 - 256.
* Note that source volume remains set for the source number even after deinitializing the
* source. If the same source numbers are consistently used for music and SFX sources, the
* source volume will only need to be set once.
*/
void setSourceVolume(uint8 source, uint16 volume);
/** Stops all notes currently playing on the MIDI device. */
void allNotesOff();
MidiChannel *allocateChannel() override {
if (_driver)
return _driver->allocateChannel();
return NULL;
}
MidiChannel *getPercussionChannel() override {
if (_driver)
return _driver->getPercussionChannel();
return NULL;
}
void setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) override {
if (_driver)
_driver->setTimerCallback(timer_param, timer_proc);
}
uint32 getBaseTempo() override {
if (_driver) {
return _driver->getBaseTempo();
}
return 1000000 / _baseFreq;
}
protected:
Common::Mutex _mutex;
MidiDriver *_driver;
bool _isOpen;
// The type of MIDI data supplied to the driver: MT-32 or General MIDI.
MusicType _midiType;
// True if the MIDI output is an MT-32 (hardware or 100% emulated),
// false if the MIDI output is a General MIDI device.
bool _nativeMT32;
// True if the General MIDI output supports Roland GS for improved MT-32 mapping.
bool _enableGS;
// Bitmask of the MIDI channels in use by the output device
uint16 _outputChannelMask;
int _baseFreq;
public:
void processXMIDITimbreChunk(const byte *timbreListPtr, uint32 timbreListSize);
private:
void initMidiDevice();
void MT32SysEx(const uint32 targetAddress, const byte *dataPtr);
uint32 calculateSysExTargetAddress(uint32 baseAddress, uint32 index);
void writeRhythmSetup(byte note, byte customTimbreId);
void writePatchTimbre(byte patchId, byte timbreGroup, byte timbreId);
void writePatchByte(byte patchId, byte index, byte patchValue);
void writeToSystemArea(byte index, byte value);
const MilesMT32InstrumentEntry *searchCustomInstrument(byte patchBank, byte patchId);
int16 searchCustomTimbre(byte patchBank, byte patchId);
void setupPatch(byte patchBank, byte patchId);
int16 installCustomTimbre(byte patchBank, byte patchId);
bool isOutputChannelUsed(uint8 outputChannel) { return _outputChannelMask & (1 << outputChannel); }
private:
/**
* This stores the values of the MIDI controllers for
* a MIDI channel. It is used to keep track of controller
* values while a channel is locked, so they can be
* restored when the channel is unlocked.
*/
struct MidiChannelControlData {
// The source that last sent an event to this channel
int8 source;
// True if the source volume has been applied to this channel
bool sourceVolumeApplied;
byte program;
uint16 pitchWheel;
byte modulation;
// The volume specified by the MIDI data
byte volume;
// The volume scaled using the source volume
byte scaledVolume;
byte panPosition;
byte expression;
bool sustain;
// Custom timbre data
byte currentPatchBank;
bool usingCustomTimbre;
byte currentCustomTimbreId;
MidiChannelControlData() : source(-1),
sourceVolumeApplied(false),
program(0),
pitchWheel(MILES_PITCHBENDER_DEFAULT),
modulation(0),
volume(0xFF),
scaledVolume(0x64),
panPosition(0x40),
expression(0x7F),
sustain(false),
currentPatchBank(0),
usingCustomTimbre(false),
currentCustomTimbreId(0) { }
};
struct MidiChannelEntry {
// True if this channel is locked. A locked channel will
// only accept MIDI messages from the source that locked it.
bool locked;
// The channel in the MIDI data of the lock source that
// is assigned to this locked output channel. This is a
// reverse lookup for MidiSource::channelMap.
// -1 if the channel is not locked.
int8 lockDataChannel;
// True if this channel is protected from locking.
// The channel can still be locked, but unprotected
// channels will be prioritized.
bool lockProtected;
// The source that protected this channel from locking.
// -1 if the channel is not protected.
int8 protectedSource;
// The number of notes currently active on the channel.
uint8 activeNotes;
// The MIDI controller values currently used by the channel.
MidiChannelControlData currentData;
// The MIDI controller values set by the sources which are
// not currently using the channel because it is locked.
// These values will be set on the channel when the channel
// is unlocked.
MidiChannelControlData unlockData;
MidiChannelEntry() : locked(false),
lockDataChannel(-1),
lockProtected(false),
protectedSource(-1),
activeNotes(0) { }
};
/**
* Send out a control change MIDI message using the specified data.
* @param controlData The new MIDI controller value will be set on this MidiChannelControlData
* @param sendMessage True if the message should be sent out to the device
*/
void controlChange(byte outputChannel, byte controllerNumber, byte controllerValue, int8 source, MidiChannelControlData &controlData, bool sendMessage);
/**
* Removes active notes from the active notes registration on the specified channel.
* @param sustainedNotes True if only sustained notes should be removed; otherwise only regular active notes will be removed
*/
void removeActiveNotes(uint8 outputChannel, bool sustainedNotes);
/**
* Find and lock an output channel and reserve it for the specified
* source. The output channel will be mapped to the specified data
* channel.
*/
void lockChannel(uint8 source, uint8 dataChannel);
/**
* Find an output channel to lock. This will be based on the number
* of active notes on the channels and whether the channel is
* protected or not.
* @param useProtectedChannels When true, protected channels are considered for locking
* @returns The output channel to lock, or -1 if no channel is available
*/
int8 findLockChannel(bool useProtectedChannels = false);
/**
* Unlock an output channel. This will stop all notes on the channel,
* restore the controller values and make it available to other sources.
*/
void unlockChannel(uint8 outputChannel);
/**
* Send a program change MIDI message using the specified data.
* @param controlData The new program value will be set on this MidiChannelControlData
* @param sendMessage True if the message should be sent out to the device
*/
void programChange(byte outputChannel, byte patchId, uint8 source, MidiChannelControlData &controlData, bool sendMessage);
void stopNotesOnChannel(uint8 outputChannelNumber);
struct MidiCustomTimbreEntry {
bool used;
bool protectionEnabled;
byte currentPatchBank;
byte currentPatchId;
uint32 lastUsedNoteCounter;
MidiCustomTimbreEntry() : used(false),
protectionEnabled(false),
currentPatchBank(0),
currentPatchId(0),
lastUsedNoteCounter(0) {}
};
struct MilesMT32SysExQueueEntry {
uint32 targetAddress;
byte dataPos;
byte data[MILES_CONTROLLER_SYSEX_QUEUE_SIZE + 1]; // 1 extra byte for terminator
MilesMT32SysExQueueEntry() : targetAddress(0),
dataPos(0) {
memset(data, 0, sizeof(data));
}
};
/**
* This stores data about a specific source of MIDI data.
*/
struct MidiSource {
// The source volume as set by ScummVM (music/SFX volume)
uint16 volume;
// The mapping of MIDI data channels to output channels
// for this source.
int8 channelMap[MILES_MIDI_CHANNEL_COUNT];
MidiSource() : volume(256) {
memset(channelMap, 0, sizeof(channelMap));
}
};
// stores information about all MIDI channels
MidiChannelEntry _midiChannels[MILES_MIDI_CHANNEL_COUNT];
// stores information about all custom timbres
MidiCustomTimbreEntry _customTimbres[MILES_MT32_CUSTOMTIMBRE_COUNT];
byte _patchesBank[MILES_MT32_PATCHES_COUNT];
// holds all instruments
MilesMT32InstrumentEntry *_instrumentTablePtr;
uint16 _instrumentTableCount;
uint32 _noteCounter; // used to figure out, which timbres are outdated
// SysEx Queues
MilesMT32SysExQueueEntry _sysExQueues[MILES_CONTROLLER_SYSEX_QUEUE_COUNT];
// MIDI sources sending messages to this driver.
MidiSource _sources[MILES_MAXIMUM_SOURCES];
struct ActiveNote {
int8 source;
uint8 channel;
uint8 note;
bool sustain;
ActiveNote() : source(0x7F),
channel(0xFF),
note(0xFF),
sustain(false) { }
};
// The maximum number of active notes that have to be tracked for this MIDI device.
uint8 _maximumActiveNotes;
// Tracks the notes being played by the MIDI device.
ActiveNote *_activeNotes;
};
extern MidiDriver *MidiDriver_Miles_AdLib_create(const Common::String &filenameAdLib, const Common::String &filenameOPL3, Common::SeekableReadStream *streamAdLib = nullptr, Common::SeekableReadStream *streamOPL3 = nullptr);
extern MidiDriver_Miles_Midi *MidiDriver_Miles_MT32_create(const Common::String &instrumentDataFilename);
extern MidiDriver_Miles_Midi *MidiDriver_Miles_MIDI_create(MusicType midiType, const Common::String &instrumentDataFilename);
extern void MidiDriver_Miles_MT32_processXMIDITimbreChunk(MidiDriver_BASE *driver, const byte *timbreListPtr, uint32 timbreListSize);
} // End of namespace Audio
#endif // AUDIO_MILES_MIDIDRIVER_H