mirror of
https://github.com/libretro/scummvm.git
synced 2024-11-27 03:10:37 +00:00
AUDIO: Remove the MT32 emulator
It's not used in ResidualVM and it's a pain to merge
This commit is contained in:
parent
84e62b6c8d
commit
e4a719d744
@ -1,83 +0,0 @@
|
||||
/* 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/stream.h"
|
||||
|
||||
namespace Audio {
|
||||
|
||||
#define MILES_MIDI_CHANNEL_COUNT 16
|
||||
|
||||
// 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_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_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_SEND 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
|
||||
|
||||
extern MidiDriver *MidiDriver_Miles_AdLib_create(const Common::String &filenameAdLib, const Common::String &filenameOPL3, Common::SeekableReadStream *streamAdLib = nullptr, Common::SeekableReadStream *streamOPL3 = nullptr);
|
||||
|
||||
extern MidiDriver *MidiDriver_Miles_MT32_create(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
|
File diff suppressed because it is too large
Load Diff
@ -1,912 +0,0 @@
|
||||
/* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "audio/miles.h"
|
||||
|
||||
#include "common/config-manager.h"
|
||||
#include "common/file.h"
|
||||
#include "common/mutex.h"
|
||||
#include "common/system.h"
|
||||
#include "common/textconsole.h"
|
||||
|
||||
namespace Audio {
|
||||
|
||||
// Miles Audio MT32 driver
|
||||
//
|
||||
|
||||
#define MILES_MT32_PATCHES_COUNT 128
|
||||
#define MILES_MT32_CUSTOMTIMBRE_COUNT 64
|
||||
|
||||
#define MILES_MT32_TIMBREBANK_STANDARD_ROLAND 0
|
||||
#define MILES_MT32_TIMBREBANK_MELODIC_MODULE 127
|
||||
|
||||
#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))
|
||||
|
||||
#define MILES_MT32_SYSEX_TERMINATOR 0xFF
|
||||
|
||||
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];
|
||||
};
|
||||
|
||||
const byte milesMT32SysExResetParameters[] = {
|
||||
0x01, MILES_MT32_SYSEX_TERMINATOR
|
||||
};
|
||||
|
||||
const byte milesMT32SysExChansSetup[] = {
|
||||
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, MILES_MT32_SYSEX_TERMINATOR
|
||||
};
|
||||
|
||||
const byte milesMT32SysExPartialReserveTable[] = {
|
||||
0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x04, MILES_MT32_SYSEX_TERMINATOR
|
||||
};
|
||||
|
||||
const byte milesMT32SysExInitReverb[] = {
|
||||
0x00, 0x03, 0x02, MILES_MT32_SYSEX_TERMINATOR // Reverb mode 0, reverb time 3, reverb level 2
|
||||
};
|
||||
|
||||
class MidiDriver_Miles_MT32 : public MidiDriver {
|
||||
public:
|
||||
MidiDriver_Miles_MT32(MilesMT32InstrumentEntry *instrumentTablePtr, uint16 instrumentTableCount);
|
||||
virtual ~MidiDriver_Miles_MT32();
|
||||
|
||||
// MidiDriver
|
||||
int open();
|
||||
void close();
|
||||
bool isOpen() const { return _isOpen; }
|
||||
|
||||
void send(uint32 b);
|
||||
|
||||
MidiChannel *allocateChannel() {
|
||||
if (_driver)
|
||||
return _driver->allocateChannel();
|
||||
return NULL;
|
||||
}
|
||||
MidiChannel *getPercussionChannel() {
|
||||
if (_driver)
|
||||
return _driver->getPercussionChannel();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) {
|
||||
if (_driver)
|
||||
_driver->setTimerCallback(timer_param, timer_proc);
|
||||
}
|
||||
|
||||
uint32 getBaseTempo() {
|
||||
if (_driver) {
|
||||
return _driver->getBaseTempo();
|
||||
}
|
||||
return 1000000 / _baseFreq;
|
||||
}
|
||||
|
||||
protected:
|
||||
Common::Mutex _mutex;
|
||||
MidiDriver *_driver;
|
||||
bool _MT32;
|
||||
bool _nativeMT32;
|
||||
|
||||
bool _isOpen;
|
||||
int _baseFreq;
|
||||
|
||||
public:
|
||||
void processXMIDITimbreChunk(const byte *timbreListPtr, uint32 timbreListSize);
|
||||
|
||||
private:
|
||||
void resetMT32();
|
||||
|
||||
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);
|
||||
|
||||
void controlChange(byte midiChannel, byte controllerNumber, byte controllerValue);
|
||||
void programChange(byte midiChannel, byte patchId);
|
||||
|
||||
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);
|
||||
|
||||
private:
|
||||
struct MidiChannelEntry {
|
||||
byte currentPatchBank;
|
||||
byte currentPatchId;
|
||||
|
||||
bool usingCustomTimbre;
|
||||
byte currentCustomTimbreId;
|
||||
|
||||
MidiChannelEntry() : currentPatchBank(0),
|
||||
currentPatchId(0),
|
||||
usingCustomTimbre(false),
|
||||
currentCustomTimbreId(0) { }
|
||||
};
|
||||
|
||||
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));
|
||||
}
|
||||
};
|
||||
|
||||
// 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];
|
||||
};
|
||||
|
||||
MidiDriver_Miles_MT32::MidiDriver_Miles_MT32(MilesMT32InstrumentEntry *instrumentTablePtr, uint16 instrumentTableCount) {
|
||||
_instrumentTablePtr = instrumentTablePtr;
|
||||
_instrumentTableCount = instrumentTableCount;
|
||||
|
||||
_driver = NULL;
|
||||
_isOpen = false;
|
||||
_MT32 = false;
|
||||
_nativeMT32 = false;
|
||||
_baseFreq = 250;
|
||||
|
||||
_noteCounter = 0;
|
||||
|
||||
memset(_patchesBank, 0, sizeof(_patchesBank));
|
||||
}
|
||||
|
||||
MidiDriver_Miles_MT32::~MidiDriver_Miles_MT32() {
|
||||
Common::StackLock lock(_mutex);
|
||||
if (_driver) {
|
||||
_driver->setTimerCallback(0, 0);
|
||||
_driver->close();
|
||||
delete _driver;
|
||||
}
|
||||
_driver = NULL;
|
||||
}
|
||||
|
||||
int MidiDriver_Miles_MT32::open() {
|
||||
assert(!_driver);
|
||||
|
||||
// Setup midi driver
|
||||
MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_MIDI | MDT_PREFER_MT32);
|
||||
MusicType musicType = MidiDriver::getMusicType(dev);
|
||||
|
||||
switch (musicType) {
|
||||
case MT_MT32:
|
||||
_nativeMT32 = true;
|
||||
break;
|
||||
case MT_GM:
|
||||
if (ConfMan.getBool("native_mt32")) {
|
||||
_nativeMT32 = true;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (!_nativeMT32) {
|
||||
error("MILES-MT32: non-mt32 currently not supported!");
|
||||
}
|
||||
|
||||
_driver = MidiDriver::createMidi(dev);
|
||||
if (!_driver)
|
||||
return 255;
|
||||
|
||||
if (_nativeMT32)
|
||||
_driver->property(MidiDriver::PROP_CHANNEL_MASK, 0x03FE);
|
||||
|
||||
int ret = _driver->open();
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (_nativeMT32) {
|
||||
_driver->sendMT32Reset();
|
||||
|
||||
resetMT32();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void MidiDriver_Miles_MT32::close() {
|
||||
if (_driver) {
|
||||
_driver->close();
|
||||
}
|
||||
}
|
||||
|
||||
void MidiDriver_Miles_MT32::resetMT32() {
|
||||
// reset all internal parameters / patches
|
||||
MT32SysEx(0x7F0000, milesMT32SysExResetParameters);
|
||||
|
||||
// init part/channel assignments
|
||||
MT32SysEx(0x10000D, milesMT32SysExChansSetup);
|
||||
|
||||
// partial reserve table
|
||||
MT32SysEx(0x100004, milesMT32SysExPartialReserveTable);
|
||||
|
||||
// init reverb
|
||||
MT32SysEx(0x100001, milesMT32SysExInitReverb);
|
||||
}
|
||||
|
||||
void MidiDriver_Miles_MT32::MT32SysEx(const uint32 targetAddress, const byte *dataPtr) {
|
||||
byte sysExMessage[270];
|
||||
uint16 sysExPos = 0;
|
||||
byte sysExByte = 0;
|
||||
uint16 sysExChecksum = 0;
|
||||
|
||||
memset(&sysExMessage, 0, sizeof(sysExMessage));
|
||||
|
||||
sysExMessage[0] = 0x41; // Roland
|
||||
sysExMessage[1] = 0x10;
|
||||
sysExMessage[2] = 0x16; // Model MT32
|
||||
sysExMessage[3] = 0x12; // Command DT1
|
||||
|
||||
sysExChecksum = 0;
|
||||
|
||||
sysExMessage[4] = (targetAddress >> 16) & 0xFF;
|
||||
sysExMessage[5] = (targetAddress >> 8) & 0xFF;
|
||||
sysExMessage[6] = targetAddress & 0xFF;
|
||||
|
||||
for (byte targetAddressByte = 4; targetAddressByte < 7; targetAddressByte++) {
|
||||
assert(sysExMessage[targetAddressByte] < 0x80); // security check
|
||||
sysExChecksum -= sysExMessage[targetAddressByte];
|
||||
}
|
||||
|
||||
sysExPos = 7;
|
||||
while (1) {
|
||||
sysExByte = *dataPtr++;
|
||||
if (sysExByte == MILES_MT32_SYSEX_TERMINATOR)
|
||||
break; // Message done
|
||||
|
||||
assert(sysExPos < sizeof(sysExMessage));
|
||||
assert(sysExByte < 0x80); // security check
|
||||
sysExMessage[sysExPos++] = sysExByte;
|
||||
sysExChecksum -= sysExByte;
|
||||
}
|
||||
|
||||
// Calculate checksum
|
||||
assert(sysExPos < sizeof(sysExMessage));
|
||||
sysExMessage[sysExPos++] = sysExChecksum & 0x7f;
|
||||
|
||||
// Send SysEx
|
||||
_driver->sysEx(sysExMessage, sysExPos);
|
||||
|
||||
// Wait the time it takes to send the SysEx data
|
||||
uint32 delay = (sysExPos + 2) * 1000 / 3125;
|
||||
|
||||
// Plus an additional delay for the MT-32 rev00
|
||||
if (_nativeMT32)
|
||||
delay += 40;
|
||||
|
||||
g_system->delayMillis(delay);
|
||||
}
|
||||
|
||||
// MIDI messages can be found at http://www.midi.org/techspecs/midimessages.php
|
||||
void MidiDriver_Miles_MT32::send(uint32 b) {
|
||||
byte command = b & 0xf0;
|
||||
byte midiChannel = b & 0xf;
|
||||
byte op1 = (b >> 8) & 0xff;
|
||||
byte op2 = (b >> 16) & 0xff;
|
||||
|
||||
switch (command) {
|
||||
case 0x80: // note off
|
||||
case 0x90: // note on
|
||||
case 0xa0: // Polyphonic key pressure (aftertouch)
|
||||
case 0xd0: // Channel pressure (aftertouch)
|
||||
case 0xe0: // pitch bend change
|
||||
_noteCounter++;
|
||||
if (_midiChannels[midiChannel].usingCustomTimbre) {
|
||||
// Remember that this timbre got used now
|
||||
_customTimbres[_midiChannels[midiChannel].currentCustomTimbreId].lastUsedNoteCounter = _noteCounter;
|
||||
}
|
||||
_driver->send(b);
|
||||
break;
|
||||
case 0xb0: // Control change
|
||||
controlChange(midiChannel, op1, op2);
|
||||
break;
|
||||
case 0xc0: // Program Change
|
||||
programChange(midiChannel, op1);
|
||||
break;
|
||||
case 0xf0: // SysEx
|
||||
warning("MILES-MT32: SysEx: %x", b);
|
||||
break;
|
||||
default:
|
||||
warning("MILES-MT32: Unknown event %02x", command);
|
||||
}
|
||||
}
|
||||
|
||||
void MidiDriver_Miles_MT32::controlChange(byte midiChannel, byte controllerNumber, byte controllerValue) {
|
||||
byte channelPatchId = 0;
|
||||
byte channelCustomTimbreId = 0;
|
||||
|
||||
switch (controllerNumber) {
|
||||
case MILES_CONTROLLER_SELECT_PATCH_BANK:
|
||||
_midiChannels[midiChannel].currentPatchBank = controllerValue;
|
||||
return;
|
||||
|
||||
case MILES_CONTROLLER_PATCH_REVERB:
|
||||
channelPatchId = _midiChannels[midiChannel].currentPatchId;
|
||||
|
||||
writePatchByte(channelPatchId, 6, controllerValue);
|
||||
_driver->send(0xC0 | midiChannel | (channelPatchId << 8)); // execute program change
|
||||
return;
|
||||
|
||||
case MILES_CONTROLLER_PATCH_BENDER:
|
||||
channelPatchId = _midiChannels[midiChannel].currentPatchId;
|
||||
|
||||
writePatchByte(channelPatchId, 4, controllerValue);
|
||||
_driver->send(0xC0 | midiChannel | (channelPatchId << 8)); // execute program change
|
||||
return;
|
||||
|
||||
case MILES_CONTROLLER_REVERB_MODE:
|
||||
writeToSystemArea(1, controllerValue);
|
||||
return;
|
||||
|
||||
case MILES_CONTROLLER_REVERB_TIME:
|
||||
writeToSystemArea(2, controllerValue);
|
||||
return;
|
||||
|
||||
case MILES_CONTROLLER_REVERB_LEVEL:
|
||||
writeToSystemArea(3, controllerValue);
|
||||
return;
|
||||
|
||||
case MILES_CONTROLLER_RHYTHM_KEY_TIMBRE:
|
||||
if (_midiChannels[midiChannel].usingCustomTimbre) {
|
||||
// custom timbre is set on current channel
|
||||
writeRhythmSetup(controllerValue, _midiChannels[midiChannel].currentCustomTimbreId);
|
||||
}
|
||||
return;
|
||||
|
||||
case MILES_CONTROLLER_PROTECT_TIMBRE:
|
||||
if (_midiChannels[midiChannel].usingCustomTimbre) {
|
||||
// custom timbre set on current channel
|
||||
channelCustomTimbreId = _midiChannels[midiChannel].currentCustomTimbreId;
|
||||
if (controllerValue >= 64) {
|
||||
// enable protection
|
||||
_customTimbres[channelCustomTimbreId].protectionEnabled = true;
|
||||
} else {
|
||||
// disable protection
|
||||
_customTimbres[channelCustomTimbreId].protectionEnabled = false;
|
||||
}
|
||||
}
|
||||
return;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if ((controllerNumber >= MILES_CONTROLLER_SYSEX_RANGE_BEGIN) && (controllerNumber <= MILES_CONTROLLER_SYSEX_RANGE_END)) {
|
||||
// send SysEx
|
||||
byte sysExQueueNr = 0;
|
||||
|
||||
// figure out which queue is accessed
|
||||
controllerNumber -= MILES_CONTROLLER_SYSEX_RANGE_BEGIN;
|
||||
while (controllerNumber > MILES_CONTROLLER_SYSEX_COMMAND_SEND) {
|
||||
sysExQueueNr++;
|
||||
controllerNumber -= (MILES_CONTROLLER_SYSEX_COMMAND_SEND + 1);
|
||||
}
|
||||
assert(sysExQueueNr < MILES_CONTROLLER_SYSEX_QUEUE_COUNT);
|
||||
|
||||
byte sysExPos = _sysExQueues[sysExQueueNr].dataPos;
|
||||
bool sysExSend = false;
|
||||
|
||||
switch(controllerNumber) {
|
||||
case MILES_CONTROLLER_SYSEX_COMMAND_ADDRESS1:
|
||||
_sysExQueues[sysExQueueNr].targetAddress &= 0x00FFFF;
|
||||
_sysExQueues[sysExQueueNr].targetAddress |= (controllerValue << 16);
|
||||
break;
|
||||
case MILES_CONTROLLER_SYSEX_COMMAND_ADDRESS2:
|
||||
_sysExQueues[sysExQueueNr].targetAddress &= 0xFF00FF;
|
||||
_sysExQueues[sysExQueueNr].targetAddress |= (controllerValue << 8);
|
||||
break;
|
||||
case MILES_CONTROLLER_SYSEX_COMMAND_ADDRESS3:
|
||||
_sysExQueues[sysExQueueNr].targetAddress &= 0xFFFF00;
|
||||
_sysExQueues[sysExQueueNr].targetAddress |= controllerValue;
|
||||
break;
|
||||
case MILES_CONTROLLER_SYSEX_COMMAND_DATA:
|
||||
if (sysExPos < MILES_CONTROLLER_SYSEX_QUEUE_SIZE) {
|
||||
// Space left? put current byte into queue
|
||||
_sysExQueues[sysExQueueNr].data[sysExPos] = controllerValue;
|
||||
sysExPos++;
|
||||
_sysExQueues[sysExQueueNr].dataPos = sysExPos;
|
||||
if (sysExPos >= MILES_CONTROLLER_SYSEX_QUEUE_SIZE) {
|
||||
// overflow? -> send it now
|
||||
sysExSend = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MILES_CONTROLLER_SYSEX_COMMAND_SEND:
|
||||
sysExSend = true;
|
||||
break;
|
||||
default:
|
||||
assert(0);
|
||||
}
|
||||
|
||||
if (sysExSend) {
|
||||
if (sysExPos > 0) {
|
||||
// data actually available? -> send it
|
||||
_sysExQueues[sysExQueueNr].data[sysExPos] = MILES_MT32_SYSEX_TERMINATOR; // put terminator
|
||||
|
||||
// Execute SysEx
|
||||
MT32SysEx(_sysExQueues[sysExQueueNr].targetAddress, _sysExQueues[sysExQueueNr].data);
|
||||
|
||||
// adjust target address to point at the end of the current data
|
||||
_sysExQueues[sysExQueueNr].targetAddress += sysExPos;
|
||||
// reset queue data buffer
|
||||
_sysExQueues[sysExQueueNr].dataPos = 0;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if ((controllerNumber >= MILES_CONTROLLER_XMIDI_RANGE_BEGIN) && (controllerNumber <= MILES_CONTROLLER_XMIDI_RANGE_END)) {
|
||||
// XMIDI controllers? ignore those
|
||||
return;
|
||||
}
|
||||
|
||||
_driver->send(0xB0 | midiChannel | (controllerNumber << 8) | (controllerValue << 16));
|
||||
}
|
||||
|
||||
void MidiDriver_Miles_MT32::programChange(byte midiChannel, byte patchId) {
|
||||
byte channelPatchBank = _midiChannels[midiChannel].currentPatchBank;
|
||||
byte activePatchBank = _patchesBank[patchId];
|
||||
|
||||
//warning("patch channel %d, patch %x, bank %x", midiChannel, patchId, channelPatchBank);
|
||||
|
||||
// remember patch id for the current MIDI-channel
|
||||
_midiChannels[midiChannel].currentPatchId = patchId;
|
||||
|
||||
if (channelPatchBank != activePatchBank) {
|
||||
// associate patch with timbre
|
||||
setupPatch(channelPatchBank, patchId);
|
||||
}
|
||||
|
||||
// If this is a custom patch, remember customTimbreId
|
||||
int16 customTimbre = searchCustomTimbre(channelPatchBank, patchId);
|
||||
if (customTimbre >= 0) {
|
||||
_midiChannels[midiChannel].usingCustomTimbre = true;
|
||||
_midiChannels[midiChannel].currentCustomTimbreId = customTimbre;
|
||||
} else {
|
||||
_midiChannels[midiChannel].usingCustomTimbre = false;
|
||||
}
|
||||
|
||||
// Finally send program change to MT32
|
||||
_driver->send(0xC0 | midiChannel | (patchId << 8));
|
||||
}
|
||||
|
||||
int16 MidiDriver_Miles_MT32::searchCustomTimbre(byte patchBank, byte patchId) {
|
||||
byte customTimbreId = 0;
|
||||
|
||||
for (customTimbreId = 0; customTimbreId < MILES_MT32_CUSTOMTIMBRE_COUNT; customTimbreId++) {
|
||||
if (_customTimbres[customTimbreId].used) {
|
||||
if ((_customTimbres[customTimbreId].currentPatchBank == patchBank) && (_customTimbres[customTimbreId].currentPatchId == patchId)) {
|
||||
return customTimbreId;
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
const MilesMT32InstrumentEntry *MidiDriver_Miles_MT32::searchCustomInstrument(byte patchBank, byte patchId) {
|
||||
const MilesMT32InstrumentEntry *instrumentPtr = _instrumentTablePtr;
|
||||
|
||||
for (uint16 instrumentNr = 0; instrumentNr < _instrumentTableCount; instrumentNr++) {
|
||||
if ((instrumentPtr->bankId == patchBank) && (instrumentPtr->patchId == patchId))
|
||||
return instrumentPtr;
|
||||
instrumentPtr++;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void MidiDriver_Miles_MT32::setupPatch(byte patchBank, byte patchId) {
|
||||
_patchesBank[patchId] = patchBank;
|
||||
|
||||
if (patchBank) {
|
||||
// non-built-in bank
|
||||
int16 customTimbreId = searchCustomTimbre(patchBank, patchId);
|
||||
if (customTimbreId >= 0) {
|
||||
// now available? -> use this timbre
|
||||
writePatchTimbre(patchId, 2, customTimbreId); // Group MEMORY
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// for built-in bank (or timbres, that are not available) use default MT32 timbres
|
||||
byte timbreId = patchId & 0x3F;
|
||||
if (!(patchId & 0x40)) {
|
||||
writePatchTimbre(patchId, 0, timbreId); // Group A
|
||||
} else {
|
||||
writePatchTimbre(patchId, 1, timbreId); // Group B
|
||||
}
|
||||
}
|
||||
|
||||
void MidiDriver_Miles_MT32::processXMIDITimbreChunk(const byte *timbreListPtr, uint32 timbreListSize) {
|
||||
uint16 timbreCount = 0;
|
||||
uint32 expectedSize = 0;
|
||||
const byte *timbreListSeeker = timbreListPtr;
|
||||
|
||||
if (timbreListSize < 2) {
|
||||
warning("MILES-MT32: XMIDI-TIMB chunk - not enough bytes in chunk");
|
||||
return;
|
||||
}
|
||||
|
||||
timbreCount = READ_LE_UINT16(timbreListPtr);
|
||||
expectedSize = timbreCount * 2;
|
||||
if (expectedSize > timbreListSize) {
|
||||
warning("MILES-MT32: XMIDI-TIMB chunk - size mismatch");
|
||||
return;
|
||||
}
|
||||
|
||||
timbreListSeeker += 2;
|
||||
|
||||
while (timbreCount) {
|
||||
const byte patchId = *timbreListSeeker++;
|
||||
const byte patchBank = *timbreListSeeker++;
|
||||
int16 customTimbreId = 0;
|
||||
|
||||
switch (patchBank) {
|
||||
case MILES_MT32_TIMBREBANK_STANDARD_ROLAND:
|
||||
case MILES_MT32_TIMBREBANK_MELODIC_MODULE:
|
||||
// ignore those 2 banks
|
||||
break;
|
||||
|
||||
default:
|
||||
// Check, if this timbre was already loaded
|
||||
customTimbreId = searchCustomTimbre(patchBank, patchId);
|
||||
|
||||
if (customTimbreId < 0) {
|
||||
// currently not loaded, try to install it
|
||||
installCustomTimbre(patchBank, patchId);
|
||||
}
|
||||
}
|
||||
timbreCount--;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
int16 MidiDriver_Miles_MT32::installCustomTimbre(byte patchBank, byte patchId) {
|
||||
switch(patchBank) {
|
||||
case MILES_MT32_TIMBREBANK_STANDARD_ROLAND: // Standard Roland MT32 bank
|
||||
case MILES_MT32_TIMBREBANK_MELODIC_MODULE: // Reserved for melodic mode
|
||||
return -1;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Original driver did a search for custom timbre here
|
||||
// and in case it was found, it would call setup_patch()
|
||||
// we are called from within setup_patch(), so this isn't needed
|
||||
|
||||
int16 customTimbreId = -1;
|
||||
int16 leastUsedTimbreId = -1;
|
||||
uint32 leastUsedTimbreNoteCounter = _noteCounter;
|
||||
const MilesMT32InstrumentEntry *instrumentPtr = NULL;
|
||||
|
||||
// Check, if requested instrument is actually available
|
||||
instrumentPtr = searchCustomInstrument(patchBank, patchId);
|
||||
if (!instrumentPtr) {
|
||||
warning("MILES-MT32: instrument not found during installCustomTimbre()");
|
||||
return -1; // not found -> bail out
|
||||
}
|
||||
|
||||
// Look for an empty timbre slot
|
||||
// or get the least used non-protected slot
|
||||
for (byte customTimbreNr = 0; customTimbreNr < MILES_MT32_CUSTOMTIMBRE_COUNT; customTimbreNr++) {
|
||||
if (!_customTimbres[customTimbreNr].used) {
|
||||
// found an empty slot -> use this one
|
||||
customTimbreId = customTimbreNr;
|
||||
break;
|
||||
} else {
|
||||
// used slot
|
||||
if (!_customTimbres[customTimbreNr].protectionEnabled) {
|
||||
// not protected
|
||||
uint32 customTimbreNoteCounter = _customTimbres[customTimbreNr].lastUsedNoteCounter;
|
||||
if (customTimbreNoteCounter < leastUsedTimbreNoteCounter) {
|
||||
leastUsedTimbreId = customTimbreNr;
|
||||
leastUsedTimbreNoteCounter = customTimbreNoteCounter;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (customTimbreId < 0) {
|
||||
// no empty slot found, check if we got a least used non-protected slot
|
||||
if (leastUsedTimbreId < 0) {
|
||||
// everything is protected, bail out
|
||||
warning("MILES-MT32: no non-protected timbre slots available during installCustomTimbre()");
|
||||
return -1;
|
||||
}
|
||||
customTimbreId = leastUsedTimbreId;
|
||||
}
|
||||
|
||||
// setup timbre slot
|
||||
_customTimbres[customTimbreId].used = true;
|
||||
_customTimbres[customTimbreId].currentPatchBank = patchBank;
|
||||
_customTimbres[customTimbreId].currentPatchId = patchId;
|
||||
_customTimbres[customTimbreId].lastUsedNoteCounter = _noteCounter;
|
||||
_customTimbres[customTimbreId].protectionEnabled = false;
|
||||
|
||||
uint32 targetAddress = 0x080000 | (customTimbreId << 9);
|
||||
uint32 targetAddressCommon = targetAddress + 0x000000;
|
||||
uint32 targetAddressPartial1 = targetAddress + 0x00000E;
|
||||
uint32 targetAddressPartial2 = targetAddress + 0x000048;
|
||||
uint32 targetAddressPartial3 = targetAddress + 0x000102;
|
||||
uint32 targetAddressPartial4 = targetAddress + 0x00013C;
|
||||
|
||||
#if 0
|
||||
byte parameterData[MILES_MT32_PATCHDATA_TOTAL_SIZE + 1];
|
||||
uint16 parameterDataPos = 0;
|
||||
|
||||
memcpy(parameterData, instrumentPtr->commonParameter, MILES_MT32_PATCHDATA_COMMONPARAMETER_SIZE);
|
||||
parameterDataPos += MILES_MT32_PATCHDATA_COMMONPARAMETER_SIZE;
|
||||
memcpy(parameterData + parameterDataPos, instrumentPtr->partialParameters[0], MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE);
|
||||
parameterDataPos += MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE;
|
||||
memcpy(parameterData + parameterDataPos, instrumentPtr->partialParameters[1], MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE);
|
||||
parameterDataPos += MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE;
|
||||
memcpy(parameterData + parameterDataPos, instrumentPtr->partialParameters[2], MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE);
|
||||
parameterDataPos += MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE;
|
||||
memcpy(parameterData + parameterDataPos, instrumentPtr->partialParameters[3], MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE);
|
||||
parameterDataPos += MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE;
|
||||
parameterData[parameterDataPos] = MILES_MT32_SYSEX_TERMINATOR;
|
||||
|
||||
MT32SysEx(targetAddressCommon, parameterData);
|
||||
#endif
|
||||
|
||||
// upload common parameter data
|
||||
MT32SysEx(targetAddressCommon, instrumentPtr->commonParameter);
|
||||
// upload partial parameter data
|
||||
MT32SysEx(targetAddressPartial1, instrumentPtr->partialParameters[0]);
|
||||
MT32SysEx(targetAddressPartial2, instrumentPtr->partialParameters[1]);
|
||||
MT32SysEx(targetAddressPartial3, instrumentPtr->partialParameters[2]);
|
||||
MT32SysEx(targetAddressPartial4, instrumentPtr->partialParameters[3]);
|
||||
|
||||
setupPatch(patchBank, patchId);
|
||||
|
||||
return customTimbreId;
|
||||
}
|
||||
|
||||
uint32 MidiDriver_Miles_MT32::calculateSysExTargetAddress(uint32 baseAddress, uint32 index) {
|
||||
uint16 targetAddressLSB = baseAddress & 0xFF;
|
||||
uint16 targetAddressKSB = (baseAddress >> 8) & 0xFF;
|
||||
uint16 targetAddressMSB = (baseAddress >> 16) & 0xFF;
|
||||
|
||||
// add index to it, but use 7-bit of the index for each byte
|
||||
targetAddressLSB += (index & 0x7F);
|
||||
targetAddressKSB += ((index >> 7) & 0x7F);
|
||||
targetAddressMSB += ((index >> 14) & 0x7F);
|
||||
|
||||
// adjust bytes, so that none of them is above or equal 0x80
|
||||
while (targetAddressLSB >= 0x80) {
|
||||
targetAddressLSB -= 0x80;
|
||||
targetAddressKSB++;
|
||||
}
|
||||
while (targetAddressKSB >= 0x80) {
|
||||
targetAddressKSB -= 0x80;
|
||||
targetAddressMSB++;
|
||||
}
|
||||
assert(targetAddressMSB < 0x80);
|
||||
|
||||
// put everything together
|
||||
return targetAddressLSB | (targetAddressKSB << 8) | (targetAddressMSB << 16);
|
||||
}
|
||||
|
||||
void MidiDriver_Miles_MT32::writeRhythmSetup(byte note, byte customTimbreId) {
|
||||
byte sysExData[2];
|
||||
uint32 targetAddress = 0;
|
||||
|
||||
targetAddress = calculateSysExTargetAddress(0x030110, ((note - 24) << 2));
|
||||
|
||||
sysExData[0] = customTimbreId;
|
||||
sysExData[1] = MILES_MT32_SYSEX_TERMINATOR; // terminator
|
||||
|
||||
MT32SysEx(targetAddress, sysExData);
|
||||
}
|
||||
|
||||
void MidiDriver_Miles_MT32::writePatchTimbre(byte patchId, byte timbreGroup, byte timbreId) {
|
||||
byte sysExData[3];
|
||||
uint32 targetAddress = 0;
|
||||
|
||||
// write to patch memory (starts at 0x050000, each entry is 8 bytes)
|
||||
targetAddress = calculateSysExTargetAddress(0x050000, patchId << 3);
|
||||
|
||||
sysExData[0] = timbreGroup; // 0 - group A, 1 - group B, 2 - memory, 3 - rhythm
|
||||
sysExData[1] = timbreId; // timbre number (0-63)
|
||||
sysExData[2] = MILES_MT32_SYSEX_TERMINATOR; // terminator
|
||||
|
||||
MT32SysEx(targetAddress, sysExData);
|
||||
}
|
||||
|
||||
void MidiDriver_Miles_MT32::writePatchByte(byte patchId, byte index, byte patchValue) {
|
||||
byte sysExData[2];
|
||||
uint32 targetAddress = 0;
|
||||
|
||||
targetAddress = calculateSysExTargetAddress(0x050000, (patchId << 3) + index);
|
||||
|
||||
sysExData[0] = patchValue;
|
||||
sysExData[1] = MILES_MT32_SYSEX_TERMINATOR; // terminator
|
||||
|
||||
MT32SysEx(targetAddress, sysExData);
|
||||
}
|
||||
|
||||
void MidiDriver_Miles_MT32::writeToSystemArea(byte index, byte value) {
|
||||
byte sysExData[2];
|
||||
uint32 targetAddress = 0;
|
||||
|
||||
targetAddress = calculateSysExTargetAddress(0x100000, index);
|
||||
|
||||
sysExData[0] = value;
|
||||
sysExData[1] = MILES_MT32_SYSEX_TERMINATOR; // terminator
|
||||
|
||||
MT32SysEx(targetAddress, sysExData);
|
||||
}
|
||||
|
||||
MidiDriver *MidiDriver_Miles_MT32_create(const Common::String &instrumentDataFilename) {
|
||||
MilesMT32InstrumentEntry *instrumentTablePtr = NULL;
|
||||
uint16 instrumentTableCount = 0;
|
||||
|
||||
if (!instrumentDataFilename.empty()) {
|
||||
// Load MT32 instrument data from file SAMPLE.MT
|
||||
Common::File *fileStream = new Common::File();
|
||||
uint32 fileSize = 0;
|
||||
byte *fileDataPtr = NULL;
|
||||
uint32 fileDataOffset = 0;
|
||||
uint32 fileDataLeft = 0;
|
||||
|
||||
byte curBankId = 0;
|
||||
byte curPatchId = 0;
|
||||
|
||||
MilesMT32InstrumentEntry *instrumentPtr = NULL;
|
||||
uint32 instrumentOffset = 0;
|
||||
uint16 instrumentDataSize = 0;
|
||||
|
||||
if (!fileStream->open(instrumentDataFilename))
|
||||
error("MILES-MT32: could not open instrument file '%s'", instrumentDataFilename.c_str());
|
||||
|
||||
fileSize = fileStream->size();
|
||||
|
||||
fileDataPtr = new byte[fileSize];
|
||||
|
||||
if (fileStream->read(fileDataPtr, fileSize) != fileSize)
|
||||
error("MILES-MT32: error while reading instrument file");
|
||||
fileStream->close();
|
||||
delete fileStream;
|
||||
|
||||
// File is like this:
|
||||
// [patch:BYTE] [bank:BYTE] [patchoffset:UINT32]
|
||||
// ...
|
||||
// until patch + bank are both 0xFF, which signals end of header
|
||||
|
||||
// First we check how many entries there are
|
||||
fileDataOffset = 0;
|
||||
fileDataLeft = fileSize;
|
||||
while (1) {
|
||||
if (fileDataLeft < 6)
|
||||
error("MILES-MT32: unexpected EOF in instrument file");
|
||||
|
||||
curPatchId = fileDataPtr[fileDataOffset++];
|
||||
curBankId = fileDataPtr[fileDataOffset++];
|
||||
|
||||
if ((curBankId == 0xFF) && (curPatchId == 0xFF))
|
||||
break;
|
||||
|
||||
fileDataOffset += 4; // skip over offset
|
||||
instrumentTableCount++;
|
||||
}
|
||||
|
||||
if (instrumentTableCount == 0)
|
||||
error("MILES-MT32: no instruments in instrument file");
|
||||
|
||||
// Allocate space for instruments
|
||||
instrumentTablePtr = new MilesMT32InstrumentEntry[instrumentTableCount];
|
||||
|
||||
// Now actually read all entries
|
||||
instrumentPtr = instrumentTablePtr;
|
||||
|
||||
fileDataOffset = 0;
|
||||
fileDataLeft = fileSize;
|
||||
while (1) {
|
||||
curPatchId = fileDataPtr[fileDataOffset++];
|
||||
curBankId = fileDataPtr[fileDataOffset++];
|
||||
|
||||
if ((curBankId == 0xFF) && (curPatchId == 0xFF))
|
||||
break;
|
||||
|
||||
instrumentOffset = READ_LE_UINT32(fileDataPtr + fileDataOffset);
|
||||
fileDataOffset += 4;
|
||||
|
||||
instrumentPtr->bankId = curBankId;
|
||||
instrumentPtr->patchId = curPatchId;
|
||||
|
||||
instrumentDataSize = READ_LE_UINT16(fileDataPtr + instrumentOffset);
|
||||
if (instrumentDataSize != (MILES_MT32_PATCHDATA_TOTAL_SIZE + 2))
|
||||
error("MILES-MT32: unsupported instrument size");
|
||||
|
||||
instrumentOffset += 2;
|
||||
// Copy common parameter data
|
||||
memcpy(instrumentPtr->commonParameter, fileDataPtr + instrumentOffset, MILES_MT32_PATCHDATA_COMMONPARAMETER_SIZE);
|
||||
instrumentPtr->commonParameter[MILES_MT32_PATCHDATA_COMMONPARAMETER_SIZE] = MILES_MT32_SYSEX_TERMINATOR; // Terminator
|
||||
instrumentOffset += MILES_MT32_PATCHDATA_COMMONPARAMETER_SIZE;
|
||||
|
||||
// Copy partial parameter data
|
||||
for (byte partialNr = 0; partialNr < MILES_MT32_PATCHDATA_PARTIALPARAMETERS_COUNT; partialNr++) {
|
||||
memcpy(&instrumentPtr->partialParameters[partialNr], fileDataPtr + instrumentOffset, MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE);
|
||||
instrumentPtr->partialParameters[partialNr][MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE] = MILES_MT32_SYSEX_TERMINATOR; // Terminator
|
||||
instrumentOffset += MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE;
|
||||
}
|
||||
|
||||
// Instrument read, next instrument please
|
||||
instrumentPtr++;
|
||||
}
|
||||
|
||||
// Free instrument file data
|
||||
delete[] fileDataPtr;
|
||||
}
|
||||
|
||||
return new MidiDriver_Miles_MT32(instrumentTablePtr, instrumentTableCount);
|
||||
}
|
||||
|
||||
void MidiDriver_Miles_MT32_processXMIDITimbreChunk(MidiDriver_BASE *driver, const byte *timbreListPtr, uint32 timbreListSize) {
|
||||
MidiDriver_Miles_MT32 *driverMT32 = dynamic_cast<MidiDriver_Miles_MT32 *>(driver);
|
||||
|
||||
if (driverMT32) {
|
||||
driverMT32->processXMIDITimbreChunk(timbreListPtr, timbreListSize);
|
||||
}
|
||||
}
|
||||
|
||||
} // End of namespace Audio
|
@ -7,8 +7,6 @@ MODULE_OBJS := \
|
||||
mididrv.o \
|
||||
midiparser_qt.o \
|
||||
midiparser.o \
|
||||
miles_adlib.o \
|
||||
miles_mt32.o \
|
||||
mixer.o \
|
||||
mpu401.o \
|
||||
musicplugin.o \
|
||||
@ -35,8 +33,7 @@ MODULE_OBJS := \
|
||||
softsynth/opl/dbopl.o \
|
||||
softsynth/opl/dosbox.o \
|
||||
softsynth/opl/mame.o \
|
||||
softsynth/fluidsynth.o \
|
||||
softsynth/mt32.o
|
||||
softsynth/fluidsynth.o
|
||||
|
||||
ifdef USE_ALSA
|
||||
MODULE_OBJS += \
|
||||
|
@ -1,477 +0,0 @@
|
||||
/* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "common/scummsys.h"
|
||||
#include "common/system.h"
|
||||
|
||||
#ifdef USE_MT32EMU
|
||||
|
||||
#include "audio/softsynth/emumidi.h"
|
||||
#include "audio/musicplugin.h"
|
||||
#include "audio/mpu401.h"
|
||||
|
||||
#include "common/config-manager.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/error.h"
|
||||
#include "common/events.h"
|
||||
#include "common/file.h"
|
||||
#include "common/system.h"
|
||||
#include "common/util.h"
|
||||
#include "common/archive.h"
|
||||
#include "common/textconsole.h"
|
||||
#include "common/translation.h"
|
||||
#include "common/osd_message_queue.h"
|
||||
|
||||
#include "graphics/fontman.h"
|
||||
#include "graphics/surface.h"
|
||||
#include "graphics/pixelformat.h"
|
||||
#include "graphics/palette.h"
|
||||
#include "graphics/font.h"
|
||||
|
||||
#include "gui/message.h"
|
||||
|
||||
// prevents load of unused FileStream API because it includes a standard library
|
||||
// include, per _sev
|
||||
#define MT32EMU_FILE_STREAM_H
|
||||
|
||||
#include "audio/softsynth/mt32/c_interface/cpp_interface.h"
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
class ScummVMReportHandler : public MT32Emu::IReportHandler {
|
||||
public:
|
||||
// Callback for debug messages, in vprintf() format
|
||||
void printDebug(const char *fmt, va_list list) {
|
||||
Common::String out = Common::String::vformat(fmt, list);
|
||||
debug(4, "%s", out.c_str());
|
||||
}
|
||||
|
||||
// Callbacks for reporting various errors and information
|
||||
void onErrorControlROM() {
|
||||
GUI::MessageDialog dialog("MT32Emu: Init Error - Missing or invalid Control ROM image", "OK");
|
||||
dialog.runModal();
|
||||
error("MT32emu: Init Error - Missing or invalid Control ROM image");
|
||||
}
|
||||
void onErrorPCMROM() {
|
||||
GUI::MessageDialog dialog("MT32Emu: Init Error - Missing PCM ROM image", "OK");
|
||||
dialog.runModal();
|
||||
error("MT32emu: Init Error - Missing PCM ROM image");
|
||||
}
|
||||
void showLCDMessage(const char *message) {
|
||||
Common::OSDMessageQueue::instance().addMessage(message);
|
||||
}
|
||||
|
||||
// Unused callbacks
|
||||
virtual void onMIDIMessagePlayed() {}
|
||||
virtual bool onMIDIQueueOverflow() { return false; }
|
||||
virtual void onMIDISystemRealtime(Bit8u /* system_realtime */) {}
|
||||
virtual void onDeviceReset() {}
|
||||
virtual void onDeviceReconfig() {}
|
||||
virtual void onNewReverbMode(Bit8u /* mode */) {}
|
||||
virtual void onNewReverbTime(Bit8u /* time */) {}
|
||||
virtual void onNewReverbLevel(Bit8u /* level */) {}
|
||||
virtual void onPolyStateChanged(Bit8u /* part_num */) {}
|
||||
virtual void onProgramChanged(Bit8u /* part_num */, const char * /* sound_group_name */, const char * /* patch_name */) {}
|
||||
|
||||
virtual ~ScummVMReportHandler() {}
|
||||
};
|
||||
|
||||
} // end of namespace MT32Emu
|
||||
|
||||
class MidiChannel_MT32 : public MidiChannel_MPU401 {
|
||||
void effectLevel(byte value) { }
|
||||
void chorusLevel(byte value) { }
|
||||
};
|
||||
|
||||
class MidiDriver_MT32 : public MidiDriver_Emulated {
|
||||
private:
|
||||
MidiChannel_MT32 _midiChannels[16];
|
||||
uint16 _channelMask;
|
||||
MT32Emu::Service _service;
|
||||
MT32Emu::ScummVMReportHandler _reportHandler;
|
||||
byte *_controlData, *_pcmData;
|
||||
Common::Mutex _mutex;
|
||||
|
||||
int _outputRate;
|
||||
|
||||
protected:
|
||||
void generateSamples(int16 *buf, int len);
|
||||
|
||||
public:
|
||||
MidiDriver_MT32(Audio::Mixer *mixer);
|
||||
virtual ~MidiDriver_MT32();
|
||||
|
||||
int open();
|
||||
void close();
|
||||
void send(uint32 b);
|
||||
void setPitchBendRange(byte channel, uint range);
|
||||
void sysEx(const byte *msg, uint16 length);
|
||||
|
||||
uint32 property(int prop, uint32 param);
|
||||
MidiChannel *allocateChannel();
|
||||
MidiChannel *getPercussionChannel();
|
||||
|
||||
// AudioStream API
|
||||
bool isStereo() const { return true; }
|
||||
int getRate() const { return _outputRate; }
|
||||
};
|
||||
|
||||
////////////////////////////////////////
|
||||
//
|
||||
// MidiDriver_MT32
|
||||
//
|
||||
////////////////////////////////////////
|
||||
|
||||
MidiDriver_MT32::MidiDriver_MT32(Audio::Mixer *mixer) : MidiDriver_Emulated(mixer) {
|
||||
_channelMask = 0xFFFF; // Permit all 16 channels by default
|
||||
uint i;
|
||||
for (i = 0; i < ARRAYSIZE(_midiChannels); ++i) {
|
||||
_midiChannels[i].init(this, i);
|
||||
}
|
||||
_outputRate = 0;
|
||||
_controlData = nullptr;
|
||||
_pcmData = nullptr;
|
||||
}
|
||||
|
||||
MidiDriver_MT32::~MidiDriver_MT32() {
|
||||
close();
|
||||
}
|
||||
|
||||
int MidiDriver_MT32::open() {
|
||||
if (_isOpen)
|
||||
return MERR_ALREADY_OPEN;
|
||||
|
||||
Graphics::PixelFormat screenFormat = g_system->getScreenFormat();
|
||||
|
||||
if (screenFormat.bytesPerPixel == 1) {
|
||||
const byte dummy_palette[] = {
|
||||
0, 0, 0, // background
|
||||
0, 171, 0, // border, font
|
||||
171, 0, 0 // fill
|
||||
};
|
||||
|
||||
g_system->getPaletteManager()->setPalette(dummy_palette, 0, 3);
|
||||
}
|
||||
|
||||
debug(4, _s("Initializing MT-32 Emulator"));
|
||||
|
||||
Common::File controlFile;
|
||||
if (!controlFile.open("CM32L_CONTROL.ROM") && !controlFile.open("MT32_CONTROL.ROM"))
|
||||
error("Error opening MT32_CONTROL.ROM / CM32L_CONTROL.ROM. Check that your Extra Path in Paths settings is set to the correct directory");
|
||||
|
||||
Common::File pcmFile;
|
||||
if (!pcmFile.open("CM32L_PCM.ROM") && !pcmFile.open("MT32_PCM.ROM"))
|
||||
error("Error opening MT32_PCM.ROM / CM32L_PCM.ROM. Check that your Extra Path in Paths settings is set to the correct directory");
|
||||
|
||||
_controlData = new byte[controlFile.size()];
|
||||
controlFile.read(_controlData, controlFile.size());
|
||||
_pcmData = new byte[pcmFile.size()];
|
||||
pcmFile.read(_pcmData, pcmFile.size());
|
||||
|
||||
_service.createContext(_reportHandler);
|
||||
|
||||
if (_service.addROMData(_controlData, controlFile.size()) != MT32EMU_RC_ADDED_CONTROL_ROM) {
|
||||
error("Adding control ROM failed. Check that your control ROM is valid");
|
||||
}
|
||||
|
||||
controlFile.close();
|
||||
|
||||
if (_service.addROMData(_pcmData, pcmFile.size()) != MT32EMU_RC_ADDED_PCM_ROM) {
|
||||
error("Adding PCM ROM failed. Check that your PCM ROM is valid");
|
||||
}
|
||||
|
||||
pcmFile.close();
|
||||
|
||||
if (_service.openSynth() != MT32EMU_RC_OK)
|
||||
return MERR_DEVICE_NOT_AVAILABLE;
|
||||
|
||||
double gain = (double)ConfMan.getInt("midi_gain") / 100.0;
|
||||
_service.setOutputGain(1.0f * gain);
|
||||
_service.setReverbOutputGain(1.0f * gain);
|
||||
// We let the synthesizer play MIDI messages immediately. Our MIDI
|
||||
// handling is synchronous to sample generation. This makes delaying MIDI
|
||||
// events result in odd sound output in some cases. For example, the
|
||||
// shattering window in the Indiana Jones and the Fate of Atlantis intro
|
||||
// will sound like a bell if we use any delay here.
|
||||
// Bug #6242 "AUDIO: Built-In MT-32 MUNT Produces Wrong Sounds".
|
||||
_service.setMIDIDelayMode(MT32Emu::MIDIDelayMode_IMMEDIATE);
|
||||
|
||||
// We need to report the sample rate MUNT renders at as sample rate of our
|
||||
// AudioStream.
|
||||
_outputRate = _service.getActualStereoOutputSamplerate();
|
||||
|
||||
MidiDriver_Emulated::open();
|
||||
|
||||
_mixer->playStream(Audio::Mixer::kPlainSoundType, &_mixerSoundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void MidiDriver_MT32::send(uint32 b) {
|
||||
Common::StackLock lock(_mutex);
|
||||
_service.playMsg(b);
|
||||
}
|
||||
|
||||
// Indiana Jones and the Fate of Atlantis (including the demo) uses
|
||||
// setPitchBendRange, if you need a game for testing purposes
|
||||
void MidiDriver_MT32::setPitchBendRange(byte channel, uint range) {
|
||||
if (range > 24) {
|
||||
warning("setPitchBendRange() called with range > 24: %d", range);
|
||||
}
|
||||
byte benderRangeSysex[4] = { 0, 0, 4, (uint8)range };
|
||||
Common::StackLock lock(_mutex);
|
||||
_service.writeSysex(channel, benderRangeSysex, 4);
|
||||
}
|
||||
|
||||
void MidiDriver_MT32::sysEx(const byte *msg, uint16 length) {
|
||||
if (msg[0] == 0xf0) {
|
||||
Common::StackLock lock(_mutex);
|
||||
_service.playSysex(msg, length);
|
||||
} else {
|
||||
enum {
|
||||
SYSEX_CMD_DT1 = 0x12,
|
||||
SYSEX_CMD_DAT = 0x42
|
||||
};
|
||||
|
||||
if (msg[3] == SYSEX_CMD_DT1 || msg[3] == SYSEX_CMD_DAT) {
|
||||
Common::StackLock lock(_mutex);
|
||||
_service.writeSysex(msg[1], msg + 4, length - 5);
|
||||
} else {
|
||||
warning("Unused sysEx command %d", msg[3]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MidiDriver_MT32::close() {
|
||||
if (!_isOpen)
|
||||
return;
|
||||
_isOpen = false;
|
||||
|
||||
// Detach the player callback handler
|
||||
setTimerCallback(NULL, NULL);
|
||||
// Detach the mixer callback handler
|
||||
_mixer->stopHandle(_mixerSoundHandle);
|
||||
|
||||
Common::StackLock lock(_mutex);
|
||||
_service.closeSynth();
|
||||
_service.freeContext();
|
||||
delete[] _controlData;
|
||||
_controlData = nullptr;
|
||||
delete[] _pcmData;
|
||||
_pcmData = nullptr;
|
||||
}
|
||||
|
||||
void MidiDriver_MT32::generateSamples(int16 *data, int len) {
|
||||
Common::StackLock lock(_mutex);
|
||||
_service.renderBit16s(data, len);
|
||||
}
|
||||
|
||||
uint32 MidiDriver_MT32::property(int prop, uint32 param) {
|
||||
switch (prop) {
|
||||
case PROP_CHANNEL_MASK:
|
||||
_channelMask = param & 0xFFFF;
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
MidiChannel *MidiDriver_MT32::allocateChannel() {
|
||||
MidiChannel_MT32 *chan;
|
||||
uint i;
|
||||
|
||||
for (i = 0; i < ARRAYSIZE(_midiChannels); ++i) {
|
||||
if (i == 9 || !(_channelMask & (1 << i)))
|
||||
continue;
|
||||
chan = &_midiChannels[i];
|
||||
if (chan->allocate()) {
|
||||
return chan;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
MidiChannel *MidiDriver_MT32::getPercussionChannel() {
|
||||
return &_midiChannels[9];
|
||||
}
|
||||
|
||||
// This code should be used when calling the timer callback from the mixer thread is undesirable.
|
||||
// Note that it results in less accurate timing.
|
||||
#if 0
|
||||
class MidiEvent_MT32 {
|
||||
public:
|
||||
MidiEvent_MT32 *_next;
|
||||
uint32 _msg; // 0xFFFFFFFF indicates a sysex message
|
||||
byte *_data;
|
||||
uint32 _len;
|
||||
|
||||
MidiEvent_MT32(uint32 msg, byte *data, uint32 len) {
|
||||
_msg = msg;
|
||||
if (len > 0) {
|
||||
_data = new byte[len];
|
||||
memcpy(_data, data, len);
|
||||
}
|
||||
_len = len;
|
||||
_next = NULL;
|
||||
}
|
||||
|
||||
MidiEvent_MT32() {
|
||||
if (_len > 0)
|
||||
delete _data;
|
||||
}
|
||||
};
|
||||
|
||||
class MidiDriver_ThreadedMT32 : public MidiDriver_MT32 {
|
||||
private:
|
||||
OSystem::Mutex _eventMutex;
|
||||
MidiEvent_MT32 *_events;
|
||||
TimerManager::TimerProc _timer_proc;
|
||||
|
||||
void pushMidiEvent(MidiEvent_MT32 *event);
|
||||
MidiEvent_MT32 *popMidiEvent();
|
||||
|
||||
protected:
|
||||
void send(uint32 b);
|
||||
void sysEx(const byte *msg, uint16 length);
|
||||
|
||||
public:
|
||||
MidiDriver_ThreadedMT32(Audio::Mixer *mixer);
|
||||
|
||||
void onTimer();
|
||||
void close();
|
||||
void setTimerCallback(void *timer_param, TimerManager::TimerProc timer_proc);
|
||||
};
|
||||
|
||||
|
||||
MidiDriver_ThreadedMT32::MidiDriver_ThreadedMT32(Audio::Mixer *mixer) : MidiDriver_MT32(mixer) {
|
||||
_events = NULL;
|
||||
_timer_proc = NULL;
|
||||
}
|
||||
|
||||
void MidiDriver_ThreadedMT32::close() {
|
||||
MidiDriver_MT32::close();
|
||||
while ((popMidiEvent() != NULL)) {
|
||||
// Just eat any leftover events
|
||||
}
|
||||
}
|
||||
|
||||
void MidiDriver_ThreadedMT32::setTimerCallback(void *timer_param, TimerManager::TimerProc timer_proc) {
|
||||
if (!_timer_proc || !timer_proc) {
|
||||
if (_timer_proc)
|
||||
_vm->_timer->removeTimerProc(_timer_proc);
|
||||
_timer_proc = timer_proc;
|
||||
if (timer_proc)
|
||||
_vm->_timer->installTimerProc(timer_proc, getBaseTempo(), timer_param, "MT32tempo");
|
||||
}
|
||||
}
|
||||
|
||||
void MidiDriver_ThreadedMT32::pushMidiEvent(MidiEvent_MT32 *event) {
|
||||
Common::StackLock lock(_eventMutex);
|
||||
if (_events == NULL) {
|
||||
_events = event;
|
||||
} else {
|
||||
MidiEvent_MT32 *last = _events;
|
||||
while (last->_next != NULL)
|
||||
last = last->_next;
|
||||
last->_next = event;
|
||||
}
|
||||
}
|
||||
|
||||
MidiEvent_MT32 *MidiDriver_ThreadedMT32::popMidiEvent() {
|
||||
Common::StackLock lock(_eventMutex);
|
||||
MidiEvent_MT32 *event;
|
||||
event = _events;
|
||||
if (event != NULL)
|
||||
_events = event->_next;
|
||||
return event;
|
||||
}
|
||||
|
||||
void MidiDriver_ThreadedMT32::send(uint32 b) {
|
||||
MidiEvent_MT32 *event = new MidiEvent_MT32(b, NULL, 0);
|
||||
pushMidiEvent(event);
|
||||
}
|
||||
|
||||
void MidiDriver_ThreadedMT32::sysEx(const byte *msg, uint16 length) {
|
||||
MidiEvent_MT32 *event = new MidiEvent_MT32(0xFFFFFFFF, msg, length);
|
||||
pushMidiEvent(event);
|
||||
}
|
||||
|
||||
void MidiDriver_ThreadedMT32::onTimer() {
|
||||
MidiEvent_MT32 *event;
|
||||
while ((event = popMidiEvent()) != NULL) {
|
||||
if (event->_msg == 0xFFFFFFFF) {
|
||||
MidiDriver_MT32::sysEx(event->_data, event->_len);
|
||||
} else {
|
||||
MidiDriver_MT32::send(event->_msg);
|
||||
}
|
||||
delete event;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
// Plugin interface
|
||||
|
||||
class MT32EmuMusicPlugin : public MusicPluginObject {
|
||||
public:
|
||||
const char *getName() const {
|
||||
return _s("MT-32 Emulator");
|
||||
}
|
||||
|
||||
const char *getId() const {
|
||||
return "mt32";
|
||||
}
|
||||
|
||||
MusicDevices getDevices() const;
|
||||
bool checkDevice(MidiDriver::DeviceHandle) const;
|
||||
Common::Error createInstance(MidiDriver **mididriver, MidiDriver::DeviceHandle = 0) const;
|
||||
};
|
||||
|
||||
MusicDevices MT32EmuMusicPlugin::getDevices() const {
|
||||
MusicDevices devices;
|
||||
devices.push_back(MusicDevice(this, "", MT_MT32));
|
||||
return devices;
|
||||
}
|
||||
|
||||
bool MT32EmuMusicPlugin::checkDevice(MidiDriver::DeviceHandle) const {
|
||||
if (!((Common::File::exists("MT32_CONTROL.ROM") && Common::File::exists("MT32_PCM.ROM")) ||
|
||||
(Common::File::exists("CM32L_CONTROL.ROM") && Common::File::exists("CM32L_PCM.ROM")))) {
|
||||
warning("The MT-32 emulator requires one of the two following file sets (not bundled with ResidualVM):\n Either 'MT32_CONTROL.ROM' and 'MT32_PCM.ROM' or 'CM32L_CONTROL.ROM' and 'CM32L_PCM.ROM'");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Common::Error MT32EmuMusicPlugin::createInstance(MidiDriver **mididriver, MidiDriver::DeviceHandle) const {
|
||||
*mididriver = new MidiDriver_MT32(g_system->getMixer());
|
||||
|
||||
return Common::kNoError;
|
||||
}
|
||||
|
||||
//#if PLUGIN_ENABLED_DYNAMIC(MT32)
|
||||
//REGISTER_PLUGIN_DYNAMIC(MT32, PLUGIN_TYPE_MUSIC, MT32EmuMusicPlugin);
|
||||
//#else
|
||||
REGISTER_PLUGIN_STATIC(MT32, PLUGIN_TYPE_MUSIC, MT32EmuMusicPlugin);
|
||||
//#endif
|
||||
|
||||
#endif
|
@ -1,334 +0,0 @@
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include "internals.h"
|
||||
|
||||
#include "Analog.h"
|
||||
#include "Synth.h"
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
#if MT32EMU_USE_FLOAT_SAMPLES
|
||||
|
||||
/* FIR approximation of the overall impulse response of the cascade composed of the sample & hold circuit and the low pass filter
|
||||
* of the MT-32 first generation.
|
||||
* The coefficients below are found by windowing the inverse DFT of the 1024 pin frequency response converted to the minimum phase.
|
||||
* The frequency response of the LPF is computed directly, the effect of the S&H is approximated by multiplying the LPF frequency
|
||||
* response by the corresponding sinc. Although, the LPF has DC gain of 3.2, we ignore this in the emulation and use normalised model.
|
||||
* The peak gain of the normalised cascade appears about 1.7 near 11.8 kHz. Relative error doesn't exceed 1% for the frequencies
|
||||
* below 12.5 kHz. In the higher frequency range, the relative error is below 8%. Peak error value is at 16 kHz.
|
||||
*/
|
||||
static const float COARSE_LPF_TAPS_MT32[] = {
|
||||
1.272473681f, -0.220267785f, -0.158039905f, 0.179603785f, -0.111484097f, 0.054137498f, -0.023518029f, 0.010997169f, -0.006935698f
|
||||
};
|
||||
|
||||
// Similar approximation for new MT-32 and CM-32L/LAPC-I LPF. As the voltage controlled amplifier was introduced, LPF has unity DC gain.
|
||||
// The peak gain value shifted towards higher frequencies and a bit higher about 1.83 near 13 kHz.
|
||||
static const float COARSE_LPF_TAPS_CM32L[] = {
|
||||
1.340615635f, -0.403331694f, 0.036005517f, 0.066156844f, -0.069672532f, 0.049563806f, -0.031113416f, 0.019169774f, -0.012421368f
|
||||
};
|
||||
|
||||
#else
|
||||
|
||||
static const unsigned int COARSE_LPF_FRACTION_BITS = 14;
|
||||
|
||||
// Integer versions of the FIRs above multiplied by (1 << 14) and rounded.
|
||||
static const SampleEx COARSE_LPF_TAPS_MT32[] = {
|
||||
20848, -3609, -2589, 2943, -1827, 887, -385, 180, -114
|
||||
};
|
||||
|
||||
static const SampleEx COARSE_LPF_TAPS_CM32L[] = {
|
||||
21965, -6608, 590, 1084, -1142, 812, -510, 314, -204
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
/* Combined FIR that both approximates the impulse response of the analogue circuits of sample & hold and the low pass filter
|
||||
* in the audible frequency range (below 20 kHz) and attenuates unwanted mirror spectra above 28 kHz as well. It is a polyphase
|
||||
* filter intended for resampling the signal to 48 kHz yet for applying high frequency boost.
|
||||
* As with the filter above, the analogue LPF frequency response is obtained for 1536 pin grid for range up to 96 kHz and multiplied
|
||||
* by the corresponding sinc. The result is further squared, windowed and passed to generalised Parks-McClellan routine as a desired response.
|
||||
* Finally, the minimum phase factor is found that's essentially the coefficients below.
|
||||
* Relative error in the audible frequency range doesn't exceed 0.0006%, attenuation in the stopband is better than 100 dB.
|
||||
* This level of performance makes it nearly bit-accurate for standard 16-bit sample resolution.
|
||||
*/
|
||||
|
||||
// FIR version for MT-32 first generation.
|
||||
static const float ACCURATE_LPF_TAPS_MT32[] = {
|
||||
0.003429281f, 0.025929869f, 0.096587777f, 0.228884848f, 0.372413431f, 0.412386503f, 0.263980018f,
|
||||
-0.014504962f, -0.237394528f, -0.257043496f, -0.103436603f, 0.063996095f, 0.124562333f, 0.083703206f,
|
||||
0.013921662f, -0.033475018f, -0.046239712f, -0.029310921f, 0.00126585f, 0.021060961f, 0.017925605f,
|
||||
0.003559874f, -0.005105248f, -0.005647917f, -0.004157918f, -0.002065664f, 0.00158747f, 0.003762585f,
|
||||
0.001867137f, -0.001090028f, -0.001433979f, -0.00022367f, 4.34308E-05f, -0.000247827f, 0.000157087f,
|
||||
0.000605823f, 0.000197317f, -0.000370511f, -0.000261202f, 9.96069E-05f, 9.85073E-05f, -5.28754E-05f,
|
||||
-1.00912E-05f, 7.69943E-05f, 2.03162E-05f, -5.67967E-05f, -3.30637E-05f, 1.61958E-05f, 1.73041E-05f
|
||||
};
|
||||
|
||||
// FIR version for new MT-32 and CM-32L/LAPC-I.
|
||||
static const float ACCURATE_LPF_TAPS_CM32L[] = {
|
||||
0.003917452f, 0.030693861f, 0.116424199f, 0.275101674f, 0.43217361f, 0.431247894f, 0.183255659f,
|
||||
-0.174955671f, -0.354240244f, -0.212401714f, 0.072259178f, 0.204655344f, 0.108336211f, -0.039099027f,
|
||||
-0.075138174f, -0.026261906f, 0.00582663f, 0.003052193f, 0.00613657f, 0.017017951f, 0.008732535f,
|
||||
-0.011027427f, -0.012933664f, 0.001158097f, 0.006765958f, 0.00046778f, -0.002191106f, 0.001561017f,
|
||||
0.001842871f, -0.001996876f, -0.002315836f, 0.000980965f, 0.001817454f, -0.000243272f, -0.000972848f,
|
||||
0.000149941f, 0.000498886f, -0.000204436f, -0.000347415f, 0.000142386f, 0.000249137f, -4.32946E-05f,
|
||||
-0.000131231f, 3.88575E-07f, 4.48813E-05f, -1.31906E-06f, -1.03499E-05f, 7.71971E-06f, 2.86721E-06f
|
||||
};
|
||||
|
||||
// According to the CM-64 PCB schematic, there is a difference in the values of the LPF entrance resistors for the reverb and non-reverb channels.
|
||||
// This effectively results in non-unity LPF DC gain for the reverb channel of 0.68 while the LPF has unity DC gain for the LA32 output channels.
|
||||
// In emulation, the reverb output gain is multiplied by this factor to compensate for the LPF gain difference.
|
||||
static const float CM32L_REVERB_TO_LA32_ANALOG_OUTPUT_GAIN_FACTOR = 0.68f;
|
||||
|
||||
static const unsigned int OUTPUT_GAIN_FRACTION_BITS = 8;
|
||||
static const float OUTPUT_GAIN_MULTIPLIER = float(1 << OUTPUT_GAIN_FRACTION_BITS);
|
||||
|
||||
static const unsigned int COARSE_LPF_DELAY_LINE_LENGTH = 8; // Must be a power of 2
|
||||
static const unsigned int ACCURATE_LPF_DELAY_LINE_LENGTH = 16; // Must be a power of 2
|
||||
static const unsigned int ACCURATE_LPF_NUMBER_OF_PHASES = 3; // Upsampling factor
|
||||
static const unsigned int ACCURATE_LPF_PHASE_INCREMENT_REGULAR = 2; // Downsampling factor
|
||||
static const unsigned int ACCURATE_LPF_PHASE_INCREMENT_OVERSAMPLED = 1; // No downsampling
|
||||
static const Bit32u ACCURATE_LPF_DELTAS_REGULAR[][ACCURATE_LPF_NUMBER_OF_PHASES] = { { 0, 0, 0 }, { 1, 1, 0 }, { 1, 2, 1 } };
|
||||
static const Bit32u ACCURATE_LPF_DELTAS_OVERSAMPLED[][ACCURATE_LPF_NUMBER_OF_PHASES] = { { 0, 0, 0 }, { 1, 0, 0 }, { 1, 0, 1 } };
|
||||
|
||||
class AbstractLowPassFilter {
|
||||
public:
|
||||
static AbstractLowPassFilter &createLowPassFilter(AnalogOutputMode mode, bool oldMT32AnalogLPF);
|
||||
|
||||
virtual ~AbstractLowPassFilter() {}
|
||||
virtual SampleEx process(SampleEx sample) = 0;
|
||||
virtual bool hasNextSample() const;
|
||||
virtual unsigned int getOutputSampleRate() const;
|
||||
virtual unsigned int estimateInSampleCount(unsigned int outSamples) const;
|
||||
virtual void addPositionIncrement(unsigned int) {}
|
||||
};
|
||||
|
||||
class NullLowPassFilter : public AbstractLowPassFilter {
|
||||
public:
|
||||
SampleEx process(SampleEx sample);
|
||||
};
|
||||
|
||||
class CoarseLowPassFilter : public AbstractLowPassFilter {
|
||||
private:
|
||||
const SampleEx * const LPF_TAPS;
|
||||
SampleEx ringBuffer[COARSE_LPF_DELAY_LINE_LENGTH];
|
||||
unsigned int ringBufferPosition;
|
||||
|
||||
public:
|
||||
CoarseLowPassFilter(bool oldMT32AnalogLPF);
|
||||
SampleEx process(SampleEx sample);
|
||||
};
|
||||
|
||||
class AccurateLowPassFilter : public AbstractLowPassFilter {
|
||||
private:
|
||||
const float * const LPF_TAPS;
|
||||
const Bit32u (* const deltas)[ACCURATE_LPF_NUMBER_OF_PHASES];
|
||||
const unsigned int phaseIncrement;
|
||||
const unsigned int outputSampleRate;
|
||||
|
||||
SampleEx ringBuffer[ACCURATE_LPF_DELAY_LINE_LENGTH];
|
||||
unsigned int ringBufferPosition;
|
||||
unsigned int phase;
|
||||
|
||||
public:
|
||||
AccurateLowPassFilter(bool oldMT32AnalogLPF, bool oversample);
|
||||
SampleEx process(SampleEx sample);
|
||||
bool hasNextSample() const;
|
||||
unsigned int getOutputSampleRate() const;
|
||||
unsigned int estimateInSampleCount(unsigned int outSamples) const;
|
||||
void addPositionIncrement(unsigned int positionIncrement);
|
||||
};
|
||||
|
||||
Analog::Analog(const AnalogOutputMode mode, const bool oldMT32AnalogLPF) :
|
||||
leftChannelLPF(AbstractLowPassFilter::createLowPassFilter(mode, oldMT32AnalogLPF)),
|
||||
rightChannelLPF(AbstractLowPassFilter::createLowPassFilter(mode, oldMT32AnalogLPF)),
|
||||
synthGain(0),
|
||||
reverbGain(0)
|
||||
{}
|
||||
|
||||
Analog::~Analog() {
|
||||
delete &leftChannelLPF;
|
||||
delete &rightChannelLPF;
|
||||
}
|
||||
|
||||
void Analog::process(Sample *outStream, const Sample *nonReverbLeft, const Sample *nonReverbRight, const Sample *reverbDryLeft, const Sample *reverbDryRight, const Sample *reverbWetLeft, const Sample *reverbWetRight, Bit32u outLength) {
|
||||
if (outStream == NULL) {
|
||||
leftChannelLPF.addPositionIncrement(outLength);
|
||||
rightChannelLPF.addPositionIncrement(outLength);
|
||||
return;
|
||||
}
|
||||
|
||||
while (0 < (outLength--)) {
|
||||
SampleEx outSampleL;
|
||||
SampleEx outSampleR;
|
||||
|
||||
if (leftChannelLPF.hasNextSample()) {
|
||||
outSampleL = leftChannelLPF.process(0);
|
||||
outSampleR = rightChannelLPF.process(0);
|
||||
} else {
|
||||
SampleEx inSampleL = (SampleEx(*(nonReverbLeft++)) + SampleEx(*(reverbDryLeft++))) * synthGain + SampleEx(*(reverbWetLeft++)) * reverbGain;
|
||||
SampleEx inSampleR = (SampleEx(*(nonReverbRight++)) + SampleEx(*(reverbDryRight++))) * synthGain + SampleEx(*(reverbWetRight++)) * reverbGain;
|
||||
|
||||
#if !MT32EMU_USE_FLOAT_SAMPLES
|
||||
inSampleL >>= OUTPUT_GAIN_FRACTION_BITS;
|
||||
inSampleR >>= OUTPUT_GAIN_FRACTION_BITS;
|
||||
#endif
|
||||
|
||||
outSampleL = leftChannelLPF.process(inSampleL);
|
||||
outSampleR = rightChannelLPF.process(inSampleR);
|
||||
}
|
||||
|
||||
*(outStream++) = Synth::clipSampleEx(outSampleL);
|
||||
*(outStream++) = Synth::clipSampleEx(outSampleR);
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int Analog::getOutputSampleRate() const {
|
||||
return leftChannelLPF.getOutputSampleRate();
|
||||
}
|
||||
|
||||
Bit32u Analog::getDACStreamsLength(Bit32u outputLength) const {
|
||||
return leftChannelLPF.estimateInSampleCount(outputLength);
|
||||
}
|
||||
|
||||
void Analog::setSynthOutputGain(float useSynthGain) {
|
||||
#if MT32EMU_USE_FLOAT_SAMPLES
|
||||
synthGain = useSynthGain;
|
||||
#else
|
||||
if (OUTPUT_GAIN_MULTIPLIER < useSynthGain) useSynthGain = OUTPUT_GAIN_MULTIPLIER;
|
||||
synthGain = SampleEx(useSynthGain * OUTPUT_GAIN_MULTIPLIER);
|
||||
#endif
|
||||
}
|
||||
|
||||
void Analog::setReverbOutputGain(float useReverbGain, bool mt32ReverbCompatibilityMode) {
|
||||
if (!mt32ReverbCompatibilityMode) useReverbGain *= CM32L_REVERB_TO_LA32_ANALOG_OUTPUT_GAIN_FACTOR;
|
||||
#if MT32EMU_USE_FLOAT_SAMPLES
|
||||
reverbGain = useReverbGain;
|
||||
#else
|
||||
if (OUTPUT_GAIN_MULTIPLIER < useReverbGain) useReverbGain = OUTPUT_GAIN_MULTIPLIER;
|
||||
reverbGain = SampleEx(useReverbGain * OUTPUT_GAIN_MULTIPLIER);
|
||||
#endif
|
||||
}
|
||||
|
||||
AbstractLowPassFilter &AbstractLowPassFilter::createLowPassFilter(AnalogOutputMode mode, bool oldMT32AnalogLPF) {
|
||||
switch (mode) {
|
||||
case AnalogOutputMode_COARSE:
|
||||
return *new CoarseLowPassFilter(oldMT32AnalogLPF);
|
||||
case AnalogOutputMode_ACCURATE:
|
||||
return *new AccurateLowPassFilter(oldMT32AnalogLPF, false);
|
||||
case AnalogOutputMode_OVERSAMPLED:
|
||||
return *new AccurateLowPassFilter(oldMT32AnalogLPF, true);
|
||||
default:
|
||||
return *new NullLowPassFilter;
|
||||
}
|
||||
}
|
||||
|
||||
bool AbstractLowPassFilter::hasNextSample() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned int AbstractLowPassFilter::getOutputSampleRate() const {
|
||||
return SAMPLE_RATE;
|
||||
}
|
||||
|
||||
unsigned int AbstractLowPassFilter::estimateInSampleCount(unsigned int outSamples) const {
|
||||
return outSamples;
|
||||
}
|
||||
|
||||
SampleEx NullLowPassFilter::process(const SampleEx inSample) {
|
||||
return inSample;
|
||||
}
|
||||
|
||||
CoarseLowPassFilter::CoarseLowPassFilter(bool oldMT32AnalogLPF) :
|
||||
LPF_TAPS(oldMT32AnalogLPF ? COARSE_LPF_TAPS_MT32 : COARSE_LPF_TAPS_CM32L),
|
||||
ringBufferPosition(0)
|
||||
{
|
||||
Synth::muteSampleBuffer(ringBuffer, COARSE_LPF_DELAY_LINE_LENGTH);
|
||||
}
|
||||
|
||||
SampleEx CoarseLowPassFilter::process(const SampleEx inSample) {
|
||||
static const unsigned int DELAY_LINE_MASK = COARSE_LPF_DELAY_LINE_LENGTH - 1;
|
||||
|
||||
SampleEx sample = LPF_TAPS[COARSE_LPF_DELAY_LINE_LENGTH] * ringBuffer[ringBufferPosition];
|
||||
ringBuffer[ringBufferPosition] = Synth::clipSampleEx(inSample);
|
||||
|
||||
for (unsigned int i = 0; i < COARSE_LPF_DELAY_LINE_LENGTH; i++) {
|
||||
sample += LPF_TAPS[i] * ringBuffer[(i + ringBufferPosition) & DELAY_LINE_MASK];
|
||||
}
|
||||
|
||||
ringBufferPosition = (ringBufferPosition - 1) & DELAY_LINE_MASK;
|
||||
|
||||
#if !MT32EMU_USE_FLOAT_SAMPLES
|
||||
sample >>= COARSE_LPF_FRACTION_BITS;
|
||||
#endif
|
||||
|
||||
return sample;
|
||||
}
|
||||
|
||||
AccurateLowPassFilter::AccurateLowPassFilter(const bool oldMT32AnalogLPF, const bool oversample) :
|
||||
LPF_TAPS(oldMT32AnalogLPF ? ACCURATE_LPF_TAPS_MT32 : ACCURATE_LPF_TAPS_CM32L),
|
||||
deltas(oversample ? ACCURATE_LPF_DELTAS_OVERSAMPLED : ACCURATE_LPF_DELTAS_REGULAR),
|
||||
phaseIncrement(oversample ? ACCURATE_LPF_PHASE_INCREMENT_OVERSAMPLED : ACCURATE_LPF_PHASE_INCREMENT_REGULAR),
|
||||
outputSampleRate(SAMPLE_RATE * ACCURATE_LPF_NUMBER_OF_PHASES / phaseIncrement),
|
||||
ringBufferPosition(0),
|
||||
phase(0)
|
||||
{
|
||||
Synth::muteSampleBuffer(ringBuffer, ACCURATE_LPF_DELAY_LINE_LENGTH);
|
||||
}
|
||||
|
||||
SampleEx AccurateLowPassFilter::process(const SampleEx inSample) {
|
||||
static const unsigned int DELAY_LINE_MASK = ACCURATE_LPF_DELAY_LINE_LENGTH - 1;
|
||||
|
||||
float sample = (phase == 0) ? LPF_TAPS[ACCURATE_LPF_DELAY_LINE_LENGTH * ACCURATE_LPF_NUMBER_OF_PHASES] * ringBuffer[ringBufferPosition] : 0.0f;
|
||||
if (!hasNextSample()) {
|
||||
ringBuffer[ringBufferPosition] = inSample;
|
||||
}
|
||||
|
||||
for (unsigned int tapIx = phase, delaySampleIx = 0; delaySampleIx < ACCURATE_LPF_DELAY_LINE_LENGTH; delaySampleIx++, tapIx += ACCURATE_LPF_NUMBER_OF_PHASES) {
|
||||
sample += LPF_TAPS[tapIx] * ringBuffer[(delaySampleIx + ringBufferPosition) & DELAY_LINE_MASK];
|
||||
}
|
||||
|
||||
phase += phaseIncrement;
|
||||
if (ACCURATE_LPF_NUMBER_OF_PHASES <= phase) {
|
||||
phase -= ACCURATE_LPF_NUMBER_OF_PHASES;
|
||||
ringBufferPosition = (ringBufferPosition - 1) & DELAY_LINE_MASK;
|
||||
}
|
||||
|
||||
return SampleEx(ACCURATE_LPF_NUMBER_OF_PHASES * sample);
|
||||
}
|
||||
|
||||
bool AccurateLowPassFilter::hasNextSample() const {
|
||||
return phaseIncrement <= phase;
|
||||
}
|
||||
|
||||
unsigned int AccurateLowPassFilter::getOutputSampleRate() const {
|
||||
return outputSampleRate;
|
||||
}
|
||||
|
||||
unsigned int AccurateLowPassFilter::estimateInSampleCount(unsigned int outSamples) const {
|
||||
Bit32u cycleCount = outSamples / ACCURATE_LPF_NUMBER_OF_PHASES;
|
||||
Bit32u remainder = outSamples - cycleCount * ACCURATE_LPF_NUMBER_OF_PHASES;
|
||||
return cycleCount * phaseIncrement + deltas[remainder][phase];
|
||||
}
|
||||
|
||||
void AccurateLowPassFilter::addPositionIncrement(const unsigned int positionIncrement) {
|
||||
phase = (phase + positionIncrement * phaseIncrement) % ACCURATE_LPF_NUMBER_OF_PHASES;
|
||||
}
|
||||
|
||||
} // namespace MT32Emu
|
@ -1,60 +0,0 @@
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MT32EMU_ANALOG_H
|
||||
#define MT32EMU_ANALOG_H
|
||||
|
||||
#include "globals.h"
|
||||
#include "internals.h"
|
||||
#include "Types.h"
|
||||
#include "Enumerations.h"
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
class AbstractLowPassFilter;
|
||||
|
||||
/* Analog class is dedicated to perform fair emulation of analogue circuitry of hardware units that is responsible
|
||||
* for processing output signal after the DAC. It appears that the analogue circuit labeled "LPF" on the schematic
|
||||
* also applies audible changes to the signal spectra. There is a significant boost of higher frequencies observed
|
||||
* aside from quite poor attenuation of the mirror spectra above 16 kHz which is due to a relatively low filter order.
|
||||
*
|
||||
* As the final mixing of multiplexed output signal is performed after the DAC, this function is migrated here from Synth.
|
||||
* Saying precisely, mixing is performed within the LPF as the entrance resistors are actually components of a LPF
|
||||
* designed using the multiple feedback topology. Nevertheless, the schematic separates them.
|
||||
*/
|
||||
class Analog {
|
||||
public:
|
||||
Analog(const AnalogOutputMode mode, const bool oldMT32AnalogLPF);
|
||||
~Analog();
|
||||
void process(Sample *outStream, const Sample *nonReverbLeft, const Sample *nonReverbRight, const Sample *reverbDryLeft, const Sample *reverbDryRight, const Sample *reverbWetLeft, const Sample *reverbWetRight, Bit32u outLength);
|
||||
unsigned int getOutputSampleRate() const;
|
||||
Bit32u getDACStreamsLength(Bit32u outputLength) const;
|
||||
void setSynthOutputGain(float synthGain);
|
||||
void setReverbOutputGain(float reverbGain, bool mt32ReverbCompatibilityMode);
|
||||
|
||||
private:
|
||||
AbstractLowPassFilter &leftChannelLPF;
|
||||
AbstractLowPassFilter &rightChannelLPF;
|
||||
SampleEx synthGain;
|
||||
SampleEx reverbGain;
|
||||
|
||||
Analog(Analog &);
|
||||
};
|
||||
|
||||
} // namespace MT32Emu
|
||||
|
||||
#endif // #ifndef MT32EMU_ANALOG_H
|
@ -1,531 +0,0 @@
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
#include "internals.h"
|
||||
|
||||
#include "BReverbModel.h"
|
||||
#include "Synth.h"
|
||||
|
||||
// Analysing of state of reverb RAM address lines gives exact sizes of the buffers of filters used. This also indicates that
|
||||
// the reverb model implemented in the real devices consists of three series allpass filters preceded by a non-feedback comb (or a delay with a LPF)
|
||||
// and followed by three parallel comb filters
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
// Because LA-32 chip makes it's output available to process by the Boss chip with a significant delay,
|
||||
// the Boss chip puts to the buffer the LA32 dry output when it is ready and performs processing of the _previously_ latched data.
|
||||
// Of course, the right way would be to use a dedicated variable for this, but our reverb model is way higher level,
|
||||
// so we can simply increase the input buffer size.
|
||||
static const Bit32u PROCESS_DELAY = 1;
|
||||
|
||||
static const Bit32u MODE_3_ADDITIONAL_DELAY = 1;
|
||||
static const Bit32u MODE_3_FEEDBACK_DELAY = 1;
|
||||
|
||||
// Default reverb settings for "new" reverb model implemented in CM-32L / LAPC-I.
|
||||
// Found by tracing reverb RAM data lines (thanks go to Lord_Nightmare & balrog).
|
||||
const BReverbSettings &BReverbModel::getCM32L_LAPCSettings(const ReverbMode mode) {
|
||||
static const Bit32u MODE_0_NUMBER_OF_ALLPASSES = 3;
|
||||
static const Bit32u MODE_0_ALLPASSES[] = {994, 729, 78};
|
||||
static const Bit32u MODE_0_NUMBER_OF_COMBS = 4; // Well, actually there are 3 comb filters, but the entrance LPF + delay can be processed via a hacked comb.
|
||||
static const Bit32u MODE_0_COMBS[] = {705 + PROCESS_DELAY, 2349, 2839, 3632};
|
||||
static const Bit32u MODE_0_OUTL[] = {2349, 141, 1960};
|
||||
static const Bit32u MODE_0_OUTR[] = {1174, 1570, 145};
|
||||
static const Bit8u MODE_0_COMB_FACTOR[] = {0xA0, 0x60, 0x60, 0x60};
|
||||
static const Bit8u MODE_0_COMB_FEEDBACK[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98,
|
||||
0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98,
|
||||
0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98};
|
||||
static const Bit8u MODE_0_DRY_AMP[] = {0xA0, 0xA0, 0xA0, 0xA0, 0xB0, 0xB0, 0xB0, 0xD0};
|
||||
static const Bit8u MODE_0_WET_AMP[] = {0x10, 0x30, 0x50, 0x70, 0x90, 0xC0, 0xF0, 0xF0};
|
||||
static const Bit8u MODE_0_LPF_AMP = 0x60;
|
||||
|
||||
static const Bit32u MODE_1_NUMBER_OF_ALLPASSES = 3;
|
||||
static const Bit32u MODE_1_ALLPASSES[] = {1324, 809, 176};
|
||||
static const Bit32u MODE_1_NUMBER_OF_COMBS = 4; // Same as for mode 0 above
|
||||
static const Bit32u MODE_1_COMBS[] = {961 + PROCESS_DELAY, 2619, 3545, 4519};
|
||||
static const Bit32u MODE_1_OUTL[] = {2618, 1760, 4518};
|
||||
static const Bit32u MODE_1_OUTR[] = {1300, 3532, 2274};
|
||||
static const Bit8u MODE_1_COMB_FACTOR[] = {0x80, 0x60, 0x60, 0x60};
|
||||
static const Bit8u MODE_1_COMB_FEEDBACK[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x28, 0x48, 0x60, 0x70, 0x78, 0x80, 0x90, 0x98,
|
||||
0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98,
|
||||
0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98};
|
||||
static const Bit8u MODE_1_DRY_AMP[] = {0xA0, 0xA0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xE0};
|
||||
static const Bit8u MODE_1_WET_AMP[] = {0x10, 0x30, 0x50, 0x70, 0x90, 0xC0, 0xF0, 0xF0};
|
||||
static const Bit8u MODE_1_LPF_AMP = 0x60;
|
||||
|
||||
static const Bit32u MODE_2_NUMBER_OF_ALLPASSES = 3;
|
||||
static const Bit32u MODE_2_ALLPASSES[] = {969, 644, 157};
|
||||
static const Bit32u MODE_2_NUMBER_OF_COMBS = 4; // Same as for mode 0 above
|
||||
static const Bit32u MODE_2_COMBS[] = {116 + PROCESS_DELAY, 2259, 2839, 3539};
|
||||
static const Bit32u MODE_2_OUTL[] = {2259, 718, 1769};
|
||||
static const Bit32u MODE_2_OUTR[] = {1136, 2128, 1};
|
||||
static const Bit8u MODE_2_COMB_FACTOR[] = {0, 0x20, 0x20, 0x20};
|
||||
static const Bit8u MODE_2_COMB_FEEDBACK[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x30, 0x58, 0x78, 0x88, 0xA0, 0xB8, 0xC0, 0xD0,
|
||||
0x30, 0x58, 0x78, 0x88, 0xA0, 0xB8, 0xC0, 0xD0,
|
||||
0x30, 0x58, 0x78, 0x88, 0xA0, 0xB8, 0xC0, 0xD0};
|
||||
static const Bit8u MODE_2_DRY_AMP[] = {0xA0, 0xA0, 0xB0, 0xB0, 0xB0, 0xB0, 0xC0, 0xE0};
|
||||
static const Bit8u MODE_2_WET_AMP[] = {0x10, 0x30, 0x50, 0x70, 0x90, 0xC0, 0xF0, 0xF0};
|
||||
static const Bit8u MODE_2_LPF_AMP = 0x80;
|
||||
|
||||
static const Bit32u MODE_3_NUMBER_OF_ALLPASSES = 0;
|
||||
static const Bit32u MODE_3_NUMBER_OF_COMBS = 1;
|
||||
static const Bit32u MODE_3_DELAY[] = {16000 + MODE_3_FEEDBACK_DELAY + PROCESS_DELAY + MODE_3_ADDITIONAL_DELAY};
|
||||
static const Bit32u MODE_3_OUTL[] = {400, 624, 960, 1488, 2256, 3472, 5280, 8000};
|
||||
static const Bit32u MODE_3_OUTR[] = {800, 1248, 1920, 2976, 4512, 6944, 10560, 16000};
|
||||
static const Bit8u MODE_3_COMB_FACTOR[] = {0x68};
|
||||
static const Bit8u MODE_3_COMB_FEEDBACK[] = {0x68, 0x60};
|
||||
static const Bit8u MODE_3_DRY_AMP[] = {0x20, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50,
|
||||
0x20, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50};
|
||||
static const Bit8u MODE_3_WET_AMP[] = {0x18, 0x18, 0x28, 0x40, 0x60, 0x80, 0xA8, 0xF8};
|
||||
|
||||
static const BReverbSettings REVERB_MODE_0_SETTINGS = {MODE_0_NUMBER_OF_ALLPASSES, MODE_0_ALLPASSES, MODE_0_NUMBER_OF_COMBS, MODE_0_COMBS, MODE_0_OUTL, MODE_0_OUTR, MODE_0_COMB_FACTOR, MODE_0_COMB_FEEDBACK, MODE_0_DRY_AMP, MODE_0_WET_AMP, MODE_0_LPF_AMP};
|
||||
static const BReverbSettings REVERB_MODE_1_SETTINGS = {MODE_1_NUMBER_OF_ALLPASSES, MODE_1_ALLPASSES, MODE_1_NUMBER_OF_COMBS, MODE_1_COMBS, MODE_1_OUTL, MODE_1_OUTR, MODE_1_COMB_FACTOR, MODE_1_COMB_FEEDBACK, MODE_1_DRY_AMP, MODE_1_WET_AMP, MODE_1_LPF_AMP};
|
||||
static const BReverbSettings REVERB_MODE_2_SETTINGS = {MODE_2_NUMBER_OF_ALLPASSES, MODE_2_ALLPASSES, MODE_2_NUMBER_OF_COMBS, MODE_2_COMBS, MODE_2_OUTL, MODE_2_OUTR, MODE_2_COMB_FACTOR, MODE_2_COMB_FEEDBACK, MODE_2_DRY_AMP, MODE_2_WET_AMP, MODE_2_LPF_AMP};
|
||||
static const BReverbSettings REVERB_MODE_3_SETTINGS = {MODE_3_NUMBER_OF_ALLPASSES, NULL, MODE_3_NUMBER_OF_COMBS, MODE_3_DELAY, MODE_3_OUTL, MODE_3_OUTR, MODE_3_COMB_FACTOR, MODE_3_COMB_FEEDBACK, MODE_3_DRY_AMP, MODE_3_WET_AMP, 0};
|
||||
|
||||
static const BReverbSettings * const REVERB_SETTINGS[] = {&REVERB_MODE_0_SETTINGS, &REVERB_MODE_1_SETTINGS, &REVERB_MODE_2_SETTINGS, &REVERB_MODE_3_SETTINGS};
|
||||
|
||||
return *REVERB_SETTINGS[mode];
|
||||
}
|
||||
|
||||
// Default reverb settings for "old" reverb model implemented in MT-32.
|
||||
// Found by tracing reverb RAM data lines (thanks go to Lord_Nightmare & balrog).
|
||||
const BReverbSettings &BReverbModel::getMT32Settings(const ReverbMode mode) {
|
||||
static const Bit32u MODE_0_NUMBER_OF_ALLPASSES = 3;
|
||||
static const Bit32u MODE_0_ALLPASSES[] = {994, 729, 78};
|
||||
static const Bit32u MODE_0_NUMBER_OF_COMBS = 4; // Same as above in the new model implementation
|
||||
static const Bit32u MODE_0_COMBS[] = {575 + PROCESS_DELAY, 2040, 2752, 3629};
|
||||
static const Bit32u MODE_0_OUTL[] = {2040, 687, 1814};
|
||||
static const Bit32u MODE_0_OUTR[] = {1019, 2072, 1};
|
||||
static const Bit8u MODE_0_COMB_FACTOR[] = {0xB0, 0x60, 0x60, 0x60};
|
||||
static const Bit8u MODE_0_COMB_FEEDBACK[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x28, 0x48, 0x60, 0x70, 0x78, 0x80, 0x90, 0x98,
|
||||
0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98,
|
||||
0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98};
|
||||
static const Bit8u MODE_0_DRY_AMP[] = {0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80};
|
||||
static const Bit8u MODE_0_WET_AMP[] = {0x10, 0x20, 0x30, 0x40, 0x50, 0x70, 0xA0, 0xE0};
|
||||
static const Bit8u MODE_0_LPF_AMP = 0x80;
|
||||
|
||||
static const Bit32u MODE_1_NUMBER_OF_ALLPASSES = 3;
|
||||
static const Bit32u MODE_1_ALLPASSES[] = {1324, 809, 176};
|
||||
static const Bit32u MODE_1_NUMBER_OF_COMBS = 4; // Same as above in the new model implementation
|
||||
static const Bit32u MODE_1_COMBS[] = {961 + PROCESS_DELAY, 2619, 3545, 4519};
|
||||
static const Bit32u MODE_1_OUTL[] = {2618, 1760, 4518};
|
||||
static const Bit32u MODE_1_OUTR[] = {1300, 3532, 2274};
|
||||
static const Bit8u MODE_1_COMB_FACTOR[] = {0x90, 0x60, 0x60, 0x60};
|
||||
static const Bit8u MODE_1_COMB_FEEDBACK[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x28, 0x48, 0x60, 0x70, 0x78, 0x80, 0x90, 0x98,
|
||||
0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98,
|
||||
0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98};
|
||||
static const Bit8u MODE_1_DRY_AMP[] = {0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80};
|
||||
static const Bit8u MODE_1_WET_AMP[] = {0x10, 0x20, 0x30, 0x40, 0x50, 0x70, 0xA0, 0xE0};
|
||||
static const Bit8u MODE_1_LPF_AMP = 0x80;
|
||||
|
||||
static const Bit32u MODE_2_NUMBER_OF_ALLPASSES = 3;
|
||||
static const Bit32u MODE_2_ALLPASSES[] = {969, 644, 157};
|
||||
static const Bit32u MODE_2_NUMBER_OF_COMBS = 4; // Same as above in the new model implementation
|
||||
static const Bit32u MODE_2_COMBS[] = {116 + PROCESS_DELAY, 2259, 2839, 3539};
|
||||
static const Bit32u MODE_2_OUTL[] = {2259, 718, 1769};
|
||||
static const Bit32u MODE_2_OUTR[] = {1136, 2128, 1};
|
||||
static const Bit8u MODE_2_COMB_FACTOR[] = {0, 0x60, 0x60, 0x60};
|
||||
static const Bit8u MODE_2_COMB_FEEDBACK[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x28, 0x48, 0x60, 0x70, 0x78, 0x80, 0x90, 0x98,
|
||||
0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98,
|
||||
0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98};
|
||||
static const Bit8u MODE_2_DRY_AMP[] = {0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80};
|
||||
static const Bit8u MODE_2_WET_AMP[] = {0x10, 0x20, 0x30, 0x40, 0x50, 0x70, 0xA0, 0xE0};
|
||||
static const Bit8u MODE_2_LPF_AMP = 0x80;
|
||||
|
||||
static const Bit32u MODE_3_NUMBER_OF_ALLPASSES = 0;
|
||||
static const Bit32u MODE_3_NUMBER_OF_COMBS = 1;
|
||||
static const Bit32u MODE_3_DELAY[] = {16000 + MODE_3_FEEDBACK_DELAY + PROCESS_DELAY + MODE_3_ADDITIONAL_DELAY};
|
||||
static const Bit32u MODE_3_OUTL[] = {400, 624, 960, 1488, 2256, 3472, 5280, 8000};
|
||||
static const Bit32u MODE_3_OUTR[] = {800, 1248, 1920, 2976, 4512, 6944, 10560, 16000};
|
||||
static const Bit8u MODE_3_COMB_FACTOR[] = {0x68};
|
||||
static const Bit8u MODE_3_COMB_FEEDBACK[] = {0x68, 0x60};
|
||||
static const Bit8u MODE_3_DRY_AMP[] = {0x10, 0x10, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x10, 0x20, 0x20, 0x10, 0x20, 0x10, 0x20, 0x10};
|
||||
static const Bit8u MODE_3_WET_AMP[] = {0x08, 0x18, 0x28, 0x40, 0x60, 0x80, 0xA8, 0xF8};
|
||||
|
||||
static const BReverbSettings REVERB_MODE_0_SETTINGS = {MODE_0_NUMBER_OF_ALLPASSES, MODE_0_ALLPASSES, MODE_0_NUMBER_OF_COMBS, MODE_0_COMBS, MODE_0_OUTL, MODE_0_OUTR, MODE_0_COMB_FACTOR, MODE_0_COMB_FEEDBACK, MODE_0_DRY_AMP, MODE_0_WET_AMP, MODE_0_LPF_AMP};
|
||||
static const BReverbSettings REVERB_MODE_1_SETTINGS = {MODE_1_NUMBER_OF_ALLPASSES, MODE_1_ALLPASSES, MODE_1_NUMBER_OF_COMBS, MODE_1_COMBS, MODE_1_OUTL, MODE_1_OUTR, MODE_1_COMB_FACTOR, MODE_1_COMB_FEEDBACK, MODE_1_DRY_AMP, MODE_1_WET_AMP, MODE_1_LPF_AMP};
|
||||
static const BReverbSettings REVERB_MODE_2_SETTINGS = {MODE_2_NUMBER_OF_ALLPASSES, MODE_2_ALLPASSES, MODE_2_NUMBER_OF_COMBS, MODE_2_COMBS, MODE_2_OUTL, MODE_2_OUTR, MODE_2_COMB_FACTOR, MODE_2_COMB_FEEDBACK, MODE_2_DRY_AMP, MODE_2_WET_AMP, MODE_2_LPF_AMP};
|
||||
static const BReverbSettings REVERB_MODE_3_SETTINGS = {MODE_3_NUMBER_OF_ALLPASSES, NULL, MODE_3_NUMBER_OF_COMBS, MODE_3_DELAY, MODE_3_OUTL, MODE_3_OUTR, MODE_3_COMB_FACTOR, MODE_3_COMB_FEEDBACK, MODE_3_DRY_AMP, MODE_3_WET_AMP, 0};
|
||||
|
||||
static const BReverbSettings * const REVERB_SETTINGS[] = {&REVERB_MODE_0_SETTINGS, &REVERB_MODE_1_SETTINGS, &REVERB_MODE_2_SETTINGS, &REVERB_MODE_3_SETTINGS};
|
||||
|
||||
return *REVERB_SETTINGS[mode];
|
||||
}
|
||||
|
||||
// This algorithm tries to emulate exactly Boss multiplication operation (at least this is what we see on reverb RAM data lines).
|
||||
// Also LA32 is suspected to use the similar one to perform PCM interpolation and ring modulation.
|
||||
static Sample weirdMul(Sample a, Bit8u addMask, Bit8u carryMask) {
|
||||
(void)carryMask;
|
||||
#if MT32EMU_USE_FLOAT_SAMPLES
|
||||
return a * addMask / 256.0f;
|
||||
#elif MT32EMU_BOSS_REVERB_PRECISE_MODE
|
||||
Bit8u mask = 0x80;
|
||||
Bit32s res = 0;
|
||||
for (int i = 0; i < 8; i++) {
|
||||
Bit32s carry = (a < 0) && (mask & carryMask) > 0 ? a & 1 : 0;
|
||||
a >>= 1;
|
||||
res += (mask & addMask) > 0 ? a + carry : 0;
|
||||
mask >>= 1;
|
||||
}
|
||||
return res;
|
||||
#else
|
||||
return Sample((Bit32s(a) * addMask) >> 8);
|
||||
#endif
|
||||
}
|
||||
|
||||
RingBuffer::RingBuffer(Bit32u newsize) : size(newsize), index(0) {
|
||||
buffer = new Sample[size];
|
||||
}
|
||||
|
||||
RingBuffer::~RingBuffer() {
|
||||
delete[] buffer;
|
||||
buffer = NULL;
|
||||
}
|
||||
|
||||
Sample RingBuffer::next() {
|
||||
if (++index >= size) {
|
||||
index = 0;
|
||||
}
|
||||
return buffer[index];
|
||||
}
|
||||
|
||||
bool RingBuffer::isEmpty() const {
|
||||
if (buffer == NULL) return true;
|
||||
|
||||
#if MT32EMU_USE_FLOAT_SAMPLES
|
||||
Sample max = 0.001f;
|
||||
#else
|
||||
Sample max = 8;
|
||||
#endif
|
||||
Sample *buf = buffer;
|
||||
for (Bit32u i = 0; i < size; i++) {
|
||||
if (*buf < -max || *buf > max) return false;
|
||||
buf++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void RingBuffer::mute() {
|
||||
Synth::muteSampleBuffer(buffer, size);
|
||||
}
|
||||
|
||||
AllpassFilter::AllpassFilter(const Bit32u useSize) : RingBuffer(useSize) {}
|
||||
|
||||
Sample AllpassFilter::process(const Sample in) {
|
||||
// This model corresponds to the allpass filter implementation of the real CM-32L device
|
||||
// found from sample analysis
|
||||
|
||||
const Sample bufferOut = next();
|
||||
|
||||
#if MT32EMU_USE_FLOAT_SAMPLES
|
||||
// store input - feedback / 2
|
||||
buffer[index] = in - 0.5f * bufferOut;
|
||||
|
||||
// return buffer output + feedforward / 2
|
||||
return bufferOut + 0.5f * buffer[index];
|
||||
#else
|
||||
// store input - feedback / 2
|
||||
buffer[index] = in - (bufferOut >> 1);
|
||||
|
||||
// return buffer output + feedforward / 2
|
||||
return bufferOut + (buffer[index] >> 1);
|
||||
#endif
|
||||
}
|
||||
|
||||
CombFilter::CombFilter(const Bit32u useSize, const Bit8u useFilterFactor) : RingBuffer(useSize), filterFactor(useFilterFactor) {}
|
||||
|
||||
void CombFilter::process(const Sample in) {
|
||||
// This model corresponds to the comb filter implementation of the real CM-32L device
|
||||
|
||||
// the previously stored value
|
||||
const Sample last = buffer[index];
|
||||
|
||||
// prepare input + feedback
|
||||
const Sample filterIn = in + weirdMul(next(), feedbackFactor, 0xF0);
|
||||
|
||||
// store input + feedback processed by a low-pass filter
|
||||
buffer[index] = weirdMul(last, filterFactor, 0xC0) - filterIn;
|
||||
}
|
||||
|
||||
Sample CombFilter::getOutputAt(const Bit32u outIndex) const {
|
||||
return buffer[(size + index - outIndex) % size];
|
||||
}
|
||||
|
||||
void CombFilter::setFeedbackFactor(const Bit8u useFeedbackFactor) {
|
||||
feedbackFactor = useFeedbackFactor;
|
||||
}
|
||||
|
||||
DelayWithLowPassFilter::DelayWithLowPassFilter(const Bit32u useSize, const Bit8u useFilterFactor, const Bit8u useAmp)
|
||||
: CombFilter(useSize, useFilterFactor), amp(useAmp) {}
|
||||
|
||||
void DelayWithLowPassFilter::process(const Sample in) {
|
||||
// the previously stored value
|
||||
const Sample last = buffer[index];
|
||||
|
||||
// move to the next index
|
||||
next();
|
||||
|
||||
// low-pass filter process
|
||||
Sample lpfOut = weirdMul(last, filterFactor, 0xFF) + in;
|
||||
|
||||
// store lpfOut multiplied by LPF amp factor
|
||||
buffer[index] = weirdMul(lpfOut, amp, 0xFF);
|
||||
}
|
||||
|
||||
TapDelayCombFilter::TapDelayCombFilter(const Bit32u useSize, const Bit8u useFilterFactor) : CombFilter(useSize, useFilterFactor) {}
|
||||
|
||||
void TapDelayCombFilter::process(const Sample in) {
|
||||
// the previously stored value
|
||||
const Sample last = buffer[index];
|
||||
|
||||
// move to the next index
|
||||
next();
|
||||
|
||||
// prepare input + feedback
|
||||
// Actually, the size of the filter varies with the TIME parameter, the feedback sample is taken from the position just below the right output
|
||||
const Sample filterIn = in + weirdMul(getOutputAt(outR + MODE_3_FEEDBACK_DELAY), feedbackFactor, 0xF0);
|
||||
|
||||
// store input + feedback processed by a low-pass filter
|
||||
buffer[index] = weirdMul(last, filterFactor, 0xF0) - filterIn;
|
||||
}
|
||||
|
||||
Sample TapDelayCombFilter::getLeftOutput() const {
|
||||
return getOutputAt(outL + PROCESS_DELAY + MODE_3_ADDITIONAL_DELAY);
|
||||
}
|
||||
|
||||
Sample TapDelayCombFilter::getRightOutput() const {
|
||||
return getOutputAt(outR + PROCESS_DELAY + MODE_3_ADDITIONAL_DELAY);
|
||||
}
|
||||
|
||||
void TapDelayCombFilter::setOutputPositions(const Bit32u useOutL, const Bit32u useOutR) {
|
||||
outL = useOutL;
|
||||
outR = useOutR;
|
||||
}
|
||||
|
||||
BReverbModel::BReverbModel(const ReverbMode mode, const bool mt32CompatibleModel) :
|
||||
allpasses(NULL), combs(NULL),
|
||||
currentSettings(mt32CompatibleModel ? getMT32Settings(mode) : getCM32L_LAPCSettings(mode)),
|
||||
tapDelayMode(mode == REVERB_MODE_TAP_DELAY) {}
|
||||
|
||||
BReverbModel::~BReverbModel() {
|
||||
close();
|
||||
}
|
||||
|
||||
void BReverbModel::open() {
|
||||
if (currentSettings.numberOfAllpasses > 0) {
|
||||
allpasses = new AllpassFilter*[currentSettings.numberOfAllpasses];
|
||||
for (Bit32u i = 0; i < currentSettings.numberOfAllpasses; i++) {
|
||||
allpasses[i] = new AllpassFilter(currentSettings.allpassSizes[i]);
|
||||
}
|
||||
}
|
||||
combs = new CombFilter*[currentSettings.numberOfCombs];
|
||||
if (tapDelayMode) {
|
||||
*combs = new TapDelayCombFilter(*currentSettings.combSizes, *currentSettings.filterFactors);
|
||||
} else {
|
||||
combs[0] = new DelayWithLowPassFilter(currentSettings.combSizes[0], currentSettings.filterFactors[0], currentSettings.lpfAmp);
|
||||
for (Bit32u i = 1; i < currentSettings.numberOfCombs; i++) {
|
||||
combs[i] = new CombFilter(currentSettings.combSizes[i], currentSettings.filterFactors[i]);
|
||||
}
|
||||
}
|
||||
mute();
|
||||
}
|
||||
|
||||
void BReverbModel::close() {
|
||||
if (allpasses != NULL) {
|
||||
for (Bit32u i = 0; i < currentSettings.numberOfAllpasses; i++) {
|
||||
if (allpasses[i] != NULL) {
|
||||
delete allpasses[i];
|
||||
allpasses[i] = NULL;
|
||||
}
|
||||
}
|
||||
delete[] allpasses;
|
||||
allpasses = NULL;
|
||||
}
|
||||
if (combs != NULL) {
|
||||
for (Bit32u i = 0; i < currentSettings.numberOfCombs; i++) {
|
||||
if (combs[i] != NULL) {
|
||||
delete combs[i];
|
||||
combs[i] = NULL;
|
||||
}
|
||||
}
|
||||
delete[] combs;
|
||||
combs = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void BReverbModel::mute() {
|
||||
if (allpasses != NULL) {
|
||||
for (Bit32u i = 0; i < currentSettings.numberOfAllpasses; i++) {
|
||||
allpasses[i]->mute();
|
||||
}
|
||||
}
|
||||
if (combs != NULL) {
|
||||
for (Bit32u i = 0; i < currentSettings.numberOfCombs; i++) {
|
||||
combs[i]->mute();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BReverbModel::setParameters(Bit8u time, Bit8u level) {
|
||||
if (combs == NULL) return;
|
||||
level &= 7;
|
||||
time &= 7;
|
||||
if (tapDelayMode) {
|
||||
TapDelayCombFilter *comb = static_cast<TapDelayCombFilter *> (*combs);
|
||||
comb->setOutputPositions(currentSettings.outLPositions[time], currentSettings.outRPositions[time & 7]);
|
||||
comb->setFeedbackFactor(currentSettings.feedbackFactors[((level < 3) || (time < 6)) ? 0 : 1]);
|
||||
} else {
|
||||
for (Bit32u i = 0; i < currentSettings.numberOfCombs; i++) {
|
||||
combs[i]->setFeedbackFactor(currentSettings.feedbackFactors[(i << 3) + time]);
|
||||
}
|
||||
}
|
||||
if (time == 0 && level == 0) {
|
||||
dryAmp = wetLevel = 0;
|
||||
} else {
|
||||
if (tapDelayMode && ((time == 0) || (time == 1 && level == 1))) {
|
||||
// Looks like MT-32 implementation has some minor quirks in this mode:
|
||||
// for odd level values, the output level changes sometimes depending on the time value which doesn't seem right.
|
||||
dryAmp = currentSettings.dryAmps[level + 8];
|
||||
} else {
|
||||
dryAmp = currentSettings.dryAmps[level];
|
||||
}
|
||||
wetLevel = currentSettings.wetLevels[level];
|
||||
}
|
||||
}
|
||||
|
||||
bool BReverbModel::isActive() const {
|
||||
if (combs == NULL) {
|
||||
return false;
|
||||
}
|
||||
for (Bit32u i = 0; i < currentSettings.numberOfAllpasses; i++) {
|
||||
if (!allpasses[i]->isEmpty()) return true;
|
||||
}
|
||||
for (Bit32u i = 0; i < currentSettings.numberOfCombs; i++) {
|
||||
if (!combs[i]->isEmpty()) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool BReverbModel::isMT32Compatible(const ReverbMode mode) const {
|
||||
return ¤tSettings == &getMT32Settings(mode);
|
||||
}
|
||||
|
||||
void BReverbModel::process(const Sample *inLeft, const Sample *inRight, Sample *outLeft, Sample *outRight, Bit32u numSamples) {
|
||||
if (combs == NULL) {
|
||||
Synth::muteSampleBuffer(outLeft, numSamples);
|
||||
Synth::muteSampleBuffer(outRight, numSamples);
|
||||
return;
|
||||
}
|
||||
|
||||
Sample dry;
|
||||
|
||||
while ((numSamples--) > 0) {
|
||||
if (tapDelayMode) {
|
||||
#if MT32EMU_USE_FLOAT_SAMPLES
|
||||
dry = (*(inLeft++) * 0.5f) + (*(inRight++) * 0.5f);
|
||||
#else
|
||||
dry = (*(inLeft++) >> 1) + (*(inRight++) >> 1);
|
||||
#endif
|
||||
} else {
|
||||
#if MT32EMU_USE_FLOAT_SAMPLES
|
||||
dry = (*(inLeft++) * 0.25f) + (*(inRight++) * 0.25f);
|
||||
#elif MT32EMU_BOSS_REVERB_PRECISE_MODE
|
||||
dry = (*(inLeft++) >> 1) / 2 + (*(inRight++) >> 1) / 2;
|
||||
#else
|
||||
dry = (*(inLeft++) >> 2) + (*(inRight++) >> 2);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Looks like dryAmp doesn't change in MT-32 but it does in CM-32L / LAPC-I
|
||||
dry = weirdMul(dry, dryAmp, 0xFF);
|
||||
|
||||
if (tapDelayMode) {
|
||||
TapDelayCombFilter *comb = static_cast<TapDelayCombFilter *> (*combs);
|
||||
comb->process(dry);
|
||||
if (outLeft != NULL) {
|
||||
*(outLeft++) = weirdMul(comb->getLeftOutput(), wetLevel, 0xFF);
|
||||
}
|
||||
if (outRight != NULL) {
|
||||
*(outRight++) = weirdMul(comb->getRightOutput(), wetLevel, 0xFF);
|
||||
}
|
||||
} else {
|
||||
// If the output position is equal to the comb size, get it now in order not to loose it
|
||||
Sample link = combs[0]->getOutputAt(currentSettings.combSizes[0] - 1);
|
||||
|
||||
// Entrance LPF. Note, comb.process() differs a bit here.
|
||||
combs[0]->process(dry);
|
||||
|
||||
#if !MT32EMU_USE_FLOAT_SAMPLES
|
||||
// This introduces reverb noise which actually makes output from the real Boss chip nondeterministic
|
||||
link = link - 1;
|
||||
#endif
|
||||
link = allpasses[0]->process(link);
|
||||
link = allpasses[1]->process(link);
|
||||
link = allpasses[2]->process(link);
|
||||
|
||||
// If the output position is equal to the comb size, get it now in order not to loose it
|
||||
Sample outL1 = combs[1]->getOutputAt(currentSettings.outLPositions[0] - 1);
|
||||
|
||||
combs[1]->process(link);
|
||||
combs[2]->process(link);
|
||||
combs[3]->process(link);
|
||||
|
||||
if (outLeft != NULL) {
|
||||
Sample outL2 = combs[2]->getOutputAt(currentSettings.outLPositions[1]);
|
||||
Sample outL3 = combs[3]->getOutputAt(currentSettings.outLPositions[2]);
|
||||
#if MT32EMU_USE_FLOAT_SAMPLES
|
||||
Sample outSample = 1.5f * (outL1 + outL2) + outL3;
|
||||
#elif MT32EMU_BOSS_REVERB_PRECISE_MODE
|
||||
/* NOTE:
|
||||
* Thanks to Mok for discovering, the adder in BOSS reverb chip is found to perform addition with saturation to avoid integer overflow.
|
||||
* Analysing of the algorithm suggests that the overflow is most probable when the combs output is added below.
|
||||
* So, despite this isn't actually accurate, we only add the check here for performance reasons.
|
||||
*/
|
||||
Sample outSample = Synth::clipSampleEx(Synth::clipSampleEx(Synth::clipSampleEx(Synth::clipSampleEx(SampleEx(outL1) + (SampleEx(outL1) >> 1)) + SampleEx(outL2)) + (SampleEx(outL2) >> 1)) + SampleEx(outL3));
|
||||
#else
|
||||
Sample outSample = Synth::clipSampleEx(SampleEx(outL1) + (SampleEx(outL1) >> 1) + SampleEx(outL2) + (SampleEx(outL2) >> 1) + SampleEx(outL3));
|
||||
#endif
|
||||
*(outLeft++) = weirdMul(outSample, wetLevel, 0xFF);
|
||||
}
|
||||
if (outRight != NULL) {
|
||||
Sample outR1 = combs[1]->getOutputAt(currentSettings.outRPositions[0]);
|
||||
Sample outR2 = combs[2]->getOutputAt(currentSettings.outRPositions[1]);
|
||||
Sample outR3 = combs[3]->getOutputAt(currentSettings.outRPositions[2]);
|
||||
#if MT32EMU_USE_FLOAT_SAMPLES
|
||||
Sample outSample = 1.5f * (outR1 + outR2) + outR3;
|
||||
#elif MT32EMU_BOSS_REVERB_PRECISE_MODE
|
||||
// See the note above for the left channel output.
|
||||
Sample outSample = Synth::clipSampleEx(Synth::clipSampleEx(Synth::clipSampleEx(Synth::clipSampleEx(SampleEx(outR1) + (SampleEx(outR1) >> 1)) + SampleEx(outR2)) + (SampleEx(outR2) >> 1)) + SampleEx(outR3));
|
||||
#else
|
||||
Sample outSample = Synth::clipSampleEx(SampleEx(outR1) + (SampleEx(outR1) >> 1) + SampleEx(outR2) + (SampleEx(outR2) >> 1) + SampleEx(outR3));
|
||||
#endif
|
||||
*(outRight++) = weirdMul(outSample, wetLevel, 0xFF);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace MT32Emu
|
@ -1,122 +0,0 @@
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MT32EMU_B_REVERB_MODEL_H
|
||||
#define MT32EMU_B_REVERB_MODEL_H
|
||||
|
||||
#include "globals.h"
|
||||
#include "internals.h"
|
||||
#include "Types.h"
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
struct BReverbSettings {
|
||||
const Bit32u numberOfAllpasses;
|
||||
const Bit32u * const allpassSizes;
|
||||
const Bit32u numberOfCombs;
|
||||
const Bit32u * const combSizes;
|
||||
const Bit32u * const outLPositions;
|
||||
const Bit32u * const outRPositions;
|
||||
const Bit8u * const filterFactors;
|
||||
const Bit8u * const feedbackFactors;
|
||||
const Bit8u * const dryAmps;
|
||||
const Bit8u * const wetLevels;
|
||||
const Bit8u lpfAmp;
|
||||
};
|
||||
|
||||
class RingBuffer {
|
||||
protected:
|
||||
Sample *buffer;
|
||||
const Bit32u size;
|
||||
Bit32u index;
|
||||
|
||||
public:
|
||||
RingBuffer(const Bit32u size);
|
||||
virtual ~RingBuffer();
|
||||
Sample next();
|
||||
bool isEmpty() const;
|
||||
void mute();
|
||||
};
|
||||
|
||||
class AllpassFilter : public RingBuffer {
|
||||
public:
|
||||
AllpassFilter(const Bit32u size);
|
||||
Sample process(const Sample in);
|
||||
};
|
||||
|
||||
class CombFilter : public RingBuffer {
|
||||
protected:
|
||||
const Bit8u filterFactor;
|
||||
Bit8u feedbackFactor;
|
||||
|
||||
public:
|
||||
CombFilter(const Bit32u size, const Bit8u useFilterFactor);
|
||||
virtual void process(const Sample in);
|
||||
Sample getOutputAt(const Bit32u outIndex) const;
|
||||
void setFeedbackFactor(const Bit8u useFeedbackFactor);
|
||||
};
|
||||
|
||||
class DelayWithLowPassFilter : public CombFilter {
|
||||
Bit8u amp;
|
||||
|
||||
public:
|
||||
DelayWithLowPassFilter(const Bit32u useSize, const Bit8u useFilterFactor, const Bit8u useAmp);
|
||||
void process(const Sample in);
|
||||
void setFeedbackFactor(const Bit8u) {}
|
||||
};
|
||||
|
||||
class TapDelayCombFilter : public CombFilter {
|
||||
Bit32u outL;
|
||||
Bit32u outR;
|
||||
|
||||
public:
|
||||
TapDelayCombFilter(const Bit32u useSize, const Bit8u useFilterFactor);
|
||||
void process(const Sample in);
|
||||
Sample getLeftOutput() const;
|
||||
Sample getRightOutput() const;
|
||||
void setOutputPositions(const Bit32u useOutL, const Bit32u useOutR);
|
||||
};
|
||||
|
||||
class BReverbModel {
|
||||
AllpassFilter **allpasses;
|
||||
CombFilter **combs;
|
||||
|
||||
const BReverbSettings ¤tSettings;
|
||||
const bool tapDelayMode;
|
||||
Bit8u dryAmp;
|
||||
Bit8u wetLevel;
|
||||
|
||||
static const BReverbSettings &getCM32L_LAPCSettings(const ReverbMode mode);
|
||||
static const BReverbSettings &getMT32Settings(const ReverbMode mode);
|
||||
|
||||
public:
|
||||
BReverbModel(const ReverbMode mode, const bool mt32CompatibleModel = false);
|
||||
~BReverbModel();
|
||||
// After construction or a close(), open() must be called at least once before any other call (with the exception of close()).
|
||||
void open();
|
||||
// May be called multiple times without an open() in between.
|
||||
void close();
|
||||
void mute();
|
||||
void setParameters(Bit8u time, Bit8u level);
|
||||
void process(const Sample *inLeft, const Sample *inRight, Sample *outLeft, Sample *outRight, Bit32u numSamples);
|
||||
bool isActive() const;
|
||||
bool isMT32Compatible(const ReverbMode mode) const;
|
||||
};
|
||||
|
||||
} // namespace MT32Emu
|
||||
|
||||
#endif // #ifndef MT32EMU_B_REVERB_MODEL_H
|
@ -1,155 +0,0 @@
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/* Using two guards since this file may be included twice with different MT32EMU_C_ENUMERATIONS define. */
|
||||
|
||||
#if (!defined MT32EMU_CPP_ENUMERATIONS_H && !defined MT32EMU_C_ENUMERATIONS) || (!defined MT32EMU_C_ENUMERATIONS_H && defined MT32EMU_C_ENUMERATIONS)
|
||||
|
||||
#ifdef MT32EMU_C_ENUMERATIONS
|
||||
|
||||
#define MT32EMU_C_ENUMERATIONS_H
|
||||
|
||||
#define MT32EMU_DAC_INPUT_MODE_NAME mt32emu_dac_input_mode
|
||||
#define MT32EMU_DAC_INPUT_MODE(ident) MT32EMU_DAC_##ident
|
||||
|
||||
#define MT32EMU_MIDI_DELAY_MODE_NAME mt32emu_midi_delay_mode
|
||||
#define MT32EMU_MIDI_DELAY_MODE(ident) MT32EMU_MDM_##ident
|
||||
|
||||
#define MT32EMU_ANALOG_OUTPUT_MODE_NAME mt32emu_analog_output_mode
|
||||
#define MT32EMU_ANALOG_OUTPUT_MODE(ident) MT32EMU_AOM_##ident
|
||||
|
||||
#define MT32EMU_PARTIAL_STATE_NAME mt32emu_partial_state
|
||||
#define MT32EMU_PARTIAL_STATE(ident) MT32EMU_PS_##ident
|
||||
|
||||
#else /* #ifdef MT32EMU_C_ENUMERATIONS */
|
||||
|
||||
#define MT32EMU_CPP_ENUMERATIONS_H
|
||||
|
||||
#define MT32EMU_DAC_INPUT_MODE_NAME DACInputMode
|
||||
#define MT32EMU_DAC_INPUT_MODE(ident) DACInputMode_##ident
|
||||
|
||||
#define MT32EMU_MIDI_DELAY_MODE_NAME MIDIDelayMode
|
||||
#define MT32EMU_MIDI_DELAY_MODE(ident) MIDIDelayMode_##ident
|
||||
|
||||
#define MT32EMU_ANALOG_OUTPUT_MODE_NAME AnalogOutputMode
|
||||
#define MT32EMU_ANALOG_OUTPUT_MODE(ident) AnalogOutputMode_##ident
|
||||
|
||||
#define MT32EMU_PARTIAL_STATE_NAME PartialState
|
||||
#define MT32EMU_PARTIAL_STATE(ident) PartialState_##ident
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
#endif /* #ifdef MT32EMU_C_ENUMERATIONS */
|
||||
|
||||
/**
|
||||
* Methods for emulating the connection between the LA32 and the DAC, which involves
|
||||
* some hacks in the real devices for doubling the volume.
|
||||
* See also http://en.wikipedia.org/wiki/Roland_MT-32#Digital_overflow
|
||||
*/
|
||||
enum MT32EMU_DAC_INPUT_MODE_NAME {
|
||||
/**
|
||||
* Produces samples at double the volume, without tricks.
|
||||
* Nicer overdrive characteristics than the DAC hacks (it simply clips samples within range)
|
||||
* Higher quality than the real devices
|
||||
*/
|
||||
MT32EMU_DAC_INPUT_MODE(NICE),
|
||||
|
||||
/**
|
||||
* Produces samples that exactly match the bits output from the emulated LA32.
|
||||
* Nicer overdrive characteristics than the DAC hacks (it simply clips samples within range)
|
||||
* Much less likely to overdrive than any other mode.
|
||||
* Half the volume of any of the other modes.
|
||||
* Output gain is ignored for both LA32 and reverb output.
|
||||
* Perfect for developers while debugging :)
|
||||
*/
|
||||
MT32EMU_DAC_INPUT_MODE(PURE),
|
||||
|
||||
/**
|
||||
* Re-orders the LA32 output bits as in early generation MT-32s (according to Wikipedia).
|
||||
* Bit order at DAC (where each number represents the original LA32 output bit number, and XX means the bit is always low):
|
||||
* 15 13 12 11 10 09 08 07 06 05 04 03 02 01 00 XX
|
||||
*/
|
||||
MT32EMU_DAC_INPUT_MODE(GENERATION1),
|
||||
|
||||
/**
|
||||
* Re-orders the LA32 output bits as in later generations (personally confirmed on my CM-32L - KG).
|
||||
* Bit order at DAC (where each number represents the original LA32 output bit number):
|
||||
* 15 13 12 11 10 09 08 07 06 05 04 03 02 01 00 14
|
||||
*/
|
||||
MT32EMU_DAC_INPUT_MODE(GENERATION2)
|
||||
};
|
||||
|
||||
/** Methods for emulating the effective delay of incoming MIDI messages introduced by a MIDI interface. */
|
||||
enum MT32EMU_MIDI_DELAY_MODE_NAME {
|
||||
/** Process incoming MIDI events immediately. */
|
||||
MT32EMU_MIDI_DELAY_MODE(IMMEDIATE),
|
||||
|
||||
/**
|
||||
* Delay incoming short MIDI messages as if they where transferred via a MIDI cable to a real hardware unit and immediate sysex processing.
|
||||
* This ensures more accurate timing of simultaneous NoteOn messages.
|
||||
*/
|
||||
MT32EMU_MIDI_DELAY_MODE(DELAY_SHORT_MESSAGES_ONLY),
|
||||
|
||||
/** Delay all incoming MIDI events as if they where transferred via a MIDI cable to a real hardware unit.*/
|
||||
MT32EMU_MIDI_DELAY_MODE(DELAY_ALL)
|
||||
};
|
||||
|
||||
/** Methods for emulating the effects of analogue circuits of real hardware units on the output signal. */
|
||||
enum MT32EMU_ANALOG_OUTPUT_MODE_NAME {
|
||||
/** Only digital path is emulated. The output samples correspond to the digital signal at the DAC entrance. */
|
||||
MT32EMU_ANALOG_OUTPUT_MODE(DIGITAL_ONLY),
|
||||
/** Coarse emulation of LPF circuit. High frequencies are boosted, sample rate remains unchanged. */
|
||||
MT32EMU_ANALOG_OUTPUT_MODE(COARSE),
|
||||
/**
|
||||
* Finer emulation of LPF circuit. Output signal is upsampled to 48 kHz to allow emulation of audible mirror spectra above 16 kHz,
|
||||
* which is passed through the LPF circuit without significant attenuation.
|
||||
*/
|
||||
MT32EMU_ANALOG_OUTPUT_MODE(ACCURATE),
|
||||
/**
|
||||
* Same as AnalogOutputMode_ACCURATE mode but the output signal is 2x oversampled, i.e. the output sample rate is 96 kHz.
|
||||
* This makes subsequent resampling easier. Besides, due to nonlinear passband of the LPF emulated, it takes fewer number of MACs
|
||||
* compared to a regular LPF FIR implementations.
|
||||
*/
|
||||
MT32EMU_ANALOG_OUTPUT_MODE(OVERSAMPLED)
|
||||
};
|
||||
|
||||
enum MT32EMU_PARTIAL_STATE_NAME {
|
||||
MT32EMU_PARTIAL_STATE(INACTIVE),
|
||||
MT32EMU_PARTIAL_STATE(ATTACK),
|
||||
MT32EMU_PARTIAL_STATE(SUSTAIN),
|
||||
MT32EMU_PARTIAL_STATE(RELEASE)
|
||||
};
|
||||
|
||||
#ifndef MT32EMU_C_ENUMERATIONS
|
||||
|
||||
} // namespace MT32Emu
|
||||
|
||||
#endif
|
||||
|
||||
#undef MT32EMU_DAC_INPUT_MODE_NAME
|
||||
#undef MT32EMU_DAC_INPUT_MODE
|
||||
|
||||
#undef MT32EMU_MIDI_DELAY_MODE_NAME
|
||||
#undef MT32EMU_MIDI_DELAY_MODE
|
||||
|
||||
#undef MT32EMU_ANALOG_OUTPUT_MODE_NAME
|
||||
#undef MT32EMU_ANALOG_OUTPUT_MODE
|
||||
|
||||
#undef MT32EMU_PARTIAL_STATE_NAME
|
||||
#undef MT32EMU_PARTIAL_STATE
|
||||
|
||||
#endif /* #if (!defined MT32EMU_CPP_ENUMERATIONS_H && !defined MT32EMU_C_ENUMERATIONS) || (!defined MT32EMU_C_ENUMERATIONS_H && defined MT32EMU_C_ENUMERATIONS) */
|
@ -1,77 +0,0 @@
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include "internals.h"
|
||||
|
||||
#include "File.h"
|
||||
#include "sha1/sha1.h"
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
AbstractFile::AbstractFile() : sha1DigestCalculated(false) {
|
||||
sha1Digest[0] = 0;
|
||||
|
||||
reserved = NULL;
|
||||
}
|
||||
|
||||
AbstractFile::AbstractFile(const SHA1Digest &useSHA1Digest) : sha1DigestCalculated(true) {
|
||||
memcpy(sha1Digest, useSHA1Digest, sizeof(SHA1Digest) - 1);
|
||||
sha1Digest[sizeof(SHA1Digest) - 1] = 0; // Ensure terminator char.
|
||||
|
||||
reserved = NULL;
|
||||
}
|
||||
|
||||
const File::SHA1Digest &AbstractFile::getSHA1() {
|
||||
if (sha1DigestCalculated) {
|
||||
return sha1Digest;
|
||||
}
|
||||
sha1DigestCalculated = true;
|
||||
|
||||
size_t size = getSize();
|
||||
if (size == 0) {
|
||||
return sha1Digest;
|
||||
}
|
||||
|
||||
const Bit8u *data = getData();
|
||||
if (data == NULL) {
|
||||
return sha1Digest;
|
||||
}
|
||||
|
||||
unsigned char fileDigest[20];
|
||||
|
||||
sha1::calc(data, int(size), fileDigest);
|
||||
sha1::toHexString(fileDigest, sha1Digest);
|
||||
return sha1Digest;
|
||||
}
|
||||
|
||||
ArrayFile::ArrayFile(const Bit8u *useData, size_t useSize) : data(useData), size(useSize)
|
||||
{}
|
||||
|
||||
ArrayFile::ArrayFile(const Bit8u *useData, size_t useSize, const SHA1Digest &useSHA1Digest) : AbstractFile(useSHA1Digest), data(useData), size(useSize)
|
||||
{}
|
||||
|
||||
size_t ArrayFile::getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
const Bit8u *ArrayFile::getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
} // namespace MT32Emu
|
@ -1,73 +0,0 @@
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MT32EMU_FILE_H
|
||||
#define MT32EMU_FILE_H
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
#include "globals.h"
|
||||
#include "Types.h"
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
class MT32EMU_EXPORT File {
|
||||
public:
|
||||
// Includes terminator char.
|
||||
typedef char SHA1Digest[41];
|
||||
|
||||
virtual ~File() {}
|
||||
virtual size_t getSize() = 0;
|
||||
virtual const Bit8u *getData() = 0;
|
||||
virtual const SHA1Digest &getSHA1() = 0;
|
||||
|
||||
virtual void close() = 0;
|
||||
};
|
||||
|
||||
class MT32EMU_EXPORT AbstractFile : public File {
|
||||
public:
|
||||
const SHA1Digest &getSHA1();
|
||||
|
||||
protected:
|
||||
AbstractFile();
|
||||
AbstractFile(const SHA1Digest &sha1Digest);
|
||||
|
||||
private:
|
||||
bool sha1DigestCalculated;
|
||||
SHA1Digest sha1Digest;
|
||||
|
||||
// Binary compatibility helper.
|
||||
void *reserved;
|
||||
};
|
||||
|
||||
class MT32EMU_EXPORT ArrayFile : public AbstractFile {
|
||||
public:
|
||||
ArrayFile(const Bit8u *data, size_t size);
|
||||
ArrayFile(const Bit8u *data, size_t size, const SHA1Digest &sha1Digest);
|
||||
|
||||
size_t getSize();
|
||||
const Bit8u *getData();
|
||||
void close() {}
|
||||
|
||||
private:
|
||||
const Bit8u *data;
|
||||
size_t size;
|
||||
};
|
||||
|
||||
} // namespace MT32Emu
|
||||
|
||||
#endif // #ifndef MT32EMU_FILE_H
|
@ -1,83 +0,0 @@
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "internals.h"
|
||||
|
||||
#include "FileStream.h"
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
using std::ios_base;
|
||||
|
||||
FileStream::FileStream() : ifsp(*new std::ifstream), data(NULL), size(0)
|
||||
{}
|
||||
|
||||
FileStream::~FileStream() {
|
||||
// destructor closes ifsp
|
||||
delete &ifsp;
|
||||
delete[] data;
|
||||
}
|
||||
|
||||
size_t FileStream::getSize() {
|
||||
if (size != 0) {
|
||||
return size;
|
||||
}
|
||||
if (!ifsp.is_open()) {
|
||||
return 0;
|
||||
}
|
||||
ifsp.seekg(0, ios_base::end);
|
||||
size = size_t(ifsp.tellg());
|
||||
return size;
|
||||
}
|
||||
|
||||
const Bit8u *FileStream::getData() {
|
||||
if (data != NULL) {
|
||||
return data;
|
||||
}
|
||||
if (!ifsp.is_open()) {
|
||||
return NULL;
|
||||
}
|
||||
if (getSize() == 0) {
|
||||
return NULL;
|
||||
}
|
||||
Bit8u *fileData = new Bit8u[size];
|
||||
if (fileData == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
ifsp.seekg(0);
|
||||
ifsp.read(reinterpret_cast<char *>(fileData), std::streamsize(size));
|
||||
if (size_t(ifsp.tellg()) != size) {
|
||||
delete[] fileData;
|
||||
return NULL;
|
||||
}
|
||||
data = fileData;
|
||||
close();
|
||||
return data;
|
||||
}
|
||||
|
||||
bool FileStream::open(const char *filename) {
|
||||
ifsp.clear();
|
||||
ifsp.open(filename, ios_base::in | ios_base::binary);
|
||||
return !ifsp.fail();
|
||||
}
|
||||
|
||||
void FileStream::close() {
|
||||
ifsp.close();
|
||||
ifsp.clear();
|
||||
}
|
||||
|
||||
} // namespace MT32Emu
|
@ -1,46 +0,0 @@
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MT32EMU_FILE_STREAM_H
|
||||
#define MT32EMU_FILE_STREAM_H
|
||||
|
||||
#include <fstream>
|
||||
|
||||
#include "globals.h"
|
||||
#include "Types.h"
|
||||
#include "File.h"
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
class FileStream : public AbstractFile {
|
||||
public:
|
||||
MT32EMU_EXPORT FileStream();
|
||||
MT32EMU_EXPORT ~FileStream();
|
||||
MT32EMU_EXPORT size_t getSize();
|
||||
MT32EMU_EXPORT const Bit8u *getData();
|
||||
MT32EMU_EXPORT bool open(const char *filename);
|
||||
MT32EMU_EXPORT void close();
|
||||
|
||||
private:
|
||||
std::ifstream &ifsp;
|
||||
const Bit8u *data;
|
||||
size_t size;
|
||||
};
|
||||
|
||||
} // namespace MT32Emu
|
||||
|
||||
#endif // #ifndef MT32EMU_FILE_STREAM_H
|
@ -1,358 +0,0 @@
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MT32EMU_LA32_WAVE_GENERATOR_CPP
|
||||
#error This file should be included from LA32WaveGenerator.cpp only.
|
||||
#endif
|
||||
|
||||
#include "mmath.h"
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
static const float MIDDLE_CUTOFF_VALUE = 128.0f;
|
||||
static const float RESONANCE_DECAY_THRESHOLD_CUTOFF_VALUE = 144.0f;
|
||||
static const float MAX_CUTOFF_VALUE = 240.0f;
|
||||
|
||||
float LA32WaveGenerator::getPCMSample(unsigned int position) {
|
||||
if (position >= pcmWaveLength) {
|
||||
if (!pcmWaveLooped) {
|
||||
return 0;
|
||||
}
|
||||
position = position % pcmWaveLength;
|
||||
}
|
||||
Bit16s pcmSample = pcmWaveAddress[position];
|
||||
float sampleValue = EXP2F(((pcmSample & 32767) - 32787.0f) / 2048.0f);
|
||||
return ((pcmSample & 32768) == 0) ? sampleValue : -sampleValue;
|
||||
}
|
||||
|
||||
void LA32WaveGenerator::initSynth(const bool useSawtoothWaveform, const Bit8u usePulseWidth, const Bit8u useResonance) {
|
||||
sawtoothWaveform = useSawtoothWaveform;
|
||||
pulseWidth = usePulseWidth;
|
||||
resonance = useResonance;
|
||||
|
||||
wavePos = 0.0f;
|
||||
lastFreq = 0.0f;
|
||||
|
||||
pcmWaveAddress = NULL;
|
||||
active = true;
|
||||
}
|
||||
|
||||
void LA32WaveGenerator::initPCM(const Bit16s * const usePCMWaveAddress, const Bit32u usePCMWaveLength, const bool usePCMWaveLooped, const bool usePCMWaveInterpolated) {
|
||||
pcmWaveAddress = usePCMWaveAddress;
|
||||
pcmWaveLength = usePCMWaveLength;
|
||||
pcmWaveLooped = usePCMWaveLooped;
|
||||
pcmWaveInterpolated = usePCMWaveInterpolated;
|
||||
|
||||
pcmPosition = 0.0f;
|
||||
active = true;
|
||||
}
|
||||
|
||||
// ampVal - Logarithmic amp of the wave generator
|
||||
// pitch - Logarithmic frequency of the resulting wave
|
||||
// cutoffRampVal - Composed of the base cutoff in range [78..178] left-shifted by 18 bits and the TVF modifier
|
||||
float LA32WaveGenerator::generateNextSample(const Bit32u ampVal, const Bit16u pitch, const Bit32u cutoffRampVal) {
|
||||
if (!active) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
float sample = 0.0f;
|
||||
|
||||
// SEMI-CONFIRMED: From sample analysis:
|
||||
// (1) Tested with a single partial playing PCM wave 77 with pitchCoarse 36 and no keyfollow, velocity follow, etc.
|
||||
// This gives results within +/- 2 at the output (before any DAC bitshifting)
|
||||
// when sustaining at levels 156 - 255 with no modifiers.
|
||||
// (2) Tested with a special square wave partial (internal capture ID tva5) at TVA envelope levels 155-255.
|
||||
// This gives deltas between -1 and 0 compared to the real output. Note that this special partial only produces
|
||||
// positive amps, so negative still needs to be explored, as well as lower levels.
|
||||
//
|
||||
// Also still partially unconfirmed is the behaviour when ramping between levels, as well as the timing.
|
||||
|
||||
float amp = EXP2F(ampVal / -1024.0f / 4096.0f);
|
||||
float freq = EXP2F(pitch / 4096.0f - 16.0f) * SAMPLE_RATE;
|
||||
|
||||
if (isPCMWave()) {
|
||||
// Render PCM waveform
|
||||
int len = pcmWaveLength;
|
||||
int intPCMPosition = (int)pcmPosition;
|
||||
if (intPCMPosition >= len && !pcmWaveLooped) {
|
||||
// We're now past the end of a non-looping PCM waveform so it's time to die.
|
||||
deactivate();
|
||||
return 0.0f;
|
||||
}
|
||||
float positionDelta = freq * 2048.0f / SAMPLE_RATE;
|
||||
|
||||
// Linear interpolation
|
||||
float firstSample = getPCMSample(intPCMPosition);
|
||||
// We observe that for partial structures with ring modulation the interpolation is not applied to the slave PCM partial.
|
||||
// It's assumed that the multiplication circuitry intended to perform the interpolation on the slave PCM partial
|
||||
// is borrowed by the ring modulation circuit (or the LA32 chip has a similar lack of resources assigned to each partial pair).
|
||||
if (pcmWaveInterpolated) {
|
||||
sample = firstSample + (getPCMSample(intPCMPosition + 1) - firstSample) * (pcmPosition - intPCMPosition);
|
||||
} else {
|
||||
sample = firstSample;
|
||||
}
|
||||
|
||||
float newPCMPosition = pcmPosition + positionDelta;
|
||||
if (pcmWaveLooped) {
|
||||
newPCMPosition = fmod(newPCMPosition, (float)pcmWaveLength);
|
||||
}
|
||||
pcmPosition = newPCMPosition;
|
||||
} else {
|
||||
// Render synthesised waveform
|
||||
wavePos *= lastFreq / freq;
|
||||
lastFreq = freq;
|
||||
|
||||
float resAmp = EXP2F(1.0f - (32 - resonance) / 4.0f);
|
||||
{
|
||||
//static const float resAmpFactor = EXP2F(-7);
|
||||
//resAmp = EXP2I(resonance << 10) * resAmpFactor;
|
||||
}
|
||||
|
||||
// The cutoffModifier may not be supposed to be directly added to the cutoff -
|
||||
// it may for example need to be multiplied in some way.
|
||||
// The 240 cutoffVal limit was determined via sample analysis (internal Munt capture IDs: glop3, glop4).
|
||||
// More research is needed to be sure that this is correct, however.
|
||||
float cutoffVal = cutoffRampVal / 262144.0f;
|
||||
if (cutoffVal > MAX_CUTOFF_VALUE) {
|
||||
cutoffVal = MAX_CUTOFF_VALUE;
|
||||
}
|
||||
|
||||
// Wave length in samples
|
||||
float waveLen = SAMPLE_RATE / freq;
|
||||
|
||||
// Init cosineLen
|
||||
float cosineLen = 0.5f * waveLen;
|
||||
if (cutoffVal > MIDDLE_CUTOFF_VALUE) {
|
||||
cosineLen *= EXP2F((cutoffVal - MIDDLE_CUTOFF_VALUE) / -16.0f); // found from sample analysis
|
||||
}
|
||||
|
||||
// Start playing in center of first cosine segment
|
||||
// relWavePos is shifted by a half of cosineLen
|
||||
float relWavePos = wavePos + 0.5f * cosineLen;
|
||||
if (relWavePos > waveLen) {
|
||||
relWavePos -= waveLen;
|
||||
}
|
||||
|
||||
// Ratio of positive segment to wave length
|
||||
float pulseLen = 0.5f;
|
||||
if (pulseWidth > 128) {
|
||||
pulseLen = EXP2F((64 - pulseWidth) / 64.0f);
|
||||
//static const float pulseLenFactor = EXP2F(-192 / 64);
|
||||
//pulseLen = EXP2I((256 - pulseWidthVal) << 6) * pulseLenFactor;
|
||||
}
|
||||
pulseLen *= waveLen;
|
||||
|
||||
float hLen = pulseLen - cosineLen;
|
||||
|
||||
// Ignore pulsewidths too high for given freq
|
||||
if (hLen < 0.0f) {
|
||||
hLen = 0.0f;
|
||||
}
|
||||
|
||||
// Ignore pulsewidths too high for given freq and cutoff
|
||||
float lLen = waveLen - hLen - 2 * cosineLen;
|
||||
if (lLen < 0.0f) {
|
||||
lLen = 0.0f;
|
||||
}
|
||||
|
||||
// Correct resAmp for cutoff in range 50..66
|
||||
if ((cutoffVal >= 128.0f) && (cutoffVal < 144.0f)) {
|
||||
resAmp *= sin(FLOAT_PI * (cutoffVal - 128.0f) / 32.0f);
|
||||
}
|
||||
|
||||
// Produce filtered square wave with 2 cosine waves on slopes
|
||||
|
||||
// 1st cosine segment
|
||||
if (relWavePos < cosineLen) {
|
||||
sample = -cos(FLOAT_PI * relWavePos / cosineLen);
|
||||
} else
|
||||
|
||||
// high linear segment
|
||||
if (relWavePos < (cosineLen + hLen)) {
|
||||
sample = 1.f;
|
||||
} else
|
||||
|
||||
// 2nd cosine segment
|
||||
if (relWavePos < (2 * cosineLen + hLen)) {
|
||||
sample = cos(FLOAT_PI * (relWavePos - (cosineLen + hLen)) / cosineLen);
|
||||
} else {
|
||||
|
||||
// low linear segment
|
||||
sample = -1.f;
|
||||
}
|
||||
|
||||
if (cutoffVal < 128.0f) {
|
||||
|
||||
// Attenuate samples below cutoff 50
|
||||
// Found by sample analysis
|
||||
sample *= EXP2F(-0.125f * (128.0f - cutoffVal));
|
||||
} else {
|
||||
|
||||
// Add resonance sine. Effective for cutoff > 50 only
|
||||
float resSample = 1.0f;
|
||||
|
||||
// Resonance decay speed factor
|
||||
float resAmpDecayFactor = Tables::getInstance().resAmpDecayFactor[resonance >> 2];
|
||||
|
||||
// Now relWavePos counts from the middle of first cosine
|
||||
relWavePos = wavePos;
|
||||
|
||||
// negative segments
|
||||
if (!(relWavePos < (cosineLen + hLen))) {
|
||||
resSample = -resSample;
|
||||
relWavePos -= cosineLen + hLen;
|
||||
|
||||
// From the digital captures, the decaying speed of the resonance sine is found a bit different for the positive and the negative segments
|
||||
resAmpDecayFactor += 0.25f;
|
||||
}
|
||||
|
||||
// Resonance sine WG
|
||||
resSample *= sin(FLOAT_PI * relWavePos / cosineLen);
|
||||
|
||||
// Resonance sine amp
|
||||
float resAmpFadeLog2 = -0.125f * resAmpDecayFactor * (relWavePos / cosineLen); // seems to be exact
|
||||
float resAmpFade = EXP2F(resAmpFadeLog2);
|
||||
|
||||
// Now relWavePos set negative to the left from center of any cosine
|
||||
relWavePos = wavePos;
|
||||
|
||||
// negative segment
|
||||
if (!(wavePos < (waveLen - 0.5f * cosineLen))) {
|
||||
relWavePos -= waveLen;
|
||||
} else
|
||||
|
||||
// positive segment
|
||||
if (!(wavePos < (hLen + 0.5f * cosineLen))) {
|
||||
relWavePos -= cosineLen + hLen;
|
||||
}
|
||||
|
||||
// To ensure the output wave has no breaks, two different windows are appied to the beginning and the ending of the resonance sine segment
|
||||
if (relWavePos < 0.5f * cosineLen) {
|
||||
float syncSine = sin(FLOAT_PI * relWavePos / cosineLen);
|
||||
if (relWavePos < 0.0f) {
|
||||
// The window is synchronous square sine here
|
||||
resAmpFade *= syncSine * syncSine;
|
||||
} else {
|
||||
// The window is synchronous sine here
|
||||
resAmpFade *= syncSine;
|
||||
}
|
||||
}
|
||||
|
||||
sample += resSample * resAmp * resAmpFade;
|
||||
}
|
||||
|
||||
// sawtooth waves
|
||||
if (sawtoothWaveform) {
|
||||
sample *= cos(FLOAT_2PI * wavePos / waveLen);
|
||||
}
|
||||
|
||||
wavePos++;
|
||||
|
||||
// wavePos isn't supposed to be > waveLen
|
||||
if (wavePos > waveLen) {
|
||||
wavePos -= waveLen;
|
||||
}
|
||||
}
|
||||
|
||||
// Multiply sample with current TVA value
|
||||
sample *= amp;
|
||||
return sample;
|
||||
}
|
||||
|
||||
void LA32WaveGenerator::deactivate() {
|
||||
active = false;
|
||||
}
|
||||
|
||||
bool LA32WaveGenerator::isActive() const {
|
||||
return active;
|
||||
}
|
||||
|
||||
bool LA32WaveGenerator::isPCMWave() const {
|
||||
return pcmWaveAddress != NULL;
|
||||
}
|
||||
|
||||
void LA32PartialPair::init(const bool useRingModulated, const bool useMixed) {
|
||||
ringModulated = useRingModulated;
|
||||
mixed = useMixed;
|
||||
masterOutputSample = 0.0f;
|
||||
slaveOutputSample = 0.0f;
|
||||
}
|
||||
|
||||
void LA32PartialPair::initSynth(const PairType useMaster, const bool sawtoothWaveform, const Bit8u pulseWidth, const Bit8u resonance) {
|
||||
if (useMaster == MASTER) {
|
||||
master.initSynth(sawtoothWaveform, pulseWidth, resonance);
|
||||
} else {
|
||||
slave.initSynth(sawtoothWaveform, pulseWidth, resonance);
|
||||
}
|
||||
}
|
||||
|
||||
void LA32PartialPair::initPCM(const PairType useMaster, const Bit16s *pcmWaveAddress, const Bit32u pcmWaveLength, const bool pcmWaveLooped) {
|
||||
if (useMaster == MASTER) {
|
||||
master.initPCM(pcmWaveAddress, pcmWaveLength, pcmWaveLooped, true);
|
||||
} else {
|
||||
slave.initPCM(pcmWaveAddress, pcmWaveLength, pcmWaveLooped, !ringModulated);
|
||||
}
|
||||
}
|
||||
|
||||
void LA32PartialPair::generateNextSample(const PairType useMaster, const Bit32u amp, const Bit16u pitch, const Bit32u cutoff) {
|
||||
if (useMaster == MASTER) {
|
||||
masterOutputSample = master.generateNextSample(amp, pitch, cutoff);
|
||||
} else {
|
||||
slaveOutputSample = slave.generateNextSample(amp, pitch, cutoff);
|
||||
}
|
||||
}
|
||||
|
||||
static inline float produceDistortedSample(float sample) {
|
||||
if (sample < -1.0f) {
|
||||
return sample + 2.0f;
|
||||
} else if (1.0f < sample) {
|
||||
return sample - 2.0f;
|
||||
}
|
||||
return sample;
|
||||
}
|
||||
|
||||
float LA32PartialPair::nextOutSample() {
|
||||
if (!ringModulated) {
|
||||
return masterOutputSample + slaveOutputSample;
|
||||
}
|
||||
/*
|
||||
* SEMI-CONFIRMED: Ring modulation model derived from sample analysis of specially constructed patches which exploit distortion.
|
||||
* LA32 ring modulator found to produce distorted output in case if the absolute value of maximal amplitude of one of the input partials exceeds 8191.
|
||||
* This is easy to reproduce using synth partials with resonance values close to the maximum. It looks like an integer overflow happens in this case.
|
||||
* As the distortion is strictly bound to the amplitude of the complete mixed square + resonance wave in the linear space,
|
||||
* it is reasonable to assume the ring modulation is performed also in the linear space by sample multiplication.
|
||||
* Most probably the overflow is caused by limited precision of the multiplication circuit as the very similar distortion occurs with panning.
|
||||
*/
|
||||
float ringModulatedSample = produceDistortedSample(masterOutputSample) * produceDistortedSample(slaveOutputSample);
|
||||
return mixed ? masterOutputSample + ringModulatedSample : ringModulatedSample;
|
||||
}
|
||||
|
||||
void LA32PartialPair::deactivate(const PairType useMaster) {
|
||||
if (useMaster == MASTER) {
|
||||
master.deactivate();
|
||||
masterOutputSample = 0.0f;
|
||||
} else {
|
||||
slave.deactivate();
|
||||
slaveOutputSample = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
bool LA32PartialPair::isActive(const PairType useMaster) const {
|
||||
return useMaster == MASTER ? master.isActive() : slave.isActive();
|
||||
}
|
||||
|
||||
} // namespace MT32Emu
|
@ -1,132 +0,0 @@
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MT32EMU_LA32_WAVE_GENERATOR_H
|
||||
#error This file should be included from LA32WaveGenerator.h only.
|
||||
#endif
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
/**
|
||||
* LA32WaveGenerator is aimed to represent the exact model of LA32 wave generator.
|
||||
* The output square wave is created by adding high / low linear segments in-between
|
||||
* the rising and falling cosine segments. Basically, it's very similar to the phase distortion synthesis.
|
||||
* Behaviour of a true resonance filter is emulated by adding decaying sine wave.
|
||||
* The beginning and the ending of the resonant sine is multiplied by a cosine window.
|
||||
* To synthesise sawtooth waves, the resulting square wave is multiplied by synchronous cosine wave.
|
||||
*/
|
||||
class LA32WaveGenerator {
|
||||
//***************************************************************************
|
||||
// The local copy of partial parameters below
|
||||
//***************************************************************************
|
||||
|
||||
bool active;
|
||||
|
||||
// True means the resulting square wave is to be multiplied by the synchronous cosine
|
||||
bool sawtoothWaveform;
|
||||
|
||||
// Values in range [1..31]
|
||||
// Value 1 correspong to the minimum resonance
|
||||
Bit8u resonance;
|
||||
|
||||
// Processed value in range [0..255]
|
||||
// Values in range [0..128] have no effect and the resulting wave remains symmetrical
|
||||
// Value 255 corresponds to the maximum possible asymmetric of the resulting wave
|
||||
Bit8u pulseWidth;
|
||||
|
||||
// Logarithmic PCM sample start address
|
||||
const Bit16s *pcmWaveAddress;
|
||||
|
||||
// Logarithmic PCM sample length
|
||||
Bit32u pcmWaveLength;
|
||||
|
||||
// true for looped logarithmic PCM samples
|
||||
bool pcmWaveLooped;
|
||||
|
||||
// false for slave PCM partials in the structures with the ring modulation
|
||||
bool pcmWaveInterpolated;
|
||||
|
||||
//***************************************************************************
|
||||
// Internal variables below
|
||||
//***************************************************************************
|
||||
|
||||
float wavePos;
|
||||
float lastFreq;
|
||||
float pcmPosition;
|
||||
|
||||
float getPCMSample(unsigned int position);
|
||||
|
||||
public:
|
||||
// Initialise the WG engine for generation of synth partial samples and set up the invariant parameters
|
||||
void initSynth(const bool sawtoothWaveform, const Bit8u pulseWidth, const Bit8u resonance);
|
||||
|
||||
// Initialise the WG engine for generation of PCM partial samples and set up the invariant parameters
|
||||
void initPCM(const Bit16s * const pcmWaveAddress, const Bit32u pcmWaveLength, const bool pcmWaveLooped, const bool pcmWaveInterpolated);
|
||||
|
||||
// Update parameters with respect to TVP, TVA and TVF, and generate next sample
|
||||
float generateNextSample(const Bit32u amp, const Bit16u pitch, const Bit32u cutoff);
|
||||
|
||||
// Deactivate the WG engine
|
||||
void deactivate();
|
||||
|
||||
// Return active state of the WG engine
|
||||
bool isActive() const;
|
||||
|
||||
// Return true if the WG engine generates PCM wave samples
|
||||
bool isPCMWave() const;
|
||||
}; // class LA32WaveGenerator
|
||||
|
||||
// LA32PartialPair contains a structure of two partials being mixed / ring modulated
|
||||
class LA32PartialPair {
|
||||
LA32WaveGenerator master;
|
||||
LA32WaveGenerator slave;
|
||||
bool ringModulated;
|
||||
bool mixed;
|
||||
float masterOutputSample;
|
||||
float slaveOutputSample;
|
||||
|
||||
public:
|
||||
enum PairType {
|
||||
MASTER,
|
||||
SLAVE
|
||||
};
|
||||
|
||||
// ringModulated should be set to false for the structures with mixing or stereo output
|
||||
// ringModulated should be set to true for the structures with ring modulation
|
||||
// mixed is used for the structures with ring modulation and indicates whether the master partial output is mixed to the ring modulator output
|
||||
void init(const bool ringModulated, const bool mixed);
|
||||
|
||||
// Initialise the WG engine for generation of synth partial samples and set up the invariant parameters
|
||||
void initSynth(const PairType master, const bool sawtoothWaveform, const Bit8u pulseWidth, const Bit8u resonance);
|
||||
|
||||
// Initialise the WG engine for generation of PCM partial samples and set up the invariant parameters
|
||||
void initPCM(const PairType master, const Bit16s * const pcmWaveAddress, const Bit32u pcmWaveLength, const bool pcmWaveLooped);
|
||||
|
||||
// Update parameters with respect to TVP, TVA and TVF, and generate next sample
|
||||
void generateNextSample(const PairType master, const Bit32u amp, const Bit16u pitch, const Bit32u cutoff);
|
||||
|
||||
// Perform mixing / ring modulation and return the result
|
||||
float nextOutSample();
|
||||
|
||||
// Deactivate the WG engine
|
||||
void deactivate(const PairType master);
|
||||
|
||||
// Return active state of the WG engine
|
||||
bool isActive(const PairType master) const;
|
||||
}; // class LA32PartialPair
|
||||
|
||||
} // namespace MT32Emu
|
@ -1,155 +0,0 @@
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
Some notes on this class:
|
||||
|
||||
This emulates the LA-32's implementation of "ramps". A ramp in this context is a smooth transition from one value to another, handled entirely within the LA-32.
|
||||
The LA-32 provides this feature for amplitude and filter cutoff values.
|
||||
|
||||
The 8095 starts ramps on the LA-32 by setting two values in memory-mapped registers:
|
||||
|
||||
(1) The target value (between 0 and 255) for the ramp to end on. This is represented by the "target" argument to startRamp().
|
||||
(2) The speed at which that value should be approached. This is represented by the "increment" argument to startRamp().
|
||||
|
||||
Once the ramp target value has been hit, the LA-32 raises an interrupt.
|
||||
|
||||
Note that the starting point of the ramp is whatever internal value the LA-32 had when the registers were set. This is usually the end point of a previously completed ramp.
|
||||
|
||||
Our handling of the "target" and "increment" values is based on sample analysis and a little guesswork.
|
||||
Here's what we're pretty confident about:
|
||||
- The most significant bit of "increment" indicates the direction that the LA32's current internal value ("current" in our emulation) should change in.
|
||||
Set means downward, clear means upward.
|
||||
- The lower 7 bits of "increment" indicate how quickly "current" should be changed.
|
||||
- If "increment" is 0, no change to "current" is made and no interrupt is raised. [SEMI-CONFIRMED by sample analysis]
|
||||
- Otherwise, if the MSb is set:
|
||||
- If "current" already corresponds to a value <= "target", "current" is set immediately to the equivalent of "target" and an interrupt is raised.
|
||||
- Otherwise, "current" is gradually reduced (at a rate determined by the lower 7 bits of "increment"), and once it reaches the equivalent of "target" an interrupt is raised.
|
||||
- Otherwise (the MSb is unset):
|
||||
- If "current" already corresponds to a value >= "target", "current" is set immediately to the equivalent of "target" and an interrupt is raised.
|
||||
- Otherwise, "current" is gradually increased (at a rate determined by the lower 7 bits of "increment"), and once it reaches the equivalent of "target" an interrupt is raised.
|
||||
|
||||
We haven't fully explored:
|
||||
- Values when ramping between levels (though this is probably correct).
|
||||
- Transition timing (may not be 100% accurate, especially for very fast ramps).
|
||||
*/
|
||||
|
||||
#include "internals.h"
|
||||
|
||||
#include "LA32Ramp.h"
|
||||
#include "Tables.h"
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
// SEMI-CONFIRMED from sample analysis.
|
||||
const int TARGET_MULT = 0x40000;
|
||||
const unsigned int MAX_CURRENT = 0xFF * TARGET_MULT;
|
||||
|
||||
// We simulate the delay in handling "target was reached" interrupts by waiting
|
||||
// this many samples before setting interruptRaised.
|
||||
// FIXME: This should vary with the sample rate, but doesn't.
|
||||
// SEMI-CONFIRMED: Since this involves asynchronous activity between the LA32
|
||||
// and the 8095, a good value is hard to pin down.
|
||||
// This one matches observed behaviour on a few digital captures I had handy,
|
||||
// and should be double-checked. We may also need a more sophisticated delay
|
||||
// scheme eventually.
|
||||
const int INTERRUPT_TIME = 7;
|
||||
|
||||
LA32Ramp::LA32Ramp() :
|
||||
current(0),
|
||||
largeTarget(0),
|
||||
largeIncrement(0),
|
||||
interruptCountdown(0),
|
||||
interruptRaised(false) {
|
||||
}
|
||||
|
||||
void LA32Ramp::startRamp(Bit8u target, Bit8u increment) {
|
||||
// CONFIRMED: From sample analysis, this appears to be very accurate.
|
||||
if (increment == 0) {
|
||||
largeIncrement = 0;
|
||||
} else {
|
||||
// Three bits in the fractional part, no need to interpolate
|
||||
// (unsigned int)(EXP2F(((increment & 0x7F) + 24) / 8.0f) + 0.125f)
|
||||
Bit32u expArg = increment & 0x7F;
|
||||
largeIncrement = 8191 - Tables::getInstance().exp9[~(expArg << 6) & 511];
|
||||
largeIncrement <<= expArg >> 3;
|
||||
largeIncrement += 64;
|
||||
largeIncrement >>= 9;
|
||||
}
|
||||
descending = (increment & 0x80) != 0;
|
||||
if (descending) {
|
||||
// CONFIRMED: From sample analysis, descending increments are slightly faster
|
||||
largeIncrement++;
|
||||
}
|
||||
|
||||
largeTarget = target * TARGET_MULT;
|
||||
interruptCountdown = 0;
|
||||
interruptRaised = false;
|
||||
}
|
||||
|
||||
Bit32u LA32Ramp::nextValue() {
|
||||
if (interruptCountdown > 0) {
|
||||
if (--interruptCountdown == 0) {
|
||||
interruptRaised = true;
|
||||
}
|
||||
} else if (largeIncrement != 0) {
|
||||
// CONFIRMED from sample analysis: When increment is 0, the LA32 does *not* change the current value at all (and of course doesn't fire an interrupt).
|
||||
if (descending) {
|
||||
// Lowering current value
|
||||
if (largeIncrement > current) {
|
||||
current = largeTarget;
|
||||
interruptCountdown = INTERRUPT_TIME;
|
||||
} else {
|
||||
current -= largeIncrement;
|
||||
if (current <= largeTarget) {
|
||||
current = largeTarget;
|
||||
interruptCountdown = INTERRUPT_TIME;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Raising current value
|
||||
if (MAX_CURRENT - current < largeIncrement) {
|
||||
current = largeTarget;
|
||||
interruptCountdown = INTERRUPT_TIME;
|
||||
} else {
|
||||
current += largeIncrement;
|
||||
if (current >= largeTarget) {
|
||||
current = largeTarget;
|
||||
interruptCountdown = INTERRUPT_TIME;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
bool LA32Ramp::checkInterrupt() {
|
||||
bool wasRaised = interruptRaised;
|
||||
interruptRaised = false;
|
||||
return wasRaised;
|
||||
}
|
||||
|
||||
void LA32Ramp::reset() {
|
||||
current = 0;
|
||||
largeTarget = 0;
|
||||
largeIncrement = 0;
|
||||
descending = false;
|
||||
interruptCountdown = 0;
|
||||
interruptRaised = false;
|
||||
}
|
||||
|
||||
} // namespace MT32Emu
|
@ -1,46 +0,0 @@
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MT32EMU_LA32RAMP_H
|
||||
#define MT32EMU_LA32RAMP_H
|
||||
|
||||
#include "globals.h"
|
||||
#include "Types.h"
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
class LA32Ramp {
|
||||
private:
|
||||
Bit32u current;
|
||||
unsigned int largeTarget;
|
||||
unsigned int largeIncrement;
|
||||
bool descending;
|
||||
|
||||
int interruptCountdown;
|
||||
bool interruptRaised;
|
||||
|
||||
public:
|
||||
LA32Ramp();
|
||||
void startRamp(Bit8u target, Bit8u increment);
|
||||
Bit32u nextValue();
|
||||
bool checkInterrupt();
|
||||
void reset();
|
||||
};
|
||||
|
||||
} // namespace MT32Emu
|
||||
|
||||
#endif // #ifndef MT32EMU_LA32RAMP_H
|
@ -1,430 +0,0 @@
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
#include "internals.h"
|
||||
|
||||
#include "LA32WaveGenerator.h"
|
||||
#include "Tables.h"
|
||||
|
||||
#if MT32EMU_USE_FLOAT_SAMPLES
|
||||
#define MT32EMU_LA32_WAVE_GENERATOR_CPP
|
||||
#include "LA32FloatWaveGenerator.cpp"
|
||||
#undef MT32EMU_LA32_WAVE_GENERATOR_CPP
|
||||
#else
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
static const Bit32u SINE_SEGMENT_RELATIVE_LENGTH = 1 << 18;
|
||||
static const Bit32u MIDDLE_CUTOFF_VALUE = 128 << 18;
|
||||
static const Bit32u RESONANCE_DECAY_THRESHOLD_CUTOFF_VALUE = 144 << 18;
|
||||
static const Bit32u MAX_CUTOFF_VALUE = 240 << 18;
|
||||
static const LogSample SILENCE = {65535, LogSample::POSITIVE};
|
||||
|
||||
Bit16u LA32Utilites::interpolateExp(const Bit16u fract) {
|
||||
Bit16u expTabIndex = fract >> 3;
|
||||
Bit16u extraBits = ~fract & 7;
|
||||
Bit16u expTabEntry2 = 8191 - Tables::getInstance().exp9[expTabIndex];
|
||||
Bit16u expTabEntry1 = expTabIndex == 0 ? 8191 : (8191 - Tables::getInstance().exp9[expTabIndex - 1]);
|
||||
return expTabEntry2 + (((expTabEntry1 - expTabEntry2) * extraBits) >> 3);
|
||||
}
|
||||
|
||||
Bit16s LA32Utilites::unlog(const LogSample &logSample) {
|
||||
//Bit16s sample = (Bit16s)EXP2F(13.0f - logSample.logValue / 1024.0f);
|
||||
Bit32u intLogValue = logSample.logValue >> 12;
|
||||
Bit16u fracLogValue = logSample.logValue & 4095;
|
||||
Bit16s sample = interpolateExp(fracLogValue) >> intLogValue;
|
||||
return logSample.sign == LogSample::POSITIVE ? sample : -sample;
|
||||
}
|
||||
|
||||
void LA32Utilites::addLogSamples(LogSample &logSample1, const LogSample &logSample2) {
|
||||
Bit32u logSampleValue = logSample1.logValue + logSample2.logValue;
|
||||
logSample1.logValue = logSampleValue < 65536 ? Bit16u(logSampleValue) : 65535;
|
||||
logSample1.sign = logSample1.sign == logSample2.sign ? LogSample::POSITIVE : LogSample::NEGATIVE;
|
||||
}
|
||||
|
||||
Bit32u LA32WaveGenerator::getSampleStep() {
|
||||
// sampleStep = EXP2F(pitch / 4096.0f + 4.0f)
|
||||
Bit32u sampleStep = LA32Utilites::interpolateExp(~pitch & 4095);
|
||||
sampleStep <<= pitch >> 12;
|
||||
sampleStep >>= 8;
|
||||
sampleStep &= ~1;
|
||||
return sampleStep;
|
||||
}
|
||||
|
||||
Bit32u LA32WaveGenerator::getResonanceWaveLengthFactor(Bit32u effectiveCutoffValue) {
|
||||
// resonanceWaveLengthFactor = (Bit32u)EXP2F(12.0f + effectiveCutoffValue / 4096.0f);
|
||||
Bit32u resonanceWaveLengthFactor = LA32Utilites::interpolateExp(~effectiveCutoffValue & 4095);
|
||||
resonanceWaveLengthFactor <<= effectiveCutoffValue >> 12;
|
||||
return resonanceWaveLengthFactor;
|
||||
}
|
||||
|
||||
Bit32u LA32WaveGenerator::getHighLinearLength(Bit32u effectiveCutoffValue) {
|
||||
// Ratio of positive segment to wave length
|
||||
Bit32u effectivePulseWidthValue = 0;
|
||||
if (pulseWidth > 128) {
|
||||
effectivePulseWidthValue = (pulseWidth - 128) << 6;
|
||||
}
|
||||
|
||||
Bit32u highLinearLength = 0;
|
||||
// highLinearLength = EXP2F(19.0f - effectivePulseWidthValue / 4096.0f + effectiveCutoffValue / 4096.0f) - 2 * SINE_SEGMENT_RELATIVE_LENGTH;
|
||||
if (effectivePulseWidthValue < effectiveCutoffValue) {
|
||||
Bit32u expArg = effectiveCutoffValue - effectivePulseWidthValue;
|
||||
highLinearLength = LA32Utilites::interpolateExp(~expArg & 4095);
|
||||
highLinearLength <<= 7 + (expArg >> 12);
|
||||
highLinearLength -= 2 * SINE_SEGMENT_RELATIVE_LENGTH;
|
||||
}
|
||||
return highLinearLength;
|
||||
}
|
||||
|
||||
void LA32WaveGenerator::computePositions(Bit32u highLinearLength, Bit32u lowLinearLength, Bit32u resonanceWaveLengthFactor) {
|
||||
// Assuming 12-bit multiplication used here
|
||||
squareWavePosition = resonanceSinePosition = (wavePosition >> 8) * (resonanceWaveLengthFactor >> 4);
|
||||
if (squareWavePosition < SINE_SEGMENT_RELATIVE_LENGTH) {
|
||||
phase = POSITIVE_RISING_SINE_SEGMENT;
|
||||
return;
|
||||
}
|
||||
squareWavePosition -= SINE_SEGMENT_RELATIVE_LENGTH;
|
||||
if (squareWavePosition < highLinearLength) {
|
||||
phase = POSITIVE_LINEAR_SEGMENT;
|
||||
return;
|
||||
}
|
||||
squareWavePosition -= highLinearLength;
|
||||
if (squareWavePosition < SINE_SEGMENT_RELATIVE_LENGTH) {
|
||||
phase = POSITIVE_FALLING_SINE_SEGMENT;
|
||||
return;
|
||||
}
|
||||
squareWavePosition -= SINE_SEGMENT_RELATIVE_LENGTH;
|
||||
resonanceSinePosition = squareWavePosition;
|
||||
if (squareWavePosition < SINE_SEGMENT_RELATIVE_LENGTH) {
|
||||
phase = NEGATIVE_FALLING_SINE_SEGMENT;
|
||||
return;
|
||||
}
|
||||
squareWavePosition -= SINE_SEGMENT_RELATIVE_LENGTH;
|
||||
if (squareWavePosition < lowLinearLength) {
|
||||
phase = NEGATIVE_LINEAR_SEGMENT;
|
||||
return;
|
||||
}
|
||||
squareWavePosition -= lowLinearLength;
|
||||
phase = NEGATIVE_RISING_SINE_SEGMENT;
|
||||
}
|
||||
|
||||
void LA32WaveGenerator::advancePosition() {
|
||||
wavePosition += getSampleStep();
|
||||
wavePosition %= 4 * SINE_SEGMENT_RELATIVE_LENGTH;
|
||||
|
||||
Bit32u effectiveCutoffValue = (cutoffVal > MIDDLE_CUTOFF_VALUE) ? (cutoffVal - MIDDLE_CUTOFF_VALUE) >> 10 : 0;
|
||||
Bit32u resonanceWaveLengthFactor = getResonanceWaveLengthFactor(effectiveCutoffValue);
|
||||
Bit32u highLinearLength = getHighLinearLength(effectiveCutoffValue);
|
||||
Bit32u lowLinearLength = (resonanceWaveLengthFactor << 8) - 4 * SINE_SEGMENT_RELATIVE_LENGTH - highLinearLength;
|
||||
computePositions(highLinearLength, lowLinearLength, resonanceWaveLengthFactor);
|
||||
|
||||
resonancePhase = ResonancePhase(((resonanceSinePosition >> 18) + (phase > POSITIVE_FALLING_SINE_SEGMENT ? 2 : 0)) & 3);
|
||||
}
|
||||
|
||||
void LA32WaveGenerator::generateNextSquareWaveLogSample() {
|
||||
Bit32u logSampleValue;
|
||||
switch (phase) {
|
||||
case POSITIVE_RISING_SINE_SEGMENT:
|
||||
case NEGATIVE_FALLING_SINE_SEGMENT:
|
||||
logSampleValue = Tables::getInstance().logsin9[(squareWavePosition >> 9) & 511];
|
||||
break;
|
||||
case POSITIVE_FALLING_SINE_SEGMENT:
|
||||
case NEGATIVE_RISING_SINE_SEGMENT:
|
||||
logSampleValue = Tables::getInstance().logsin9[~(squareWavePosition >> 9) & 511];
|
||||
break;
|
||||
case POSITIVE_LINEAR_SEGMENT:
|
||||
case NEGATIVE_LINEAR_SEGMENT:
|
||||
default:
|
||||
logSampleValue = 0;
|
||||
break;
|
||||
}
|
||||
logSampleValue <<= 2;
|
||||
logSampleValue += amp >> 10;
|
||||
if (cutoffVal < MIDDLE_CUTOFF_VALUE) {
|
||||
logSampleValue += (MIDDLE_CUTOFF_VALUE - cutoffVal) >> 9;
|
||||
}
|
||||
|
||||
squareLogSample.logValue = logSampleValue < 65536 ? Bit16u(logSampleValue) : 65535;
|
||||
squareLogSample.sign = phase < NEGATIVE_FALLING_SINE_SEGMENT ? LogSample::POSITIVE : LogSample::NEGATIVE;
|
||||
}
|
||||
|
||||
void LA32WaveGenerator::generateNextResonanceWaveLogSample() {
|
||||
Bit32u logSampleValue;
|
||||
if (resonancePhase == POSITIVE_FALLING_RESONANCE_SINE_SEGMENT || resonancePhase == NEGATIVE_RISING_RESONANCE_SINE_SEGMENT) {
|
||||
logSampleValue = Tables::getInstance().logsin9[~(resonanceSinePosition >> 9) & 511];
|
||||
} else {
|
||||
logSampleValue = Tables::getInstance().logsin9[(resonanceSinePosition >> 9) & 511];
|
||||
}
|
||||
logSampleValue <<= 2;
|
||||
logSampleValue += amp >> 10;
|
||||
|
||||
// From the digital captures, the decaying speed of the resonance sine is found a bit different for the positive and the negative segments
|
||||
Bit32u decayFactor = phase < NEGATIVE_FALLING_SINE_SEGMENT ? resAmpDecayFactor : resAmpDecayFactor + 1;
|
||||
// Unsure about resonanceSinePosition here. It's possible that dedicated counter & decrement are used. Although, cutoff is finely ramped, so maybe not.
|
||||
logSampleValue += resonanceAmpSubtraction + (((resonanceSinePosition >> 4) * decayFactor) >> 8);
|
||||
|
||||
// To ensure the output wave has no breaks, two different windows are appied to the beginning and the ending of the resonance sine segment
|
||||
if (phase == POSITIVE_RISING_SINE_SEGMENT || phase == NEGATIVE_FALLING_SINE_SEGMENT) {
|
||||
// The window is synchronous sine here
|
||||
logSampleValue += Tables::getInstance().logsin9[(squareWavePosition >> 9) & 511] << 2;
|
||||
} else if (phase == POSITIVE_FALLING_SINE_SEGMENT || phase == NEGATIVE_RISING_SINE_SEGMENT) {
|
||||
// The window is synchronous square sine here
|
||||
logSampleValue += Tables::getInstance().logsin9[~(squareWavePosition >> 9) & 511] << 3;
|
||||
}
|
||||
|
||||
if (cutoffVal < MIDDLE_CUTOFF_VALUE) {
|
||||
// For the cutoff values below the cutoff middle point, it seems the amp of the resonance wave is expotentially decayed
|
||||
logSampleValue += 31743 + ((MIDDLE_CUTOFF_VALUE - cutoffVal) >> 9);
|
||||
} else if (cutoffVal < RESONANCE_DECAY_THRESHOLD_CUTOFF_VALUE) {
|
||||
// For the cutoff values below this point, the amp of the resonance wave is sinusoidally decayed
|
||||
Bit32u sineIx = (cutoffVal - MIDDLE_CUTOFF_VALUE) >> 13;
|
||||
logSampleValue += Tables::getInstance().logsin9[sineIx] << 2;
|
||||
}
|
||||
|
||||
// After all the amp decrements are added, it should be safe now to adjust the amp of the resonance wave to what we see on captures
|
||||
logSampleValue -= 1 << 12;
|
||||
|
||||
resonanceLogSample.logValue = logSampleValue < 65536 ? Bit16u(logSampleValue) : 65535;
|
||||
resonanceLogSample.sign = resonancePhase < NEGATIVE_FALLING_RESONANCE_SINE_SEGMENT ? LogSample::POSITIVE : LogSample::NEGATIVE;
|
||||
}
|
||||
|
||||
void LA32WaveGenerator::generateNextSawtoothCosineLogSample(LogSample &logSample) const {
|
||||
Bit32u sawtoothCosinePosition = wavePosition + (1 << 18);
|
||||
if ((sawtoothCosinePosition & (1 << 18)) > 0) {
|
||||
logSample.logValue = Tables::getInstance().logsin9[~(sawtoothCosinePosition >> 9) & 511];
|
||||
} else {
|
||||
logSample.logValue = Tables::getInstance().logsin9[(sawtoothCosinePosition >> 9) & 511];
|
||||
}
|
||||
logSample.logValue <<= 2;
|
||||
logSample.sign = ((sawtoothCosinePosition & (1 << 19)) == 0) ? LogSample::POSITIVE : LogSample::NEGATIVE;
|
||||
}
|
||||
|
||||
void LA32WaveGenerator::pcmSampleToLogSample(LogSample &logSample, const Bit16s pcmSample) const {
|
||||
Bit32u logSampleValue = (32787 - (pcmSample & 32767)) << 1;
|
||||
logSampleValue += amp >> 10;
|
||||
logSample.logValue = logSampleValue < 65536 ? Bit16u(logSampleValue) : 65535;
|
||||
logSample.sign = pcmSample < 0 ? LogSample::NEGATIVE : LogSample::POSITIVE;
|
||||
}
|
||||
|
||||
void LA32WaveGenerator::generateNextPCMWaveLogSamples() {
|
||||
// This should emulate the ladder we see in the PCM captures for pitches 01, 02, 07, etc.
|
||||
// The most probable cause is the factor in the interpolation formula is one bit less
|
||||
// accurate than the sample position counter
|
||||
pcmInterpolationFactor = (wavePosition & 255) >> 1;
|
||||
Bit32u pcmWaveTableIx = wavePosition >> 8;
|
||||
pcmSampleToLogSample(firstPCMLogSample, pcmWaveAddress[pcmWaveTableIx]);
|
||||
if (pcmWaveInterpolated) {
|
||||
pcmWaveTableIx++;
|
||||
if (pcmWaveTableIx < pcmWaveLength) {
|
||||
pcmSampleToLogSample(secondPCMLogSample, pcmWaveAddress[pcmWaveTableIx]);
|
||||
} else {
|
||||
if (pcmWaveLooped) {
|
||||
pcmWaveTableIx -= pcmWaveLength;
|
||||
pcmSampleToLogSample(secondPCMLogSample, pcmWaveAddress[pcmWaveTableIx]);
|
||||
} else {
|
||||
secondPCMLogSample = SILENCE;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
secondPCMLogSample = SILENCE;
|
||||
}
|
||||
// pcmSampleStep = (Bit32u)EXP2F(pitch / 4096.0f + 3.0f);
|
||||
Bit32u pcmSampleStep = LA32Utilites::interpolateExp(~pitch & 4095);
|
||||
pcmSampleStep <<= pitch >> 12;
|
||||
// Seeing the actual lengths of the PCM wave for pitches 00..12,
|
||||
// the pcmPosition counter can be assumed to have 8-bit fractions
|
||||
pcmSampleStep >>= 9;
|
||||
wavePosition += pcmSampleStep;
|
||||
if (wavePosition >= (pcmWaveLength << 8)) {
|
||||
if (pcmWaveLooped) {
|
||||
wavePosition -= pcmWaveLength << 8;
|
||||
} else {
|
||||
deactivate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LA32WaveGenerator::initSynth(const bool useSawtoothWaveform, const Bit8u usePulseWidth, const Bit8u useResonance) {
|
||||
sawtoothWaveform = useSawtoothWaveform;
|
||||
pulseWidth = usePulseWidth;
|
||||
resonance = useResonance;
|
||||
|
||||
wavePosition = 0;
|
||||
|
||||
squareWavePosition = 0;
|
||||
phase = POSITIVE_RISING_SINE_SEGMENT;
|
||||
|
||||
resonanceSinePosition = 0;
|
||||
resonancePhase = POSITIVE_RISING_RESONANCE_SINE_SEGMENT;
|
||||
resonanceAmpSubtraction = (32 - resonance) << 10;
|
||||
resAmpDecayFactor = Tables::getInstance().resAmpDecayFactor[resonance >> 2] << 2;
|
||||
|
||||
pcmWaveAddress = NULL;
|
||||
active = true;
|
||||
}
|
||||
|
||||
void LA32WaveGenerator::initPCM(const Bit16s * const usePCMWaveAddress, const Bit32u usePCMWaveLength, const bool usePCMWaveLooped, const bool usePCMWaveInterpolated) {
|
||||
pcmWaveAddress = usePCMWaveAddress;
|
||||
pcmWaveLength = usePCMWaveLength;
|
||||
pcmWaveLooped = usePCMWaveLooped;
|
||||
pcmWaveInterpolated = usePCMWaveInterpolated;
|
||||
|
||||
wavePosition = 0;
|
||||
active = true;
|
||||
}
|
||||
|
||||
void LA32WaveGenerator::generateNextSample(const Bit32u useAmp, const Bit16u usePitch, const Bit32u useCutoffVal) {
|
||||
if (!active) {
|
||||
return;
|
||||
}
|
||||
|
||||
amp = useAmp;
|
||||
pitch = usePitch;
|
||||
|
||||
if (isPCMWave()) {
|
||||
generateNextPCMWaveLogSamples();
|
||||
return;
|
||||
}
|
||||
|
||||
// The 240 cutoffVal limit was determined via sample analysis (internal Munt capture IDs: glop3, glop4).
|
||||
// More research is needed to be sure that this is correct, however.
|
||||
cutoffVal = (useCutoffVal > MAX_CUTOFF_VALUE) ? MAX_CUTOFF_VALUE : useCutoffVal;
|
||||
|
||||
generateNextSquareWaveLogSample();
|
||||
generateNextResonanceWaveLogSample();
|
||||
if (sawtoothWaveform) {
|
||||
LogSample cosineLogSample;
|
||||
generateNextSawtoothCosineLogSample(cosineLogSample);
|
||||
LA32Utilites::addLogSamples(squareLogSample, cosineLogSample);
|
||||
LA32Utilites::addLogSamples(resonanceLogSample, cosineLogSample);
|
||||
}
|
||||
advancePosition();
|
||||
}
|
||||
|
||||
LogSample LA32WaveGenerator::getOutputLogSample(const bool first) const {
|
||||
if (!isActive()) {
|
||||
return SILENCE;
|
||||
}
|
||||
if (isPCMWave()) {
|
||||
return first ? firstPCMLogSample : secondPCMLogSample;
|
||||
}
|
||||
return first ? squareLogSample : resonanceLogSample;
|
||||
}
|
||||
|
||||
void LA32WaveGenerator::deactivate() {
|
||||
active = false;
|
||||
}
|
||||
|
||||
bool LA32WaveGenerator::isActive() const {
|
||||
return active;
|
||||
}
|
||||
|
||||
bool LA32WaveGenerator::isPCMWave() const {
|
||||
return pcmWaveAddress != NULL;
|
||||
}
|
||||
|
||||
Bit32u LA32WaveGenerator::getPCMInterpolationFactor() const {
|
||||
return pcmInterpolationFactor;
|
||||
}
|
||||
|
||||
void LA32PartialPair::init(const bool useRingModulated, const bool useMixed) {
|
||||
ringModulated = useRingModulated;
|
||||
mixed = useMixed;
|
||||
}
|
||||
|
||||
void LA32PartialPair::initSynth(const PairType useMaster, const bool sawtoothWaveform, const Bit8u pulseWidth, const Bit8u resonance) {
|
||||
if (useMaster == MASTER) {
|
||||
master.initSynth(sawtoothWaveform, pulseWidth, resonance);
|
||||
} else {
|
||||
slave.initSynth(sawtoothWaveform, pulseWidth, resonance);
|
||||
}
|
||||
}
|
||||
|
||||
void LA32PartialPair::initPCM(const PairType useMaster, const Bit16s *pcmWaveAddress, const Bit32u pcmWaveLength, const bool pcmWaveLooped) {
|
||||
if (useMaster == MASTER) {
|
||||
master.initPCM(pcmWaveAddress, pcmWaveLength, pcmWaveLooped, true);
|
||||
} else {
|
||||
slave.initPCM(pcmWaveAddress, pcmWaveLength, pcmWaveLooped, !ringModulated);
|
||||
}
|
||||
}
|
||||
|
||||
void LA32PartialPair::generateNextSample(const PairType useMaster, const Bit32u amp, const Bit16u pitch, const Bit32u cutoff) {
|
||||
if (useMaster == MASTER) {
|
||||
master.generateNextSample(amp, pitch, cutoff);
|
||||
} else {
|
||||
slave.generateNextSample(amp, pitch, cutoff);
|
||||
}
|
||||
}
|
||||
|
||||
Bit16s LA32PartialPair::unlogAndMixWGOutput(const LA32WaveGenerator &wg) {
|
||||
if (!wg.isActive()) {
|
||||
return 0;
|
||||
}
|
||||
Bit16s firstSample = LA32Utilites::unlog(wg.getOutputLogSample(true));
|
||||
Bit16s secondSample = LA32Utilites::unlog(wg.getOutputLogSample(false));
|
||||
if (wg.isPCMWave()) {
|
||||
return Bit16s(firstSample + (((Bit32s(secondSample) - Bit32s(firstSample)) * wg.getPCMInterpolationFactor()) >> 7));
|
||||
}
|
||||
return firstSample + secondSample;
|
||||
}
|
||||
|
||||
Bit16s LA32PartialPair::nextOutSample() {
|
||||
if (!ringModulated) {
|
||||
return unlogAndMixWGOutput(master) + unlogAndMixWGOutput(slave);
|
||||
}
|
||||
|
||||
/*
|
||||
* SEMI-CONFIRMED: Ring modulation model derived from sample analysis of specially constructed patches which exploit distortion.
|
||||
* LA32 ring modulator found to produce distorted output in case if the absolute value of maximal amplitude of one of the input partials exceeds 8191.
|
||||
* This is easy to reproduce using synth partials with resonance values close to the maximum. It looks like an integer overflow happens in this case.
|
||||
* As the distortion is strictly bound to the amplitude of the complete mixed square + resonance wave in the linear space,
|
||||
* it is reasonable to assume the ring modulation is performed also in the linear space by sample multiplication.
|
||||
* Most probably the overflow is caused by limited precision of the multiplication circuit as the very similar distortion occurs with panning.
|
||||
*/
|
||||
Bit16s nonOverdrivenMasterSample = unlogAndMixWGOutput(master); // Store master partial sample for further mixing
|
||||
Bit16s masterSample = nonOverdrivenMasterSample << 2;
|
||||
masterSample >>= 2;
|
||||
|
||||
/* SEMI-CONFIRMED from sample analysis:
|
||||
* We observe that for partial structures with ring modulation the interpolation is not applied to the slave PCM partial.
|
||||
* It's assumed that the multiplication circuitry intended to perform the interpolation on the slave PCM partial
|
||||
* is borrowed by the ring modulation circuit (or the LA32 chip has a similar lack of resources assigned to each partial pair).
|
||||
*/
|
||||
Bit16s slaveSample = slave.isPCMWave() ? LA32Utilites::unlog(slave.getOutputLogSample(true)) : unlogAndMixWGOutput(slave);
|
||||
slaveSample <<= 2;
|
||||
slaveSample >>= 2;
|
||||
Bit16s ringModulatedSample = Bit16s((Bit32s(masterSample) * Bit32s(slaveSample)) >> 13);
|
||||
return mixed ? nonOverdrivenMasterSample + ringModulatedSample : ringModulatedSample;
|
||||
}
|
||||
|
||||
void LA32PartialPair::deactivate(const PairType useMaster) {
|
||||
if (useMaster == MASTER) {
|
||||
master.deactivate();
|
||||
} else {
|
||||
slave.deactivate();
|
||||
}
|
||||
}
|
||||
|
||||
bool LA32PartialPair::isActive(const PairType useMaster) const {
|
||||
return useMaster == MASTER ? master.isActive() : slave.isActive();
|
||||
}
|
||||
|
||||
} // namespace MT32Emu
|
||||
|
||||
#endif // #if MT32EMU_USE_FLOAT_SAMPLES
|
@ -1,252 +0,0 @@
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MT32EMU_LA32_WAVE_GENERATOR_H
|
||||
#define MT32EMU_LA32_WAVE_GENERATOR_H
|
||||
|
||||
#include "globals.h"
|
||||
#include "internals.h"
|
||||
#include "Types.h"
|
||||
|
||||
#if MT32EMU_USE_FLOAT_SAMPLES
|
||||
#include "LA32FloatWaveGenerator.h"
|
||||
#else
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
/**
|
||||
* LA32 performs wave generation in the log-space that allows replacing multiplications by cheap additions
|
||||
* It's assumed that only low-bit multiplications occur in a few places which are unavoidable like these:
|
||||
* - interpolation of exponent table (obvious, a delta value has 4 bits)
|
||||
* - computation of resonance amp decay envelope (the table contains values with 1-2 "1" bits except the very first value 31 but this case can be found using inversion)
|
||||
* - interpolation of PCM samples (obvious, the wave position counter is in the linear space, there is no log() table in the chip)
|
||||
* and it seems to be implemented in the same way as in the Boss chip, i.e. right shifted additions which involved noticeable precision loss
|
||||
* Subtraction is supposed to be replaced by simple inversion
|
||||
* As the logarithmic sine is always negative, all the logarithmic values are treated as decrements
|
||||
*/
|
||||
struct LogSample {
|
||||
// 16-bit fixed point value, includes 12-bit fractional part
|
||||
// 4-bit integer part allows to present any 16-bit sample in the log-space
|
||||
// Obviously, the log value doesn't contain the sign of the resulting sample
|
||||
Bit16u logValue;
|
||||
enum {
|
||||
POSITIVE,
|
||||
NEGATIVE
|
||||
} sign;
|
||||
};
|
||||
|
||||
class LA32Utilites {
|
||||
public:
|
||||
static Bit16u interpolateExp(const Bit16u fract);
|
||||
static Bit16s unlog(const LogSample &logSample);
|
||||
static void addLogSamples(LogSample &logSample1, const LogSample &logSample2);
|
||||
};
|
||||
|
||||
/**
|
||||
* LA32WaveGenerator is aimed to represent the exact model of LA32 wave generator.
|
||||
* The output square wave is created by adding high / low linear segments in-between
|
||||
* the rising and falling cosine segments. Basically, it's very similar to the phase distortion synthesis.
|
||||
* Behaviour of a true resonance filter is emulated by adding decaying sine wave.
|
||||
* The beginning and the ending of the resonant sine is multiplied by a cosine window.
|
||||
* To synthesise sawtooth waves, the resulting square wave is multiplied by synchronous cosine wave.
|
||||
*/
|
||||
class LA32WaveGenerator {
|
||||
//***************************************************************************
|
||||
// The local copy of partial parameters below
|
||||
//***************************************************************************
|
||||
|
||||
bool active;
|
||||
|
||||
// True means the resulting square wave is to be multiplied by the synchronous cosine
|
||||
bool sawtoothWaveform;
|
||||
|
||||
// Logarithmic amp of the wave generator
|
||||
Bit32u amp;
|
||||
|
||||
// Logarithmic frequency of the resulting wave
|
||||
Bit16u pitch;
|
||||
|
||||
// Values in range [1..31]
|
||||
// Value 1 correspong to the minimum resonance
|
||||
Bit8u resonance;
|
||||
|
||||
// Processed value in range [0..255]
|
||||
// Values in range [0..128] have no effect and the resulting wave remains symmetrical
|
||||
// Value 255 corresponds to the maximum possible asymmetric of the resulting wave
|
||||
Bit8u pulseWidth;
|
||||
|
||||
// Composed of the base cutoff in range [78..178] left-shifted by 18 bits and the TVF modifier
|
||||
Bit32u cutoffVal;
|
||||
|
||||
// Logarithmic PCM sample start address
|
||||
const Bit16s *pcmWaveAddress;
|
||||
|
||||
// Logarithmic PCM sample length
|
||||
Bit32u pcmWaveLength;
|
||||
|
||||
// true for looped logarithmic PCM samples
|
||||
bool pcmWaveLooped;
|
||||
|
||||
// false for slave PCM partials in the structures with the ring modulation
|
||||
bool pcmWaveInterpolated;
|
||||
|
||||
//***************************************************************************
|
||||
// Internal variables below
|
||||
//***************************************************************************
|
||||
|
||||
// Relative position within either the synth wave or the PCM sampled wave
|
||||
// 0 - start of the positive rising sine segment of the square wave or start of the PCM sample
|
||||
// 1048576 (2^20) - end of the negative rising sine segment of the square wave
|
||||
// For PCM waves, the address of the currently playing sample equals (wavePosition / 256)
|
||||
Bit32u wavePosition;
|
||||
|
||||
// Relative position within a square wave phase:
|
||||
// 0 - start of the phase
|
||||
// 262144 (2^18) - end of a sine phase in the square wave
|
||||
Bit32u squareWavePosition;
|
||||
|
||||
// Relative position within the positive or negative wave segment:
|
||||
// 0 - start of the corresponding positive or negative segment of the square wave
|
||||
// 262144 (2^18) - corresponds to end of the first sine phase in the square wave
|
||||
// The same increment sampleStep is used to indicate the current position
|
||||
// since the length of the resonance wave is always equal to four square wave sine segments.
|
||||
Bit32u resonanceSinePosition;
|
||||
|
||||
// The amp of the resonance sine wave grows with the resonance value
|
||||
// As the resonance value cannot change while the partial is active, it is initialised once
|
||||
Bit32u resonanceAmpSubtraction;
|
||||
|
||||
// The decay speed of resonance sine wave, depends on the resonance value
|
||||
Bit32u resAmpDecayFactor;
|
||||
|
||||
// Fractional part of the pcmPosition
|
||||
Bit32u pcmInterpolationFactor;
|
||||
|
||||
// Current phase of the square wave
|
||||
enum {
|
||||
POSITIVE_RISING_SINE_SEGMENT,
|
||||
POSITIVE_LINEAR_SEGMENT,
|
||||
POSITIVE_FALLING_SINE_SEGMENT,
|
||||
NEGATIVE_FALLING_SINE_SEGMENT,
|
||||
NEGATIVE_LINEAR_SEGMENT,
|
||||
NEGATIVE_RISING_SINE_SEGMENT
|
||||
} phase;
|
||||
|
||||
// Current phase of the resonance wave
|
||||
enum ResonancePhase {
|
||||
POSITIVE_RISING_RESONANCE_SINE_SEGMENT,
|
||||
POSITIVE_FALLING_RESONANCE_SINE_SEGMENT,
|
||||
NEGATIVE_FALLING_RESONANCE_SINE_SEGMENT,
|
||||
NEGATIVE_RISING_RESONANCE_SINE_SEGMENT
|
||||
} resonancePhase;
|
||||
|
||||
// Resulting log-space samples of the square and resonance waves
|
||||
LogSample squareLogSample;
|
||||
LogSample resonanceLogSample;
|
||||
|
||||
// Processed neighbour log-space samples of the PCM wave
|
||||
LogSample firstPCMLogSample;
|
||||
LogSample secondPCMLogSample;
|
||||
|
||||
//***************************************************************************
|
||||
// Internal methods below
|
||||
//***************************************************************************
|
||||
|
||||
Bit32u getSampleStep();
|
||||
Bit32u getResonanceWaveLengthFactor(Bit32u effectiveCutoffValue);
|
||||
Bit32u getHighLinearLength(Bit32u effectiveCutoffValue);
|
||||
|
||||
void computePositions(Bit32u highLinearLength, Bit32u lowLinearLength, Bit32u resonanceWaveLengthFactor);
|
||||
void advancePosition();
|
||||
|
||||
void generateNextSquareWaveLogSample();
|
||||
void generateNextResonanceWaveLogSample();
|
||||
void generateNextSawtoothCosineLogSample(LogSample &logSample) const;
|
||||
|
||||
void pcmSampleToLogSample(LogSample &logSample, const Bit16s pcmSample) const;
|
||||
void generateNextPCMWaveLogSamples();
|
||||
|
||||
public:
|
||||
// Initialise the WG engine for generation of synth partial samples and set up the invariant parameters
|
||||
void initSynth(const bool sawtoothWaveform, const Bit8u pulseWidth, const Bit8u resonance);
|
||||
|
||||
// Initialise the WG engine for generation of PCM partial samples and set up the invariant parameters
|
||||
void initPCM(const Bit16s * const pcmWaveAddress, const Bit32u pcmWaveLength, const bool pcmWaveLooped, const bool pcmWaveInterpolated);
|
||||
|
||||
// Update parameters with respect to TVP, TVA and TVF, and generate next sample
|
||||
void generateNextSample(const Bit32u amp, const Bit16u pitch, const Bit32u cutoff);
|
||||
|
||||
// WG output in the log-space consists of two components which are to be added (or ring modulated) in the linear-space afterwards
|
||||
LogSample getOutputLogSample(const bool first) const;
|
||||
|
||||
// Deactivate the WG engine
|
||||
void deactivate();
|
||||
|
||||
// Return active state of the WG engine
|
||||
bool isActive() const;
|
||||
|
||||
// Return true if the WG engine generates PCM wave samples
|
||||
bool isPCMWave() const;
|
||||
|
||||
// Return current PCM interpolation factor
|
||||
Bit32u getPCMInterpolationFactor() const;
|
||||
}; // class LA32WaveGenerator
|
||||
|
||||
// LA32PartialPair contains a structure of two partials being mixed / ring modulated
|
||||
class LA32PartialPair {
|
||||
LA32WaveGenerator master;
|
||||
LA32WaveGenerator slave;
|
||||
bool ringModulated;
|
||||
bool mixed;
|
||||
|
||||
static Bit16s unlogAndMixWGOutput(const LA32WaveGenerator &wg);
|
||||
|
||||
public:
|
||||
enum PairType {
|
||||
MASTER,
|
||||
SLAVE
|
||||
};
|
||||
|
||||
// ringModulated should be set to false for the structures with mixing or stereo output
|
||||
// ringModulated should be set to true for the structures with ring modulation
|
||||
// mixed is used for the structures with ring modulation and indicates whether the master partial output is mixed to the ring modulator output
|
||||
void init(const bool ringModulated, const bool mixed);
|
||||
|
||||
// Initialise the WG engine for generation of synth partial samples and set up the invariant parameters
|
||||
void initSynth(const PairType master, const bool sawtoothWaveform, const Bit8u pulseWidth, const Bit8u resonance);
|
||||
|
||||
// Initialise the WG engine for generation of PCM partial samples and set up the invariant parameters
|
||||
void initPCM(const PairType master, const Bit16s * const pcmWaveAddress, const Bit32u pcmWaveLength, const bool pcmWaveLooped);
|
||||
|
||||
// Update parameters with respect to TVP, TVA and TVF, and generate next sample
|
||||
void generateNextSample(const PairType master, const Bit32u amp, const Bit16u pitch, const Bit32u cutoff);
|
||||
|
||||
// Perform mixing / ring modulation and return the result
|
||||
Bit16s nextOutSample();
|
||||
|
||||
// Deactivate the WG engine
|
||||
void deactivate(const PairType master);
|
||||
|
||||
// Return active state of the WG engine
|
||||
bool isActive(const PairType master) const;
|
||||
}; // class LA32PartialPair
|
||||
|
||||
} // namespace MT32Emu
|
||||
|
||||
#endif // #if MT32EMU_USE_FLOAT_SAMPLES
|
||||
|
||||
#endif // #ifndef MT32EMU_LA32_WAVE_GENERATOR_H
|
@ -1,132 +0,0 @@
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MT32EMU_MEMORY_REGION_H
|
||||
#define MT32EMU_MEMORY_REGION_H
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
#include "globals.h"
|
||||
#include "Types.h"
|
||||
#include "Structures.h"
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
enum MemoryRegionType {
|
||||
MR_PatchTemp, MR_RhythmTemp, MR_TimbreTemp, MR_Patches, MR_Timbres, MR_System, MR_Display, MR_Reset
|
||||
};
|
||||
|
||||
class Synth;
|
||||
|
||||
class MemoryRegion {
|
||||
private:
|
||||
Synth *synth;
|
||||
Bit8u *realMemory;
|
||||
Bit8u *maxTable;
|
||||
public:
|
||||
MemoryRegionType type;
|
||||
Bit32u startAddr, entrySize, entries;
|
||||
|
||||
MemoryRegion(Synth *useSynth, Bit8u *useRealMemory, Bit8u *useMaxTable, MemoryRegionType useType, Bit32u useStartAddr, Bit32u useEntrySize, Bit32u useEntries) {
|
||||
synth = useSynth;
|
||||
realMemory = useRealMemory;
|
||||
maxTable = useMaxTable;
|
||||
type = useType;
|
||||
startAddr = useStartAddr;
|
||||
entrySize = useEntrySize;
|
||||
entries = useEntries;
|
||||
}
|
||||
int lastTouched(Bit32u addr, Bit32u len) const {
|
||||
return (offset(addr) + len - 1) / entrySize;
|
||||
}
|
||||
int firstTouchedOffset(Bit32u addr) const {
|
||||
return offset(addr) % entrySize;
|
||||
}
|
||||
int firstTouched(Bit32u addr) const {
|
||||
return offset(addr) / entrySize;
|
||||
}
|
||||
Bit32u regionEnd() const {
|
||||
return startAddr + entrySize * entries;
|
||||
}
|
||||
bool contains(Bit32u addr) const {
|
||||
return addr >= startAddr && addr < regionEnd();
|
||||
}
|
||||
int offset(Bit32u addr) const {
|
||||
return addr - startAddr;
|
||||
}
|
||||
Bit32u getClampedLen(Bit32u addr, Bit32u len) const {
|
||||
if (addr + len > regionEnd())
|
||||
return regionEnd() - addr;
|
||||
return len;
|
||||
}
|
||||
Bit32u next(Bit32u addr, Bit32u len) const {
|
||||
if (addr + len > regionEnd()) {
|
||||
return regionEnd() - addr;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
Bit8u getMaxValue(int off) const {
|
||||
if (maxTable == NULL)
|
||||
return 0xFF;
|
||||
return maxTable[off % entrySize];
|
||||
}
|
||||
Bit8u *getRealMemory() const {
|
||||
return realMemory;
|
||||
}
|
||||
bool isReadable() const {
|
||||
return getRealMemory() != NULL;
|
||||
}
|
||||
void read(unsigned int entry, unsigned int off, Bit8u *dst, unsigned int len) const;
|
||||
void write(unsigned int entry, unsigned int off, const Bit8u *src, unsigned int len, bool init = false) const;
|
||||
}; // class MemoryRegion
|
||||
|
||||
class PatchTempMemoryRegion : public MemoryRegion {
|
||||
public:
|
||||
PatchTempMemoryRegion(Synth *useSynth, Bit8u *useRealMemory, Bit8u *useMaxTable) : MemoryRegion(useSynth, useRealMemory, useMaxTable, MR_PatchTemp, MT32EMU_MEMADDR(0x030000), sizeof(MemParams::PatchTemp), 9) {}
|
||||
};
|
||||
class RhythmTempMemoryRegion : public MemoryRegion {
|
||||
public:
|
||||
RhythmTempMemoryRegion(Synth *useSynth, Bit8u *useRealMemory, Bit8u *useMaxTable) : MemoryRegion(useSynth, useRealMemory, useMaxTable, MR_RhythmTemp, MT32EMU_MEMADDR(0x030110), sizeof(MemParams::RhythmTemp), 85) {}
|
||||
};
|
||||
class TimbreTempMemoryRegion : public MemoryRegion {
|
||||
public:
|
||||
TimbreTempMemoryRegion(Synth *useSynth, Bit8u *useRealMemory, Bit8u *useMaxTable) : MemoryRegion(useSynth, useRealMemory, useMaxTable, MR_TimbreTemp, MT32EMU_MEMADDR(0x040000), sizeof(TimbreParam), 8) {}
|
||||
};
|
||||
class PatchesMemoryRegion : public MemoryRegion {
|
||||
public:
|
||||
PatchesMemoryRegion(Synth *useSynth, Bit8u *useRealMemory, Bit8u *useMaxTable) : MemoryRegion(useSynth, useRealMemory, useMaxTable, MR_Patches, MT32EMU_MEMADDR(0x050000), sizeof(PatchParam), 128) {}
|
||||
};
|
||||
class TimbresMemoryRegion : public MemoryRegion {
|
||||
public:
|
||||
TimbresMemoryRegion(Synth *useSynth, Bit8u *useRealMemory, Bit8u *useMaxTable) : MemoryRegion(useSynth, useRealMemory, useMaxTable, MR_Timbres, MT32EMU_MEMADDR(0x080000), sizeof(MemParams::PaddedTimbre), 64 + 64 + 64 + 64) {}
|
||||
};
|
||||
class SystemMemoryRegion : public MemoryRegion {
|
||||
public:
|
||||
SystemMemoryRegion(Synth *useSynth, Bit8u *useRealMemory, Bit8u *useMaxTable) : MemoryRegion(useSynth, useRealMemory, useMaxTable, MR_System, MT32EMU_MEMADDR(0x100000), sizeof(MemParams::System), 1) {}
|
||||
};
|
||||
class DisplayMemoryRegion : public MemoryRegion {
|
||||
public:
|
||||
DisplayMemoryRegion(Synth *useSynth) : MemoryRegion(useSynth, NULL, NULL, MR_Display, MT32EMU_MEMADDR(0x200000), SYSEX_BUFFER_SIZE - 1, 1) {}
|
||||
};
|
||||
class ResetMemoryRegion : public MemoryRegion {
|
||||
public:
|
||||
ResetMemoryRegion(Synth *useSynth) : MemoryRegion(useSynth, NULL, NULL, MR_Reset, MT32EMU_MEMADDR(0x7F0000), 0x3FFF, 1) {}
|
||||
};
|
||||
|
||||
} // namespace MT32Emu
|
||||
|
||||
#endif // #ifndef MT32EMU_MEMORY_REGION_H
|
@ -1,71 +0,0 @@
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MT32EMU_MIDI_EVENT_QUEUE_H
|
||||
#define MT32EMU_MIDI_EVENT_QUEUE_H
|
||||
|
||||
#include "globals.h"
|
||||
#include "Types.h"
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
/**
|
||||
* Used to safely store timestamped MIDI events in a local queue.
|
||||
*/
|
||||
struct MidiEvent {
|
||||
Bit32u shortMessageData;
|
||||
const Bit8u *sysexData;
|
||||
Bit32u sysexLength;
|
||||
Bit32u timestamp;
|
||||
|
||||
~MidiEvent();
|
||||
void setShortMessage(Bit32u shortMessageData, Bit32u timestamp);
|
||||
void setSysex(const Bit8u *sysexData, Bit32u sysexLength, Bit32u timestamp);
|
||||
};
|
||||
|
||||
/**
|
||||
* Simple queue implementation using a ring buffer to store incoming MIDI event before the synth actually processes it.
|
||||
* It is intended to:
|
||||
* - get rid of prerenderer while retaining graceful partial abortion
|
||||
* - add fair emulation of the MIDI interface delays
|
||||
* - extend the synth interface with the default implementation of a typical rendering loop.
|
||||
* THREAD SAFETY:
|
||||
* It is safe to use either in a single thread environment or when there are only two threads - one performs only reading
|
||||
* and one performs only writing. More complicated usage requires external synchronisation.
|
||||
*/
|
||||
class MidiEventQueue {
|
||||
private:
|
||||
MidiEvent * const ringBuffer;
|
||||
const Bit32u ringBufferMask;
|
||||
volatile Bit32u startPosition;
|
||||
volatile Bit32u endPosition;
|
||||
|
||||
public:
|
||||
MidiEventQueue(Bit32u ringBufferSize = DEFAULT_MIDI_EVENT_QUEUE_SIZE); // Must be a power of 2
|
||||
~MidiEventQueue();
|
||||
void reset();
|
||||
bool pushShortMessage(Bit32u shortMessageData, Bit32u timestamp);
|
||||
bool pushSysex(const Bit8u *sysexData, Bit32u sysexLength, Bit32u timestamp);
|
||||
const MidiEvent *peekMidiEvent();
|
||||
void dropMidiEvent();
|
||||
bool isFull() const;
|
||||
bool inline isEmpty() const;
|
||||
};
|
||||
|
||||
} // namespace MT32Emu
|
||||
|
||||
#endif // #ifndef MT32EMU_MIDI_EVENT_QUEUE_H
|
@ -1,289 +0,0 @@
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
#include "internals.h"
|
||||
|
||||
#include "MidiStreamParser.h"
|
||||
#include "Synth.h"
|
||||
|
||||
using namespace MT32Emu;
|
||||
|
||||
DefaultMidiStreamParser::DefaultMidiStreamParser(Synth &useSynth, Bit32u initialStreamBufferCapacity) :
|
||||
MidiStreamParser(initialStreamBufferCapacity), synth(useSynth), timestampSet(false) {}
|
||||
|
||||
void DefaultMidiStreamParser::setTimestamp(const Bit32u useTimestamp) {
|
||||
timestampSet = true;
|
||||
timestamp = useTimestamp;
|
||||
}
|
||||
|
||||
void DefaultMidiStreamParser::resetTimestamp() {
|
||||
timestampSet = false;
|
||||
}
|
||||
|
||||
void DefaultMidiStreamParser::handleShortMessage(const Bit32u message) {
|
||||
do {
|
||||
if (timestampSet) {
|
||||
if (synth.playMsg(message, timestamp)) return;
|
||||
}
|
||||
else {
|
||||
if (synth.playMsg(message)) return;
|
||||
}
|
||||
} while (synth.reportHandler->onMIDIQueueOverflow());
|
||||
}
|
||||
|
||||
void DefaultMidiStreamParser::handleSysex(const Bit8u *stream, const Bit32u length) {
|
||||
do {
|
||||
if (timestampSet) {
|
||||
if (synth.playSysex(stream, length, timestamp)) return;
|
||||
}
|
||||
else {
|
||||
if (synth.playSysex(stream, length)) return;
|
||||
}
|
||||
} while (synth.reportHandler->onMIDIQueueOverflow());
|
||||
}
|
||||
|
||||
void DefaultMidiStreamParser::handleSystemRealtimeMessage(const Bit8u realtime) {
|
||||
synth.reportHandler->onMIDISystemRealtime(realtime);
|
||||
}
|
||||
|
||||
void DefaultMidiStreamParser::printDebug(const char *debugMessage) {
|
||||
synth.printDebug("%s", debugMessage);
|
||||
}
|
||||
|
||||
MidiStreamParser::MidiStreamParser(Bit32u initialStreamBufferCapacity) :
|
||||
MidiStreamParserImpl(*this, *this, initialStreamBufferCapacity) {}
|
||||
|
||||
MidiStreamParserImpl::MidiStreamParserImpl(MidiReceiver &useReceiver, MidiReporter &useReporter, Bit32u initialStreamBufferCapacity) :
|
||||
midiReceiver(useReceiver), midiReporter(useReporter)
|
||||
{
|
||||
if (initialStreamBufferCapacity < SYSEX_BUFFER_SIZE) initialStreamBufferCapacity = SYSEX_BUFFER_SIZE;
|
||||
if (MAX_STREAM_BUFFER_SIZE < initialStreamBufferCapacity) initialStreamBufferCapacity = MAX_STREAM_BUFFER_SIZE;
|
||||
streamBufferCapacity = initialStreamBufferCapacity;
|
||||
streamBuffer = new Bit8u[streamBufferCapacity];
|
||||
streamBufferSize = 0;
|
||||
runningStatus = 0;
|
||||
|
||||
reserved = NULL;
|
||||
}
|
||||
|
||||
MidiStreamParserImpl::~MidiStreamParserImpl() {
|
||||
delete[] streamBuffer;
|
||||
}
|
||||
|
||||
void MidiStreamParserImpl::parseStream(const Bit8u *stream, Bit32u length) {
|
||||
while (length > 0) {
|
||||
Bit32u parsedMessageLength = 0;
|
||||
if (0xF8 <= *stream) {
|
||||
// Process System Realtime immediately and go on
|
||||
midiReceiver.handleSystemRealtimeMessage(*stream);
|
||||
parsedMessageLength = 1;
|
||||
// No effect on the running status
|
||||
} else if (streamBufferSize > 0) {
|
||||
// Check if there is something in streamBuffer waiting for being processed
|
||||
if (*streamBuffer == 0xF0) {
|
||||
parsedMessageLength = parseSysexFragment(stream, length);
|
||||
} else {
|
||||
parsedMessageLength = parseShortMessageDataBytes(stream, length);
|
||||
}
|
||||
} else {
|
||||
if (*stream == 0xF0) {
|
||||
runningStatus = 0; // SysEx clears the running status
|
||||
parsedMessageLength = parseSysex(stream, length);
|
||||
} else {
|
||||
parsedMessageLength = parseShortMessageStatus(stream);
|
||||
}
|
||||
}
|
||||
|
||||
// Parsed successfully
|
||||
stream += parsedMessageLength;
|
||||
length -= parsedMessageLength;
|
||||
}
|
||||
}
|
||||
|
||||
void MidiStreamParserImpl::processShortMessage(const Bit32u message) {
|
||||
// Adds running status to the MIDI message if it doesn't contain one
|
||||
Bit8u status = Bit8u(message);
|
||||
if (0xF8 <= status) {
|
||||
midiReceiver.handleSystemRealtimeMessage(status);
|
||||
} else if (processStatusByte(status)) {
|
||||
midiReceiver.handleShortMessage((message << 8) | status);
|
||||
} else if (0x80 <= status) { // If no running status available yet, skip this message
|
||||
midiReceiver.handleShortMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
// We deal with SysEx messages below 512 bytes long in most cases. Nevertheless, it seems reasonable to support a possibility
|
||||
// to load bulk dumps using a single message. However, this is known to fail with a real device due to limited input buffer size.
|
||||
bool MidiStreamParserImpl::checkStreamBufferCapacity(const bool preserveContent) {
|
||||
if (streamBufferSize < streamBufferCapacity) return true;
|
||||
if (streamBufferCapacity < MAX_STREAM_BUFFER_SIZE) {
|
||||
Bit8u *oldStreamBuffer = streamBuffer;
|
||||
streamBufferCapacity = MAX_STREAM_BUFFER_SIZE;
|
||||
streamBuffer = new Bit8u[streamBufferCapacity];
|
||||
if (preserveContent) memcpy(streamBuffer, oldStreamBuffer, streamBufferSize);
|
||||
delete[] oldStreamBuffer;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Checks input byte whether it is a status byte. If not, replaces it with running status when available.
|
||||
// Returns true if the input byte was changed to running status.
|
||||
bool MidiStreamParserImpl::processStatusByte(Bit8u &status) {
|
||||
if (status < 0x80) {
|
||||
// First byte isn't status, try running status
|
||||
if (runningStatus < 0x80) {
|
||||
// No running status available yet
|
||||
midiReporter.printDebug("processStatusByte: No valid running status yet, MIDI message ignored");
|
||||
return false;
|
||||
}
|
||||
status = runningStatus;
|
||||
return true;
|
||||
} else if (status < 0xF0) {
|
||||
// Store current status as running for a Voice message
|
||||
runningStatus = status;
|
||||
} else if (status < 0xF8) {
|
||||
// System Common clears running status
|
||||
runningStatus = 0;
|
||||
} // System Realtime doesn't affect running status
|
||||
return false;
|
||||
}
|
||||
|
||||
// Returns # of bytes parsed
|
||||
Bit32u MidiStreamParserImpl::parseShortMessageStatus(const Bit8u stream[]) {
|
||||
Bit8u status = *stream;
|
||||
Bit32u parsedLength = processStatusByte(status) ? 0 : 1;
|
||||
if (0x80 <= status) { // If no running status available yet, skip one byte
|
||||
*streamBuffer = status;
|
||||
++streamBufferSize;
|
||||
}
|
||||
return parsedLength;
|
||||
}
|
||||
|
||||
// Returns # of bytes parsed
|
||||
Bit32u MidiStreamParserImpl::parseShortMessageDataBytes(const Bit8u stream[], Bit32u length) {
|
||||
const Bit32u shortMessageLength = Synth::getShortMessageLength(*streamBuffer);
|
||||
Bit32u parsedLength = 0;
|
||||
|
||||
// Append incoming bytes to streamBuffer
|
||||
while ((streamBufferSize < shortMessageLength) && (length-- > 0)) {
|
||||
Bit8u dataByte = *(stream++);
|
||||
if (dataByte < 0x80) {
|
||||
// Add data byte to streamBuffer
|
||||
streamBuffer[streamBufferSize++] = dataByte;
|
||||
} else if (dataByte < 0xF8) {
|
||||
// Discard invalid bytes and start over
|
||||
char s[128];
|
||||
sprintf(s, "parseShortMessageDataBytes: Invalid short message: status %02x, expected length %i, actual %i -> ignored", *streamBuffer, shortMessageLength, streamBufferSize);
|
||||
midiReporter.printDebug(s);
|
||||
streamBufferSize = 0; // Clear streamBuffer
|
||||
return parsedLength;
|
||||
} else {
|
||||
// Bypass System Realtime message
|
||||
midiReceiver.handleSystemRealtimeMessage(dataByte);
|
||||
}
|
||||
++parsedLength;
|
||||
}
|
||||
if (streamBufferSize < shortMessageLength) return parsedLength; // Still lacks data bytes
|
||||
|
||||
// Assemble short message
|
||||
Bit32u shortMessage = streamBuffer[0];
|
||||
for (Bit32u i = 1; i < shortMessageLength; ++i) {
|
||||
shortMessage |= streamBuffer[i] << (i << 3);
|
||||
}
|
||||
midiReceiver.handleShortMessage(shortMessage);
|
||||
streamBufferSize = 0; // Clear streamBuffer
|
||||
return parsedLength;
|
||||
}
|
||||
|
||||
// Returns # of bytes parsed
|
||||
Bit32u MidiStreamParserImpl::parseSysex(const Bit8u stream[], const Bit32u length) {
|
||||
// Find SysEx length
|
||||
Bit32u sysexLength = 1;
|
||||
while (sysexLength < length) {
|
||||
Bit8u nextByte = stream[sysexLength++];
|
||||
if (0x80 <= nextByte) {
|
||||
if (nextByte == 0xF7) {
|
||||
// End of SysEx
|
||||
midiReceiver.handleSysex(stream, sysexLength);
|
||||
return sysexLength;
|
||||
}
|
||||
if (0xF8 <= nextByte) {
|
||||
// The System Realtime message must be processed right after return
|
||||
// but the SysEx is actually fragmented and to be reconstructed in streamBuffer
|
||||
--sysexLength;
|
||||
break;
|
||||
}
|
||||
// Illegal status byte in SysEx message, aborting
|
||||
midiReporter.printDebug("parseSysex: SysEx message lacks end-of-sysex (0xf7), ignored");
|
||||
// Continue parsing from that point
|
||||
return sysexLength - 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Store incomplete SysEx message for further processing
|
||||
streamBufferSize = sysexLength;
|
||||
if (checkStreamBufferCapacity(false)) {
|
||||
memcpy(streamBuffer, stream, sysexLength);
|
||||
} else {
|
||||
// Not enough buffer capacity, don't care about the real buffer content, just mark the first byte
|
||||
*streamBuffer = *stream;
|
||||
streamBufferSize = streamBufferCapacity;
|
||||
}
|
||||
return sysexLength;
|
||||
}
|
||||
|
||||
// Returns # of bytes parsed
|
||||
Bit32u MidiStreamParserImpl::parseSysexFragment(const Bit8u stream[], const Bit32u length) {
|
||||
Bit32u parsedLength = 0;
|
||||
while (parsedLength < length) {
|
||||
Bit8u nextByte = stream[parsedLength++];
|
||||
if (nextByte < 0x80) {
|
||||
// Add SysEx data byte to streamBuffer
|
||||
if (checkStreamBufferCapacity(true)) streamBuffer[streamBufferSize++] = nextByte;
|
||||
continue;
|
||||
}
|
||||
if (0xF8 <= nextByte) {
|
||||
// Bypass System Realtime message
|
||||
midiReceiver.handleSystemRealtimeMessage(nextByte);
|
||||
continue;
|
||||
}
|
||||
if (nextByte != 0xF7) {
|
||||
// Illegal status byte in SysEx message, aborting
|
||||
midiReporter.printDebug("parseSysexFragment: SysEx message lacks end-of-sysex (0xf7), ignored");
|
||||
// Clear streamBuffer and continue parsing from that point
|
||||
streamBufferSize = 0;
|
||||
--parsedLength;
|
||||
break;
|
||||
}
|
||||
// End of SysEx
|
||||
if (checkStreamBufferCapacity(true)) {
|
||||
streamBuffer[streamBufferSize++] = nextByte;
|
||||
midiReceiver.handleSysex(streamBuffer, streamBufferSize);
|
||||
streamBufferSize = 0; // Clear streamBuffer
|
||||
break;
|
||||
}
|
||||
// Encountered streamBuffer overrun
|
||||
midiReporter.printDebug("parseSysexFragment: streamBuffer overrun while receiving SysEx message, ignored. Max allowed size of fragmented SysEx is 32768 bytes.");
|
||||
streamBufferSize = 0; // Clear streamBuffer
|
||||
break;
|
||||
}
|
||||
return parsedLength;
|
||||
}
|
@ -1,124 +0,0 @@
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MT32EMU_MIDI_STREAM_PARSER_H
|
||||
#define MT32EMU_MIDI_STREAM_PARSER_H
|
||||
|
||||
#include "globals.h"
|
||||
#include "Types.h"
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
class Synth;
|
||||
|
||||
// Interface for a user-supplied class to receive parsed well-formed MIDI messages.
|
||||
class MT32EMU_EXPORT MidiReceiver {
|
||||
public:
|
||||
// Invoked when a complete short MIDI message is parsed in the input MIDI stream.
|
||||
virtual void handleShortMessage(const Bit32u message) = 0;
|
||||
|
||||
// Invoked when a complete well-formed System Exclusive MIDI message is parsed in the input MIDI stream.
|
||||
virtual void handleSysex(const Bit8u stream[], const Bit32u length) = 0;
|
||||
|
||||
// Invoked when a System Realtime MIDI message is parsed in the input MIDI stream.
|
||||
virtual void handleSystemRealtimeMessage(const Bit8u realtime) = 0;
|
||||
|
||||
protected:
|
||||
~MidiReceiver() {}
|
||||
};
|
||||
|
||||
// Interface for a user-supplied class to receive notifications of input MIDI stream parse errors.
|
||||
class MT32EMU_EXPORT MidiReporter {
|
||||
public:
|
||||
// Invoked when an error occurs during processing the input MIDI stream.
|
||||
virtual void printDebug(const char *debugMessage) = 0;
|
||||
|
||||
protected:
|
||||
~MidiReporter() {}
|
||||
};
|
||||
|
||||
// Provides a context for parsing a stream of MIDI events coming from a single source.
|
||||
// There can be multiple MIDI sources feeding MIDI events to a single Synth object.
|
||||
// NOTE: Calls from multiple threads which feed a single Synth object with data must be explicitly synchronised,
|
||||
// although, no synchronisation is required with the rendering thread.
|
||||
class MT32EMU_EXPORT MidiStreamParserImpl {
|
||||
public:
|
||||
// The first two arguments provide for implementations of essential interfaces needed.
|
||||
// The third argument specifies streamBuffer initial capacity. The buffer capacity should be large enough to fit the longest SysEx expected.
|
||||
// If a longer SysEx occurs, streamBuffer is reallocated to the maximum size of MAX_STREAM_BUFFER_SIZE (32768 bytes).
|
||||
// Default capacity is SYSEX_BUFFER_SIZE (1000 bytes) which is enough to fit SysEx messages in common use.
|
||||
MidiStreamParserImpl(MidiReceiver &, MidiReporter &, Bit32u initialStreamBufferCapacity = SYSEX_BUFFER_SIZE);
|
||||
virtual ~MidiStreamParserImpl();
|
||||
|
||||
// Parses a block of raw MIDI bytes. All the parsed MIDI messages are sent in sequence to the user-supplied methods for further processing.
|
||||
// SysEx messages are allowed to be fragmented across several calls to this method. Running status is also handled for short messages.
|
||||
// NOTE: the total length of a SysEx message being fragmented shall not exceed MAX_STREAM_BUFFER_SIZE (32768 bytes).
|
||||
void parseStream(const Bit8u *stream, Bit32u length);
|
||||
|
||||
// Convenience method which accepts a Bit32u-encoded short MIDI message and sends it to the user-supplied method for further processing.
|
||||
// The short MIDI message may contain no status byte, the running status is used in this case.
|
||||
void processShortMessage(const Bit32u message);
|
||||
|
||||
private:
|
||||
Bit8u runningStatus;
|
||||
Bit8u *streamBuffer;
|
||||
Bit32u streamBufferCapacity;
|
||||
Bit32u streamBufferSize;
|
||||
MidiReceiver &midiReceiver;
|
||||
MidiReporter &midiReporter;
|
||||
|
||||
// Binary compatibility helper.
|
||||
void *reserved;
|
||||
|
||||
bool checkStreamBufferCapacity(const bool preserveContent);
|
||||
bool processStatusByte(Bit8u &status);
|
||||
Bit32u parseShortMessageStatus(const Bit8u stream[]);
|
||||
Bit32u parseShortMessageDataBytes(const Bit8u stream[], Bit32u length);
|
||||
Bit32u parseSysex(const Bit8u stream[], const Bit32u length);
|
||||
Bit32u parseSysexFragment(const Bit8u stream[], const Bit32u length);
|
||||
}; // class MidiStreamParserImpl
|
||||
|
||||
// An abstract class that provides a context for parsing a stream of MIDI events coming from a single source.
|
||||
class MT32EMU_EXPORT MidiStreamParser : public MidiStreamParserImpl, protected MidiReceiver, protected MidiReporter {
|
||||
public:
|
||||
// The argument specifies streamBuffer initial capacity. The buffer capacity should be large enough to fit the longest SysEx expected.
|
||||
// If a longer SysEx occurs, streamBuffer is reallocated to the maximum size of MAX_STREAM_BUFFER_SIZE (32768 bytes).
|
||||
// Default capacity is SYSEX_BUFFER_SIZE (1000 bytes) which is enough to fit SysEx messages in common use.
|
||||
explicit MidiStreamParser(Bit32u initialStreamBufferCapacity = SYSEX_BUFFER_SIZE);
|
||||
};
|
||||
|
||||
class MT32EMU_EXPORT DefaultMidiStreamParser : public MidiStreamParser {
|
||||
public:
|
||||
explicit DefaultMidiStreamParser(Synth &synth, Bit32u initialStreamBufferCapacity = SYSEX_BUFFER_SIZE);
|
||||
void setTimestamp(const Bit32u useTimestamp);
|
||||
void resetTimestamp();
|
||||
|
||||
protected:
|
||||
void handleShortMessage(const Bit32u message);
|
||||
void handleSysex(const Bit8u *stream, const Bit32u length);
|
||||
void handleSystemRealtimeMessage(const Bit8u realtime);
|
||||
void printDebug(const char *debugMessage);
|
||||
|
||||
private:
|
||||
Synth &synth;
|
||||
bool timestampSet;
|
||||
Bit32u timestamp;
|
||||
};
|
||||
|
||||
} // namespace MT32Emu
|
||||
|
||||
#endif // MT32EMU_MIDI_STREAM_PARSER_H
|
@ -1,695 +0,0 @@
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
#include "internals.h"
|
||||
|
||||
#include "Part.h"
|
||||
#include "Partial.h"
|
||||
#include "PartialManager.h"
|
||||
#include "Poly.h"
|
||||
#include "Synth.h"
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
static const Bit8u PartialStruct[13] = {
|
||||
0, 0, 2, 2, 1, 3,
|
||||
3, 0, 3, 0, 2, 1, 3
|
||||
};
|
||||
|
||||
static const Bit8u PartialMixStruct[13] = {
|
||||
0, 1, 0, 1, 1, 0,
|
||||
1, 3, 3, 2, 2, 2, 2
|
||||
};
|
||||
|
||||
RhythmPart::RhythmPart(Synth *useSynth, unsigned int usePartNum): Part(useSynth, usePartNum) {
|
||||
strcpy(name, "Rhythm");
|
||||
rhythmTemp = &synth->mt32ram.rhythmTemp[0];
|
||||
refresh();
|
||||
}
|
||||
|
||||
Part::Part(Synth *useSynth, unsigned int usePartNum) {
|
||||
synth = useSynth;
|
||||
partNum = usePartNum;
|
||||
patchCache[0].dirty = true;
|
||||
holdpedal = false;
|
||||
patchTemp = &synth->mt32ram.patchTemp[partNum];
|
||||
if (usePartNum == 8) {
|
||||
// Nasty hack for rhythm
|
||||
timbreTemp = NULL;
|
||||
} else {
|
||||
sprintf(name, "Part %d", partNum + 1);
|
||||
timbreTemp = &synth->mt32ram.timbreTemp[partNum];
|
||||
}
|
||||
currentInstr[0] = 0;
|
||||
currentInstr[10] = 0;
|
||||
modulation = 0;
|
||||
expression = 100;
|
||||
pitchBend = 0;
|
||||
activePartialCount = 0;
|
||||
memset(patchCache, 0, sizeof(patchCache));
|
||||
}
|
||||
|
||||
Part::~Part() {
|
||||
while (!activePolys.isEmpty()) {
|
||||
delete activePolys.takeFirst();
|
||||
}
|
||||
}
|
||||
|
||||
void Part::setDataEntryMSB(unsigned char midiDataEntryMSB) {
|
||||
if (nrpn) {
|
||||
// The last RPN-related control change was for an NRPN,
|
||||
// which the real synths don't support.
|
||||
return;
|
||||
}
|
||||
if (rpn != 0) {
|
||||
// The RPN has been set to something other than 0,
|
||||
// which is the only RPN that these synths support
|
||||
return;
|
||||
}
|
||||
patchTemp->patch.benderRange = midiDataEntryMSB > 24 ? 24 : midiDataEntryMSB;
|
||||
updatePitchBenderRange();
|
||||
}
|
||||
|
||||
void Part::setNRPN() {
|
||||
nrpn = true;
|
||||
}
|
||||
|
||||
void Part::setRPNLSB(unsigned char midiRPNLSB) {
|
||||
nrpn = false;
|
||||
rpn = (rpn & 0xFF00) | midiRPNLSB;
|
||||
}
|
||||
|
||||
void Part::setRPNMSB(unsigned char midiRPNMSB) {
|
||||
nrpn = false;
|
||||
rpn = (rpn & 0x00FF) | (midiRPNMSB << 8);
|
||||
}
|
||||
|
||||
void Part::setHoldPedal(bool pressed) {
|
||||
if (holdpedal && !pressed) {
|
||||
holdpedal = false;
|
||||
stopPedalHold();
|
||||
} else {
|
||||
holdpedal = pressed;
|
||||
}
|
||||
}
|
||||
|
||||
Bit32s Part::getPitchBend() const {
|
||||
return pitchBend;
|
||||
}
|
||||
|
||||
void Part::setBend(unsigned int midiBend) {
|
||||
// CONFIRMED:
|
||||
pitchBend = ((signed(midiBend) - 8192) * pitchBenderRange) >> 14; // PORTABILITY NOTE: Assumes arithmetic shift
|
||||
}
|
||||
|
||||
Bit8u Part::getModulation() const {
|
||||
return modulation;
|
||||
}
|
||||
|
||||
void Part::setModulation(unsigned int midiModulation) {
|
||||
modulation = Bit8u(midiModulation);
|
||||
}
|
||||
|
||||
void Part::resetAllControllers() {
|
||||
modulation = 0;
|
||||
expression = 100;
|
||||
pitchBend = 0;
|
||||
setHoldPedal(false);
|
||||
}
|
||||
|
||||
void Part::reset() {
|
||||
resetAllControllers();
|
||||
allSoundOff();
|
||||
rpn = 0xFFFF;
|
||||
}
|
||||
|
||||
void RhythmPart::refresh() {
|
||||
// (Re-)cache all the mapped timbres ahead of time
|
||||
for (unsigned int drumNum = 0; drumNum < synth->controlROMMap->rhythmSettingsCount; drumNum++) {
|
||||
int drumTimbreNum = rhythmTemp[drumNum].timbre;
|
||||
if (drumTimbreNum >= 127) { // 94 on MT-32
|
||||
continue;
|
||||
}
|
||||
PatchCache *cache = drumCache[drumNum];
|
||||
backupCacheToPartials(cache);
|
||||
for (int t = 0; t < 4; t++) {
|
||||
// Common parameters, stored redundantly
|
||||
cache[t].dirty = true;
|
||||
cache[t].reverb = rhythmTemp[drumNum].reverbSwitch > 0;
|
||||
}
|
||||
}
|
||||
updatePitchBenderRange();
|
||||
}
|
||||
|
||||
void Part::refresh() {
|
||||
backupCacheToPartials(patchCache);
|
||||
for (int t = 0; t < 4; t++) {
|
||||
// Common parameters, stored redundantly
|
||||
patchCache[t].dirty = true;
|
||||
patchCache[t].reverb = patchTemp->patch.reverbSwitch > 0;
|
||||
}
|
||||
memcpy(currentInstr, timbreTemp->common.name, 10);
|
||||
synth->newTimbreSet(partNum, patchTemp->patch.timbreGroup, patchTemp->patch.timbreNum, currentInstr);
|
||||
updatePitchBenderRange();
|
||||
}
|
||||
|
||||
const char *Part::getCurrentInstr() const {
|
||||
return ¤tInstr[0];
|
||||
}
|
||||
|
||||
void RhythmPart::refreshTimbre(unsigned int absTimbreNum) {
|
||||
for (int m = 0; m < 85; m++) {
|
||||
if (rhythmTemp[m].timbre == absTimbreNum - 128) {
|
||||
drumCache[m][0].dirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Part::refreshTimbre(unsigned int absTimbreNum) {
|
||||
if (getAbsTimbreNum() == absTimbreNum) {
|
||||
memcpy(currentInstr, timbreTemp->common.name, 10);
|
||||
patchCache[0].dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
void Part::setPatch(const PatchParam *patch) {
|
||||
patchTemp->patch = *patch;
|
||||
}
|
||||
|
||||
void RhythmPart::setTimbre(TimbreParam * /*timbre*/) {
|
||||
synth->printDebug("%s: Attempted to call setTimbre() - doesn't make sense for rhythm", name);
|
||||
}
|
||||
|
||||
void Part::setTimbre(TimbreParam *timbre) {
|
||||
*timbreTemp = *timbre;
|
||||
}
|
||||
|
||||
unsigned int RhythmPart::getAbsTimbreNum() const {
|
||||
synth->printDebug("%s: Attempted to call getAbsTimbreNum() - doesn't make sense for rhythm", name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
unsigned int Part::getAbsTimbreNum() const {
|
||||
return (patchTemp->patch.timbreGroup * 64) + patchTemp->patch.timbreNum;
|
||||
}
|
||||
|
||||
#if MT32EMU_MONITOR_MIDI > 0
|
||||
void RhythmPart::setProgram(unsigned int patchNum) {
|
||||
synth->printDebug("%s: Attempt to set program (%d) on rhythm is invalid", name, patchNum);
|
||||
}
|
||||
#else
|
||||
void RhythmPart::setProgram(unsigned int) { }
|
||||
#endif
|
||||
|
||||
void Part::setProgram(unsigned int patchNum) {
|
||||
setPatch(&synth->mt32ram.patches[patchNum]);
|
||||
holdpedal = false;
|
||||
allSoundOff();
|
||||
setTimbre(&synth->mt32ram.timbres[getAbsTimbreNum()].timbre);
|
||||
refresh();
|
||||
}
|
||||
|
||||
void Part::updatePitchBenderRange() {
|
||||
pitchBenderRange = patchTemp->patch.benderRange * 683;
|
||||
}
|
||||
|
||||
void Part::backupCacheToPartials(PatchCache cache[4]) {
|
||||
// check if any partials are still playing with the old patch cache
|
||||
// if so then duplicate the cached data from the part to the partial so that
|
||||
// we can change the part's cache without affecting the partial.
|
||||
// We delay this until now to avoid a copy operation with every note played
|
||||
for (Poly *poly = activePolys.getFirst(); poly != NULL; poly = poly->getNext()) {
|
||||
poly->backupCacheToPartials(cache);
|
||||
}
|
||||
}
|
||||
|
||||
void Part::cacheTimbre(PatchCache cache[4], const TimbreParam *timbre) {
|
||||
backupCacheToPartials(cache);
|
||||
int partialCount = 0;
|
||||
for (int t = 0; t < 4; t++) {
|
||||
if (((timbre->common.partialMute >> t) & 0x1) == 1) {
|
||||
cache[t].playPartial = true;
|
||||
partialCount++;
|
||||
} else {
|
||||
cache[t].playPartial = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Calculate and cache common parameters
|
||||
cache[t].srcPartial = timbre->partial[t];
|
||||
|
||||
cache[t].pcm = timbre->partial[t].wg.pcmWave;
|
||||
|
||||
switch (t) {
|
||||
case 0:
|
||||
cache[t].PCMPartial = (PartialStruct[int(timbre->common.partialStructure12)] & 0x2) ? true : false;
|
||||
cache[t].structureMix = PartialMixStruct[int(timbre->common.partialStructure12)];
|
||||
cache[t].structurePosition = 0;
|
||||
cache[t].structurePair = 1;
|
||||
break;
|
||||
case 1:
|
||||
cache[t].PCMPartial = (PartialStruct[int(timbre->common.partialStructure12)] & 0x1) ? true : false;
|
||||
cache[t].structureMix = PartialMixStruct[int(timbre->common.partialStructure12)];
|
||||
cache[t].structurePosition = 1;
|
||||
cache[t].structurePair = 0;
|
||||
break;
|
||||
case 2:
|
||||
cache[t].PCMPartial = (PartialStruct[int(timbre->common.partialStructure34)] & 0x2) ? true : false;
|
||||
cache[t].structureMix = PartialMixStruct[int(timbre->common.partialStructure34)];
|
||||
cache[t].structurePosition = 0;
|
||||
cache[t].structurePair = 3;
|
||||
break;
|
||||
case 3:
|
||||
cache[t].PCMPartial = (PartialStruct[int(timbre->common.partialStructure34)] & 0x1) ? true : false;
|
||||
cache[t].structureMix = PartialMixStruct[int(timbre->common.partialStructure34)];
|
||||
cache[t].structurePosition = 1;
|
||||
cache[t].structurePair = 2;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
cache[t].partialParam = &timbre->partial[t];
|
||||
|
||||
cache[t].waveform = timbre->partial[t].wg.waveform;
|
||||
}
|
||||
for (int t = 0; t < 4; t++) {
|
||||
// Common parameters, stored redundantly
|
||||
cache[t].dirty = false;
|
||||
cache[t].partialCount = partialCount;
|
||||
cache[t].sustain = (timbre->common.noSustain == 0);
|
||||
}
|
||||
//synth->printDebug("Res 1: %d 2: %d 3: %d 4: %d", cache[0].waveform, cache[1].waveform, cache[2].waveform, cache[3].waveform);
|
||||
|
||||
#if MT32EMU_MONITOR_INSTRUMENTS > 0
|
||||
synth->printDebug("%s (%s): Recached timbre", name, currentInstr);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
synth->printDebug(" %d: play=%s, pcm=%s (%d), wave=%d", i, cache[i].playPartial ? "YES" : "NO", cache[i].PCMPartial ? "YES" : "NO", timbre->partial[i].wg.pcmWave, timbre->partial[i].wg.waveform);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
const char *Part::getName() const {
|
||||
return name;
|
||||
}
|
||||
|
||||
void Part::setVolume(unsigned int midiVolume) {
|
||||
// CONFIRMED: This calculation matches the table used in the control ROM
|
||||
patchTemp->outputLevel = Bit8u(midiVolume * 100 / 127);
|
||||
//synth->printDebug("%s (%s): Set volume to %d", name, currentInstr, midiVolume);
|
||||
}
|
||||
|
||||
Bit8u Part::getVolume() const {
|
||||
return patchTemp->outputLevel;
|
||||
}
|
||||
|
||||
Bit8u Part::getExpression() const {
|
||||
return expression;
|
||||
}
|
||||
|
||||
void Part::setExpression(unsigned int midiExpression) {
|
||||
// CONFIRMED: This calculation matches the table used in the control ROM
|
||||
expression = Bit8u(midiExpression * 100 / 127);
|
||||
}
|
||||
|
||||
void RhythmPart::setPan(unsigned int midiPan) {
|
||||
// CONFIRMED: This does change patchTemp, but has no actual effect on playback.
|
||||
#if MT32EMU_MONITOR_MIDI > 0
|
||||
synth->printDebug("%s: Pointlessly setting pan (%d) on rhythm part", name, midiPan);
|
||||
#endif
|
||||
Part::setPan(midiPan);
|
||||
}
|
||||
|
||||
void Part::setPan(unsigned int midiPan) {
|
||||
// NOTE: Panning is inverted compared to GM.
|
||||
|
||||
// CM-32L: Divide by 8.5
|
||||
patchTemp->panpot = Bit8u((midiPan << 3) / 68);
|
||||
// FIXME: MT-32: Divide by 9
|
||||
//patchTemp->panpot = Bit8u(midiPan / 9);
|
||||
|
||||
//synth->printDebug("%s (%s): Set pan to %d", name, currentInstr, panpot);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies key shift to a MIDI key and converts it into an internal key value in the range 12-108.
|
||||
*/
|
||||
unsigned int Part::midiKeyToKey(unsigned int midiKey) {
|
||||
int key = midiKey + patchTemp->patch.keyShift;
|
||||
if (key < 36) {
|
||||
// After keyShift is applied, key < 36, so move up by octaves
|
||||
while (key < 36) {
|
||||
key += 12;
|
||||
}
|
||||
} else if (key > 132) {
|
||||
// After keyShift is applied, key > 132, so move down by octaves
|
||||
while (key > 132) {
|
||||
key -= 12;
|
||||
}
|
||||
}
|
||||
key -= 24;
|
||||
return key;
|
||||
}
|
||||
|
||||
void RhythmPart::noteOn(unsigned int midiKey, unsigned int velocity) {
|
||||
if (midiKey < 24 || midiKey > 108) { /*> 87 on MT-32)*/
|
||||
synth->printDebug("%s: Attempted to play invalid key %d (velocity %d)", name, midiKey, velocity);
|
||||
return;
|
||||
}
|
||||
unsigned int key = midiKey;
|
||||
unsigned int drumNum = key - 24;
|
||||
int drumTimbreNum = rhythmTemp[drumNum].timbre;
|
||||
const int drumTimbreCount = 64 + synth->controlROMMap->timbreRCount; // 94 on MT-32, 128 on LAPC-I/CM32-L
|
||||
if (drumTimbreNum == 127 || drumTimbreNum >= drumTimbreCount) { // timbre #127 is OFF, no sense to play it
|
||||
synth->printDebug("%s: Attempted to play unmapped key %d (velocity %d)", name, midiKey, velocity);
|
||||
return;
|
||||
}
|
||||
// CONFIRMED: Two special cases described by Mok
|
||||
if (drumTimbreNum == 64 + 6) {
|
||||
noteOff(0);
|
||||
key = 1;
|
||||
} else if (drumTimbreNum == 64 + 7) {
|
||||
// This noteOff(0) is not performed on MT-32, only LAPC-I
|
||||
noteOff(0);
|
||||
key = 0;
|
||||
}
|
||||
int absTimbreNum = drumTimbreNum + 128;
|
||||
TimbreParam *timbre = &synth->mt32ram.timbres[absTimbreNum].timbre;
|
||||
memcpy(currentInstr, timbre->common.name, 10);
|
||||
if (drumCache[drumNum][0].dirty) {
|
||||
cacheTimbre(drumCache[drumNum], timbre);
|
||||
}
|
||||
#if MT32EMU_MONITOR_INSTRUMENTS > 0
|
||||
synth->printDebug("%s (%s): Start poly (drum %d, timbre %d): midiKey %u, key %u, velo %u, mod %u, exp %u, bend %u", name, currentInstr, drumNum, absTimbreNum, midiKey, key, velocity, modulation, expression, pitchBend);
|
||||
#if MT32EMU_MONITOR_INSTRUMENTS > 1
|
||||
// According to info from Mok, keyShift does not appear to affect anything on rhythm part on LAPC-I, but may do on MT-32 - needs investigation
|
||||
synth->printDebug(" Patch: (timbreGroup %u), (timbreNum %u), (keyShift %u), fineTune %u, benderRange %u, assignMode %u, (reverbSwitch %u)", patchTemp->patch.timbreGroup, patchTemp->patch.timbreNum, patchTemp->patch.keyShift, patchTemp->patch.fineTune, patchTemp->patch.benderRange, patchTemp->patch.assignMode, patchTemp->patch.reverbSwitch);
|
||||
synth->printDebug(" PatchTemp: outputLevel %u, (panpot %u)", patchTemp->outputLevel, patchTemp->panpot);
|
||||
synth->printDebug(" RhythmTemp: timbre %u, outputLevel %u, panpot %u, reverbSwitch %u", rhythmTemp[drumNum].timbre, rhythmTemp[drumNum].outputLevel, rhythmTemp[drumNum].panpot, rhythmTemp[drumNum].reverbSwitch);
|
||||
#endif
|
||||
#endif
|
||||
playPoly(drumCache[drumNum], &rhythmTemp[drumNum], midiKey, key, velocity);
|
||||
}
|
||||
|
||||
void Part::noteOn(unsigned int midiKey, unsigned int velocity) {
|
||||
unsigned int key = midiKeyToKey(midiKey);
|
||||
if (patchCache[0].dirty) {
|
||||
cacheTimbre(patchCache, timbreTemp);
|
||||
}
|
||||
#if MT32EMU_MONITOR_INSTRUMENTS > 0
|
||||
synth->printDebug("%s (%s): Start poly: midiKey %u, key %u, velo %u, mod %u, exp %u, bend %u", name, currentInstr, midiKey, key, velocity, modulation, expression, pitchBend);
|
||||
#if MT32EMU_MONITOR_INSTRUMENTS > 1
|
||||
synth->printDebug(" Patch: timbreGroup %u, timbreNum %u, keyShift %u, fineTune %u, benderRange %u, assignMode %u, reverbSwitch %u", patchTemp->patch.timbreGroup, patchTemp->patch.timbreNum, patchTemp->patch.keyShift, patchTemp->patch.fineTune, patchTemp->patch.benderRange, patchTemp->patch.assignMode, patchTemp->patch.reverbSwitch);
|
||||
synth->printDebug(" PatchTemp: outputLevel %u, panpot %u", patchTemp->outputLevel, patchTemp->panpot);
|
||||
#endif
|
||||
#endif
|
||||
playPoly(patchCache, NULL, midiKey, key, velocity);
|
||||
}
|
||||
|
||||
bool Part::abortFirstPoly(unsigned int key) {
|
||||
for (Poly *poly = activePolys.getFirst(); poly != NULL; poly = poly->getNext()) {
|
||||
if (poly->getKey() == key) {
|
||||
return poly->startAbort();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Part::abortFirstPoly(PolyState polyState) {
|
||||
for (Poly *poly = activePolys.getFirst(); poly != NULL; poly = poly->getNext()) {
|
||||
if (poly->getState() == polyState) {
|
||||
return poly->startAbort();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Part::abortFirstPolyPreferHeld() {
|
||||
if (abortFirstPoly(POLY_Held)) {
|
||||
return true;
|
||||
}
|
||||
return abortFirstPoly();
|
||||
}
|
||||
|
||||
bool Part::abortFirstPoly() {
|
||||
if (activePolys.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
return activePolys.getFirst()->startAbort();
|
||||
}
|
||||
|
||||
void Part::playPoly(const PatchCache cache[4], const MemParams::RhythmTemp *rhythmTemp, unsigned int midiKey, unsigned int key, unsigned int velocity) {
|
||||
// CONFIRMED: Even in single-assign mode, we don't abort playing polys if the timbre to play is completely muted.
|
||||
unsigned int needPartials = cache[0].partialCount;
|
||||
if (needPartials == 0) {
|
||||
synth->printDebug("%s (%s): Completely muted instrument", name, currentInstr);
|
||||
return;
|
||||
}
|
||||
|
||||
if ((patchTemp->patch.assignMode & 2) == 0) {
|
||||
// Single-assign mode
|
||||
abortFirstPoly(key);
|
||||
if (synth->isAbortingPoly()) return;
|
||||
}
|
||||
|
||||
if (!synth->partialManager->freePartials(needPartials, partNum)) {
|
||||
#if MT32EMU_MONITOR_PARTIALS > 0
|
||||
synth->printDebug("%s (%s): Insufficient free partials to play key %d (velocity %d); needed=%d, free=%d, assignMode=%d", name, currentInstr, midiKey, velocity, needPartials, synth->partialManager->getFreePartialCount(), patchTemp->patch.assignMode);
|
||||
synth->printPartialUsage();
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
if (synth->isAbortingPoly()) return;
|
||||
|
||||
Poly *poly = synth->partialManager->assignPolyToPart(this);
|
||||
if (poly == NULL) {
|
||||
synth->printDebug("%s (%s): No free poly to play key %d (velocity %d)", name, currentInstr, midiKey, velocity);
|
||||
return;
|
||||
}
|
||||
if (patchTemp->patch.assignMode & 1) {
|
||||
// Priority to data first received
|
||||
activePolys.prepend(poly);
|
||||
} else {
|
||||
activePolys.append(poly);
|
||||
}
|
||||
|
||||
Partial *partials[4];
|
||||
for (int x = 0; x < 4; x++) {
|
||||
if (cache[x].playPartial) {
|
||||
partials[x] = synth->partialManager->allocPartial(partNum);
|
||||
activePartialCount++;
|
||||
} else {
|
||||
partials[x] = NULL;
|
||||
}
|
||||
}
|
||||
poly->reset(key, velocity, cache[0].sustain, partials);
|
||||
|
||||
for (int x = 0; x < 4; x++) {
|
||||
if (partials[x] != NULL) {
|
||||
#if MT32EMU_MONITOR_PARTIALS > 2
|
||||
synth->printDebug("%s (%s): Allocated partial %d", name, currentInstr, partials[x]->debugGetPartialNum());
|
||||
#endif
|
||||
partials[x]->startPartial(this, poly, &cache[x], rhythmTemp, partials[cache[x].structurePair]);
|
||||
}
|
||||
}
|
||||
#if MT32EMU_MONITOR_PARTIALS > 1
|
||||
synth->printPartialUsage();
|
||||
#endif
|
||||
synth->reportHandler->onPolyStateChanged(Bit8u(partNum));
|
||||
}
|
||||
|
||||
void Part::allNotesOff() {
|
||||
// The MIDI specification states - and Mok confirms - that all notes off (0x7B)
|
||||
// should treat the hold pedal as usual.
|
||||
for (Poly *poly = activePolys.getFirst(); poly != NULL; poly = poly->getNext()) {
|
||||
// FIXME: This has special handling of key 0 in NoteOff that Mok has not yet confirmed applies to AllNotesOff.
|
||||
// if (poly->canSustain() || poly->getKey() == 0) {
|
||||
// FIXME: The real devices are found to be ignoring non-sustaining polys while processing AllNotesOff. Need to be confirmed.
|
||||
if (poly->canSustain()) {
|
||||
poly->noteOff(holdpedal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Part::allSoundOff() {
|
||||
// MIDI "All sound off" (0x78) should release notes immediately regardless of the hold pedal.
|
||||
// This controller is not actually implemented by the synths, though (according to the docs and Mok) -
|
||||
// we're only using this method internally.
|
||||
for (Poly *poly = activePolys.getFirst(); poly != NULL; poly = poly->getNext()) {
|
||||
poly->startDecay();
|
||||
}
|
||||
}
|
||||
|
||||
void Part::stopPedalHold() {
|
||||
for (Poly *poly = activePolys.getFirst(); poly != NULL; poly = poly->getNext()) {
|
||||
poly->stopPedalHold();
|
||||
}
|
||||
}
|
||||
|
||||
void RhythmPart::noteOff(unsigned int midiKey) {
|
||||
stopNote(midiKey);
|
||||
}
|
||||
|
||||
void Part::noteOff(unsigned int midiKey) {
|
||||
stopNote(midiKeyToKey(midiKey));
|
||||
}
|
||||
|
||||
void Part::stopNote(unsigned int key) {
|
||||
#if MT32EMU_MONITOR_INSTRUMENTS > 0
|
||||
synth->printDebug("%s (%s): stopping key %d", name, currentInstr, key);
|
||||
#endif
|
||||
|
||||
for (Poly *poly = activePolys.getFirst(); poly != NULL; poly = poly->getNext()) {
|
||||
// Generally, non-sustaining instruments ignore note off. They die away eventually anyway.
|
||||
// Key 0 (only used by special cases on rhythm part) reacts to note off even if non-sustaining or pedal held.
|
||||
if (poly->getKey() == key && (poly->canSustain() || key == 0)) {
|
||||
if (poly->noteOff(holdpedal && key != 0)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const MemParams::PatchTemp *Part::getPatchTemp() const {
|
||||
return patchTemp;
|
||||
}
|
||||
|
||||
unsigned int Part::getActivePartialCount() const {
|
||||
return activePartialCount;
|
||||
}
|
||||
|
||||
const Poly *Part::getFirstActivePoly() const {
|
||||
return activePolys.getFirst();
|
||||
}
|
||||
|
||||
unsigned int Part::getActiveNonReleasingPartialCount() const {
|
||||
unsigned int activeNonReleasingPartialCount = 0;
|
||||
for (Poly *poly = activePolys.getFirst(); poly != NULL; poly = poly->getNext()) {
|
||||
if (poly->getState() != POLY_Releasing) {
|
||||
activeNonReleasingPartialCount += poly->getActivePartialCount();
|
||||
}
|
||||
}
|
||||
return activeNonReleasingPartialCount;
|
||||
}
|
||||
|
||||
Synth *Part::getSynth() const {
|
||||
return synth;
|
||||
}
|
||||
|
||||
void Part::partialDeactivated(Poly *poly) {
|
||||
activePartialCount--;
|
||||
if (!poly->isActive()) {
|
||||
activePolys.remove(poly);
|
||||
synth->partialManager->polyFreed(poly);
|
||||
synth->reportHandler->onPolyStateChanged(Bit8u(partNum));
|
||||
}
|
||||
}
|
||||
|
||||
PolyList::PolyList() : firstPoly(NULL), lastPoly(NULL) {}
|
||||
|
||||
bool PolyList::isEmpty() const {
|
||||
#ifdef MT32EMU_POLY_LIST_DEBUG
|
||||
if ((firstPoly == NULL || lastPoly == NULL) && firstPoly != lastPoly) {
|
||||
printf("PolyList: desynchronised firstPoly & lastPoly pointers\n");
|
||||
}
|
||||
#endif
|
||||
return firstPoly == NULL && lastPoly == NULL;
|
||||
}
|
||||
|
||||
Poly *PolyList::getFirst() const {
|
||||
return firstPoly;
|
||||
}
|
||||
|
||||
Poly *PolyList::getLast() const {
|
||||
return lastPoly;
|
||||
}
|
||||
|
||||
void PolyList::prepend(Poly *poly) {
|
||||
#ifdef MT32EMU_POLY_LIST_DEBUG
|
||||
if (poly->getNext() != NULL) {
|
||||
printf("PolyList: Non-NULL next field in a Poly being prepended is ignored\n");
|
||||
}
|
||||
#endif
|
||||
poly->setNext(firstPoly);
|
||||
firstPoly = poly;
|
||||
if (lastPoly == NULL) {
|
||||
lastPoly = poly;
|
||||
}
|
||||
}
|
||||
|
||||
void PolyList::append(Poly *poly) {
|
||||
#ifdef MT32EMU_POLY_LIST_DEBUG
|
||||
if (poly->getNext() != NULL) {
|
||||
printf("PolyList: Non-NULL next field in a Poly being appended is ignored\n");
|
||||
}
|
||||
#endif
|
||||
poly->setNext(NULL);
|
||||
if (lastPoly != NULL) {
|
||||
#ifdef MT32EMU_POLY_LIST_DEBUG
|
||||
if (lastPoly->getNext() != NULL) {
|
||||
printf("PolyList: Non-NULL next field in the lastPoly\n");
|
||||
}
|
||||
#endif
|
||||
lastPoly->setNext(poly);
|
||||
}
|
||||
lastPoly = poly;
|
||||
if (firstPoly == NULL) {
|
||||
firstPoly = poly;
|
||||
}
|
||||
}
|
||||
|
||||
Poly *PolyList::takeFirst() {
|
||||
Poly *oldFirst = firstPoly;
|
||||
firstPoly = oldFirst->getNext();
|
||||
if (firstPoly == NULL) {
|
||||
#ifdef MT32EMU_POLY_LIST_DEBUG
|
||||
if (lastPoly != oldFirst) {
|
||||
printf("PolyList: firstPoly != lastPoly in a list with a single Poly\n");
|
||||
}
|
||||
#endif
|
||||
lastPoly = NULL;
|
||||
}
|
||||
oldFirst->setNext(NULL);
|
||||
return oldFirst;
|
||||
}
|
||||
|
||||
void PolyList::remove(Poly * const polyToRemove) {
|
||||
if (polyToRemove == firstPoly) {
|
||||
takeFirst();
|
||||
return;
|
||||
}
|
||||
for (Poly *poly = firstPoly; poly != NULL; poly = poly->getNext()) {
|
||||
if (poly->getNext() == polyToRemove) {
|
||||
if (polyToRemove == lastPoly) {
|
||||
#ifdef MT32EMU_POLY_LIST_DEBUG
|
||||
if (lastPoly->getNext() != NULL) {
|
||||
printf("PolyList: Non-NULL next field in the lastPoly\n");
|
||||
}
|
||||
#endif
|
||||
lastPoly = poly;
|
||||
}
|
||||
poly->setNext(polyToRemove->getNext());
|
||||
polyToRemove->setNext(NULL);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace MT32Emu
|
@ -1,153 +0,0 @@
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MT32EMU_PART_H
|
||||
#define MT32EMU_PART_H
|
||||
|
||||
#include "globals.h"
|
||||
#include "internals.h"
|
||||
#include "Types.h"
|
||||
#include "Structures.h"
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
class Poly;
|
||||
class Synth;
|
||||
|
||||
class PolyList {
|
||||
private:
|
||||
Poly *firstPoly;
|
||||
Poly *lastPoly;
|
||||
|
||||
public:
|
||||
PolyList();
|
||||
bool isEmpty() const;
|
||||
Poly *getFirst() const;
|
||||
Poly *getLast() const;
|
||||
void prepend(Poly *poly);
|
||||
void append(Poly *poly);
|
||||
Poly *takeFirst();
|
||||
void remove(Poly * const poly);
|
||||
};
|
||||
|
||||
class Part {
|
||||
private:
|
||||
// Direct pointer to sysex-addressable memory dedicated to this part (valid for parts 1-8, NULL for rhythm)
|
||||
TimbreParam *timbreTemp;
|
||||
|
||||
// 0=Part 1, .. 7=Part 8, 8=Rhythm
|
||||
unsigned int partNum;
|
||||
|
||||
bool holdpedal;
|
||||
|
||||
unsigned int activePartialCount;
|
||||
PatchCache patchCache[4];
|
||||
PolyList activePolys;
|
||||
|
||||
void setPatch(const PatchParam *patch);
|
||||
unsigned int midiKeyToKey(unsigned int midiKey);
|
||||
|
||||
bool abortFirstPoly(unsigned int key);
|
||||
|
||||
protected:
|
||||
Synth *synth;
|
||||
// Direct pointer into sysex-addressable memory
|
||||
MemParams::PatchTemp *patchTemp;
|
||||
char name[8]; // "Part 1".."Part 8", "Rhythm"
|
||||
char currentInstr[11];
|
||||
Bit8u modulation;
|
||||
Bit8u expression;
|
||||
Bit32s pitchBend;
|
||||
bool nrpn;
|
||||
Bit16u rpn;
|
||||
Bit16u pitchBenderRange; // (patchTemp->patch.benderRange * 683) at the time of the last MIDI program change or MIDI data entry.
|
||||
|
||||
void backupCacheToPartials(PatchCache cache[4]);
|
||||
void cacheTimbre(PatchCache cache[4], const TimbreParam *timbre);
|
||||
void playPoly(const PatchCache cache[4], const MemParams::RhythmTemp *rhythmTemp, unsigned int midiKey, unsigned int key, unsigned int velocity);
|
||||
void stopNote(unsigned int key);
|
||||
const char *getName() const;
|
||||
|
||||
public:
|
||||
Part(Synth *synth, unsigned int usePartNum);
|
||||
virtual ~Part();
|
||||
void reset();
|
||||
void setDataEntryMSB(unsigned char midiDataEntryMSB);
|
||||
void setNRPN();
|
||||
void setRPNLSB(unsigned char midiRPNLSB);
|
||||
void setRPNMSB(unsigned char midiRPNMSB);
|
||||
void resetAllControllers();
|
||||
virtual void noteOn(unsigned int midiKey, unsigned int velocity);
|
||||
virtual void noteOff(unsigned int midiKey);
|
||||
void allNotesOff();
|
||||
void allSoundOff();
|
||||
Bit8u getVolume() const; // Internal volume, 0-100, exposed for use by ExternalInterface
|
||||
void setVolume(unsigned int midiVolume);
|
||||
Bit8u getModulation() const;
|
||||
void setModulation(unsigned int midiModulation);
|
||||
Bit8u getExpression() const;
|
||||
void setExpression(unsigned int midiExpression);
|
||||
virtual void setPan(unsigned int midiPan);
|
||||
Bit32s getPitchBend() const;
|
||||
void setBend(unsigned int midiBend);
|
||||
virtual void setProgram(unsigned int midiProgram);
|
||||
void setHoldPedal(bool pedalval);
|
||||
void stopPedalHold();
|
||||
void updatePitchBenderRange();
|
||||
virtual void refresh();
|
||||
virtual void refreshTimbre(unsigned int absTimbreNum);
|
||||
virtual void setTimbre(TimbreParam *timbre);
|
||||
virtual unsigned int getAbsTimbreNum() const;
|
||||
const char *getCurrentInstr() const;
|
||||
const Poly *getFirstActivePoly() const;
|
||||
unsigned int getActivePartialCount() const;
|
||||
unsigned int getActiveNonReleasingPartialCount() const;
|
||||
Synth *getSynth() const;
|
||||
|
||||
const MemParams::PatchTemp *getPatchTemp() const;
|
||||
|
||||
// This should only be called by Poly
|
||||
void partialDeactivated(Poly *poly);
|
||||
|
||||
// These are rather specialised, and should probably only be used by PartialManager
|
||||
bool abortFirstPoly(PolyState polyState);
|
||||
// Abort the first poly in PolyState_HELD, or if none exists, the first active poly in any state.
|
||||
bool abortFirstPolyPreferHeld();
|
||||
bool abortFirstPoly();
|
||||
}; // class Part
|
||||
|
||||
class RhythmPart: public Part {
|
||||
// Pointer to the area of the MT-32's memory dedicated to rhythm
|
||||
const MemParams::RhythmTemp *rhythmTemp;
|
||||
|
||||
// This caches the timbres/settings in use by the rhythm part
|
||||
PatchCache drumCache[85][4];
|
||||
public:
|
||||
RhythmPart(Synth *synth, unsigned int usePartNum);
|
||||
void refresh();
|
||||
void refreshTimbre(unsigned int timbreNum);
|
||||
void setTimbre(TimbreParam *timbre);
|
||||
void noteOn(unsigned int key, unsigned int velocity);
|
||||
void noteOff(unsigned int midiKey);
|
||||
unsigned int getAbsTimbreNum() const;
|
||||
void setPan(unsigned int midiPan);
|
||||
void setProgram(unsigned int patchNum);
|
||||
};
|
||||
|
||||
} // namespace MT32Emu
|
||||
|
||||
#endif // #ifndef MT32EMU_PART_H
|
@ -1,349 +0,0 @@
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
#include "internals.h"
|
||||
|
||||
#include "Partial.h"
|
||||
#include "Part.h"
|
||||
#include "Poly.h"
|
||||
#include "Synth.h"
|
||||
#include "Tables.h"
|
||||
#include "TVA.h"
|
||||
#include "TVF.h"
|
||||
#include "TVP.h"
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
static const Bit8u PAN_NUMERATOR_MASTER[] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7};
|
||||
static const Bit8u PAN_NUMERATOR_SLAVE[] = {0, 1, 2, 3, 4, 5, 6, 7, 7, 7, 7, 7, 7, 7, 7};
|
||||
|
||||
static const Bit32s PAN_FACTORS[] = {0, 18, 37, 55, 73, 91, 110, 128, 146, 165, 183, 201, 219, 238, 256};
|
||||
|
||||
Partial::Partial(Synth *useSynth, int useDebugPartialNum) :
|
||||
synth(useSynth), debugPartialNum(useDebugPartialNum), sampleNum(0) {
|
||||
// Initialisation of tva, tvp and tvf uses 'this' pointer
|
||||
// and thus should not be in the initializer list to avoid a compiler warning
|
||||
tva = new TVA(this, &Ramp);
|
||||
tvp = new TVP(this);
|
||||
tvf = new TVF(this, &cutoffModifierRamp);
|
||||
ownerPart = -1;
|
||||
poly = NULL;
|
||||
pair = NULL;
|
||||
}
|
||||
|
||||
Partial::~Partial() {
|
||||
delete tva;
|
||||
delete tvp;
|
||||
delete tvf;
|
||||
}
|
||||
|
||||
// Only used for debugging purposes
|
||||
int Partial::debugGetPartialNum() const {
|
||||
return debugPartialNum;
|
||||
}
|
||||
|
||||
// Only used for debugging purposes
|
||||
Bit32u Partial::debugGetSampleNum() const {
|
||||
return sampleNum;
|
||||
}
|
||||
|
||||
int Partial::getOwnerPart() const {
|
||||
return ownerPart;
|
||||
}
|
||||
|
||||
bool Partial::isActive() const {
|
||||
return ownerPart > -1;
|
||||
}
|
||||
|
||||
const Poly *Partial::getPoly() const {
|
||||
return poly;
|
||||
}
|
||||
|
||||
void Partial::activate(int part) {
|
||||
// This just marks the partial as being assigned to a part
|
||||
ownerPart = part;
|
||||
}
|
||||
|
||||
void Partial::deactivate() {
|
||||
if (!isActive()) {
|
||||
return;
|
||||
}
|
||||
ownerPart = -1;
|
||||
if (poly != NULL) {
|
||||
poly->partialDeactivated(this);
|
||||
}
|
||||
#if MT32EMU_MONITOR_PARTIALS > 2
|
||||
synth->printDebug("[+%lu] [Partial %d] Deactivated", sampleNum, debugPartialNum);
|
||||
synth->printPartialUsage(sampleNum);
|
||||
#endif
|
||||
if (isRingModulatingSlave()) {
|
||||
pair->la32Pair.deactivate(LA32PartialPair::SLAVE);
|
||||
} else {
|
||||
la32Pair.deactivate(LA32PartialPair::MASTER);
|
||||
if (hasRingModulatingSlave()) {
|
||||
pair->deactivate();
|
||||
pair = NULL;
|
||||
}
|
||||
}
|
||||
if (pair != NULL) {
|
||||
pair->pair = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void Partial::startPartial(const Part *part, Poly *usePoly, const PatchCache *usePatchCache, const MemParams::RhythmTemp *rhythmTemp, Partial *pairPartial) {
|
||||
if (usePoly == NULL || usePatchCache == NULL) {
|
||||
synth->printDebug("[Partial %d] *** Error: Starting partial for owner %d, usePoly=%s, usePatchCache=%s", debugPartialNum, ownerPart, usePoly == NULL ? "*** NULL ***" : "OK", usePatchCache == NULL ? "*** NULL ***" : "OK");
|
||||
return;
|
||||
}
|
||||
patchCache = usePatchCache;
|
||||
poly = usePoly;
|
||||
mixType = patchCache->structureMix;
|
||||
structurePosition = patchCache->structurePosition;
|
||||
|
||||
Bit8u panSetting = rhythmTemp != NULL ? rhythmTemp->panpot : part->getPatchTemp()->panpot;
|
||||
if (mixType == 3) {
|
||||
if (structurePosition == 0) {
|
||||
panSetting = PAN_NUMERATOR_MASTER[panSetting] << 1;
|
||||
} else {
|
||||
panSetting = PAN_NUMERATOR_SLAVE[panSetting] << 1;
|
||||
}
|
||||
// Do a normal mix independent of any pair partial.
|
||||
mixType = 0;
|
||||
pairPartial = NULL;
|
||||
} else {
|
||||
// Mok wanted an option for smoother panning, and we love Mok.
|
||||
#ifndef INACCURATE_SMOOTH_PAN
|
||||
// CONFIRMED by Mok: exactly bytes like this (right shifted?) are sent to the LA32.
|
||||
panSetting &= 0x0E;
|
||||
#endif
|
||||
}
|
||||
|
||||
leftPanValue = synth->reversedStereoEnabled ? 14 - panSetting : panSetting;
|
||||
rightPanValue = 14 - leftPanValue;
|
||||
|
||||
#if !MT32EMU_USE_FLOAT_SAMPLES
|
||||
leftPanValue = PAN_FACTORS[leftPanValue];
|
||||
rightPanValue = PAN_FACTORS[rightPanValue];
|
||||
#endif
|
||||
|
||||
// SEMI-CONFIRMED: From sample analysis:
|
||||
// Found that timbres with 3 or 4 partials (i.e. one using two partial pairs) are mixed in two different ways.
|
||||
// Either partial pairs are added or subtracted, it depends on how the partial pairs are allocated.
|
||||
// It seems that partials are grouped into quarters and if the partial pairs are allocated in different quarters the subtraction happens.
|
||||
// Though, this matters little for the majority of timbres, it becomes crucial for timbres which contain several partials that sound very close.
|
||||
// In this case that timbre can sound totally different depending of the way it is mixed up.
|
||||
// Most easily this effect can be displayed with the help of a special timbre consisting of several identical square wave partials (3 or 4).
|
||||
// Say, it is 3-partial timbre. Just play any two notes simultaneously and the polys very probably are mixed differently.
|
||||
// Moreover, the partial allocator retains the last partial assignment it did and all the subsequent notes will sound the same as the last released one.
|
||||
// The situation is better with 4-partial timbres since then a whole quarter is assigned for each poly. However, if a 3-partial timbre broke the normal
|
||||
// whole-quarter assignment or after some partials got aborted, even 4-partial timbres can be found sounding differently.
|
||||
// This behaviour is also confirmed with two more special timbres: one with identical sawtooth partials, and one with PCM wave 02.
|
||||
// For my personal taste, this behaviour rather enriches the sounding and should be emulated.
|
||||
// Also, the current partial allocator model probably needs to be refined.
|
||||
if (debugPartialNum & 8) {
|
||||
leftPanValue = -leftPanValue;
|
||||
rightPanValue = -rightPanValue;
|
||||
}
|
||||
|
||||
if (patchCache->PCMPartial) {
|
||||
pcmNum = patchCache->pcm;
|
||||
if (synth->controlROMMap->pcmCount > 128) {
|
||||
// CM-32L, etc. support two "banks" of PCMs, selectable by waveform type parameter.
|
||||
if (patchCache->waveform > 1) {
|
||||
pcmNum += 128;
|
||||
}
|
||||
}
|
||||
pcmWave = &synth->pcmWaves[pcmNum];
|
||||
} else {
|
||||
pcmWave = NULL;
|
||||
}
|
||||
|
||||
// CONFIRMED: pulseWidthVal calculation is based on information from Mok
|
||||
pulseWidthVal = (poly->getVelocity() - 64) * (patchCache->srcPartial.wg.pulseWidthVeloSensitivity - 7) + Tables::getInstance().pulseWidth100To255[patchCache->srcPartial.wg.pulseWidth];
|
||||
if (pulseWidthVal < 0) {
|
||||
pulseWidthVal = 0;
|
||||
} else if (pulseWidthVal > 255) {
|
||||
pulseWidthVal = 255;
|
||||
}
|
||||
|
||||
pair = pairPartial;
|
||||
alreadyOutputed = false;
|
||||
tva->reset(part, patchCache->partialParam, rhythmTemp);
|
||||
tvp->reset(part, patchCache->partialParam);
|
||||
tvf->reset(patchCache->partialParam, tvp->getBasePitch());
|
||||
|
||||
LA32PartialPair::PairType pairType;
|
||||
LA32PartialPair *useLA32Pair;
|
||||
if (isRingModulatingSlave()) {
|
||||
pairType = LA32PartialPair::SLAVE;
|
||||
useLA32Pair = &pair->la32Pair;
|
||||
} else {
|
||||
pairType = LA32PartialPair::MASTER;
|
||||
la32Pair.init(hasRingModulatingSlave(), mixType == 1);
|
||||
useLA32Pair = &la32Pair;
|
||||
}
|
||||
if (isPCM()) {
|
||||
useLA32Pair->initPCM(pairType, &synth->pcmROMData[pcmWave->addr], pcmWave->len, pcmWave->loop);
|
||||
} else {
|
||||
useLA32Pair->initSynth(pairType, (patchCache->waveform & 1) != 0, pulseWidthVal, patchCache->srcPartial.tvf.resonance + 1);
|
||||
}
|
||||
if (!hasRingModulatingSlave()) {
|
||||
la32Pair.deactivate(LA32PartialPair::SLAVE);
|
||||
}
|
||||
}
|
||||
|
||||
Bit32u Partial::getAmpValue() {
|
||||
// SEMI-CONFIRMED: From sample analysis:
|
||||
// (1) Tested with a single partial playing PCM wave 77 with pitchCoarse 36 and no keyfollow, velocity follow, etc.
|
||||
// This gives results within +/- 2 at the output (before any DAC bitshifting)
|
||||
// when sustaining at levels 156 - 255 with no modifiers.
|
||||
// (2) Tested with a special square wave partial (internal capture ID tva5) at TVA envelope levels 155-255.
|
||||
// This gives deltas between -1 and 0 compared to the real output. Note that this special partial only produces
|
||||
// positive amps, so negative still needs to be explored, as well as lower levels.
|
||||
//
|
||||
// Also still partially unconfirmed is the behaviour when ramping between levels, as well as the timing.
|
||||
// TODO: The tests above were performed using the float model, to be refined
|
||||
Bit32u ampRampVal = 67117056 - ampRamp.nextValue();
|
||||
if (ampRamp.checkInterrupt()) {
|
||||
tva->handleInterrupt();
|
||||
}
|
||||
return ampRampVal;
|
||||
}
|
||||
|
||||
Bit32u Partial::getCutoffValue() {
|
||||
if (isPCM()) {
|
||||
return 0;
|
||||
}
|
||||
Bit32u cutoffModifierRampVal = cutoffModifierRamp.nextValue();
|
||||
if (cutoffModifierRamp.checkInterrupt()) {
|
||||
tvf->handleInterrupt();
|
||||
}
|
||||
return (tvf->getBaseCutoff() << 18) + cutoffModifierRampVal;
|
||||
}
|
||||
|
||||
bool Partial::hasRingModulatingSlave() const {
|
||||
return pair != NULL && structurePosition == 0 && (mixType == 1 || mixType == 2);
|
||||
}
|
||||
|
||||
bool Partial::isRingModulatingSlave() const {
|
||||
return pair != NULL && structurePosition == 1 && (mixType == 1 || mixType == 2);
|
||||
}
|
||||
|
||||
bool Partial::isPCM() const {
|
||||
return pcmWave != NULL;
|
||||
}
|
||||
|
||||
const ControlROMPCMStruct *Partial::getControlROMPCMStruct() const {
|
||||
if (pcmWave != NULL) {
|
||||
return pcmWave->controlROMPCMStruct;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Synth *Partial::getSynth() const {
|
||||
return synth;
|
||||
}
|
||||
|
||||
TVA *Partial::getTVA() const {
|
||||
return tva;
|
||||
}
|
||||
|
||||
void Partial::backupCache(const PatchCache &cache) {
|
||||
if (patchCache == &cache) {
|
||||
cachebackup = cache;
|
||||
patchCache = &cachebackup;
|
||||
}
|
||||
}
|
||||
|
||||
bool Partial::produceOutput(Sample *leftBuf, Sample *rightBuf, Bit32u length) {
|
||||
if (!isActive() || alreadyOutputed || isRingModulatingSlave()) {
|
||||
return false;
|
||||
}
|
||||
if (poly == NULL) {
|
||||
synth->printDebug("[Partial %d] *** ERROR: poly is NULL at Partial::produceOutput()!", debugPartialNum);
|
||||
return false;
|
||||
}
|
||||
alreadyOutputed = true;
|
||||
|
||||
for (sampleNum = 0; sampleNum < length; sampleNum++) {
|
||||
if (!tva->isPlaying() || !la32Pair.isActive(LA32PartialPair::MASTER)) {
|
||||
deactivate();
|
||||
break;
|
||||
}
|
||||
la32Pair.generateNextSample(LA32PartialPair::MASTER, getAmpValue(), tvp->nextPitch(), getCutoffValue());
|
||||
if (hasRingModulatingSlave()) {
|
||||
la32Pair.generateNextSample(LA32PartialPair::SLAVE, pair->getAmpValue(), pair->tvp->nextPitch(), pair->getCutoffValue());
|
||||
if (!pair->tva->isPlaying() || !la32Pair.isActive(LA32PartialPair::SLAVE)) {
|
||||
pair->deactivate();
|
||||
if (mixType == 2) {
|
||||
deactivate();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Although, LA32 applies panning itself, we assume here it is applied in the mixer, not within a pair.
|
||||
// Applying the pan value in the log-space looks like a waste of unlog resources. Though, it needs clarification.
|
||||
Sample sample = la32Pair.nextOutSample();
|
||||
|
||||
// FIXME: Sample analysis suggests that the use of panVal is linear, but there are some quirks that still need to be resolved.
|
||||
#if MT32EMU_USE_FLOAT_SAMPLES
|
||||
Sample leftOut = (sample * (float)leftPanValue) / 14.0f;
|
||||
Sample rightOut = (sample * (float)rightPanValue) / 14.0f;
|
||||
*(leftBuf++) += leftOut;
|
||||
*(rightBuf++) += rightOut;
|
||||
#else
|
||||
// FIXME: Dividing by 7 (or by 14 in a Mok-friendly way) looks of course pointless. Need clarification.
|
||||
// FIXME2: LA32 may produce distorted sound in case if the absolute value of maximal amplitude of the input exceeds 8191
|
||||
// when the panning value is non-zero. Most probably the distortion occurs in the same way it does with ring modulation,
|
||||
// and it seems to be caused by limited precision of the common multiplication circuit.
|
||||
// From analysis of this overflow, it is obvious that the right channel output is actually found
|
||||
// by subtraction of the left channel output from the input.
|
||||
// Though, it is unknown whether this overflow is exploited somewhere.
|
||||
Sample leftOut = Sample((sample * leftPanValue) >> 8);
|
||||
Sample rightOut = Sample((sample * rightPanValue) >> 8);
|
||||
*leftBuf = Synth::clipSampleEx(SampleEx(*leftBuf) + SampleEx(leftOut));
|
||||
*rightBuf = Synth::clipSampleEx(SampleEx(*rightBuf) + SampleEx(rightOut));
|
||||
leftBuf++;
|
||||
rightBuf++;
|
||||
#endif
|
||||
}
|
||||
sampleNum = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Partial::shouldReverb() {
|
||||
if (!isActive()) {
|
||||
return false;
|
||||
}
|
||||
return patchCache->reverb;
|
||||
}
|
||||
|
||||
void Partial::startAbort() {
|
||||
// This is called when the partial manager needs to terminate partials for re-use by a new Poly.
|
||||
tva->startAbort();
|
||||
}
|
||||
|
||||
void Partial::startDecayAll() {
|
||||
tva->startDecay();
|
||||
tvp->startDecay();
|
||||
tvf->startDecay();
|
||||
}
|
||||
|
||||
} // namespace MT32Emu
|
@ -1,118 +0,0 @@
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MT32EMU_PARTIAL_H
|
||||
#define MT32EMU_PARTIAL_H
|
||||
|
||||
#include "globals.h"
|
||||
#include "internals.h"
|
||||
#include "Types.h"
|
||||
#include "Structures.h"
|
||||
#include "LA32Ramp.h"
|
||||
#include "LA32WaveGenerator.h"
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
class Part;
|
||||
class Poly;
|
||||
class Synth;
|
||||
class TVA;
|
||||
class TVF;
|
||||
class TVP;
|
||||
struct ControlROMPCMStruct;
|
||||
|
||||
// A partial represents one of up to four waveform generators currently playing within a poly.
|
||||
class Partial {
|
||||
private:
|
||||
Synth *synth;
|
||||
const int debugPartialNum; // Only used for debugging
|
||||
// Number of the sample currently being rendered by produceOutput(), or 0 if no run is in progress
|
||||
// This is only kept available for debugging purposes.
|
||||
Bit32u sampleNum;
|
||||
|
||||
// Actually, this is a 4-bit register but we abuse this to emulate inverted mixing.
|
||||
// Also we double the value to enable INACCURATE_SMOOTH_PAN, with respect to MoK.
|
||||
Bit32s leftPanValue, rightPanValue;
|
||||
|
||||
int ownerPart; // -1 if unassigned
|
||||
int mixType;
|
||||
int structurePosition; // 0 or 1 of a structure pair
|
||||
|
||||
// Only used for PCM partials
|
||||
int pcmNum;
|
||||
// FIXME: Give this a better name (e.g. pcmWaveInfo)
|
||||
PCMWaveEntry *pcmWave;
|
||||
|
||||
// Final pulse width value, with velfollow applied, matching what is sent to the LA32.
|
||||
// Range: 0-255
|
||||
int pulseWidthVal;
|
||||
|
||||
Poly *poly;
|
||||
Partial *pair;
|
||||
|
||||
TVA *tva;
|
||||
TVP *tvp;
|
||||
TVF *tvf;
|
||||
|
||||
LA32Ramp ampRamp;
|
||||
LA32Ramp cutoffModifierRamp;
|
||||
|
||||
// TODO: This should be owned by PartialPair
|
||||
LA32PartialPair la32Pair;
|
||||
|
||||
const PatchCache *patchCache;
|
||||
PatchCache cachebackup;
|
||||
|
||||
Bit32u getAmpValue();
|
||||
Bit32u getCutoffValue();
|
||||
|
||||
public:
|
||||
bool alreadyOutputed;
|
||||
|
||||
Partial(Synth *synth, int debugPartialNum);
|
||||
~Partial();
|
||||
|
||||
int debugGetPartialNum() const;
|
||||
Bit32u debugGetSampleNum() const;
|
||||
|
||||
int getOwnerPart() const;
|
||||
const Poly *getPoly() const;
|
||||
bool isActive() const;
|
||||
void activate(int part);
|
||||
void deactivate(void);
|
||||
void startPartial(const Part *part, Poly *usePoly, const PatchCache *useCache, const MemParams::RhythmTemp *rhythmTemp, Partial *pairPartial);
|
||||
void startAbort();
|
||||
void startDecayAll();
|
||||
bool shouldReverb();
|
||||
bool hasRingModulatingSlave() const;
|
||||
bool isRingModulatingSlave() const;
|
||||
bool isPCM() const;
|
||||
const ControlROMPCMStruct *getControlROMPCMStruct() const;
|
||||
Synth *getSynth() const;
|
||||
TVA *getTVA() const;
|
||||
|
||||
void backupCache(const PatchCache &cache);
|
||||
|
||||
// Returns true only if data written to buffer
|
||||
// This function (unlike the one below it) returns processed stereo samples
|
||||
// made from combining this single partial with its pair, if it has one.
|
||||
bool produceOutput(Sample *leftBuf, Sample *rightBuf, Bit32u length);
|
||||
}; // class Partial
|
||||
|
||||
} // namespace MT32Emu
|
||||
|
||||
#endif // #ifndef MT32EMU_PARTIAL_H
|
@ -1,294 +0,0 @@
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
|
||||
#include "internals.h"
|
||||
|
||||
#include "PartialManager.h"
|
||||
#include "Part.h"
|
||||
#include "Partial.h"
|
||||
#include "Poly.h"
|
||||
#include "Synth.h"
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
PartialManager::PartialManager(Synth *useSynth, Part **useParts) {
|
||||
synth = useSynth;
|
||||
parts = useParts;
|
||||
partialTable = new Partial *[synth->getPartialCount()];
|
||||
freePolys = new Poly *[synth->getPartialCount()];
|
||||
firstFreePolyIndex = 0;
|
||||
for (unsigned int i = 0; i < synth->getPartialCount(); i++) {
|
||||
partialTable[i] = new Partial(synth, i);
|
||||
freePolys[i] = new Poly();
|
||||
}
|
||||
}
|
||||
|
||||
PartialManager::~PartialManager(void) {
|
||||
for (unsigned int i = 0; i < synth->getPartialCount(); i++) {
|
||||
delete partialTable[i];
|
||||
if (freePolys[i] != NULL) delete freePolys[i];
|
||||
}
|
||||
delete[] partialTable;
|
||||
delete[] freePolys;
|
||||
}
|
||||
|
||||
void PartialManager::clearAlreadyOutputed() {
|
||||
for (unsigned int i = 0; i < synth->getPartialCount(); i++) {
|
||||
partialTable[i]->alreadyOutputed = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool PartialManager::shouldReverb(int i) {
|
||||
return partialTable[i]->shouldReverb();
|
||||
}
|
||||
|
||||
bool PartialManager::produceOutput(int i, Sample *leftBuf, Sample *rightBuf, Bit32u bufferLength) {
|
||||
return partialTable[i]->produceOutput(leftBuf, rightBuf, bufferLength);
|
||||
}
|
||||
|
||||
void PartialManager::deactivateAll() {
|
||||
for (unsigned int i = 0; i < synth->getPartialCount(); i++) {
|
||||
partialTable[i]->deactivate();
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int PartialManager::setReserve(Bit8u *rset) {
|
||||
unsigned int pr = 0;
|
||||
for (int x = 0; x <= 8; x++) {
|
||||
numReservedPartialsForPart[x] = rset[x];
|
||||
pr += rset[x];
|
||||
}
|
||||
return pr;
|
||||
}
|
||||
|
||||
Partial *PartialManager::allocPartial(int partNum) {
|
||||
Partial *outPartial = NULL;
|
||||
|
||||
// Get the first inactive partial
|
||||
for (unsigned int partialNum = 0; partialNum < synth->getPartialCount(); partialNum++) {
|
||||
if (!partialTable[partialNum]->isActive()) {
|
||||
outPartial = partialTable[partialNum];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (outPartial != NULL) {
|
||||
outPartial->activate(partNum);
|
||||
}
|
||||
return outPartial;
|
||||
}
|
||||
|
||||
unsigned int PartialManager::getFreePartialCount(void) {
|
||||
int count = 0;
|
||||
for (unsigned int i = 0; i < synth->getPartialCount(); i++) {
|
||||
if (!partialTable[i]->isActive()) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
// This function is solely used to gather data for debug output at the moment.
|
||||
void PartialManager::getPerPartPartialUsage(unsigned int perPartPartialUsage[9]) {
|
||||
memset(perPartPartialUsage, 0, 9 * sizeof(unsigned int));
|
||||
for (unsigned int i = 0; i < synth->getPartialCount(); i++) {
|
||||
if (partialTable[i]->isActive()) {
|
||||
perPartPartialUsage[partialTable[i]->getOwnerPart()]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Finds the lowest-priority part that is exceeding its reserved partial allocation and has a poly
|
||||
// in POLY_Releasing, then kills its first releasing poly.
|
||||
// Parts with higher priority than minPart are not checked.
|
||||
// Assumes that getFreePartials() has been called to make numReservedPartialsForPart up-to-date.
|
||||
bool PartialManager::abortFirstReleasingPolyWhereReserveExceeded(int minPart) {
|
||||
if (minPart == 8) {
|
||||
// Rhythm is highest priority
|
||||
minPart = -1;
|
||||
}
|
||||
for (int partNum = 7; partNum >= minPart; partNum--) {
|
||||
int usePartNum = partNum == -1 ? 8 : partNum;
|
||||
if (parts[usePartNum]->getActivePartialCount() > numReservedPartialsForPart[usePartNum]) {
|
||||
// This part has exceeded its reserved partial count.
|
||||
// If it has any releasing polys, kill its first one and we're done.
|
||||
if (parts[usePartNum]->abortFirstPoly(POLY_Releasing)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Finds the lowest-priority part that is exceeding its reserved partial allocation and has a poly, then kills
|
||||
// its first poly in POLY_Held - or failing that, its first poly in any state.
|
||||
// Parts with higher priority than minPart are not checked.
|
||||
// Assumes that getFreePartials() has been called to make numReservedPartialsForPart up-to-date.
|
||||
bool PartialManager::abortFirstPolyPreferHeldWhereReserveExceeded(int minPart) {
|
||||
if (minPart == 8) {
|
||||
// Rhythm is highest priority
|
||||
minPart = -1;
|
||||
}
|
||||
for (int partNum = 7; partNum >= minPart; partNum--) {
|
||||
int usePartNum = partNum == -1 ? 8 : partNum;
|
||||
if (parts[usePartNum]->getActivePartialCount() > numReservedPartialsForPart[usePartNum]) {
|
||||
// This part has exceeded its reserved partial count.
|
||||
// If it has any polys, kill its first (preferably held) one and we're done.
|
||||
if (parts[usePartNum]->abortFirstPolyPreferHeld()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PartialManager::freePartials(unsigned int needed, int partNum) {
|
||||
// CONFIRMED: Barring bugs, this matches the real LAPC-I according to information from Mok.
|
||||
|
||||
// BUG: There's a bug in the LAPC-I implementation:
|
||||
// When allocating for rhythm part, or when allocating for a part that is using fewer partials than it has reserved,
|
||||
// held and playing polys on the rhythm part can potentially be aborted before releasing polys on the rhythm part.
|
||||
// This bug isn't present on MT-32.
|
||||
// I consider this to be a bug because I think that playing polys should always have priority over held polys,
|
||||
// and held polys should always have priority over releasing polys.
|
||||
|
||||
// NOTE: This code generally aborts polys in parts (according to certain conditions) in the following order:
|
||||
// 7, 6, 5, 4, 3, 2, 1, 0, 8 (rhythm)
|
||||
// (from lowest priority, meaning most likely to have polys aborted, to highest priority, meaning least likely)
|
||||
|
||||
if (needed == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Note that calling getFreePartialCount() also ensures that numReservedPartialsPerPart is up-to-date
|
||||
if (getFreePartialCount() >= needed) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Note: These #ifdefs are temporary until we have proper "quirk" configuration.
|
||||
// Also, the MT-32 version isn't properly confirmed yet.
|
||||
#ifdef MT32EMU_QUIRK_FREE_PARTIALS_MT32
|
||||
// On MT-32, we bail out before even killing releasing partials if the allocating part has exceeded its reserve and is configured for priority-to-earlier-polys.
|
||||
if (parts[partNum]->getActiveNonReleasingPartialCount() + needed > numReservedPartialsForPart[partNum] && (synth->getPart(partNum)->getPatchTemp()->patch.assignMode & 1)) {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
for (;;) {
|
||||
#ifdef MT32EMU_QUIRK_FREE_PARTIALS_MT32
|
||||
// Abort releasing polys in parts that have exceeded their partial reservation (working backwards from part 7, with rhythm last)
|
||||
if (!abortFirstReleasingPolyWhereReserveExceeded(-1)) {
|
||||
break;
|
||||
}
|
||||
#else
|
||||
// Abort releasing polys in non-rhythm parts that have exceeded their partial reservation (working backwards from part 7)
|
||||
if (!abortFirstReleasingPolyWhereReserveExceeded(0)) {
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
if (synth->isAbortingPoly() || getFreePartialCount() >= needed) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (parts[partNum]->getActiveNonReleasingPartialCount() + needed > numReservedPartialsForPart[partNum]) {
|
||||
// With the new partials we're freeing for, we would end up using more partials than we have reserved.
|
||||
if (synth->getPart(partNum)->getPatchTemp()->patch.assignMode & 1) {
|
||||
// Priority is given to earlier polys, so just give up
|
||||
return false;
|
||||
}
|
||||
// Only abort held polys in the target part and parts that have a lower priority
|
||||
// (higher part number = lower priority, except for rhythm, which has the highest priority).
|
||||
for (;;) {
|
||||
if (!abortFirstPolyPreferHeldWhereReserveExceeded(partNum)) {
|
||||
break;
|
||||
}
|
||||
if (synth->isAbortingPoly() || getFreePartialCount() >= needed) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (needed > numReservedPartialsForPart[partNum]) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// At this point, we're certain that we've reserved enough partials to play our poly.
|
||||
// Check all parts from lowest to highest priority to see whether they've exceeded their
|
||||
// reserve, and abort their polys until until we have enough free partials or they're within
|
||||
// their reserve allocation.
|
||||
for (;;) {
|
||||
if (!abortFirstPolyPreferHeldWhereReserveExceeded(-1)) {
|
||||
break;
|
||||
}
|
||||
if (synth->isAbortingPoly() || getFreePartialCount() >= needed) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Abort polys in the target part until there are enough free partials for the new one
|
||||
for (;;) {
|
||||
if (!parts[partNum]->abortFirstPolyPreferHeld()) {
|
||||
break;
|
||||
}
|
||||
if (synth->isAbortingPoly() || getFreePartialCount() >= needed) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Aww, not enough partials for you.
|
||||
return false;
|
||||
}
|
||||
|
||||
const Partial *PartialManager::getPartial(unsigned int partialNum) const {
|
||||
if (partialNum > synth->getPartialCount() - 1) {
|
||||
return NULL;
|
||||
}
|
||||
return partialTable[partialNum];
|
||||
}
|
||||
|
||||
Poly *PartialManager::assignPolyToPart(Part *part) {
|
||||
if (firstFreePolyIndex < synth->getPartialCount()) {
|
||||
Poly *poly = freePolys[firstFreePolyIndex];
|
||||
freePolys[firstFreePolyIndex] = NULL;
|
||||
firstFreePolyIndex++;
|
||||
poly->setPart(part);
|
||||
return poly;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void PartialManager::polyFreed(Poly *poly) {
|
||||
if (0 == firstFreePolyIndex) {
|
||||
synth->printDebug("Cannot return freed poly, currently active polys:\n");
|
||||
for (Bit32u partNum = 0; partNum < 9; partNum++) {
|
||||
const Poly *activePoly = synth->getPart(partNum)->getFirstActivePoly();
|
||||
Bit32u polyCount = 0;
|
||||
while (activePoly != NULL) {
|
||||
activePoly = activePoly->getNext();
|
||||
polyCount++;
|
||||
}
|
||||
synth->printDebug("Part: %i, active poly count: %i\n", partNum, polyCount);
|
||||
}
|
||||
}
|
||||
poly->setPart(NULL);
|
||||
firstFreePolyIndex--;
|
||||
freePolys[firstFreePolyIndex] = poly;
|
||||
}
|
||||
|
||||
} // namespace MT32Emu
|
@ -1,63 +0,0 @@
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MT32EMU_PARTIALMANAGER_H
|
||||
#define MT32EMU_PARTIALMANAGER_H
|
||||
|
||||
#include "globals.h"
|
||||
#include "internals.h"
|
||||
#include "Types.h"
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
class Part;
|
||||
class Partial;
|
||||
class Poly;
|
||||
class Synth;
|
||||
|
||||
class PartialManager {
|
||||
private:
|
||||
Synth *synth;
|
||||
Part **parts;
|
||||
Poly **freePolys;
|
||||
Partial **partialTable;
|
||||
Bit8u numReservedPartialsForPart[9];
|
||||
Bit32u firstFreePolyIndex;
|
||||
|
||||
bool abortFirstReleasingPolyWhereReserveExceeded(int minPart);
|
||||
bool abortFirstPolyPreferHeldWhereReserveExceeded(int minPart);
|
||||
|
||||
public:
|
||||
PartialManager(Synth *synth, Part **parts);
|
||||
~PartialManager();
|
||||
Partial *allocPartial(int partNum);
|
||||
unsigned int getFreePartialCount(void);
|
||||
void getPerPartPartialUsage(unsigned int perPartPartialUsage[9]);
|
||||
bool freePartials(unsigned int needed, int partNum);
|
||||
unsigned int setReserve(Bit8u *rset);
|
||||
void deactivateAll();
|
||||
bool produceOutput(int i, Sample *leftBuf, Sample *rightBuf, Bit32u bufferLength);
|
||||
bool shouldReverb(int i);
|
||||
void clearAlreadyOutputed();
|
||||
const Partial *getPartial(unsigned int partialNum) const;
|
||||
Poly *assignPolyToPart(Part *part);
|
||||
void polyFreed(Poly *poly);
|
||||
}; // class PartialManager
|
||||
|
||||
} // namespace MT32Emu
|
||||
|
||||
#endif // #ifndef MT32EMU_PARTIALMANAGER_H
|
@ -1,190 +0,0 @@
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
#include "internals.h"
|
||||
|
||||
#include "Poly.h"
|
||||
#include "Part.h"
|
||||
#include "Partial.h"
|
||||
#include "Synth.h"
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
Poly::Poly() {
|
||||
part = NULL;
|
||||
key = 255;
|
||||
velocity = 255;
|
||||
sustain = false;
|
||||
activePartialCount = 0;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
partials[i] = NULL;
|
||||
}
|
||||
state = POLY_Inactive;
|
||||
next = NULL;
|
||||
}
|
||||
|
||||
void Poly::setPart(Part *usePart) {
|
||||
part = usePart;
|
||||
}
|
||||
|
||||
void Poly::reset(unsigned int newKey, unsigned int newVelocity, bool newSustain, Partial **newPartials) {
|
||||
if (isActive()) {
|
||||
// This should never happen
|
||||
part->getSynth()->printDebug("Resetting active poly. Active partial count: %i\n", activePartialCount);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (partials[i] != NULL && partials[i]->isActive()) {
|
||||
partials[i]->deactivate();
|
||||
activePartialCount--;
|
||||
}
|
||||
}
|
||||
state = POLY_Inactive;
|
||||
}
|
||||
|
||||
key = newKey;
|
||||
velocity = newVelocity;
|
||||
sustain = newSustain;
|
||||
|
||||
activePartialCount = 0;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
partials[i] = newPartials[i];
|
||||
if (newPartials[i] != NULL) {
|
||||
activePartialCount++;
|
||||
state = POLY_Playing;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Poly::noteOff(bool pedalHeld) {
|
||||
// Generally, non-sustaining instruments ignore note off. They die away eventually anyway.
|
||||
// Key 0 (only used by special cases on rhythm part) reacts to note off even if non-sustaining or pedal held.
|
||||
if (state == POLY_Inactive || state == POLY_Releasing) {
|
||||
return false;
|
||||
}
|
||||
if (pedalHeld) {
|
||||
if (state == POLY_Held) {
|
||||
return false;
|
||||
}
|
||||
state = POLY_Held;
|
||||
} else {
|
||||
startDecay();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Poly::stopPedalHold() {
|
||||
if (state != POLY_Held) {
|
||||
return false;
|
||||
}
|
||||
return startDecay();
|
||||
}
|
||||
|
||||
bool Poly::startDecay() {
|
||||
if (state == POLY_Inactive || state == POLY_Releasing) {
|
||||
return false;
|
||||
}
|
||||
state = POLY_Releasing;
|
||||
|
||||
for (int t = 0; t < 4; t++) {
|
||||
Partial *partial = partials[t];
|
||||
if (partial != NULL) {
|
||||
partial->startDecayAll();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Poly::startAbort() {
|
||||
if (state == POLY_Inactive || part->getSynth()->isAbortingPoly()) {
|
||||
return false;
|
||||
}
|
||||
for (int t = 0; t < 4; t++) {
|
||||
Partial *partial = partials[t];
|
||||
if (partial != NULL) {
|
||||
partial->startAbort();
|
||||
part->getSynth()->abortingPoly = this;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Poly::backupCacheToPartials(PatchCache cache[4]) {
|
||||
for (int partialNum = 0; partialNum < 4; partialNum++) {
|
||||
Partial *partial = partials[partialNum];
|
||||
if (partial != NULL) {
|
||||
partial->backupCache(cache[partialNum]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the internal key identifier.
|
||||
* For non-rhythm, this is within the range 12 to 108.
|
||||
* For rhythm on MT-32, this is 0 or 1 (special cases) or within the range 24 to 87.
|
||||
* For rhythm on devices with extended PCM sounds (e.g. CM-32L), this is 0, 1 or 24 to 108
|
||||
*/
|
||||
unsigned int Poly::getKey() const {
|
||||
return key;
|
||||
}
|
||||
|
||||
unsigned int Poly::getVelocity() const {
|
||||
return velocity;
|
||||
}
|
||||
|
||||
bool Poly::canSustain() const {
|
||||
return sustain;
|
||||
}
|
||||
|
||||
PolyState Poly::getState() const {
|
||||
return state;
|
||||
}
|
||||
|
||||
unsigned int Poly::getActivePartialCount() const {
|
||||
return activePartialCount;
|
||||
}
|
||||
|
||||
bool Poly::isActive() const {
|
||||
return state != POLY_Inactive;
|
||||
}
|
||||
|
||||
// This is called by Partial to inform the poly that the Partial has deactivated
|
||||
void Poly::partialDeactivated(Partial *partial) {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (partials[i] == partial) {
|
||||
partials[i] = NULL;
|
||||
activePartialCount--;
|
||||
}
|
||||
}
|
||||
if (activePartialCount == 0) {
|
||||
state = POLY_Inactive;
|
||||
if (part->getSynth()->abortingPoly == this) {
|
||||
part->getSynth()->abortingPoly = NULL;
|
||||
}
|
||||
}
|
||||
part->partialDeactivated(this);
|
||||
}
|
||||
|
||||
Poly *Poly::getNext() const {
|
||||
return next;
|
||||
}
|
||||
|
||||
void Poly::setNext(Poly *poly) {
|
||||
next = poly;
|
||||
}
|
||||
|
||||
} // namespace MT32Emu
|
@ -1,70 +0,0 @@
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MT32EMU_POLY_H
|
||||
#define MT32EMU_POLY_H
|
||||
|
||||
#include "globals.h"
|
||||
#include "internals.h"
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
class Part;
|
||||
class Partial;
|
||||
struct PatchCache;
|
||||
|
||||
class Poly {
|
||||
private:
|
||||
Part *part;
|
||||
unsigned int key;
|
||||
unsigned int velocity;
|
||||
unsigned int activePartialCount;
|
||||
bool sustain;
|
||||
|
||||
PolyState state;
|
||||
|
||||
Partial *partials[4];
|
||||
|
||||
Poly *next;
|
||||
|
||||
public:
|
||||
Poly();
|
||||
void setPart(Part *usePart);
|
||||
void reset(unsigned int key, unsigned int velocity, bool sustain, Partial **partials);
|
||||
bool noteOff(bool pedalHeld);
|
||||
bool stopPedalHold();
|
||||
bool startDecay();
|
||||
bool startAbort();
|
||||
|
||||
void backupCacheToPartials(PatchCache cache[4]);
|
||||
|
||||
unsigned int getKey() const;
|
||||
unsigned int getVelocity() const;
|
||||
bool canSustain() const;
|
||||
PolyState getState() const;
|
||||
unsigned int getActivePartialCount() const;
|
||||
bool isActive() const;
|
||||
|
||||
void partialDeactivated(Partial *partial);
|
||||
|
||||
Poly *getNext() const;
|
||||
void setNext(Poly *poly);
|
||||
}; // class Poly
|
||||
|
||||
} // namespace MT32Emu
|
||||
|
||||
#endif // #ifndef MT32EMU_POLY_H
|
@ -1,117 +0,0 @@
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include "internals.h"
|
||||
|
||||
#include "ROMInfo.h"
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
static const ROMInfo *getKnownROMInfoFromList(Bit32u index) {
|
||||
// Known ROMs
|
||||
static const ROMInfo CTRL_MT32_V1_04 = {65536, "5a5cb5a77d7d55ee69657c2f870416daed52dea7", ROMInfo::Control, "ctrl_mt32_1_04", "MT-32 Control v1.04", ROMInfo::Full, NULL};
|
||||
static const ROMInfo CTRL_MT32_V1_05 = {65536, "e17a3a6d265bf1fa150312061134293d2b58288c", ROMInfo::Control, "ctrl_mt32_1_05", "MT-32 Control v1.05", ROMInfo::Full, NULL};
|
||||
static const ROMInfo CTRL_MT32_V1_06 = {65536, "a553481f4e2794c10cfe597fef154eef0d8257de", ROMInfo::Control, "ctrl_mt32_1_06", "MT-32 Control v1.06", ROMInfo::Full, NULL};
|
||||
static const ROMInfo CTRL_MT32_V1_07 = {65536, "b083518fffb7f66b03c23b7eb4f868e62dc5a987", ROMInfo::Control, "ctrl_mt32_1_07", "MT-32 Control v1.07", ROMInfo::Full, NULL};
|
||||
static const ROMInfo CTRL_MT32_BLUER = {65536, "7b8c2a5ddb42fd0732e2f22b3340dcf5360edf92", ROMInfo::Control, "ctrl_mt32_bluer", "MT-32 Control BlueRidge", ROMInfo::Full, NULL};
|
||||
|
||||
static const ROMInfo CTRL_CM32L_V1_00 = {65536, "73683d585cd6948cc19547942ca0e14a0319456d", ROMInfo::Control, "ctrl_cm32l_1_00", "CM-32L/LAPC-I Control v1.00", ROMInfo::Full, NULL};
|
||||
static const ROMInfo CTRL_CM32L_V1_02 = {65536, "a439fbb390da38cada95a7cbb1d6ca199cd66ef8", ROMInfo::Control, "ctrl_cm32l_1_02", "CM-32L/LAPC-I Control v1.02", ROMInfo::Full, NULL};
|
||||
|
||||
static const ROMInfo PCM_MT32 = {524288, "f6b1eebc4b2d200ec6d3d21d51325d5b48c60252", ROMInfo::PCM, "pcm_mt32", "MT-32 PCM ROM", ROMInfo::Full, NULL};
|
||||
static const ROMInfo PCM_CM32L = {1048576, "289cc298ad532b702461bfc738009d9ebe8025ea", ROMInfo::PCM, "pcm_cm32l", "CM-32L/CM-64/LAPC-I PCM ROM", ROMInfo::Full, NULL};
|
||||
|
||||
static const ROMInfo * const ROM_INFOS[] = {
|
||||
&CTRL_MT32_V1_04,
|
||||
&CTRL_MT32_V1_05,
|
||||
&CTRL_MT32_V1_06,
|
||||
&CTRL_MT32_V1_07,
|
||||
&CTRL_MT32_BLUER,
|
||||
&CTRL_CM32L_V1_00,
|
||||
&CTRL_CM32L_V1_02,
|
||||
&PCM_MT32,
|
||||
&PCM_CM32L,
|
||||
NULL};
|
||||
|
||||
return ROM_INFOS[index];
|
||||
}
|
||||
|
||||
const ROMInfo* ROMInfo::getROMInfo(File *file) {
|
||||
size_t fileSize = file->getSize();
|
||||
for (Bit32u i = 0; getKnownROMInfoFromList(i) != NULL; i++) {
|
||||
const ROMInfo *romInfo = getKnownROMInfoFromList(i);
|
||||
if (fileSize == romInfo->fileSize && !strcmp(file->getSHA1(), romInfo->sha1Digest)) {
|
||||
return romInfo;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void ROMInfo::freeROMInfo(const ROMInfo *romInfo) {
|
||||
(void) romInfo;
|
||||
}
|
||||
|
||||
static Bit32u getROMCount() {
|
||||
Bit32u count;
|
||||
for(count = 0; getKnownROMInfoFromList(count) != NULL; count++) {
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
const ROMInfo** ROMInfo::getROMInfoList(Bit32u types, Bit32u pairTypes) {
|
||||
const ROMInfo **romInfoList = new const ROMInfo*[getROMCount() + 1];
|
||||
const ROMInfo **currentROMInList = romInfoList;
|
||||
for (Bit32u i = 0; getKnownROMInfoFromList(i) != NULL; i++) {
|
||||
const ROMInfo *romInfo = getKnownROMInfoFromList(i);
|
||||
if ((types & (1 << romInfo->type)) && (pairTypes & (1 << romInfo->pairType))) {
|
||||
*currentROMInList++ = romInfo;
|
||||
}
|
||||
}
|
||||
*currentROMInList = NULL;
|
||||
return romInfoList;
|
||||
}
|
||||
|
||||
void ROMInfo::freeROMInfoList(const ROMInfo **romInfoList) {
|
||||
delete[] romInfoList;
|
||||
}
|
||||
|
||||
ROMImage::ROMImage(File *useFile) : file(useFile), romInfo(ROMInfo::getROMInfo(file))
|
||||
{}
|
||||
|
||||
ROMImage::~ROMImage() {
|
||||
ROMInfo::freeROMInfo(romInfo);
|
||||
}
|
||||
|
||||
const ROMImage* ROMImage::makeROMImage(File *file) {
|
||||
return new ROMImage(file);
|
||||
}
|
||||
|
||||
void ROMImage::freeROMImage(const ROMImage *romImage) {
|
||||
delete romImage;
|
||||
}
|
||||
|
||||
File* ROMImage::getFile() const {
|
||||
return file;
|
||||
}
|
||||
|
||||
const ROMInfo* ROMImage::getROMInfo() const {
|
||||
return romInfo;
|
||||
}
|
||||
|
||||
} // namespace MT32Emu
|
@ -1,80 +0,0 @@
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MT32EMU_ROMINFO_H
|
||||
#define MT32EMU_ROMINFO_H
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
#include "globals.h"
|
||||
#include "File.h"
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
// Defines vital info about ROM file to be used by synth and applications
|
||||
|
||||
struct ROMInfo {
|
||||
public:
|
||||
size_t fileSize;
|
||||
const File::SHA1Digest &sha1Digest;
|
||||
enum Type {PCM, Control, Reverb} type;
|
||||
const char *shortName;
|
||||
const char *description;
|
||||
enum PairType {Full, FirstHalf, SecondHalf, Mux0, Mux1} pairType;
|
||||
ROMInfo *pairROMInfo;
|
||||
|
||||
// Returns a ROMInfo struct by inspecting the size and the SHA1 hash
|
||||
MT32EMU_EXPORT static const ROMInfo* getROMInfo(File *file);
|
||||
|
||||
// Currently no-op
|
||||
MT32EMU_EXPORT static void freeROMInfo(const ROMInfo *romInfo);
|
||||
|
||||
// Allows retrieving a NULL-terminated list of ROMInfos for a range of types and pairTypes
|
||||
// (specified by bitmasks)
|
||||
// Useful for GUI/console app to output information on what ROMs it supports
|
||||
MT32EMU_EXPORT static const ROMInfo** getROMInfoList(Bit32u types, Bit32u pairTypes);
|
||||
|
||||
// Frees the list of ROMInfos given
|
||||
MT32EMU_EXPORT static void freeROMInfoList(const ROMInfo **romInfos);
|
||||
};
|
||||
|
||||
// Synth::open() is to require a full control ROMImage and a full PCM ROMImage to work
|
||||
|
||||
class ROMImage {
|
||||
private:
|
||||
File * const file;
|
||||
const ROMInfo * const romInfo;
|
||||
|
||||
ROMImage(File *file);
|
||||
~ROMImage();
|
||||
|
||||
public:
|
||||
// Creates a ROMImage object given a ROMInfo and a File. Keeps a reference
|
||||
// to the File and ROMInfo given, which must be freed separately by the user
|
||||
// after the ROMImage is freed
|
||||
MT32EMU_EXPORT static const ROMImage* makeROMImage(File *file);
|
||||
|
||||
// Must only be done after all Synths using the ROMImage are deleted
|
||||
MT32EMU_EXPORT static void freeROMImage(const ROMImage *romImage);
|
||||
|
||||
MT32EMU_EXPORT File *getFile() const;
|
||||
MT32EMU_EXPORT const ROMInfo *getROMInfo() const;
|
||||
};
|
||||
|
||||
} // namespace MT32Emu
|
||||
|
||||
#endif // #ifndef MT32EMU_ROMINFO_H
|
@ -1,97 +0,0 @@
|
||||
/* Copyright (C) 2015-2017 Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "SampleRateConverter.h"
|
||||
|
||||
#if MT32EMU_WITH_LIBSOXR_RESAMPLER
|
||||
#include "srchelper/SoxrAdapter.h"
|
||||
#elif MT32EMU_WITH_LIBSAMPLERATE_RESAMPLER
|
||||
#include "srchelper/SamplerateAdapter.h"
|
||||
#else
|
||||
#include "srchelper/InternalResampler.h"
|
||||
#endif
|
||||
|
||||
#include "Synth.h"
|
||||
|
||||
using namespace MT32Emu;
|
||||
|
||||
static inline void *createDelegate(Synth &synth, double targetSampleRate, SampleRateConverter::Quality quality) {
|
||||
#if MT32EMU_WITH_LIBSOXR_RESAMPLER
|
||||
return new SoxrAdapter(synth, targetSampleRate, quality);
|
||||
#elif MT32EMU_WITH_LIBSAMPLERATE_RESAMPLER
|
||||
return new SamplerateAdapter(synth, targetSampleRate, quality);
|
||||
#else
|
||||
return new InternalResampler(synth, targetSampleRate, quality);
|
||||
#endif
|
||||
}
|
||||
|
||||
AnalogOutputMode SampleRateConverter::getBestAnalogOutputMode(double targetSampleRate) {
|
||||
if (Synth::getStereoOutputSampleRate(AnalogOutputMode_ACCURATE) < targetSampleRate) {
|
||||
return AnalogOutputMode_OVERSAMPLED;
|
||||
} else if (Synth::getStereoOutputSampleRate(AnalogOutputMode_COARSE) < targetSampleRate) {
|
||||
return AnalogOutputMode_ACCURATE;
|
||||
}
|
||||
return AnalogOutputMode_COARSE;
|
||||
}
|
||||
|
||||
SampleRateConverter::SampleRateConverter(Synth &useSynth, double targetSampleRate, Quality useQuality) :
|
||||
synthInternalToTargetSampleRateRatio(SAMPLE_RATE / targetSampleRate),
|
||||
srcDelegate(createDelegate(useSynth, targetSampleRate, useQuality))
|
||||
{}
|
||||
|
||||
SampleRateConverter::~SampleRateConverter() {
|
||||
#if MT32EMU_WITH_LIBSOXR_RESAMPLER
|
||||
delete static_cast<SoxrAdapter *>(srcDelegate);
|
||||
#elif MT32EMU_WITH_LIBSAMPLERATE_RESAMPLER
|
||||
delete static_cast<SamplerateAdapter *>(srcDelegate);
|
||||
#else
|
||||
delete static_cast<InternalResampler *>(srcDelegate);
|
||||
#endif
|
||||
}
|
||||
|
||||
void SampleRateConverter::getOutputSamples(float *buffer, unsigned int length) {
|
||||
#if MT32EMU_WITH_LIBSOXR_RESAMPLER
|
||||
static_cast<SoxrAdapter *>(srcDelegate)->getOutputSamples(buffer, length);
|
||||
#elif MT32EMU_WITH_LIBSAMPLERATE_RESAMPLER
|
||||
static_cast<SamplerateAdapter *>(srcDelegate)->getOutputSamples(buffer, length);
|
||||
#else
|
||||
static_cast<InternalResampler *>(srcDelegate)->getOutputSamples(buffer, length);
|
||||
#endif
|
||||
}
|
||||
|
||||
void SampleRateConverter::getOutputSamples(Bit16s *outBuffer, unsigned int length) {
|
||||
static const unsigned int CHANNEL_COUNT = 2;
|
||||
|
||||
float floatBuffer[CHANNEL_COUNT * MAX_SAMPLES_PER_RUN];
|
||||
while (length > 0) {
|
||||
const unsigned int size = MAX_SAMPLES_PER_RUN < length ? MAX_SAMPLES_PER_RUN : length;
|
||||
getOutputSamples(floatBuffer, size);
|
||||
float *outs = floatBuffer;
|
||||
float *ends = floatBuffer + CHANNEL_COUNT * size;
|
||||
while (outs < ends) {
|
||||
*(outBuffer++) = Synth::convertSample(*(outs++));
|
||||
}
|
||||
length -= size;
|
||||
}
|
||||
}
|
||||
|
||||
double SampleRateConverter::convertOutputToSynthTimestamp(double outputTimestamp) const {
|
||||
return outputTimestamp * synthInternalToTargetSampleRateRatio;
|
||||
}
|
||||
|
||||
double SampleRateConverter::convertSynthToOutputTimestamp(double synthTimestamp) const {
|
||||
return synthTimestamp / synthInternalToTargetSampleRateRatio;
|
||||
}
|
@ -1,78 +0,0 @@
|
||||
/* Copyright (C) 2015-2017 Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef SAMPLE_RATE_CONVERTER_H
|
||||
#define SAMPLE_RATE_CONVERTER_H
|
||||
|
||||
#include "globals.h"
|
||||
#include "Types.h"
|
||||
#include "Enumerations.h"
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
class Synth;
|
||||
|
||||
/* SampleRateConverter class allows to convert the synthesiser output to any desired sample rate.
|
||||
* It processes the completely mixed stereo output signal as it passes the analogue circuit emulation,
|
||||
* so emulating the synthesiser output signal passing further through an ADC.
|
||||
* Several conversion quality options are provided which allow to trade-off the conversion speed vs. the passband width.
|
||||
* All the options except FASTEST guarantee full suppression of the aliasing noise in terms of the 16-bit integer samples.
|
||||
*/
|
||||
class MT32EMU_EXPORT SampleRateConverter {
|
||||
public:
|
||||
enum Quality {
|
||||
// Use this only when the speed is more important than the audio quality.
|
||||
FASTEST,
|
||||
FAST,
|
||||
GOOD,
|
||||
BEST
|
||||
};
|
||||
|
||||
// Returns the value of AnalogOutputMode for which the output signal may retain its full frequency spectrum
|
||||
// at the sample rate specified by the targetSampleRate argument.
|
||||
static AnalogOutputMode getBestAnalogOutputMode(double targetSampleRate);
|
||||
|
||||
// Creates a SampleRateConverter instance that converts output signal from the synth to the given sample rate
|
||||
// with the specified conversion quality.
|
||||
SampleRateConverter(Synth &synth, double targetSampleRate, Quality quality);
|
||||
~SampleRateConverter();
|
||||
|
||||
// Fills the provided output buffer with the results of the sample rate conversion.
|
||||
// The input samples are automatically retrieved from the synth as necessary.
|
||||
void getOutputSamples(MT32Emu::Bit16s *buffer, unsigned int length);
|
||||
|
||||
// Fills the provided output buffer with the results of the sample rate conversion.
|
||||
// The input samples are automatically retrieved from the synth as necessary.
|
||||
void getOutputSamples(float *buffer, unsigned int length);
|
||||
|
||||
// Returns the number of samples produced at the internal synth sample rate (32000 Hz)
|
||||
// that correspond to the number of samples at the target sample rate.
|
||||
// Intended to facilitate audio time synchronisation.
|
||||
double convertOutputToSynthTimestamp(double outputTimestamp) const;
|
||||
|
||||
// Returns the number of samples produced at the target sample rate
|
||||
// that correspond to the number of samples at the internal synth sample rate (32000 Hz).
|
||||
// Intended to facilitate audio time synchronisation.
|
||||
double convertSynthToOutputTimestamp(double synthTimestamp) const;
|
||||
|
||||
private:
|
||||
const double synthInternalToTargetSampleRateRatio;
|
||||
void * const srcDelegate;
|
||||
}; // class SampleRateConverter
|
||||
|
||||
} // namespace MT32Emu
|
||||
|
||||
#endif // SAMPLE_RATE_CONVERTER_H
|
@ -1,259 +0,0 @@
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MT32EMU_STRUCTURES_H
|
||||
#define MT32EMU_STRUCTURES_H
|
||||
|
||||
#include "globals.h"
|
||||
#include "Types.h"
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
// MT32EMU_MEMADDR() converts from sysex-padded, MT32EMU_SYSEXMEMADDR converts to it
|
||||
// Roland provides documentation using the sysex-padded addresses, so we tend to use that in code and output
|
||||
#define MT32EMU_MEMADDR(x) ((((x) & 0x7f0000) >> 2) | (((x) & 0x7f00) >> 1) | ((x) & 0x7f))
|
||||
#define MT32EMU_SYSEXMEMADDR(x) ((((x) & 0x1FC000) << 2) | (((x) & 0x3F80) << 1) | ((x) & 0x7f))
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define MT32EMU_ALIGN_PACKED __declspec(align(1))
|
||||
#else
|
||||
#define MT32EMU_ALIGN_PACKED __attribute__((packed))
|
||||
#endif
|
||||
|
||||
// The following structures represent the MT-32's memory
|
||||
// Since sysex allows this memory to be written to in blocks of bytes,
|
||||
// we keep this packed so that we can copy data into the various
|
||||
// banks directly
|
||||
#if defined(_MSC_VER) || defined(__MINGW32__)
|
||||
#pragma pack(push, 1)
|
||||
#else
|
||||
#pragma pack(1)
|
||||
#endif
|
||||
|
||||
struct TimbreParam {
|
||||
struct CommonParam {
|
||||
char name[10];
|
||||
Bit8u partialStructure12; // 1 & 2 0-12 (1-13)
|
||||
Bit8u partialStructure34; // 3 & 4 0-12 (1-13)
|
||||
Bit8u partialMute; // 0-15 (0000-1111)
|
||||
Bit8u noSustain; // ENV MODE 0-1 (Normal, No sustain)
|
||||
} MT32EMU_ALIGN_PACKED common;
|
||||
|
||||
struct PartialParam {
|
||||
struct WGParam {
|
||||
Bit8u pitchCoarse; // 0-96 (C1,C#1-C9)
|
||||
Bit8u pitchFine; // 0-100 (-50 to +50 (cents - confirmed by Mok))
|
||||
Bit8u pitchKeyfollow; // 0-16 (-1, -1/2, -1/4, 0, 1/8, 1/4, 3/8, 1/2, 5/8, 3/4, 7/8, 1, 5/4, 3/2, 2, s1, s2)
|
||||
Bit8u pitchBenderEnabled; // 0-1 (OFF, ON)
|
||||
Bit8u waveform; // MT-32: 0-1 (SQU/SAW); LAPC-I: WG WAVEFORM/PCM BANK 0 - 3 (SQU/1, SAW/1, SQU/2, SAW/2)
|
||||
Bit8u pcmWave; // 0-127 (1-128)
|
||||
Bit8u pulseWidth; // 0-100
|
||||
Bit8u pulseWidthVeloSensitivity; // 0-14 (-7 - +7)
|
||||
} MT32EMU_ALIGN_PACKED wg;
|
||||
|
||||
struct PitchEnvParam {
|
||||
Bit8u depth; // 0-10
|
||||
Bit8u veloSensitivity; // 0-100
|
||||
Bit8u timeKeyfollow; // 0-4
|
||||
Bit8u time[4]; // 0-100
|
||||
Bit8u level[5]; // 0-100 (-50 - +50) // [3]: SUSTAIN LEVEL, [4]: END LEVEL
|
||||
} MT32EMU_ALIGN_PACKED pitchEnv;
|
||||
|
||||
struct PitchLFOParam {
|
||||
Bit8u rate; // 0-100
|
||||
Bit8u depth; // 0-100
|
||||
Bit8u modSensitivity; // 0-100
|
||||
} MT32EMU_ALIGN_PACKED pitchLFO;
|
||||
|
||||
struct TVFParam {
|
||||
Bit8u cutoff; // 0-100
|
||||
Bit8u resonance; // 0-30
|
||||
Bit8u keyfollow; // -1, -1/2, -1/4, 0, 1/8, 1/4, 3/8, 1/2, 5/8, 3/4, 7/8, 1, 5/4, 3/2, 2
|
||||
Bit8u biasPoint; // 0-127 (<1A-<7C >1A-7C)
|
||||
Bit8u biasLevel; // 0-14 (-7 - +7)
|
||||
Bit8u envDepth; // 0-100
|
||||
Bit8u envVeloSensitivity; // 0-100
|
||||
Bit8u envDepthKeyfollow; // DEPTH KEY FOLL0W 0-4
|
||||
Bit8u envTimeKeyfollow; // TIME KEY FOLLOW 0-4
|
||||
Bit8u envTime[5]; // 0-100
|
||||
Bit8u envLevel[4]; // 0-100 // [3]: SUSTAIN LEVEL
|
||||
} MT32EMU_ALIGN_PACKED tvf;
|
||||
|
||||
struct TVAParam {
|
||||
Bit8u level; // 0-100
|
||||
Bit8u veloSensitivity; // 0-100
|
||||
Bit8u biasPoint1; // 0-127 (<1A-<7C >1A-7C)
|
||||
Bit8u biasLevel1; // 0-12 (-12 - 0)
|
||||
Bit8u biasPoint2; // 0-127 (<1A-<7C >1A-7C)
|
||||
Bit8u biasLevel2; // 0-12 (-12 - 0)
|
||||
Bit8u envTimeKeyfollow; // TIME KEY FOLLOW 0-4
|
||||
Bit8u envTimeVeloSensitivity; // VELOS KEY FOLL0W 0-4
|
||||
Bit8u envTime[5]; // 0-100
|
||||
Bit8u envLevel[4]; // 0-100 // [3]: SUSTAIN LEVEL
|
||||
} MT32EMU_ALIGN_PACKED tva;
|
||||
} MT32EMU_ALIGN_PACKED partial[4]; // struct PartialParam
|
||||
} MT32EMU_ALIGN_PACKED; // struct TimbreParam
|
||||
|
||||
struct PatchParam {
|
||||
Bit8u timbreGroup; // TIMBRE GROUP 0-3 (group A, group B, Memory, Rhythm)
|
||||
Bit8u timbreNum; // TIMBRE NUMBER 0-63
|
||||
Bit8u keyShift; // KEY SHIFT 0-48 (-24 - +24 semitones)
|
||||
Bit8u fineTune; // FINE TUNE 0-100 (-50 - +50 cents)
|
||||
Bit8u benderRange; // BENDER RANGE 0-24
|
||||
Bit8u assignMode; // ASSIGN MODE 0-3 (POLY1, POLY2, POLY3, POLY4)
|
||||
Bit8u reverbSwitch; // REVERB SWITCH 0-1 (OFF,ON)
|
||||
Bit8u dummy; // (DUMMY)
|
||||
} MT32EMU_ALIGN_PACKED;
|
||||
|
||||
const unsigned int SYSTEM_MASTER_TUNE_OFF = 0;
|
||||
const unsigned int SYSTEM_REVERB_MODE_OFF = 1;
|
||||
const unsigned int SYSTEM_REVERB_TIME_OFF = 2;
|
||||
const unsigned int SYSTEM_REVERB_LEVEL_OFF = 3;
|
||||
const unsigned int SYSTEM_RESERVE_SETTINGS_START_OFF = 4;
|
||||
const unsigned int SYSTEM_RESERVE_SETTINGS_END_OFF = 12;
|
||||
const unsigned int SYSTEM_CHAN_ASSIGN_START_OFF = 13;
|
||||
const unsigned int SYSTEM_CHAN_ASSIGN_END_OFF = 21;
|
||||
const unsigned int SYSTEM_MASTER_VOL_OFF = 22;
|
||||
|
||||
struct MemParams {
|
||||
// NOTE: The MT-32 documentation only specifies PatchTemp areas for parts 1-8.
|
||||
// The LAPC-I documentation specified an additional area for rhythm at the end,
|
||||
// where all parameters but fine tune, assign mode and output level are ignored
|
||||
struct PatchTemp {
|
||||
PatchParam patch;
|
||||
Bit8u outputLevel; // OUTPUT LEVEL 0-100
|
||||
Bit8u panpot; // PANPOT 0-14 (R-L)
|
||||
Bit8u dummyv[6];
|
||||
} MT32EMU_ALIGN_PACKED patchTemp[9];
|
||||
|
||||
struct RhythmTemp {
|
||||
Bit8u timbre; // TIMBRE 0-94 (M1-M64,R1-30,OFF); LAPC-I: 0-127 (M01-M64,R01-R63)
|
||||
Bit8u outputLevel; // OUTPUT LEVEL 0-100
|
||||
Bit8u panpot; // PANPOT 0-14 (R-L)
|
||||
Bit8u reverbSwitch; // REVERB SWITCH 0-1 (OFF,ON)
|
||||
} MT32EMU_ALIGN_PACKED rhythmTemp[85];
|
||||
|
||||
TimbreParam timbreTemp[8];
|
||||
|
||||
PatchParam patches[128];
|
||||
|
||||
// NOTE: There are only 30 timbres in the "rhythm" bank for MT-32; the additional 34 are for LAPC-I and above
|
||||
struct PaddedTimbre {
|
||||
TimbreParam timbre;
|
||||
Bit8u padding[10];
|
||||
} MT32EMU_ALIGN_PACKED timbres[64 + 64 + 64 + 64]; // Group A, Group B, Memory, Rhythm
|
||||
|
||||
struct System {
|
||||
Bit8u masterTune; // MASTER TUNE 0-127 432.1-457.6Hz
|
||||
Bit8u reverbMode; // REVERB MODE 0-3 (room, hall, plate, tap delay)
|
||||
Bit8u reverbTime; // REVERB TIME 0-7 (1-8)
|
||||
Bit8u reverbLevel; // REVERB LEVEL 0-7 (1-8)
|
||||
Bit8u reserveSettings[9]; // PARTIAL RESERVE (PART 1) 0-32
|
||||
Bit8u chanAssign[9]; // MIDI CHANNEL (PART1) 0-16 (1-16,OFF)
|
||||
Bit8u masterVol; // MASTER VOLUME 0-100
|
||||
} MT32EMU_ALIGN_PACKED system;
|
||||
}; // struct MemParams
|
||||
|
||||
struct SoundGroup {
|
||||
Bit8u timbreNumberTableAddrLow;
|
||||
Bit8u timbreNumberTableAddrHigh;
|
||||
Bit8u displayPosition;
|
||||
Bit8u name[9];
|
||||
Bit8u timbreCount;
|
||||
Bit8u pad;
|
||||
} MT32EMU_ALIGN_PACKED;
|
||||
|
||||
#if defined(_MSC_VER) || defined(__MINGW32__)
|
||||
#pragma pack(pop)
|
||||
#else
|
||||
#pragma pack()
|
||||
#endif
|
||||
|
||||
struct ControlROMFeatureSet {
|
||||
unsigned int quirkPitchEnvelopeOverflow : 1;
|
||||
|
||||
// Features below don't actually depend on control ROM version, which is used to identify hardware model
|
||||
unsigned int defaultReverbMT32Compatible : 1;
|
||||
unsigned int oldMT32AnalogLPF : 1;
|
||||
};
|
||||
|
||||
struct ControlROMMap {
|
||||
const char *shortName;
|
||||
const ControlROMFeatureSet &featureSet;
|
||||
Bit16u pcmTable; // 4 * pcmCount bytes
|
||||
Bit16u pcmCount;
|
||||
Bit16u timbreAMap; // 128 bytes
|
||||
Bit16u timbreAOffset;
|
||||
bool timbreACompressed;
|
||||
Bit16u timbreBMap; // 128 bytes
|
||||
Bit16u timbreBOffset;
|
||||
bool timbreBCompressed;
|
||||
Bit16u timbreRMap; // 2 * timbreRCount bytes
|
||||
Bit16u timbreRCount;
|
||||
Bit16u rhythmSettings; // 4 * rhythmSettingsCount bytes
|
||||
Bit16u rhythmSettingsCount;
|
||||
Bit16u reserveSettings; // 9 bytes
|
||||
Bit16u panSettings; // 8 bytes
|
||||
Bit16u programSettings; // 8 bytes
|
||||
Bit16u rhythmMaxTable; // 4 bytes
|
||||
Bit16u patchMaxTable; // 16 bytes
|
||||
Bit16u systemMaxTable; // 23 bytes
|
||||
Bit16u timbreMaxTable; // 72 bytes
|
||||
Bit16u soundGroupsTable; // 14 bytes each entry
|
||||
Bit16u soundGroupsCount;
|
||||
};
|
||||
|
||||
struct ControlROMPCMStruct {
|
||||
Bit8u pos;
|
||||
Bit8u len;
|
||||
Bit8u pitchLSB;
|
||||
Bit8u pitchMSB;
|
||||
};
|
||||
|
||||
struct PCMWaveEntry {
|
||||
Bit32u addr;
|
||||
Bit32u len;
|
||||
bool loop;
|
||||
ControlROMPCMStruct *controlROMPCMStruct;
|
||||
};
|
||||
|
||||
// This is basically a per-partial, pre-processed combination of timbre and patch/rhythm settings
|
||||
struct PatchCache {
|
||||
bool playPartial;
|
||||
bool PCMPartial;
|
||||
int pcm;
|
||||
Bit8u waveform;
|
||||
|
||||
Bit32u structureMix;
|
||||
int structurePosition;
|
||||
int structurePair;
|
||||
|
||||
// The following fields are actually common to all partials in the timbre
|
||||
bool dirty;
|
||||
Bit32u partialCount;
|
||||
bool sustain;
|
||||
bool reverb;
|
||||
|
||||
TimbreParam::PartialParam srcPartial;
|
||||
|
||||
// The following directly points into live sysex-addressable memory
|
||||
const TimbreParam::PartialParam *partialParam;
|
||||
};
|
||||
|
||||
} // namespace MT32Emu
|
||||
|
||||
#endif // #ifndef MT32EMU_STRUCTURES_H
|
File diff suppressed because it is too large
Load Diff
@ -1,479 +0,0 @@
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MT32EMU_SYNTH_H
|
||||
#define MT32EMU_SYNTH_H
|
||||
|
||||
#include <cstdarg>
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
|
||||
#include "globals.h"
|
||||
#include "Types.h"
|
||||
#include "Enumerations.h"
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
class Analog;
|
||||
class BReverbModel;
|
||||
class MemoryRegion;
|
||||
class MidiEventQueue;
|
||||
class Part;
|
||||
class Poly;
|
||||
class Partial;
|
||||
class PartialManager;
|
||||
class Renderer;
|
||||
class ROMImage;
|
||||
|
||||
class PatchTempMemoryRegion;
|
||||
class RhythmTempMemoryRegion;
|
||||
class TimbreTempMemoryRegion;
|
||||
class PatchesMemoryRegion;
|
||||
class TimbresMemoryRegion;
|
||||
class SystemMemoryRegion;
|
||||
class DisplayMemoryRegion;
|
||||
class ResetMemoryRegion;
|
||||
|
||||
struct ControlROMFeatureSet;
|
||||
struct ControlROMMap;
|
||||
struct PCMWaveEntry;
|
||||
struct MemParams;
|
||||
|
||||
const Bit8u SYSEX_MANUFACTURER_ROLAND = 0x41;
|
||||
|
||||
const Bit8u SYSEX_MDL_MT32 = 0x16;
|
||||
const Bit8u SYSEX_MDL_D50 = 0x14;
|
||||
|
||||
const Bit8u SYSEX_CMD_RQ1 = 0x11; // Request data #1
|
||||
const Bit8u SYSEX_CMD_DT1 = 0x12; // Data set 1
|
||||
const Bit8u SYSEX_CMD_WSD = 0x40; // Want to send data
|
||||
const Bit8u SYSEX_CMD_RQD = 0x41; // Request data
|
||||
const Bit8u SYSEX_CMD_DAT = 0x42; // Data set
|
||||
const Bit8u SYSEX_CMD_ACK = 0x43; // Acknowledge
|
||||
const Bit8u SYSEX_CMD_EOD = 0x45; // End of data
|
||||
const Bit8u SYSEX_CMD_ERR = 0x4E; // Communications error
|
||||
const Bit8u SYSEX_CMD_RJC = 0x4F; // Rejection
|
||||
|
||||
const Bit32u CONTROL_ROM_SIZE = 64 * 1024;
|
||||
|
||||
// Set of multiplexed output streams appeared at the DAC entrance.
|
||||
template <class T>
|
||||
struct DACOutputStreams {
|
||||
T *nonReverbLeft;
|
||||
T *nonReverbRight;
|
||||
T *reverbDryLeft;
|
||||
T *reverbDryRight;
|
||||
T *reverbWetLeft;
|
||||
T *reverbWetRight;
|
||||
};
|
||||
|
||||
// Class for the client to supply callbacks for reporting various errors and information
|
||||
class MT32EMU_EXPORT ReportHandler {
|
||||
public:
|
||||
virtual ~ReportHandler() {}
|
||||
|
||||
// Callback for debug messages, in vprintf() format
|
||||
virtual void printDebug(const char *fmt, va_list list);
|
||||
// Callbacks for reporting errors
|
||||
virtual void onErrorControlROM() {}
|
||||
virtual void onErrorPCMROM() {}
|
||||
// Callback for reporting about displaying a new custom message on LCD
|
||||
virtual void showLCDMessage(const char *message);
|
||||
// Callback for reporting actual processing of a MIDI message
|
||||
virtual void onMIDIMessagePlayed() {}
|
||||
// Callback for reporting an overflow of the input MIDI queue.
|
||||
// Returns true if a recovery action was taken and yet another attempt to enqueue the MIDI event is desired.
|
||||
virtual bool onMIDIQueueOverflow() { return false; }
|
||||
// Callback invoked when a System Realtime MIDI message is detected at the input.
|
||||
virtual void onMIDISystemRealtime(Bit8u /* systemRealtime */) {}
|
||||
// Callbacks for reporting system events
|
||||
virtual void onDeviceReset() {}
|
||||
virtual void onDeviceReconfig() {}
|
||||
// Callbacks for reporting changes of reverb settings
|
||||
virtual void onNewReverbMode(Bit8u /* mode */) {}
|
||||
virtual void onNewReverbTime(Bit8u /* time */) {}
|
||||
virtual void onNewReverbLevel(Bit8u /* level */) {}
|
||||
// Callbacks for reporting various information
|
||||
virtual void onPolyStateChanged(Bit8u /* partNum */) {}
|
||||
virtual void onProgramChanged(Bit8u /* partNum */, const char * /* soundGroupName */, const char * /* patchName */) {}
|
||||
};
|
||||
|
||||
class Synth {
|
||||
friend class DefaultMidiStreamParser;
|
||||
friend class Part;
|
||||
friend class Partial;
|
||||
friend class PartialManager;
|
||||
friend class Poly;
|
||||
friend class Renderer;
|
||||
friend class RhythmPart;
|
||||
friend class SamplerateAdapter;
|
||||
friend class SoxrAdapter;
|
||||
friend class TVA;
|
||||
friend class TVP;
|
||||
|
||||
private:
|
||||
// **************************** Implementation fields **************************
|
||||
|
||||
PatchTempMemoryRegion *patchTempMemoryRegion;
|
||||
RhythmTempMemoryRegion *rhythmTempMemoryRegion;
|
||||
TimbreTempMemoryRegion *timbreTempMemoryRegion;
|
||||
PatchesMemoryRegion *patchesMemoryRegion;
|
||||
TimbresMemoryRegion *timbresMemoryRegion;
|
||||
SystemMemoryRegion *systemMemoryRegion;
|
||||
DisplayMemoryRegion *displayMemoryRegion;
|
||||
ResetMemoryRegion *resetMemoryRegion;
|
||||
|
||||
Bit8u *paddedTimbreMaxTable;
|
||||
|
||||
PCMWaveEntry *pcmWaves; // Array
|
||||
|
||||
const ControlROMFeatureSet *controlROMFeatures;
|
||||
const ControlROMMap *controlROMMap;
|
||||
Bit8u controlROMData[CONTROL_ROM_SIZE];
|
||||
Bit16s *pcmROMData;
|
||||
size_t pcmROMSize; // This is in 16-bit samples, therefore half the number of bytes in the ROM
|
||||
|
||||
Bit8u soundGroupIx[128]; // For each standard timbre
|
||||
const char (*soundGroupNames)[9]; // Array
|
||||
|
||||
Bit32u partialCount;
|
||||
Bit8u chantable[16]; // NOTE: value above 8 means that the channel is not assigned
|
||||
|
||||
MidiEventQueue *midiQueue;
|
||||
volatile Bit32u lastReceivedMIDIEventTimestamp;
|
||||
volatile Bit32u renderedSampleCount;
|
||||
|
||||
MemParams &mt32ram, &mt32default;
|
||||
|
||||
BReverbModel *reverbModels[4];
|
||||
BReverbModel *reverbModel;
|
||||
bool reverbOverridden;
|
||||
|
||||
MIDIDelayMode midiDelayMode;
|
||||
DACInputMode dacInputMode;
|
||||
|
||||
float outputGain;
|
||||
float reverbOutputGain;
|
||||
|
||||
bool reversedStereoEnabled;
|
||||
|
||||
bool opened;
|
||||
bool activated;
|
||||
|
||||
bool isDefaultReportHandler;
|
||||
ReportHandler *reportHandler;
|
||||
|
||||
PartialManager *partialManager;
|
||||
Part *parts[9];
|
||||
|
||||
// When a partial needs to be aborted to free it up for use by a new Poly,
|
||||
// the controller will busy-loop waiting for the sound to finish.
|
||||
// We emulate this by delaying new MIDI events processing until abortion finishes.
|
||||
Poly *abortingPoly;
|
||||
|
||||
Analog *analog;
|
||||
Renderer &renderer;
|
||||
|
||||
// Binary compatibility helper.
|
||||
void *reserved;
|
||||
|
||||
// **************************** Implementation methods **************************
|
||||
|
||||
Bit32u addMIDIInterfaceDelay(Bit32u len, Bit32u timestamp);
|
||||
bool isAbortingPoly() const { return abortingPoly != NULL; }
|
||||
|
||||
void readSysex(Bit8u channel, const Bit8u *sysex, Bit32u len) const;
|
||||
void initMemoryRegions();
|
||||
void deleteMemoryRegions();
|
||||
MemoryRegion *findMemoryRegion(Bit32u addr);
|
||||
void writeMemoryRegion(const MemoryRegion *region, Bit32u addr, Bit32u len, const Bit8u *data);
|
||||
void readMemoryRegion(const MemoryRegion *region, Bit32u addr, Bit32u len, Bit8u *data);
|
||||
|
||||
bool loadControlROM(const ROMImage &controlROMImage);
|
||||
bool loadPCMROM(const ROMImage &pcmROMImage);
|
||||
|
||||
bool initPCMList(Bit16u mapAddress, Bit16u count);
|
||||
bool initTimbres(Bit16u mapAddress, Bit16u offset, Bit16u timbreCount, Bit16u startTimbre, bool compressed);
|
||||
bool initCompressedTimbre(Bit16u drumNum, const Bit8u *mem, Bit32u memLen);
|
||||
void initReverbModels(bool mt32CompatibleMode);
|
||||
void initSoundGroups(char newSoundGroupNames[][9]);
|
||||
|
||||
void refreshSystemMasterTune();
|
||||
void refreshSystemReverbParameters();
|
||||
void refreshSystemReserveSettings();
|
||||
void refreshSystemChanAssign(Bit8u firstPart, Bit8u lastPart);
|
||||
void refreshSystemMasterVol();
|
||||
void refreshSystem();
|
||||
void reset();
|
||||
void dispose();
|
||||
|
||||
void printPartialUsage(Bit32u sampleOffset = 0);
|
||||
|
||||
void newTimbreSet(Bit8u partNum, Bit8u timbreGroup, Bit8u timbreNumber, const char patchName[]);
|
||||
void printDebug(const char *fmt, ...);
|
||||
|
||||
// partNum should be 0..7 for Part 1..8, or 8 for Rhythm
|
||||
const Part *getPart(Bit8u partNum) const;
|
||||
|
||||
public:
|
||||
static inline Bit16s clipSampleEx(Bit32s sampleEx) {
|
||||
// Clamp values above 32767 to 32767, and values below -32768 to -32768
|
||||
// FIXME: Do we really need this stuff? I think these branches are very well predicted. Instead, this introduces a chain.
|
||||
// The version below is actually a bit faster on my system...
|
||||
//return ((sampleEx + 0x8000) & ~0xFFFF) ? Bit16s((sampleEx >> 31) ^ 0x7FFF) : (Bit16s)sampleEx;
|
||||
return ((-0x8000 <= sampleEx) && (sampleEx <= 0x7FFF)) ? Bit16s(sampleEx) : Bit16s((sampleEx >> 31) ^ 0x7FFF);
|
||||
}
|
||||
|
||||
static inline float clipSampleEx(float sampleEx) {
|
||||
return sampleEx;
|
||||
}
|
||||
|
||||
template <class S>
|
||||
static inline void muteSampleBuffer(S *buffer, Bit32u len) {
|
||||
if (buffer == NULL) return;
|
||||
memset(buffer, 0, len * sizeof(S));
|
||||
}
|
||||
|
||||
static inline void muteSampleBuffer(float *buffer, Bit32u len) {
|
||||
if (buffer == NULL) return;
|
||||
// FIXME: Use memset() where compatibility is guaranteed (if this turns out to be a win)
|
||||
while (len--) {
|
||||
*(buffer++) = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
static inline Bit16s convertSample(float sample) {
|
||||
return Synth::clipSampleEx(Bit32s(sample * 16384.0f)); // This multiplier takes into account the DAC bit shift
|
||||
}
|
||||
|
||||
static inline float convertSample(Bit16s sample) {
|
||||
return float(sample) / 16384.0f; // This multiplier takes into account the DAC bit shift
|
||||
}
|
||||
|
||||
// Returns library version as an integer in format: 0x00MMmmpp, where:
|
||||
// MM - major version number
|
||||
// mm - minor version number
|
||||
// pp - patch number
|
||||
MT32EMU_EXPORT static Bit32u getLibraryVersionInt();
|
||||
// Returns library version as a C-string in format: "MAJOR.MINOR.PATCH"
|
||||
MT32EMU_EXPORT static const char *getLibraryVersionString();
|
||||
|
||||
MT32EMU_EXPORT static Bit32u getShortMessageLength(Bit32u msg);
|
||||
MT32EMU_EXPORT static Bit8u calcSysexChecksum(const Bit8u *data, const Bit32u len, const Bit8u initChecksum = 0);
|
||||
|
||||
// Returns output sample rate used in emulation of stereo analog circuitry of hardware units.
|
||||
// See comment for AnalogOutputMode.
|
||||
MT32EMU_EXPORT static Bit32u getStereoOutputSampleRate(AnalogOutputMode analogOutputMode);
|
||||
|
||||
// Optionally sets callbacks for reporting various errors, information and debug messages
|
||||
MT32EMU_EXPORT explicit Synth(ReportHandler *useReportHandler = NULL);
|
||||
MT32EMU_EXPORT ~Synth();
|
||||
|
||||
// Used to initialise the MT-32. Must be called before any other function.
|
||||
// Returns true if initialization was sucessful, otherwise returns false.
|
||||
// controlROMImage and pcmROMImage represent Control and PCM ROM images for use by synth.
|
||||
// usePartialCount sets the maximum number of partials playing simultaneously for this session (optional).
|
||||
// analogOutputMode sets the mode for emulation of analogue circuitry of the hardware units (optional).
|
||||
MT32EMU_EXPORT bool open(const ROMImage &controlROMImage, const ROMImage &pcmROMImage, Bit32u usePartialCount = DEFAULT_MAX_PARTIALS, AnalogOutputMode analogOutputMode = AnalogOutputMode_COARSE);
|
||||
|
||||
// Overloaded method which opens the synth with default partial count.
|
||||
MT32EMU_EXPORT bool open(const ROMImage &controlROMImage, const ROMImage &pcmROMImage, AnalogOutputMode analogOutputMode);
|
||||
|
||||
// Closes the MT-32 and deallocates any memory used by the synthesizer
|
||||
MT32EMU_EXPORT void close();
|
||||
|
||||
// Returns true if the synth is in completely initialized state, otherwise returns false.
|
||||
MT32EMU_EXPORT bool isOpen() const;
|
||||
|
||||
// All the enqueued events are processed by the synth immediately.
|
||||
MT32EMU_EXPORT void flushMIDIQueue();
|
||||
|
||||
// Sets size of the internal MIDI event queue. The queue size is set to the minimum power of 2 that is greater or equal to the size specified.
|
||||
// The queue is flushed before reallocation.
|
||||
// Returns the actual queue size being used.
|
||||
MT32EMU_EXPORT Bit32u setMIDIEventQueueSize(Bit32u);
|
||||
|
||||
// Enqueues a MIDI event for subsequent playback.
|
||||
// The MIDI event will be processed not before the specified timestamp.
|
||||
// The timestamp is measured as the global rendered sample count since the synth was created (at the native sample rate 32000 Hz).
|
||||
// The minimum delay involves emulation of the delay introduced while the event is transferred via MIDI interface
|
||||
// and emulation of the MCU busy-loop while it frees partials for use by a new Poly.
|
||||
// Calls from multiple threads must be synchronised, although, no synchronisation is required with the rendering thread.
|
||||
// The methods return false if the MIDI event queue is full and the message cannot be enqueued.
|
||||
|
||||
// Enqueues a single short MIDI message to play at specified time. The message must contain a status byte.
|
||||
MT32EMU_EXPORT bool playMsg(Bit32u msg, Bit32u timestamp);
|
||||
// Enqueues a single well formed System Exclusive MIDI message to play at specified time.
|
||||
MT32EMU_EXPORT bool playSysex(const Bit8u *sysex, Bit32u len, Bit32u timestamp);
|
||||
|
||||
// Enqueues a single short MIDI message to be processed ASAP. The message must contain a status byte.
|
||||
MT32EMU_EXPORT bool playMsg(Bit32u msg);
|
||||
// Enqueues a single well formed System Exclusive MIDI message to be processed ASAP.
|
||||
MT32EMU_EXPORT bool playSysex(const Bit8u *sysex, Bit32u len);
|
||||
|
||||
// WARNING:
|
||||
// The methods below don't ensure minimum 1-sample delay between sequential MIDI events,
|
||||
// and a sequence of NoteOn and immediately succeeding NoteOff messages is always silent.
|
||||
// A thread that invokes these methods must be explicitly synchronised with the thread performing sample rendering.
|
||||
|
||||
// Sends a short MIDI message to the synth for immediate playback. The message must contain a status byte.
|
||||
// See the WARNING above.
|
||||
MT32EMU_EXPORT void playMsgNow(Bit32u msg);
|
||||
// Sends unpacked short MIDI message to the synth for immediate playback. The message must contain a status byte.
|
||||
// See the WARNING above.
|
||||
MT32EMU_EXPORT void playMsgOnPart(Bit8u part, Bit8u code, Bit8u note, Bit8u velocity);
|
||||
|
||||
// Sends a single well formed System Exclusive MIDI message for immediate processing. The length is in bytes.
|
||||
// See the WARNING above.
|
||||
MT32EMU_EXPORT void playSysexNow(const Bit8u *sysex, Bit32u len);
|
||||
// Sends inner body of a System Exclusive MIDI message for direct processing. The length is in bytes.
|
||||
// See the WARNING above.
|
||||
MT32EMU_EXPORT void playSysexWithoutFraming(const Bit8u *sysex, Bit32u len);
|
||||
// Sends inner body of a System Exclusive MIDI message for direct processing. The length is in bytes.
|
||||
// See the WARNING above.
|
||||
MT32EMU_EXPORT void playSysexWithoutHeader(Bit8u device, Bit8u command, const Bit8u *sysex, Bit32u len);
|
||||
// Sends inner body of a System Exclusive MIDI message for direct processing. The length is in bytes.
|
||||
// See the WARNING above.
|
||||
MT32EMU_EXPORT void writeSysex(Bit8u channel, const Bit8u *sysex, Bit32u len);
|
||||
|
||||
// Allows to disable wet reverb output altogether.
|
||||
MT32EMU_EXPORT void setReverbEnabled(bool reverbEnabled);
|
||||
// Returns whether wet reverb output is enabled.
|
||||
MT32EMU_EXPORT bool isReverbEnabled() const;
|
||||
// Sets override reverb mode. In this mode, emulation ignores sysexes (or the related part of them) which control the reverb parameters.
|
||||
// This mode is in effect until it is turned off. When the synth is re-opened, the override mode is unchanged but the state
|
||||
// of the reverb model is reset to default.
|
||||
MT32EMU_EXPORT void setReverbOverridden(bool reverbOverridden);
|
||||
// Returns whether reverb settings are overridden.
|
||||
MT32EMU_EXPORT bool isReverbOverridden() const;
|
||||
// Forces reverb model compatibility mode. By default, the compatibility mode corresponds to the used control ROM version.
|
||||
// Invoking this method with the argument set to true forces emulation of old MT-32 reverb circuit.
|
||||
// When the argument is false, emulation of the reverb circuit used in new generation of MT-32 compatible modules is enforced
|
||||
// (these include CM-32L and LAPC-I).
|
||||
MT32EMU_EXPORT void setReverbCompatibilityMode(bool mt32CompatibleMode);
|
||||
// Returns whether reverb is in old MT-32 compatibility mode.
|
||||
MT32EMU_EXPORT bool isMT32ReverbCompatibilityMode() const;
|
||||
// Returns whether default reverb compatibility mode is the old MT-32 compatibility mode.
|
||||
MT32EMU_EXPORT bool isDefaultReverbMT32Compatible() const;
|
||||
// Sets new DAC input mode. See DACInputMode for details.
|
||||
MT32EMU_EXPORT void setDACInputMode(DACInputMode mode);
|
||||
// Returns current DAC input mode. See DACInputMode for details.
|
||||
MT32EMU_EXPORT DACInputMode getDACInputMode() const;
|
||||
// Sets new MIDI delay mode. See MIDIDelayMode for details.
|
||||
MT32EMU_EXPORT void setMIDIDelayMode(MIDIDelayMode mode);
|
||||
// Returns current MIDI delay mode. See MIDIDelayMode for details.
|
||||
MT32EMU_EXPORT MIDIDelayMode getMIDIDelayMode() const;
|
||||
|
||||
// Sets output gain factor for synth output channels. Applied to all output samples and unrelated with the synth's Master volume,
|
||||
// it rather corresponds to the gain of the output analog circuitry of the hardware units. However, together with setReverbOutputGain()
|
||||
// it offers to the user a capability to control the gain of reverb and non-reverb output channels independently.
|
||||
// Ignored in DACInputMode_PURE
|
||||
MT32EMU_EXPORT void setOutputGain(float gain);
|
||||
// Returns current output gain factor for synth output channels.
|
||||
MT32EMU_EXPORT float getOutputGain() const;
|
||||
|
||||
// Sets output gain factor for the reverb wet output channels. It rather corresponds to the gain of the output
|
||||
// analog circuitry of the hardware units. However, together with setOutputGain() it offers to the user a capability
|
||||
// to control the gain of reverb and non-reverb output channels independently.
|
||||
//
|
||||
// Note: We're currently emulate CM-32L/CM-64 reverb quite accurately and the reverb output level closely
|
||||
// corresponds to the level of digital capture. Although, according to the CM-64 PCB schematic,
|
||||
// there is a difference in the reverb analogue circuit, and the resulting output gain is 0.68
|
||||
// of that for LA32 analogue output. This factor is applied to the reverb output gain.
|
||||
// Ignored in DACInputMode_PURE
|
||||
MT32EMU_EXPORT void setReverbOutputGain(float gain);
|
||||
// Returns current output gain factor for reverb wet output channels.
|
||||
MT32EMU_EXPORT float getReverbOutputGain() const;
|
||||
|
||||
// Swaps left and right output channels.
|
||||
MT32EMU_EXPORT void setReversedStereoEnabled(bool enabled);
|
||||
// Returns whether left and right output channels are swapped.
|
||||
MT32EMU_EXPORT bool isReversedStereoEnabled() const;
|
||||
|
||||
// Returns actual sample rate used in emulation of stereo analog circuitry of hardware units.
|
||||
// See comment for render() below.
|
||||
MT32EMU_EXPORT Bit32u getStereoOutputSampleRate() const;
|
||||
|
||||
// Renders samples to the specified output stream as if they were sampled at the analog stereo output.
|
||||
// When AnalogOutputMode is set to ACCURATE (OVERSAMPLED), the output signal is upsampled to 48 (96) kHz in order
|
||||
// to retain emulation accuracy in whole audible frequency spectra. Otherwise, native digital signal sample rate is retained.
|
||||
// getStereoOutputSampleRate() can be used to query actual sample rate of the output signal.
|
||||
// The length is in frames, not bytes (in 16-bit stereo, one frame is 4 bytes). Uses NATIVE byte ordering.
|
||||
MT32EMU_EXPORT void render(Bit16s *stream, Bit32u len);
|
||||
// Same as above but outputs to a float stereo stream.
|
||||
MT32EMU_EXPORT void render(float *stream, Bit32u len);
|
||||
|
||||
// Renders samples to the specified output streams as if they appeared at the DAC entrance.
|
||||
// No further processing performed in analog circuitry emulation is applied to the signal.
|
||||
// NULL may be specified in place of any or all of the stream buffers to skip it.
|
||||
// The length is in samples, not bytes. Uses NATIVE byte ordering.
|
||||
MT32EMU_EXPORT void renderStreams(Bit16s *nonReverbLeft, Bit16s *nonReverbRight, Bit16s *reverbDryLeft, Bit16s *reverbDryRight, Bit16s *reverbWetLeft, Bit16s *reverbWetRight, Bit32u len);
|
||||
void renderStreams(const DACOutputStreams<Bit16s> &streams, Bit32u len) {
|
||||
renderStreams(streams.nonReverbLeft, streams.nonReverbRight, streams.reverbDryLeft, streams.reverbDryRight, streams.reverbWetLeft, streams.reverbWetRight, len);
|
||||
}
|
||||
// Same as above but outputs to float streams.
|
||||
MT32EMU_EXPORT void renderStreams(float *nonReverbLeft, float *nonReverbRight, float *reverbDryLeft, float *reverbDryRight, float *reverbWetLeft, float *reverbWetRight, Bit32u len);
|
||||
void renderStreams(const DACOutputStreams<float> &streams, Bit32u len) {
|
||||
renderStreams(streams.nonReverbLeft, streams.nonReverbRight, streams.reverbDryLeft, streams.reverbDryRight, streams.reverbWetLeft, streams.reverbWetRight, len);
|
||||
}
|
||||
|
||||
// Returns true when there is at least one active partial, otherwise false.
|
||||
MT32EMU_EXPORT bool hasActivePartials() const;
|
||||
|
||||
// Returns true if the synth is active and subsequent calls to render() may result in non-trivial output (i.e. silence).
|
||||
// The synth is considered active when either there are pending MIDI events in the queue, there is at least one active partial,
|
||||
// or the reverb is (somewhat unreliably) detected as being active.
|
||||
MT32EMU_EXPORT bool isActive();
|
||||
|
||||
// Returns the maximum number of partials playing simultaneously.
|
||||
MT32EMU_EXPORT Bit32u getPartialCount() const;
|
||||
|
||||
// Fills in current states of all the parts into the array provided. The array must have at least 9 entries to fit values for all the parts.
|
||||
// If the value returned for a part is true, there is at least one active non-releasing partial playing on this part.
|
||||
// This info is useful in emulating behaviour of LCD display of the hardware units.
|
||||
MT32EMU_EXPORT void getPartStates(bool *partStates) const;
|
||||
|
||||
// Returns current states of all the parts as a bit set. The least significant bit corresponds to the state of part 1,
|
||||
// total of 9 bits hold the states of all the parts. If the returned bit for a part is set, there is at least one active
|
||||
// non-releasing partial playing on this part. This info is useful in emulating behaviour of LCD display of the hardware units.
|
||||
MT32EMU_EXPORT Bit32u getPartStates() const;
|
||||
|
||||
// Fills in current states of all the partials into the array provided. The array must be large enough to accommodate states of all the partials.
|
||||
MT32EMU_EXPORT void getPartialStates(PartialState *partialStates) const;
|
||||
|
||||
// Fills in current states of all the partials into the array provided. Each byte in the array holds states of 4 partials
|
||||
// starting from the least significant bits. The state of each partial is packed in a pair of bits.
|
||||
// The array must be large enough to accommodate states of all the partials (see getPartialCount()).
|
||||
MT32EMU_EXPORT void getPartialStates(Bit8u *partialStates) const;
|
||||
|
||||
// Fills in information about currently playing notes on the specified part into the arrays provided. The arrays must be large enough
|
||||
// to accommodate data for all the playing notes. The maximum number of simultaneously playing notes cannot exceed the number of partials.
|
||||
// Argument partNumber should be 0..7 for Part 1..8, or 8 for Rhythm.
|
||||
// Returns the number of currently playing notes on the specified part.
|
||||
MT32EMU_EXPORT Bit32u getPlayingNotes(Bit8u partNumber, Bit8u *keys, Bit8u *velocities) const;
|
||||
|
||||
// Returns name of the patch set on the specified part.
|
||||
// Argument partNumber should be 0..7 for Part 1..8, or 8 for Rhythm.
|
||||
MT32EMU_EXPORT const char *getPatchName(Bit8u partNumber) const;
|
||||
|
||||
// Stores internal state of emulated synth into an array provided (as it would be acquired from hardware).
|
||||
MT32EMU_EXPORT void readMemory(Bit32u addr, Bit32u len, Bit8u *data);
|
||||
}; // class Synth
|
||||
|
||||
} // namespace MT32Emu
|
||||
|
||||
#endif // #ifndef MT32EMU_SYNTH_H
|
@ -1,370 +0,0 @@
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This class emulates the calculations performed by the 8095 microcontroller in order to configure the LA-32's amplitude ramp for a single partial at each stage of its TVA envelope.
|
||||
* Unless we introduced bugs, it should be pretty much 100% accurate according to Mok's specifications.
|
||||
*/
|
||||
|
||||
#include "internals.h"
|
||||
|
||||
#include "TVA.h"
|
||||
#include "Part.h"
|
||||
#include "Partial.h"
|
||||
#include "Poly.h"
|
||||
#include "Synth.h"
|
||||
#include "Tables.h"
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
// CONFIRMED: Matches a table in ROM - haven't got around to coming up with a formula for it yet.
|
||||
static Bit8u biasLevelToAmpSubtractionCoeff[13] = {255, 187, 137, 100, 74, 54, 40, 29, 21, 15, 10, 5, 0};
|
||||
|
||||
TVA::TVA(const Partial *usePartial, LA32Ramp *useAmpRamp) :
|
||||
partial(usePartial), ampRamp(useAmpRamp), system(&usePartial->getSynth()->mt32ram.system), phase(TVA_PHASE_DEAD) {
|
||||
}
|
||||
|
||||
void TVA::startRamp(Bit8u newTarget, Bit8u newIncrement, int newPhase) {
|
||||
target = newTarget;
|
||||
phase = newPhase;
|
||||
ampRamp->startRamp(newTarget, newIncrement);
|
||||
#if MT32EMU_MONITOR_TVA >= 1
|
||||
partial->getSynth()->printDebug("[+%lu] [Partial %d] TVA,ramp,%d,%d,%d,%d", partial->debugGetSampleNum(), partial->debugGetPartialNum(), (newIncrement & 0x80) ? -1 : 1, (newIncrement & 0x7F), newPhase);
|
||||
#endif
|
||||
}
|
||||
|
||||
void TVA::end(int newPhase) {
|
||||
phase = newPhase;
|
||||
playing = false;
|
||||
#if MT32EMU_MONITOR_TVA >= 1
|
||||
partial->getSynth()->printDebug("[+%lu] [Partial %d] TVA,end,%d", partial->debugGetSampleNum(), partial->debugGetPartialNum(), newPhase);
|
||||
#endif
|
||||
}
|
||||
|
||||
static int multBias(Bit8u biasLevel, int bias) {
|
||||
return (bias * biasLevelToAmpSubtractionCoeff[biasLevel]) >> 5;
|
||||
}
|
||||
|
||||
static int calcBiasAmpSubtraction(Bit8u biasPoint, Bit8u biasLevel, int key) {
|
||||
if ((biasPoint & 0x40) == 0) {
|
||||
int bias = biasPoint + 33 - key;
|
||||
if (bias > 0) {
|
||||
return multBias(biasLevel, bias);
|
||||
}
|
||||
} else {
|
||||
int bias = biasPoint - 31 - key;
|
||||
if (bias < 0) {
|
||||
bias = -bias;
|
||||
return multBias(biasLevel, bias);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int calcBiasAmpSubtractions(const TimbreParam::PartialParam *partialParam, int key) {
|
||||
int biasAmpSubtraction1 = calcBiasAmpSubtraction(partialParam->tva.biasPoint1, partialParam->tva.biasLevel1, key);
|
||||
if (biasAmpSubtraction1 > 255) {
|
||||
return 255;
|
||||
}
|
||||
int biasAmpSubtraction2 = calcBiasAmpSubtraction(partialParam->tva.biasPoint2, partialParam->tva.biasLevel2, key);
|
||||
if (biasAmpSubtraction2 > 255) {
|
||||
return 255;
|
||||
}
|
||||
int biasAmpSubtraction = biasAmpSubtraction1 + biasAmpSubtraction2;
|
||||
if (biasAmpSubtraction > 255) {
|
||||
return 255;
|
||||
}
|
||||
return biasAmpSubtraction;
|
||||
}
|
||||
|
||||
static int calcVeloAmpSubtraction(Bit8u veloSensitivity, unsigned int velocity) {
|
||||
// FIXME:KG: Better variable names
|
||||
int velocityMult = veloSensitivity - 50;
|
||||
int absVelocityMult = velocityMult < 0 ? -velocityMult : velocityMult;
|
||||
velocityMult = signed(unsigned(velocityMult * (signed(velocity) - 64)) << 2);
|
||||
return absVelocityMult - (velocityMult >> 8); // PORTABILITY NOTE: Assumes arithmetic shift
|
||||
}
|
||||
|
||||
static int calcBasicAmp(const Tables *tables, const Partial *partial, const MemParams::System *system, const TimbreParam::PartialParam *partialParam, const MemParams::PatchTemp *patchTemp, const MemParams::RhythmTemp *rhythmTemp, int biasAmpSubtraction, int veloAmpSubtraction, Bit8u expression) {
|
||||
int amp = 155;
|
||||
|
||||
if (!partial->isRingModulatingSlave()) {
|
||||
amp -= tables->masterVolToAmpSubtraction[system->masterVol];
|
||||
if (amp < 0) {
|
||||
return 0;
|
||||
}
|
||||
amp -= tables->levelToAmpSubtraction[patchTemp->outputLevel];
|
||||
if (amp < 0) {
|
||||
return 0;
|
||||
}
|
||||
amp -= tables->levelToAmpSubtraction[expression];
|
||||
if (amp < 0) {
|
||||
return 0;
|
||||
}
|
||||
if (rhythmTemp != NULL) {
|
||||
amp -= tables->levelToAmpSubtraction[rhythmTemp->outputLevel];
|
||||
if (amp < 0) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
amp -= biasAmpSubtraction;
|
||||
if (amp < 0) {
|
||||
return 0;
|
||||
}
|
||||
amp -= tables->levelToAmpSubtraction[partialParam->tva.level];
|
||||
if (amp < 0) {
|
||||
return 0;
|
||||
}
|
||||
amp -= veloAmpSubtraction;
|
||||
if (amp < 0) {
|
||||
return 0;
|
||||
}
|
||||
if (amp > 155) {
|
||||
amp = 155;
|
||||
}
|
||||
amp -= partialParam->tvf.resonance >> 1;
|
||||
if (amp < 0) {
|
||||
return 0;
|
||||
}
|
||||
return amp;
|
||||
}
|
||||
|
||||
static int calcKeyTimeSubtraction(Bit8u envTimeKeyfollow, int key) {
|
||||
if (envTimeKeyfollow == 0) {
|
||||
return 0;
|
||||
}
|
||||
return (key - 60) >> (5 - envTimeKeyfollow); // PORTABILITY NOTE: Assumes arithmetic shift
|
||||
}
|
||||
|
||||
void TVA::reset(const Part *newPart, const TimbreParam::PartialParam *newPartialParam, const MemParams::RhythmTemp *newRhythmTemp) {
|
||||
part = newPart;
|
||||
partialParam = newPartialParam;
|
||||
patchTemp = newPart->getPatchTemp();
|
||||
rhythmTemp = newRhythmTemp;
|
||||
|
||||
playing = true;
|
||||
|
||||
const Tables *tables = &Tables::getInstance();
|
||||
|
||||
int key = partial->getPoly()->getKey();
|
||||
int velocity = partial->getPoly()->getVelocity();
|
||||
|
||||
keyTimeSubtraction = calcKeyTimeSubtraction(partialParam->tva.envTimeKeyfollow, key);
|
||||
|
||||
biasAmpSubtraction = calcBiasAmpSubtractions(partialParam, key);
|
||||
veloAmpSubtraction = calcVeloAmpSubtraction(partialParam->tva.veloSensitivity, velocity);
|
||||
|
||||
int newTarget = calcBasicAmp(tables, partial, system, partialParam, patchTemp, newRhythmTemp, biasAmpSubtraction, veloAmpSubtraction, part->getExpression());
|
||||
int newPhase;
|
||||
if (partialParam->tva.envTime[0] == 0) {
|
||||
// Initially go to the TVA_PHASE_ATTACK target amp, and spend the next phase going from there to the TVA_PHASE_2 target amp
|
||||
// Note that this means that velocity never affects time for this partial.
|
||||
newTarget += partialParam->tva.envLevel[0];
|
||||
newPhase = TVA_PHASE_ATTACK; // The first target used in nextPhase() will be TVA_PHASE_2
|
||||
} else {
|
||||
// Initially go to the base amp determined by TVA level, part volume, etc., and spend the next phase going from there to the full TVA_PHASE_ATTACK target amp.
|
||||
newPhase = TVA_PHASE_BASIC; // The first target used in nextPhase() will be TVA_PHASE_ATTACK
|
||||
}
|
||||
|
||||
ampRamp->reset();//currentAmp = 0;
|
||||
|
||||
// "Go downward as quickly as possible".
|
||||
// Since the current value is 0, the LA32Ramp will notice that we're already at or below the target and trying to go downward,
|
||||
// and therefore jump to the target immediately and raise an interrupt.
|
||||
startRamp(Bit8u(newTarget), 0x80 | 127, newPhase);
|
||||
}
|
||||
|
||||
void TVA::startAbort() {
|
||||
startRamp(64, 0x80 | 127, TVA_PHASE_RELEASE);
|
||||
}
|
||||
|
||||
void TVA::startDecay() {
|
||||
if (phase >= TVA_PHASE_RELEASE) {
|
||||
return;
|
||||
}
|
||||
Bit8u newIncrement;
|
||||
if (partialParam->tva.envTime[4] == 0) {
|
||||
newIncrement = 1;
|
||||
} else {
|
||||
newIncrement = -partialParam->tva.envTime[4];
|
||||
}
|
||||
// The next time nextPhase() is called, it will think TVA_PHASE_RELEASE has finished and the partial will be aborted
|
||||
startRamp(0, newIncrement, TVA_PHASE_RELEASE);
|
||||
}
|
||||
|
||||
void TVA::handleInterrupt() {
|
||||
nextPhase();
|
||||
}
|
||||
|
||||
void TVA::recalcSustain() {
|
||||
// We get pinged periodically by the pitch code to recalculate our values when in sustain.
|
||||
// This is done so that the TVA will respond to things like MIDI expression and volume changes while it's sustaining, which it otherwise wouldn't do.
|
||||
|
||||
// The check for envLevel[3] == 0 strikes me as slightly dumb. FIXME: Explain why
|
||||
if (phase != TVA_PHASE_SUSTAIN || partialParam->tva.envLevel[3] == 0) {
|
||||
return;
|
||||
}
|
||||
// We're sustaining. Recalculate all the values
|
||||
const Tables *tables = &Tables::getInstance();
|
||||
int newTarget = calcBasicAmp(tables, partial, system, partialParam, patchTemp, rhythmTemp, biasAmpSubtraction, veloAmpSubtraction, part->getExpression());
|
||||
newTarget += partialParam->tva.envLevel[3];
|
||||
// Since we're in TVA_PHASE_SUSTAIN at this point, we know that target has been reached and an interrupt fired, so we can rely on it being the current amp.
|
||||
int targetDelta = newTarget - target;
|
||||
|
||||
// Calculate an increment to get to the new amp value in a short, more or less consistent amount of time
|
||||
Bit8u newIncrement;
|
||||
if (targetDelta >= 0) {
|
||||
newIncrement = tables->envLogarithmicTime[Bit8u(targetDelta)] - 2;
|
||||
} else {
|
||||
newIncrement = (tables->envLogarithmicTime[Bit8u(-targetDelta)] - 2) | 0x80;
|
||||
}
|
||||
// Configure so that once the transition's complete and nextPhase() is called, we'll just re-enter sustain phase (or decay phase, depending on parameters at the time).
|
||||
startRamp(newTarget, newIncrement, TVA_PHASE_SUSTAIN - 1);
|
||||
}
|
||||
|
||||
bool TVA::isPlaying() const {
|
||||
return playing;
|
||||
}
|
||||
|
||||
int TVA::getPhase() const {
|
||||
return phase;
|
||||
}
|
||||
|
||||
void TVA::nextPhase() {
|
||||
const Tables *tables = &Tables::getInstance();
|
||||
|
||||
if (phase >= TVA_PHASE_DEAD || !playing) {
|
||||
partial->getSynth()->printDebug("TVA::nextPhase(): Shouldn't have got here with phase %d, playing=%s", phase, playing ? "true" : "false");
|
||||
return;
|
||||
}
|
||||
int newPhase = phase + 1;
|
||||
|
||||
if (newPhase == TVA_PHASE_DEAD) {
|
||||
end(newPhase);
|
||||
return;
|
||||
}
|
||||
|
||||
bool allLevelsZeroFromNowOn = false;
|
||||
if (partialParam->tva.envLevel[3] == 0) {
|
||||
if (newPhase == TVA_PHASE_4) {
|
||||
allLevelsZeroFromNowOn = true;
|
||||
} else if (partialParam->tva.envLevel[2] == 0) {
|
||||
if (newPhase == TVA_PHASE_3) {
|
||||
allLevelsZeroFromNowOn = true;
|
||||
} else if (partialParam->tva.envLevel[1] == 0) {
|
||||
if (newPhase == TVA_PHASE_2) {
|
||||
allLevelsZeroFromNowOn = true;
|
||||
} else if (partialParam->tva.envLevel[0] == 0) {
|
||||
if (newPhase == TVA_PHASE_ATTACK) { // this line added, missing in ROM - FIXME: Add description of repercussions
|
||||
allLevelsZeroFromNowOn = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int newTarget;
|
||||
int newIncrement = 0; // Initialised to please compilers
|
||||
int envPointIndex = phase;
|
||||
|
||||
if (!allLevelsZeroFromNowOn) {
|
||||
newTarget = calcBasicAmp(tables, partial, system, partialParam, patchTemp, rhythmTemp, biasAmpSubtraction, veloAmpSubtraction, part->getExpression());
|
||||
|
||||
if (newPhase == TVA_PHASE_SUSTAIN || newPhase == TVA_PHASE_RELEASE) {
|
||||
if (partialParam->tva.envLevel[3] == 0) {
|
||||
end(newPhase);
|
||||
return;
|
||||
}
|
||||
if (!partial->getPoly()->canSustain()) {
|
||||
newPhase = TVA_PHASE_RELEASE;
|
||||
newTarget = 0;
|
||||
newIncrement = -partialParam->tva.envTime[4];
|
||||
if (newIncrement == 0) {
|
||||
// We can't let the increment be 0, or there would be no emulated interrupt.
|
||||
// So we do an "upward" increment, which should set the amp to 0 extremely quickly
|
||||
// and cause an "interrupt" to bring us back to nextPhase().
|
||||
newIncrement = 1;
|
||||
}
|
||||
} else {
|
||||
newTarget += partialParam->tva.envLevel[3];
|
||||
newIncrement = 0;
|
||||
}
|
||||
} else {
|
||||
newTarget += partialParam->tva.envLevel[envPointIndex];
|
||||
}
|
||||
} else {
|
||||
newTarget = 0;
|
||||
}
|
||||
|
||||
if ((newPhase != TVA_PHASE_SUSTAIN && newPhase != TVA_PHASE_RELEASE) || allLevelsZeroFromNowOn) {
|
||||
int envTimeSetting = partialParam->tva.envTime[envPointIndex];
|
||||
|
||||
if (newPhase == TVA_PHASE_ATTACK) {
|
||||
envTimeSetting -= (signed(partial->getPoly()->getVelocity()) - 64) >> (6 - partialParam->tva.envTimeVeloSensitivity); // PORTABILITY NOTE: Assumes arithmetic shift
|
||||
|
||||
if (envTimeSetting <= 0 && partialParam->tva.envTime[envPointIndex] != 0) {
|
||||
envTimeSetting = 1;
|
||||
}
|
||||
} else {
|
||||
envTimeSetting -= keyTimeSubtraction;
|
||||
}
|
||||
if (envTimeSetting > 0) {
|
||||
int targetDelta = newTarget - target;
|
||||
if (targetDelta <= 0) {
|
||||
if (targetDelta == 0) {
|
||||
// target and newTarget are the same.
|
||||
// We can't have an increment of 0 or we wouldn't get an emulated interrupt.
|
||||
// So instead make the target one less than it really should be and set targetDelta accordingly.
|
||||
targetDelta = -1;
|
||||
newTarget--;
|
||||
if (newTarget < 0) {
|
||||
// Oops, newTarget is less than zero now, so let's do it the other way:
|
||||
// Make newTarget one more than it really should've been and set targetDelta accordingly.
|
||||
// FIXME (apparent bug in real firmware):
|
||||
// This means targetDelta will be positive just below here where it's inverted, and we'll end up using envLogarithmicTime[-1], and we'll be setting newIncrement to be descending later on, etc..
|
||||
targetDelta = 1;
|
||||
newTarget = -newTarget;
|
||||
}
|
||||
}
|
||||
targetDelta = -targetDelta;
|
||||
newIncrement = tables->envLogarithmicTime[Bit8u(targetDelta)] - envTimeSetting;
|
||||
if (newIncrement <= 0) {
|
||||
newIncrement = 1;
|
||||
}
|
||||
newIncrement = newIncrement | 0x80;
|
||||
} else {
|
||||
// FIXME: The last 22 or so entries in this table are 128 - surely that fucks things up, since that ends up being -128 signed?
|
||||
newIncrement = tables->envLogarithmicTime[Bit8u(targetDelta)] - envTimeSetting;
|
||||
if (newIncrement <= 0) {
|
||||
newIncrement = 1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
newIncrement = newTarget >= target ? (0x80 | 127) : 127;
|
||||
}
|
||||
|
||||
// FIXME: What's the point of this? It's checked or set to non-zero everywhere above
|
||||
if (newIncrement == 0) {
|
||||
newIncrement = 1;
|
||||
}
|
||||
}
|
||||
|
||||
startRamp(Bit8u(newTarget), Bit8u(newIncrement), newPhase);
|
||||
}
|
||||
|
||||
} // namespace MT32Emu
|
@ -1,100 +0,0 @@
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MT32EMU_TVA_H
|
||||
#define MT32EMU_TVA_H
|
||||
|
||||
#include "globals.h"
|
||||
#include "Types.h"
|
||||
#include "Structures.h"
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
class LA32Ramp;
|
||||
class Part;
|
||||
class Partial;
|
||||
|
||||
// Note that when entering nextPhase(), newPhase is set to phase + 1, and the descriptions/names below refer to
|
||||
// newPhase's value.
|
||||
enum {
|
||||
// In this phase, the base amp (as calculated in calcBasicAmp()) is targeted with an instant time.
|
||||
// This phase is entered by reset() only if time[0] != 0.
|
||||
TVA_PHASE_BASIC = 0,
|
||||
|
||||
// In this phase, level[0] is targeted within time[0], and velocity potentially affects time
|
||||
TVA_PHASE_ATTACK = 1,
|
||||
|
||||
// In this phase, level[1] is targeted within time[1]
|
||||
TVA_PHASE_2 = 2,
|
||||
|
||||
// In this phase, level[2] is targeted within time[2]
|
||||
TVA_PHASE_3 = 3,
|
||||
|
||||
// In this phase, level[3] is targeted within time[3]
|
||||
TVA_PHASE_4 = 4,
|
||||
|
||||
// In this phase, immediately goes to PHASE_RELEASE unless the poly is set to sustain.
|
||||
// Aborts the partial if level[3] is 0.
|
||||
// Otherwise level[3] is continued, no phase change will occur until some external influence (like pedal release)
|
||||
TVA_PHASE_SUSTAIN = 5,
|
||||
|
||||
// In this phase, 0 is targeted within time[4] (the time calculation is quite different from the other phases)
|
||||
TVA_PHASE_RELEASE = 6,
|
||||
|
||||
// It's PHASE_DEAD, Jim.
|
||||
TVA_PHASE_DEAD = 7
|
||||
};
|
||||
|
||||
class TVA {
|
||||
private:
|
||||
const Partial * const partial;
|
||||
LA32Ramp *ampRamp;
|
||||
const MemParams::System * const system;
|
||||
|
||||
const Part *part;
|
||||
const TimbreParam::PartialParam *partialParam;
|
||||
const MemParams::PatchTemp *patchTemp;
|
||||
const MemParams::RhythmTemp *rhythmTemp;
|
||||
|
||||
bool playing;
|
||||
|
||||
int biasAmpSubtraction;
|
||||
int veloAmpSubtraction;
|
||||
int keyTimeSubtraction;
|
||||
|
||||
Bit8u target;
|
||||
int phase;
|
||||
|
||||
void startRamp(Bit8u newTarget, Bit8u newIncrement, int newPhase);
|
||||
void end(int newPhase);
|
||||
void nextPhase();
|
||||
|
||||
public:
|
||||
TVA(const Partial *partial, LA32Ramp *ampRamp);
|
||||
void reset(const Part *part, const TimbreParam::PartialParam *partialParam, const MemParams::RhythmTemp *rhythmTemp);
|
||||
void handleInterrupt();
|
||||
void recalcSustain();
|
||||
void startDecay();
|
||||
void startAbort();
|
||||
|
||||
bool isPlaying() const;
|
||||
int getPhase() const;
|
||||
}; // class TVA
|
||||
|
||||
} // namespace MT32Emu
|
||||
|
||||
#endif // #ifndef MT32EMU_TVA_H
|
@ -1,233 +0,0 @@
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "internals.h"
|
||||
|
||||
#include "TVF.h"
|
||||
#include "LA32Ramp.h"
|
||||
#include "Partial.h"
|
||||
#include "Poly.h"
|
||||
#include "Tables.h"
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
// Note that when entering nextPhase(), newPhase is set to phase + 1, and the descriptions/names below refer to
|
||||
// newPhase's value.
|
||||
enum {
|
||||
// When this is the target phase, level[0] is targeted within time[0]
|
||||
// Note that this phase is always set up in reset(), not nextPhase()
|
||||
PHASE_ATTACK = 1,
|
||||
|
||||
// When this is the target phase, level[1] is targeted within time[1]
|
||||
PHASE_2 = 2,
|
||||
|
||||
// When this is the target phase, level[2] is targeted within time[2]
|
||||
PHASE_3 = 3,
|
||||
|
||||
// When this is the target phase, level[3] is targeted within time[3]
|
||||
PHASE_4 = 4,
|
||||
|
||||
// When this is the target phase, immediately goes to PHASE_RELEASE unless the poly is set to sustain.
|
||||
// Otherwise level[3] is continued with increment 0 - no phase change will occur until some external influence (like pedal release)
|
||||
PHASE_SUSTAIN = 5,
|
||||
|
||||
// 0 is targeted within time[4] (the time calculation is quite different from the other phases)
|
||||
PHASE_RELEASE = 6,
|
||||
|
||||
// 0 is targeted with increment 0 (thus theoretically staying that way forever)
|
||||
PHASE_DONE = 7
|
||||
};
|
||||
|
||||
static int calcBaseCutoff(const TimbreParam::PartialParam *partialParam, Bit32u basePitch, unsigned int key) {
|
||||
// This table matches the values used by a real LAPC-I.
|
||||
static const Bit8s biasLevelToBiasMult[] = {85, 42, 21, 16, 10, 5, 2, 0, -2, -5, -10, -16, -21, -74, -85};
|
||||
// These values represent unique options with no consistent pattern, so we have to use something like a table in any case.
|
||||
// The table entries, when divided by 21, match approximately what the manual claims:
|
||||
// -1, -1/2, -1/4, 0, 1/8, 1/4, 3/8, 1/2, 5/8, 3/4, 7/8, 1, 5/4, 3/2, 2, s1, s2
|
||||
// Note that the entry for 1/8 is rounded to 2 (from 1/8 * 21 = 2.625), which seems strangely inaccurate compared to the others.
|
||||
static const Bit8s keyfollowMult21[] = {-21, -10, -5, 0, 2, 5, 8, 10, 13, 16, 18, 21, 26, 32, 42, 21, 21};
|
||||
int baseCutoff = keyfollowMult21[partialParam->tvf.keyfollow] - keyfollowMult21[partialParam->wg.pitchKeyfollow];
|
||||
// baseCutoff range now: -63 to 63
|
||||
baseCutoff *= int(key) - 60;
|
||||
// baseCutoff range now: -3024 to 3024
|
||||
int biasPoint = partialParam->tvf.biasPoint;
|
||||
if ((biasPoint & 0x40) == 0) {
|
||||
// biasPoint range here: 0 to 63
|
||||
int bias = biasPoint + 33 - key; // bias range here: -75 to 84
|
||||
if (bias > 0) {
|
||||
bias = -bias; // bias range here: -1 to -84
|
||||
baseCutoff += bias * biasLevelToBiasMult[partialParam->tvf.biasLevel]; // Calculation range: -7140 to 7140
|
||||
// baseCutoff range now: -10164 to 10164
|
||||
}
|
||||
} else {
|
||||
// biasPoint range here: 64 to 127
|
||||
int bias = biasPoint - 31 - key; // bias range here: -75 to 84
|
||||
if (bias < 0) {
|
||||
baseCutoff += bias * biasLevelToBiasMult[partialParam->tvf.biasLevel]; // Calculation range: -6375 to 6375
|
||||
// baseCutoff range now: -9399 to 9399
|
||||
}
|
||||
}
|
||||
// baseCutoff range now: -10164 to 10164
|
||||
baseCutoff += ((partialParam->tvf.cutoff << 4) - 800);
|
||||
// baseCutoff range now: -10964 to 10964
|
||||
if (baseCutoff >= 0) {
|
||||
// FIXME: Potentially bad if baseCutoff ends up below -2056?
|
||||
int pitchDeltaThing = (basePitch >> 4) + baseCutoff - 3584;
|
||||
if (pitchDeltaThing > 0) {
|
||||
baseCutoff -= pitchDeltaThing;
|
||||
}
|
||||
} else if (baseCutoff < -2048) {
|
||||
baseCutoff = -2048;
|
||||
}
|
||||
baseCutoff += 2056;
|
||||
baseCutoff >>= 4; // PORTABILITY NOTE: Hmm... Depends whether it could've been below -2056, but maybe arithmetic shift assumed?
|
||||
if (baseCutoff > 255) {
|
||||
baseCutoff = 255;
|
||||
}
|
||||
return Bit8u(baseCutoff);
|
||||
}
|
||||
|
||||
TVF::TVF(const Partial *usePartial, LA32Ramp *useCutoffModifierRamp) :
|
||||
partial(usePartial), cutoffModifierRamp(useCutoffModifierRamp) {
|
||||
}
|
||||
|
||||
void TVF::startRamp(Bit8u newTarget, Bit8u newIncrement, int newPhase) {
|
||||
target = newTarget;
|
||||
phase = newPhase;
|
||||
cutoffModifierRamp->startRamp(newTarget, newIncrement);
|
||||
#if MT32EMU_MONITOR_TVF >= 1
|
||||
partial->getSynth()->printDebug("[+%lu] [Partial %d] TVF,ramp,%d,%d,%d,%d", partial->debugGetSampleNum(), partial->debugGetPartialNum(), newTarget, (newIncrement & 0x80) ? -1 : 1, (newIncrement & 0x7F), newPhase);
|
||||
#endif
|
||||
}
|
||||
|
||||
void TVF::reset(const TimbreParam::PartialParam *newPartialParam, unsigned int basePitch) {
|
||||
partialParam = newPartialParam;
|
||||
|
||||
unsigned int key = partial->getPoly()->getKey();
|
||||
unsigned int velocity = partial->getPoly()->getVelocity();
|
||||
|
||||
const Tables *tables = &Tables::getInstance();
|
||||
|
||||
baseCutoff = calcBaseCutoff(newPartialParam, basePitch, key);
|
||||
#if MT32EMU_MONITOR_TVF >= 1
|
||||
partial->getSynth()->printDebug("[+%lu] [Partial %d] TVF,base,%d", partial->debugGetSampleNum(), partial->debugGetPartialNum(), baseCutoff);
|
||||
#endif
|
||||
|
||||
int newLevelMult = velocity * newPartialParam->tvf.envVeloSensitivity;
|
||||
newLevelMult >>= 6;
|
||||
newLevelMult += 109 - newPartialParam->tvf.envVeloSensitivity;
|
||||
newLevelMult += (signed(key) - 60) >> (4 - newPartialParam->tvf.envDepthKeyfollow);
|
||||
if (newLevelMult < 0) {
|
||||
newLevelMult = 0;
|
||||
}
|
||||
newLevelMult *= newPartialParam->tvf.envDepth;
|
||||
newLevelMult >>= 6;
|
||||
if (newLevelMult > 255) {
|
||||
newLevelMult = 255;
|
||||
}
|
||||
levelMult = newLevelMult;
|
||||
|
||||
if (newPartialParam->tvf.envTimeKeyfollow != 0) {
|
||||
keyTimeSubtraction = (signed(key) - 60) >> (5 - newPartialParam->tvf.envTimeKeyfollow);
|
||||
} else {
|
||||
keyTimeSubtraction = 0;
|
||||
}
|
||||
|
||||
int newTarget = (newLevelMult * newPartialParam->tvf.envLevel[0]) >> 8;
|
||||
int envTimeSetting = newPartialParam->tvf.envTime[0] - keyTimeSubtraction;
|
||||
int newIncrement;
|
||||
if (envTimeSetting <= 0) {
|
||||
newIncrement = (0x80 | 127);
|
||||
} else {
|
||||
newIncrement = tables->envLogarithmicTime[newTarget] - envTimeSetting;
|
||||
if (newIncrement <= 0) {
|
||||
newIncrement = 1;
|
||||
}
|
||||
}
|
||||
cutoffModifierRamp->reset();
|
||||
startRamp(newTarget, newIncrement, PHASE_2 - 1);
|
||||
}
|
||||
|
||||
Bit8u TVF::getBaseCutoff() const {
|
||||
return baseCutoff;
|
||||
}
|
||||
|
||||
void TVF::handleInterrupt() {
|
||||
nextPhase();
|
||||
}
|
||||
|
||||
void TVF::startDecay() {
|
||||
if (phase >= PHASE_RELEASE) {
|
||||
return;
|
||||
}
|
||||
if (partialParam->tvf.envTime[4] == 0) {
|
||||
startRamp(0, 1, PHASE_DONE - 1);
|
||||
} else {
|
||||
startRamp(0, -partialParam->tvf.envTime[4], PHASE_DONE - 1);
|
||||
}
|
||||
}
|
||||
|
||||
void TVF::nextPhase() {
|
||||
const Tables *tables = &Tables::getInstance();
|
||||
int newPhase = phase + 1;
|
||||
|
||||
switch (newPhase) {
|
||||
case PHASE_DONE:
|
||||
startRamp(0, 0, newPhase);
|
||||
return;
|
||||
case PHASE_SUSTAIN:
|
||||
case PHASE_RELEASE:
|
||||
// FIXME: Afaict newPhase should never be PHASE_RELEASE here. And if it were, this is an odd way to handle it.
|
||||
if (!partial->getPoly()->canSustain()) {
|
||||
phase = newPhase; // FIXME: Correct?
|
||||
startDecay(); // FIXME: This should actually start decay even if phase is already 6. Does that matter?
|
||||
return;
|
||||
}
|
||||
startRamp((levelMult * partialParam->tvf.envLevel[3]) >> 8, 0, newPhase);
|
||||
return;
|
||||
}
|
||||
|
||||
int envPointIndex = phase;
|
||||
int envTimeSetting = partialParam->tvf.envTime[envPointIndex] - keyTimeSubtraction;
|
||||
|
||||
int newTarget = (levelMult * partialParam->tvf.envLevel[envPointIndex]) >> 8;
|
||||
int newIncrement;
|
||||
if (envTimeSetting > 0) {
|
||||
int targetDelta = newTarget - target;
|
||||
if (targetDelta == 0) {
|
||||
if (newTarget == 0) {
|
||||
targetDelta = 1;
|
||||
newTarget = 1;
|
||||
} else {
|
||||
targetDelta = -1;
|
||||
newTarget--;
|
||||
}
|
||||
}
|
||||
newIncrement = tables->envLogarithmicTime[targetDelta < 0 ? -targetDelta : targetDelta] - envTimeSetting;
|
||||
if (newIncrement <= 0) {
|
||||
newIncrement = 1;
|
||||
}
|
||||
if (targetDelta < 0) {
|
||||
newIncrement |= 0x80;
|
||||
}
|
||||
} else {
|
||||
newIncrement = newTarget >= target ? (0x80 | 127) : 127;
|
||||
}
|
||||
startRamp(newTarget, newIncrement, newPhase);
|
||||
}
|
||||
|
||||
} // namespace MT32Emu
|
@ -1,61 +0,0 @@
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MT32EMU_TVF_H
|
||||
#define MT32EMU_TVF_H
|
||||
|
||||
#include "globals.h"
|
||||
#include "Types.h"
|
||||
#include "Structures.h"
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
class LA32Ramp;
|
||||
class Partial;
|
||||
|
||||
class TVF {
|
||||
private:
|
||||
const Partial * const partial;
|
||||
LA32Ramp *cutoffModifierRamp;
|
||||
const TimbreParam::PartialParam *partialParam;
|
||||
|
||||
Bit8u baseCutoff;
|
||||
int keyTimeSubtraction;
|
||||
unsigned int levelMult;
|
||||
|
||||
Bit8u target;
|
||||
unsigned int phase;
|
||||
|
||||
void startRamp(Bit8u newTarget, Bit8u newIncrement, int newPhase);
|
||||
void nextPhase();
|
||||
|
||||
public:
|
||||
TVF(const Partial *partial, LA32Ramp *cutoffModifierRamp);
|
||||
void reset(const TimbreParam::PartialParam *partialParam, Bit32u basePitch);
|
||||
// Returns the base cutoff (without envelope modification).
|
||||
// The base cutoff is calculated when reset() is called and remains static
|
||||
// for the lifetime of the partial.
|
||||
// Barring bugs, the number returned is confirmed accurate
|
||||
// (based on specs from Mok).
|
||||
Bit8u getBaseCutoff() const;
|
||||
void handleInterrupt();
|
||||
void startDecay();
|
||||
}; // class TVF
|
||||
|
||||
} // namespace MT32Emu
|
||||
|
||||
#endif // #ifndef MT32EMU_TVF_H
|
@ -1,330 +0,0 @@
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
#include "internals.h"
|
||||
|
||||
#include "TVP.h"
|
||||
#include "Part.h"
|
||||
#include "Partial.h"
|
||||
#include "Poly.h"
|
||||
#include "Synth.h"
|
||||
#include "TVA.h"
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
// FIXME: Add Explanation
|
||||
static Bit16u lowerDurationToDivisor[] = {34078, 37162, 40526, 44194, 48194, 52556, 57312, 62499};
|
||||
|
||||
// These values represent unique options with no consistent pattern, so we have to use something like a table in any case.
|
||||
// The table matches exactly what the manual claims (when divided by 8192):
|
||||
// -1, -1/2, -1/4, 0, 1/8, 1/4, 3/8, 1/2, 5/8, 3/4, 7/8, 1, 5/4, 3/2, 2, s1, s2
|
||||
// ...except for the last two entries, which are supposed to be "1 cent above 1" and "2 cents above 1", respectively. They can only be roughly approximated with this integer math.
|
||||
static Bit16s pitchKeyfollowMult[] = {-8192, -4096, -2048, 0, 1024, 2048, 3072, 4096, 5120, 6144, 7168, 8192, 10240, 12288, 16384, 8198, 8226};
|
||||
|
||||
// Note: Keys < 60 use keyToPitchTable[60 - key], keys >= 60 use keyToPitchTable[key - 60].
|
||||
// FIXME: This table could really be shorter, since we never use e.g. key 127.
|
||||
static Bit16u keyToPitchTable[] = {
|
||||
0, 341, 683, 1024, 1365, 1707, 2048, 2389,
|
||||
2731, 3072, 3413, 3755, 4096, 4437, 4779, 5120,
|
||||
5461, 5803, 6144, 6485, 6827, 7168, 7509, 7851,
|
||||
8192, 8533, 8875, 9216, 9557, 9899, 10240, 10581,
|
||||
10923, 11264, 11605, 11947, 12288, 12629, 12971, 13312,
|
||||
13653, 13995, 14336, 14677, 15019, 15360, 15701, 16043,
|
||||
16384, 16725, 17067, 17408, 17749, 18091, 18432, 18773,
|
||||
19115, 19456, 19797, 20139, 20480, 20821, 21163, 21504,
|
||||
21845, 22187, 22528, 22869
|
||||
};
|
||||
|
||||
TVP::TVP(const Partial *usePartial) :
|
||||
partial(usePartial), system(&usePartial->getSynth()->mt32ram.system) {
|
||||
// We want to do processing 4000 times per second. FIXME: This is pretty arbitrary.
|
||||
maxCounter = SAMPLE_RATE / 4000;
|
||||
// The timer runs at 500kHz. We only need to bother updating it every maxCounter samples, before we do processing.
|
||||
// This is how much to increment it by every maxCounter samples.
|
||||
processTimerIncrement = 500000 * maxCounter / SAMPLE_RATE;
|
||||
}
|
||||
|
||||
static Bit16s keyToPitch(unsigned int key) {
|
||||
// We're using a table to do: return round_to_nearest_or_even((key - 60) * (4096.0 / 12.0))
|
||||
// Banker's rounding is just slightly annoying to do in C++
|
||||
int k = int(key);
|
||||
Bit16s pitch = keyToPitchTable[abs(k - 60)];
|
||||
return key < 60 ? -pitch : pitch;
|
||||
}
|
||||
|
||||
static inline Bit32s coarseToPitch(Bit8u coarse) {
|
||||
return (coarse - 36) * 4096 / 12; // One semitone per coarse offset
|
||||
}
|
||||
|
||||
static inline Bit32s fineToPitch(Bit8u fine) {
|
||||
return (fine - 50) * 4096 / 1200; // One cent per fine offset
|
||||
}
|
||||
|
||||
static Bit32u calcBasePitch(const Partial *partial, const TimbreParam::PartialParam *partialParam, const MemParams::PatchTemp *patchTemp, unsigned int key) {
|
||||
Bit32s basePitch = keyToPitch(key);
|
||||
basePitch = (basePitch * pitchKeyfollowMult[partialParam->wg.pitchKeyfollow]) >> 13; // PORTABILITY NOTE: Assumes arithmetic shift
|
||||
basePitch += coarseToPitch(partialParam->wg.pitchCoarse);
|
||||
basePitch += fineToPitch(partialParam->wg.pitchFine);
|
||||
// NOTE:Mok: This is done on MT-32, but not LAPC-I:
|
||||
//pitch += coarseToPitch(patchTemp->patch.keyShift + 12);
|
||||
basePitch += fineToPitch(patchTemp->patch.fineTune);
|
||||
|
||||
const ControlROMPCMStruct *controlROMPCMStruct = partial->getControlROMPCMStruct();
|
||||
if (controlROMPCMStruct != NULL) {
|
||||
basePitch += (Bit32s(controlROMPCMStruct->pitchMSB) << 8) | Bit32s(controlROMPCMStruct->pitchLSB);
|
||||
} else {
|
||||
if ((partialParam->wg.waveform & 1) == 0) {
|
||||
basePitch += 37133; // This puts Middle C at around 261.64Hz (assuming no other modifications, masterTune of 64, etc.)
|
||||
} else {
|
||||
// Sawtooth waves are effectively double the frequency of square waves.
|
||||
// Thus we add 4096 less than for square waves here, which results in halving the frequency.
|
||||
basePitch += 33037;
|
||||
}
|
||||
}
|
||||
if (basePitch < 0) {
|
||||
basePitch = 0;
|
||||
}
|
||||
if (basePitch > 59392) {
|
||||
basePitch = 59392;
|
||||
}
|
||||
return Bit32u(basePitch);
|
||||
}
|
||||
|
||||
static Bit32u calcVeloMult(Bit8u veloSensitivity, unsigned int velocity) {
|
||||
if (veloSensitivity == 0 || veloSensitivity > 3) {
|
||||
// Note that on CM-32L/LAPC-I veloSensitivity is never > 3, since it's clipped to 3 by the max tables.
|
||||
return 21845; // aka floor(4096 / 12 * 64), aka ~64 semitones
|
||||
}
|
||||
// When velocity is 127, the multiplier is 21845, aka ~64 semitones (regardless of veloSensitivity).
|
||||
// The lower the velocity, the lower the multiplier. The veloSensitivity determines the amount decreased per velocity value.
|
||||
// The minimum multiplier (with velocity 0, veloSensitivity 3) is 170 (~half a semitone).
|
||||
Bit32u veloMult = 32768;
|
||||
veloMult -= (127 - velocity) << (5 + veloSensitivity);
|
||||
veloMult *= 21845;
|
||||
veloMult >>= 15;
|
||||
return veloMult;
|
||||
}
|
||||
|
||||
static Bit32s calcTargetPitchOffsetWithoutLFO(const TimbreParam::PartialParam *partialParam, int levelIndex, unsigned int velocity) {
|
||||
int veloMult = calcVeloMult(partialParam->pitchEnv.veloSensitivity, velocity);
|
||||
int targetPitchOffsetWithoutLFO = partialParam->pitchEnv.level[levelIndex] - 50;
|
||||
targetPitchOffsetWithoutLFO = (targetPitchOffsetWithoutLFO * veloMult) >> (16 - partialParam->pitchEnv.depth); // PORTABILITY NOTE: Assumes arithmetic shift
|
||||
return targetPitchOffsetWithoutLFO;
|
||||
}
|
||||
|
||||
void TVP::reset(const Part *usePart, const TimbreParam::PartialParam *usePartialParam) {
|
||||
part = usePart;
|
||||
partialParam = usePartialParam;
|
||||
patchTemp = part->getPatchTemp();
|
||||
|
||||
unsigned int key = partial->getPoly()->getKey();
|
||||
unsigned int velocity = partial->getPoly()->getVelocity();
|
||||
|
||||
// FIXME: We're using a per-TVP timer instead of a system-wide one for convenience.
|
||||
timeElapsed = 0;
|
||||
|
||||
basePitch = calcBasePitch(partial, partialParam, patchTemp, key);
|
||||
currentPitchOffset = calcTargetPitchOffsetWithoutLFO(partialParam, 0, velocity);
|
||||
targetPitchOffsetWithoutLFO = currentPitchOffset;
|
||||
phase = 0;
|
||||
|
||||
if (partialParam->pitchEnv.timeKeyfollow) {
|
||||
timeKeyfollowSubtraction = Bit32s(key - 60) >> (5 - partialParam->pitchEnv.timeKeyfollow); // PORTABILITY NOTE: Assumes arithmetic shift
|
||||
} else {
|
||||
timeKeyfollowSubtraction = 0;
|
||||
}
|
||||
lfoPitchOffset = 0;
|
||||
counter = 0;
|
||||
pitch = basePitch;
|
||||
|
||||
// These don't really need to be initialised, but it aids debugging.
|
||||
pitchOffsetChangePerBigTick = 0;
|
||||
targetPitchOffsetReachedBigTick = 0;
|
||||
shifts = 0;
|
||||
}
|
||||
|
||||
Bit32u TVP::getBasePitch() const {
|
||||
return basePitch;
|
||||
}
|
||||
|
||||
void TVP::updatePitch() {
|
||||
Bit32s newPitch = basePitch + currentPitchOffset;
|
||||
if (!partial->isPCM() || (partial->getControlROMPCMStruct()->len & 0x01) == 0) { // FIXME: Use !partial->pcmWaveEntry->unaffectedByMasterTune instead
|
||||
// FIXME: masterTune recalculation doesn't really happen here, and there are various bugs not yet emulated
|
||||
// 171 is ~half a semitone.
|
||||
newPitch += ((system->masterTune - 64) * 171) >> 6; // PORTABILITY NOTE: Assumes arithmetic shift.
|
||||
}
|
||||
if ((partialParam->wg.pitchBenderEnabled & 1) != 0) {
|
||||
newPitch += part->getPitchBend();
|
||||
}
|
||||
if (newPitch < 0) {
|
||||
newPitch = 0;
|
||||
}
|
||||
|
||||
// Skipping this check seems about right emulation of MT-32 GEN0 quirk exploited in Colonel's Bequest timbre "Lightning"
|
||||
if (partial->getSynth()->controlROMFeatures->quirkPitchEnvelopeOverflow == 0) {
|
||||
if (newPitch > 59392) {
|
||||
newPitch = 59392;
|
||||
}
|
||||
}
|
||||
pitch = Bit16u(newPitch);
|
||||
|
||||
// FIXME: We're doing this here because that's what the CM-32L does - we should probably move this somewhere more appropriate in future.
|
||||
partial->getTVA()->recalcSustain();
|
||||
}
|
||||
|
||||
void TVP::targetPitchOffsetReached() {
|
||||
currentPitchOffset = targetPitchOffsetWithoutLFO + lfoPitchOffset;
|
||||
|
||||
switch (phase) {
|
||||
case 3:
|
||||
case 4:
|
||||
{
|
||||
int newLFOPitchOffset = (part->getModulation() * partialParam->pitchLFO.modSensitivity) >> 7;
|
||||
newLFOPitchOffset = (newLFOPitchOffset + partialParam->pitchLFO.depth) << 1;
|
||||
if (pitchOffsetChangePerBigTick > 0) {
|
||||
// Go in the opposite direction to last time
|
||||
newLFOPitchOffset = -newLFOPitchOffset;
|
||||
}
|
||||
lfoPitchOffset = newLFOPitchOffset;
|
||||
int targetPitchOffset = targetPitchOffsetWithoutLFO + lfoPitchOffset;
|
||||
setupPitchChange(targetPitchOffset, 101 - partialParam->pitchLFO.rate);
|
||||
updatePitch();
|
||||
break;
|
||||
}
|
||||
case 6:
|
||||
updatePitch();
|
||||
break;
|
||||
default:
|
||||
nextPhase();
|
||||
}
|
||||
}
|
||||
|
||||
void TVP::nextPhase() {
|
||||
phase++;
|
||||
int envIndex = phase == 6 ? 4 : phase;
|
||||
|
||||
targetPitchOffsetWithoutLFO = calcTargetPitchOffsetWithoutLFO(partialParam, envIndex, partial->getPoly()->getVelocity()); // pitch we'll reach at the end
|
||||
|
||||
int changeDuration = partialParam->pitchEnv.time[envIndex - 1];
|
||||
changeDuration -= timeKeyfollowSubtraction;
|
||||
if (changeDuration > 0) {
|
||||
setupPitchChange(targetPitchOffsetWithoutLFO, changeDuration); // changeDuration between 0 and 112 now
|
||||
updatePitch();
|
||||
} else {
|
||||
targetPitchOffsetReached();
|
||||
}
|
||||
}
|
||||
|
||||
// Shifts val to the left until bit 31 is 1 and returns the number of shifts
|
||||
static Bit8u normalise(Bit32u &val) {
|
||||
Bit8u leftShifts;
|
||||
for (leftShifts = 0; leftShifts < 31; leftShifts++) {
|
||||
if ((val & 0x80000000) != 0) {
|
||||
break;
|
||||
}
|
||||
val = val << 1;
|
||||
}
|
||||
return leftShifts;
|
||||
}
|
||||
|
||||
void TVP::setupPitchChange(int targetPitchOffset, Bit8u changeDuration) {
|
||||
bool negativeDelta = targetPitchOffset < currentPitchOffset;
|
||||
Bit32s pitchOffsetDelta = targetPitchOffset - currentPitchOffset;
|
||||
if (pitchOffsetDelta > 32767 || pitchOffsetDelta < -32768) {
|
||||
pitchOffsetDelta = 32767;
|
||||
}
|
||||
if (negativeDelta) {
|
||||
pitchOffsetDelta = -pitchOffsetDelta;
|
||||
}
|
||||
// We want to maximise the number of bits of the Bit16s "pitchOffsetChangePerBigTick" we use in order to get the best possible precision later
|
||||
Bit32u absPitchOffsetDelta = pitchOffsetDelta << 16;
|
||||
Bit8u normalisationShifts = normalise(absPitchOffsetDelta); // FIXME: Double-check: normalisationShifts is usually between 0 and 15 here, unless the delta is 0, in which case it's 31
|
||||
absPitchOffsetDelta = absPitchOffsetDelta >> 1; // Make room for the sign bit
|
||||
|
||||
changeDuration--; // changeDuration's now between 0 and 111
|
||||
unsigned int upperDuration = changeDuration >> 3; // upperDuration's now between 0 and 13
|
||||
shifts = normalisationShifts + upperDuration + 2;
|
||||
Bit16u divisor = lowerDurationToDivisor[changeDuration & 7];
|
||||
Bit16s newPitchOffsetChangePerBigTick = ((absPitchOffsetDelta & 0xFFFF0000) / divisor) >> 1; // Result now fits within 15 bits. FIXME: Check nothing's getting sign-extended incorrectly
|
||||
if (negativeDelta) {
|
||||
newPitchOffsetChangePerBigTick = -newPitchOffsetChangePerBigTick;
|
||||
}
|
||||
pitchOffsetChangePerBigTick = newPitchOffsetChangePerBigTick;
|
||||
|
||||
int currentBigTick = timeElapsed >> 8;
|
||||
int durationInBigTicks = divisor >> (12 - upperDuration);
|
||||
if (durationInBigTicks > 32767) {
|
||||
durationInBigTicks = 32767;
|
||||
}
|
||||
// The result of the addition may exceed 16 bits, but wrapping is fine and intended here.
|
||||
targetPitchOffsetReachedBigTick = currentBigTick + durationInBigTicks;
|
||||
}
|
||||
|
||||
void TVP::startDecay() {
|
||||
phase = 5;
|
||||
lfoPitchOffset = 0;
|
||||
targetPitchOffsetReachedBigTick = timeElapsed >> 8; // FIXME: Afaict there's no good reason for this - check
|
||||
}
|
||||
|
||||
Bit16u TVP::nextPitch() {
|
||||
// FIXME: Write explanation of counter and time increment
|
||||
if (counter == 0) {
|
||||
timeElapsed += processTimerIncrement;
|
||||
timeElapsed = timeElapsed & 0x00FFFFFF;
|
||||
process();
|
||||
}
|
||||
counter = (counter + 1) % maxCounter;
|
||||
return pitch;
|
||||
}
|
||||
|
||||
void TVP::process() {
|
||||
if (phase == 0) {
|
||||
targetPitchOffsetReached();
|
||||
return;
|
||||
}
|
||||
if (phase == 5) {
|
||||
nextPhase();
|
||||
return;
|
||||
}
|
||||
if (phase > 7) {
|
||||
updatePitch();
|
||||
return;
|
||||
}
|
||||
|
||||
Bit16s negativeBigTicksRemaining = (timeElapsed >> 8) - targetPitchOffsetReachedBigTick;
|
||||
if (negativeBigTicksRemaining >= 0) {
|
||||
// We've reached the time for a phase change
|
||||
targetPitchOffsetReached();
|
||||
return;
|
||||
}
|
||||
// FIXME: Write explanation for this stuff
|
||||
int rightShifts = shifts;
|
||||
if (rightShifts > 13) {
|
||||
rightShifts -= 13;
|
||||
negativeBigTicksRemaining = negativeBigTicksRemaining >> rightShifts; // PORTABILITY NOTE: Assumes arithmetic shift
|
||||
rightShifts = 13;
|
||||
}
|
||||
int newResult = (negativeBigTicksRemaining * pitchOffsetChangePerBigTick) >> rightShifts; // PORTABILITY NOTE: Assumes arithmetic shift
|
||||
newResult += targetPitchOffsetWithoutLFO + lfoPitchOffset;
|
||||
currentPitchOffset = newResult;
|
||||
updatePitch();
|
||||
}
|
||||
|
||||
} // namespace MT32Emu
|
@ -1,74 +0,0 @@
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MT32EMU_TVP_H
|
||||
#define MT32EMU_TVP_H
|
||||
|
||||
#include "globals.h"
|
||||
#include "Types.h"
|
||||
#include "Structures.h"
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
class Part;
|
||||
class Partial;
|
||||
|
||||
class TVP {
|
||||
private:
|
||||
const Partial * const partial;
|
||||
const MemParams::System * const system; // FIXME: Only necessary because masterTune calculation is done in the wrong place atm.
|
||||
|
||||
const Part *part;
|
||||
const TimbreParam::PartialParam *partialParam;
|
||||
const MemParams::PatchTemp *patchTemp;
|
||||
|
||||
int maxCounter;
|
||||
int processTimerIncrement;
|
||||
int counter;
|
||||
Bit32u timeElapsed;
|
||||
|
||||
int phase;
|
||||
Bit32u basePitch;
|
||||
Bit32s targetPitchOffsetWithoutLFO;
|
||||
Bit32s currentPitchOffset;
|
||||
|
||||
Bit16s lfoPitchOffset;
|
||||
// In range -12 - 36
|
||||
Bit8s timeKeyfollowSubtraction;
|
||||
|
||||
Bit16s pitchOffsetChangePerBigTick;
|
||||
Bit16u targetPitchOffsetReachedBigTick;
|
||||
unsigned int shifts;
|
||||
|
||||
Bit16u pitch;
|
||||
|
||||
void updatePitch();
|
||||
void setupPitchChange(int targetPitchOffset, Bit8u changeDuration);
|
||||
void targetPitchOffsetReached();
|
||||
void nextPhase();
|
||||
void process();
|
||||
public:
|
||||
TVP(const Partial *partial);
|
||||
void reset(const Part *part, const TimbreParam::PartialParam *partialParam);
|
||||
Bit32u getBasePitch() const;
|
||||
Bit16u nextPitch();
|
||||
void startDecay();
|
||||
}; // class TVP
|
||||
|
||||
} // namespace MT32Emu
|
||||
|
||||
#endif // #ifndef MT32EMU_TVP_H
|
@ -1,97 +0,0 @@
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "internals.h"
|
||||
|
||||
#include "Tables.h"
|
||||
#include "mmath.h"
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
// UNUSED: const int MIDDLEC = 60;
|
||||
|
||||
const Tables &Tables::getInstance() {
|
||||
static const Tables instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
Tables::Tables() {
|
||||
for (int lf = 0; lf <= 100; lf++) {
|
||||
// CONFIRMED:KG: This matches a ROM table found by Mok
|
||||
float fVal = (2.0f - LOG10F(float(lf) + 1.0f)) * 128.0f;
|
||||
int val = int(fVal + 1.0);
|
||||
if (val > 255) {
|
||||
val = 255;
|
||||
}
|
||||
levelToAmpSubtraction[lf] = Bit8u(val);
|
||||
}
|
||||
|
||||
envLogarithmicTime[0] = 64;
|
||||
for (int lf = 1; lf <= 255; lf++) {
|
||||
// CONFIRMED:KG: This matches a ROM table found by Mok
|
||||
envLogarithmicTime[lf] = Bit8u(ceil(64.0f + LOG2F(float(lf)) * 8.0f));
|
||||
}
|
||||
|
||||
#if 0
|
||||
// The table below is to be used in conjunction with emulation of VCA of newer generation units which is currently missing.
|
||||
// These relatively small values are rather intended to fine-tune the overall amplification of the VCA.
|
||||
// CONFIRMED: Based on a table found by Mok in the LAPC-I control ROM
|
||||
// Note that this matches the MT-32 table, but with the values clamped to a maximum of 8.
|
||||
memset(masterVolToAmpSubtraction, 8, 71);
|
||||
memset(masterVolToAmpSubtraction + 71, 7, 3);
|
||||
memset(masterVolToAmpSubtraction + 74, 6, 4);
|
||||
memset(masterVolToAmpSubtraction + 78, 5, 3);
|
||||
memset(masterVolToAmpSubtraction + 81, 4, 4);
|
||||
memset(masterVolToAmpSubtraction + 85, 3, 3);
|
||||
memset(masterVolToAmpSubtraction + 88, 2, 4);
|
||||
memset(masterVolToAmpSubtraction + 92, 1, 4);
|
||||
memset(masterVolToAmpSubtraction + 96, 0, 5);
|
||||
#else
|
||||
// CONFIRMED: Based on a table found by Mok in the MT-32 control ROM
|
||||
masterVolToAmpSubtraction[0] = 255;
|
||||
for (int masterVol = 1; masterVol <= 100; masterVol++) {
|
||||
masterVolToAmpSubtraction[masterVol] = Bit8u(106.31 - 16.0f * LOG2F(float(masterVol)));
|
||||
}
|
||||
#endif
|
||||
|
||||
for (int i = 0; i <= 100; i++) {
|
||||
pulseWidth100To255[i] = Bit8u(i * 255 / 100.0f + 0.5f);
|
||||
//synth->printDebug("%d: %d", i, pulseWidth100To255[i]);
|
||||
}
|
||||
|
||||
// The LA32 chip contains an exponent table inside. The table contains 12-bit integer values.
|
||||
// The actual table size is 512 rows. The 9 higher bits of the fractional part of the argument are used as a lookup address.
|
||||
// To improve the precision of computations, the lower bits are supposed to be used for interpolation as the LA32 chip also
|
||||
// contains another 512-row table with inverted differences between the main table values.
|
||||
for (int i = 0; i < 512; i++) {
|
||||
exp9[i] = Bit16u(8191.5f - EXP2F(13.0f + ~i / 512.0f));
|
||||
}
|
||||
|
||||
// There is a logarithmic sine table inside the LA32 chip. The table contains 13-bit integer values.
|
||||
for (int i = 1; i < 512; i++) {
|
||||
logsin9[i] = Bit16u(0.5f - LOG2F(sin((i + 0.5f) / 1024.0f * FLOAT_PI)) * 1024.0f);
|
||||
}
|
||||
|
||||
// The very first value is clamped to the maximum possible 13-bit integer
|
||||
logsin9[0] = 8191;
|
||||
|
||||
// found from sample analysis
|
||||
static const Bit8u resAmpDecayFactorTable[] = {31, 16, 12, 8, 5, 3, 2, 1};
|
||||
resAmpDecayFactor = resAmpDecayFactorTable;
|
||||
}
|
||||
|
||||
} // namespace MT32Emu
|
@ -1,62 +0,0 @@
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MT32EMU_TABLES_H
|
||||
#define MT32EMU_TABLES_H
|
||||
|
||||
#include "globals.h"
|
||||
#include "Types.h"
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
class Tables {
|
||||
private:
|
||||
Tables();
|
||||
Tables(Tables &);
|
||||
~Tables() {}
|
||||
|
||||
public:
|
||||
static const Tables &getInstance();
|
||||
|
||||
// Constant LUTs
|
||||
|
||||
// CONFIRMED: This is used to convert several parameters to amp-modifying values in the TVA envelope:
|
||||
// - PatchTemp.outputLevel
|
||||
// - RhythmTemp.outlevel
|
||||
// - PartialParam.tva.level
|
||||
// - expression
|
||||
// It's used to determine how much to subtract from the amp envelope's target value
|
||||
Bit8u levelToAmpSubtraction[101];
|
||||
|
||||
// CONFIRMED: ...
|
||||
Bit8u envLogarithmicTime[256];
|
||||
|
||||
// CONFIRMED: ...
|
||||
Bit8u masterVolToAmpSubtraction[101];
|
||||
|
||||
// CONFIRMED:
|
||||
Bit8u pulseWidth100To255[101];
|
||||
|
||||
Bit16u exp9[512];
|
||||
Bit16u logsin9[512];
|
||||
|
||||
const Bit8u *resAmpDecayFactor;
|
||||
}; // class Tables
|
||||
|
||||
} // namespace MT32Emu
|
||||
|
||||
#endif // #ifndef MT32EMU_TABLES_H
|
@ -1,32 +0,0 @@
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MT32EMU_TYPES_H
|
||||
#define MT32EMU_TYPES_H
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
typedef unsigned int Bit32u;
|
||||
typedef signed int Bit32s;
|
||||
typedef unsigned short int Bit16u;
|
||||
typedef signed short int Bit16s;
|
||||
typedef unsigned char Bit8u;
|
||||
typedef signed char Bit8s;
|
||||
|
||||
}
|
||||
|
||||
#endif
|
@ -1,28 +0,0 @@
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MT32EMU_CONFIG_H
|
||||
#define MT32EMU_CONFIG_H
|
||||
|
||||
#define MT32EMU_VERSION "2.0.3"
|
||||
#define MT32EMU_VERSION_MAJOR 2
|
||||
#define MT32EMU_VERSION_MINOR 0
|
||||
#define MT32EMU_VERSION_PATCH 3
|
||||
|
||||
#define MT32EMU_EXPORTS_TYPE 3
|
||||
|
||||
#endif
|
@ -1,119 +0,0 @@
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MT32EMU_GLOBALS_H
|
||||
#define MT32EMU_GLOBALS_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
/* Support for compiling shared library. */
|
||||
#ifdef MT32EMU_SHARED
|
||||
#if defined _WIN32 || defined __CYGWIN__
|
||||
#ifdef _MSC_VER
|
||||
#ifdef mt32emu_EXPORTS
|
||||
#define MT32EMU_EXPORT_ATTRIBUTE _declspec(dllexport)
|
||||
#else /* #ifdef mt32emu_EXPORTS */
|
||||
#define MT32EMU_EXPORT_ATTRIBUTE _declspec(dllimport)
|
||||
#endif /* #ifdef mt32emu_EXPORTS */
|
||||
#else /* #ifdef _MSC_VER */
|
||||
#ifdef mt32emu_EXPORTS
|
||||
#define MT32EMU_EXPORT_ATTRIBUTE __attribute__ ((dllexport))
|
||||
#else /* #ifdef mt32emu_EXPORTS */
|
||||
#define MT32EMU_EXPORT_ATTRIBUTE __attribute__ ((dllimport))
|
||||
#endif /* #ifdef mt32emu_EXPORTS */
|
||||
#endif /* #ifdef _MSC_VER */
|
||||
#else /* #if defined _WIN32 || defined __CYGWIN__ */
|
||||
#define MT32EMU_EXPORT_ATTRIBUTE __attribute__ ((visibility("default")))
|
||||
#endif /* #if defined _WIN32 || defined __CYGWIN__ */
|
||||
#else /* #ifdef MT32EMU_SHARED */
|
||||
#define MT32EMU_EXPORT_ATTRIBUTE
|
||||
#endif /* #ifdef MT32EMU_SHARED */
|
||||
|
||||
#if MT32EMU_EXPORTS_TYPE == 1 || MT32EMU_EXPORTS_TYPE == 2
|
||||
#define MT32EMU_EXPORT
|
||||
#else
|
||||
#define MT32EMU_EXPORT MT32EMU_EXPORT_ATTRIBUTE
|
||||
#endif
|
||||
|
||||
/* Useful constants */
|
||||
|
||||
/* Sample rate to use in mixing. With the progress of development, we've found way too many thing dependent.
|
||||
* In order to achieve further advance in emulation accuracy, sample rate made fixed throughout the emulator,
|
||||
* except the emulation of analogue path.
|
||||
* The output from the synth is supposed to be resampled externally in order to convert to the desired sample rate.
|
||||
*/
|
||||
#define MT32EMU_SAMPLE_RATE 32000
|
||||
|
||||
/* The default value for the maximum number of partials playing simultaneously. */
|
||||
#define MT32EMU_DEFAULT_MAX_PARTIALS 32
|
||||
|
||||
/* The higher this number, the more memory will be used, but the more samples can be processed in one run -
|
||||
* various parts of sample generation can be processed more efficiently in a single run.
|
||||
* A run's maximum length is that given to Synth::render(), so giving a value here higher than render() is ever
|
||||
* called with will give no gain (but simply waste the memory).
|
||||
* Note that this value does *not* in any way impose limitations on the length given to render(), and has no effect
|
||||
* on the generated audio.
|
||||
* This value must be >= 1.
|
||||
*/
|
||||
#define MT32EMU_MAX_SAMPLES_PER_RUN 4096
|
||||
|
||||
/* The default size of the internal MIDI event queue.
|
||||
* It holds the incoming MIDI events before the rendering engine actually processes them.
|
||||
* The main goal is to fairly emulate the real hardware behaviour which obviously
|
||||
* uses an internal MIDI event queue to gather incoming data as well as the delays
|
||||
* introduced by transferring data via the MIDI interface.
|
||||
* This also facilitates building of an external rendering loop
|
||||
* as the queue stores timestamped MIDI events.
|
||||
*/
|
||||
#define MT32EMU_DEFAULT_MIDI_EVENT_QUEUE_SIZE 1024
|
||||
|
||||
/* Maximum allowed size of MIDI parser input stream buffer.
|
||||
* Should suffice for any reasonable bulk dump SysEx, as the h/w units have only 32K of RAM onboard.
|
||||
*/
|
||||
#define MT32EMU_MAX_STREAM_BUFFER_SIZE 32768
|
||||
|
||||
/* This should correspond to the MIDI buffer size used in real h/w devices.
|
||||
* CM-32L control ROM seems using 1000 bytes, old MT-32 isn't confirmed by now.
|
||||
*/
|
||||
#define MT32EMU_SYSEX_BUFFER_SIZE 1000
|
||||
|
||||
#if defined(__cplusplus) && MT32EMU_API_TYPE != 1
|
||||
|
||||
namespace MT32Emu
|
||||
{
|
||||
const unsigned int SAMPLE_RATE = MT32EMU_SAMPLE_RATE;
|
||||
#undef MT32EMU_SAMPLE_RATE
|
||||
|
||||
const unsigned int DEFAULT_MAX_PARTIALS = MT32EMU_DEFAULT_MAX_PARTIALS;
|
||||
#undef MT32EMU_DEFAULT_MAX_PARTIALS
|
||||
|
||||
const unsigned int MAX_SAMPLES_PER_RUN = MT32EMU_MAX_SAMPLES_PER_RUN;
|
||||
#undef MT32EMU_MAX_SAMPLES_PER_RUN
|
||||
|
||||
const unsigned int DEFAULT_MIDI_EVENT_QUEUE_SIZE = MT32EMU_DEFAULT_MIDI_EVENT_QUEUE_SIZE;
|
||||
#undef MT32EMU_DEFAULT_MIDI_EVENT_QUEUE_SIZE
|
||||
|
||||
const unsigned int MAX_STREAM_BUFFER_SIZE = MT32EMU_MAX_STREAM_BUFFER_SIZE;
|
||||
#undef MT32EMU_MAX_STREAM_BUFFER_SIZE
|
||||
|
||||
const unsigned int SYSEX_BUFFER_SIZE = MT32EMU_SYSEX_BUFFER_SIZE;
|
||||
#undef MT32EMU_SYSEX_BUFFER_SIZE
|
||||
}
|
||||
|
||||
#endif /* #if defined(__cplusplus) && MT32EMU_API_TYPE != 1 */
|
||||
|
||||
#endif /* #ifndef MT32EMU_GLOBALS_H */
|
@ -1,128 +0,0 @@
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MT32EMU_INTERNALS_H
|
||||
#define MT32EMU_INTERNALS_H
|
||||
|
||||
#include "Types.h"
|
||||
|
||||
// Debugging
|
||||
|
||||
// 0: Standard debug output is not stamped with the rendered sample count
|
||||
// 1: Standard debug output is stamped with the rendered sample count
|
||||
// NOTE: The "samplestamp" corresponds to the end of the last completed rendering run.
|
||||
// This is important to bear in mind for debug output that occurs during a run.
|
||||
#ifndef MT32EMU_DEBUG_SAMPLESTAMPS
|
||||
#define MT32EMU_DEBUG_SAMPLESTAMPS 0
|
||||
#endif
|
||||
|
||||
// 0: No debug output for initialisation progress
|
||||
// 1: Debug output for initialisation progress
|
||||
#ifndef MT32EMU_MONITOR_INIT
|
||||
#define MT32EMU_MONITOR_INIT 0
|
||||
#endif
|
||||
|
||||
// 0: No debug output for MIDI events
|
||||
// 1: Debug output for weird MIDI events
|
||||
#ifndef MT32EMU_MONITOR_MIDI
|
||||
#define MT32EMU_MONITOR_MIDI 0
|
||||
#endif
|
||||
|
||||
// 0: No debug output for note on/off
|
||||
// 1: Basic debug output for note on/off
|
||||
// 2: Comprehensive debug output for note on/off
|
||||
#ifndef MT32EMU_MONITOR_INSTRUMENTS
|
||||
#define MT32EMU_MONITOR_INSTRUMENTS 0
|
||||
#endif
|
||||
|
||||
// 0: No debug output for partial allocations
|
||||
// 1: Show partial stats when an allocation fails
|
||||
// 2: Show partial stats with every new poly
|
||||
// 3: Show individual partial allocations/deactivations
|
||||
#ifndef MT32EMU_MONITOR_PARTIALS
|
||||
#define MT32EMU_MONITOR_PARTIALS 0
|
||||
#endif
|
||||
|
||||
// 0: No debug output for sysex
|
||||
// 1: Basic debug output for sysex
|
||||
#ifndef MT32EMU_MONITOR_SYSEX
|
||||
#define MT32EMU_MONITOR_SYSEX 0
|
||||
#endif
|
||||
|
||||
// 0: No debug output for sysex writes to the timbre areas
|
||||
// 1: Debug output with the name and location of newly-written timbres
|
||||
// 2: Complete dump of timbre parameters for newly-written timbres
|
||||
#ifndef MT32EMU_MONITOR_TIMBRES
|
||||
#define MT32EMU_MONITOR_TIMBRES 0
|
||||
#endif
|
||||
|
||||
// 0: No TVA/TVF-related debug output.
|
||||
// 1: Shows changes to TVA/TVF target, increment and phase.
|
||||
#ifndef MT32EMU_MONITOR_TVA
|
||||
#define MT32EMU_MONITOR_TVA 0
|
||||
#endif
|
||||
#ifndef MT32EMU_MONITOR_TVF
|
||||
#define MT32EMU_MONITOR_TVF 0
|
||||
#endif
|
||||
|
||||
// Configuration
|
||||
|
||||
// 0: Use 16-bit signed samples and refined wave generator based on logarithmic fixed-point computations and LUTs. Maximum emulation accuracy and speed.
|
||||
// 1: Use float samples in the wave generator and renderer. Maximum output quality and minimum noise.
|
||||
#ifndef MT32EMU_USE_FLOAT_SAMPLES
|
||||
#define MT32EMU_USE_FLOAT_SAMPLES 0
|
||||
#endif
|
||||
|
||||
// If non-zero, deletes reverb buffers that are not in use to save memory.
|
||||
// If zero, keeps reverb buffers for all modes around all the time to avoid allocating/freeing in the critical path.
|
||||
#ifndef MT32EMU_REDUCE_REVERB_MEMORY
|
||||
#define MT32EMU_REDUCE_REVERB_MEMORY 1
|
||||
#endif
|
||||
|
||||
// 0: Maximum speed at the cost of a bit lower emulation accuracy.
|
||||
// 1: Maximum achievable emulation accuracy.
|
||||
#ifndef MT32EMU_BOSS_REVERB_PRECISE_MODE
|
||||
#define MT32EMU_BOSS_REVERB_PRECISE_MODE 0
|
||||
#endif
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
enum PolyState {
|
||||
POLY_Playing,
|
||||
POLY_Held, // This marks keys that have been released on the keyboard, but are being held by the pedal
|
||||
POLY_Releasing,
|
||||
POLY_Inactive
|
||||
};
|
||||
|
||||
enum ReverbMode {
|
||||
REVERB_MODE_ROOM,
|
||||
REVERB_MODE_HALL,
|
||||
REVERB_MODE_PLATE,
|
||||
REVERB_MODE_TAP_DELAY
|
||||
};
|
||||
|
||||
#if MT32EMU_USE_FLOAT_SAMPLES
|
||||
typedef float Sample;
|
||||
typedef float SampleEx;
|
||||
#else
|
||||
typedef Bit16s Sample;
|
||||
typedef Bit32s SampleEx;
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
#endif // #ifndef MT32EMU_INTERNALS_H
|
@ -1,68 +0,0 @@
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MT32EMU_MMATH_H
|
||||
#define MT32EMU_MMATH_H
|
||||
|
||||
#include <cmath>
|
||||
|
||||
namespace MT32Emu {
|
||||
|
||||
// Mathematical constants
|
||||
const double DOUBLE_PI = 3.141592653589793;
|
||||
const double DOUBLE_LN_10 = 2.302585092994046;
|
||||
const float FLOAT_PI = 3.1415927f;
|
||||
const float FLOAT_2PI = 6.2831853f;
|
||||
const float FLOAT_LN_2 = 0.6931472f;
|
||||
const float FLOAT_LN_10 = 2.3025851f;
|
||||
|
||||
static inline float POWF(float x, float y) {
|
||||
return pow(x, y);
|
||||
}
|
||||
|
||||
static inline float EXPF(float x) {
|
||||
return exp(x);
|
||||
}
|
||||
|
||||
static inline float EXP2F(float x) {
|
||||
#ifdef __APPLE__
|
||||
// on OSX exp2f() is 1.59 times faster than "exp() and the multiplication with FLOAT_LN_2"
|
||||
return exp2f(x);
|
||||
#else
|
||||
return exp(FLOAT_LN_2 * x);
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline float EXP10F(float x) {
|
||||
return exp(FLOAT_LN_10 * x);
|
||||
}
|
||||
|
||||
static inline float LOGF(float x) {
|
||||
return log(x);
|
||||
}
|
||||
|
||||
static inline float LOG2F(float x) {
|
||||
return log(x) / FLOAT_LN_2;
|
||||
}
|
||||
|
||||
static inline float LOG10F(float x) {
|
||||
return log10(x);
|
||||
}
|
||||
|
||||
} // namespace MT32Emu
|
||||
|
||||
#endif // #ifndef MT32EMU_MMATH_H
|
@ -1,40 +0,0 @@
|
||||
MODULE := audio/softsynth/mt32
|
||||
|
||||
MODULE_OBJS := \
|
||||
Analog.o \
|
||||
BReverbModel.o \
|
||||
File.o \
|
||||
FileStream.o \
|
||||
LA32Ramp.o \
|
||||
LA32WaveGenerator.o \
|
||||
MidiStreamParser.o \
|
||||
Part.o \
|
||||
Partial.o \
|
||||
PartialManager.o \
|
||||
Poly.o \
|
||||
ROMInfo.o \
|
||||
Synth.o \
|
||||
Tables.o \
|
||||
TVA.o \
|
||||
TVF.o \
|
||||
TVP.o \
|
||||
sha1/sha1.o \
|
||||
c_interface/c_interface.o
|
||||
|
||||
# SampleRateConverter.o \
|
||||
# srchelper/InternalResampler.o \
|
||||
# srchelper/SamplerateAdapter.o \
|
||||
# srchelper/SoxrAdapter.o \
|
||||
# srchelper/srctools/src/FIRResampler.o \
|
||||
# srchelper/srctools/src/IIR2xResampler.o \
|
||||
# srchelper/srctools/src/LinearResampler.o \
|
||||
# srchelper/srctools/src/ResamplerModel.o \
|
||||
# srchelper/srctools/src/SincResampler.o
|
||||
# TODO: The Munt SampleRateConverter requires these additional -I options.
|
||||
# This is not a very nice way of doing that, though, as it adds them globally.
|
||||
# INCLUDES += -I $(srcdir)/$(MODULE)/srchelper/srctools/include
|
||||
# INCLUDES += -I $(srcdir)/$(MODULE)/
|
||||
|
||||
|
||||
# Include common rules
|
||||
include $(srcdir)/rules.mk
|
@ -1,84 +0,0 @@
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MT32EMU_MT32EMU_H
|
||||
#define MT32EMU_MT32EMU_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
/* API Configuration */
|
||||
|
||||
/* 0: Use full-featured C++ API. Well suitable when the library is to be linked statically.
|
||||
* When the library is shared, ABI compatibility may be an issue. Therefore, it should
|
||||
* only be used within a project comprising of several modules to share the library code.
|
||||
* 1: Use C-compatible API. Make the library looks as a regular C library with well-defined ABI.
|
||||
* This is also crucial when the library is to be linked with modules in a different
|
||||
* language, either statically or dynamically.
|
||||
* 2: Use plugin-like API via C-interface wrapped in a C++ class. This is mainly intended
|
||||
* for a shared library being dynamically loaded in run-time. To get access to all the library
|
||||
* services, a client application only needs to bind with a single factory function.
|
||||
* 3: Use optimised C++ API compatible with the plugin API (type 2). The facade class also wraps
|
||||
* the C functions but they are invoked directly. This enables the compiler to generate better
|
||||
* code for the library when linked statically yet being consistent with the plugin-like API.
|
||||
*/
|
||||
|
||||
#ifdef MT32EMU_API_TYPE
|
||||
#if MT32EMU_API_TYPE == 0 && (MT32EMU_EXPORTS_TYPE == 1 || MT32EMU_EXPORTS_TYPE == 2)
|
||||
#error Incompatible setting MT32EMU_API_TYPE=0
|
||||
#elif MT32EMU_API_TYPE == 1 && (MT32EMU_EXPORTS_TYPE == 0 || MT32EMU_EXPORTS_TYPE == 2)
|
||||
#error Incompatible setting MT32EMU_API_TYPE=1
|
||||
#elif MT32EMU_API_TYPE == 2 && (MT32EMU_EXPORTS_TYPE == 0)
|
||||
#error Incompatible setting MT32EMU_API_TYPE=2
|
||||
#elif MT32EMU_API_TYPE == 3 && (MT32EMU_EXPORTS_TYPE == 0)
|
||||
#error Incompatible setting MT32EMU_API_TYPE=3
|
||||
#endif
|
||||
#else /* #ifdef MT32EMU_API_TYPE */
|
||||
#if 0 < MT32EMU_EXPORTS_TYPE && MT32EMU_EXPORTS_TYPE < 3
|
||||
#define MT32EMU_API_TYPE MT32EMU_EXPORTS_TYPE
|
||||
#else
|
||||
#define MT32EMU_API_TYPE 0
|
||||
#endif
|
||||
#endif /* #ifdef MT32EMU_API_TYPE */
|
||||
|
||||
/* MT32EMU_SHARED should be defined when building shared library, especially for Windows platforms. */
|
||||
/*
|
||||
#define MT32EMU_SHARED
|
||||
*/
|
||||
|
||||
#include "globals.h"
|
||||
|
||||
#if !defined(__cplusplus) || MT32EMU_API_TYPE == 1
|
||||
|
||||
#include "c_interface/c_interface.h"
|
||||
|
||||
#elif MT32EMU_API_TYPE == 2 || MT32EMU_API_TYPE == 3
|
||||
|
||||
#include "c_interface/cpp_interface.h"
|
||||
|
||||
#else /* #if !defined(__cplusplus) || MT32EMU_API_TYPE == 1 */
|
||||
|
||||
#include "Types.h"
|
||||
#include "File.h"
|
||||
#include "FileStream.h"
|
||||
#include "ROMInfo.h"
|
||||
#include "Synth.h"
|
||||
#include "MidiStreamParser.h"
|
||||
#include "SampleRateConverter.h"
|
||||
|
||||
#endif /* #if !defined(__cplusplus) || MT32EMU_API_TYPE == 1 */
|
||||
|
||||
#endif /* #ifndef MT32EMU_MT32EMU_H */
|
@ -1,185 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2011, Micael Hildenborg
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of Micael Hildenborg nor the
|
||||
names of its contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY Micael Hildenborg ''AS IS'' AND ANY
|
||||
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL Micael Hildenborg BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
/*
|
||||
Contributors:
|
||||
Gustav
|
||||
Several members in the gamedev.se forum.
|
||||
Gregory Petrosyan
|
||||
*/
|
||||
|
||||
#include "sha1.h"
|
||||
|
||||
namespace sha1
|
||||
{
|
||||
namespace // local
|
||||
{
|
||||
// Rotate an integer value to left.
|
||||
inline unsigned int rol(const unsigned int value,
|
||||
const unsigned int steps)
|
||||
{
|
||||
return ((value << steps) | (value >> (32 - steps)));
|
||||
}
|
||||
|
||||
// Sets the first 16 integers in the buffert to zero.
|
||||
// Used for clearing the W buffert.
|
||||
inline void clearWBuffert(unsigned int* buffert)
|
||||
{
|
||||
for (int pos = 16; --pos >= 0;)
|
||||
{
|
||||
buffert[pos] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void innerHash(unsigned int* result, unsigned int* w)
|
||||
{
|
||||
unsigned int a = result[0];
|
||||
unsigned int b = result[1];
|
||||
unsigned int c = result[2];
|
||||
unsigned int d = result[3];
|
||||
unsigned int e = result[4];
|
||||
|
||||
int round = 0;
|
||||
|
||||
#define sha1macro(func,val) \
|
||||
{ \
|
||||
const unsigned int t = rol(a, 5) + (func) + e + val + w[round]; \
|
||||
e = d; \
|
||||
d = c; \
|
||||
c = rol(b, 30); \
|
||||
b = a; \
|
||||
a = t; \
|
||||
}
|
||||
|
||||
while (round < 16)
|
||||
{
|
||||
sha1macro((b & c) | (~b & d), 0x5a827999)
|
||||
++round;
|
||||
}
|
||||
while (round < 20)
|
||||
{
|
||||
w[round] = rol((w[round - 3] ^ w[round - 8] ^ w[round - 14] ^ w[round - 16]), 1);
|
||||
sha1macro((b & c) | (~b & d), 0x5a827999)
|
||||
++round;
|
||||
}
|
||||
while (round < 40)
|
||||
{
|
||||
w[round] = rol((w[round - 3] ^ w[round - 8] ^ w[round - 14] ^ w[round - 16]), 1);
|
||||
sha1macro(b ^ c ^ d, 0x6ed9eba1)
|
||||
++round;
|
||||
}
|
||||
while (round < 60)
|
||||
{
|
||||
w[round] = rol((w[round - 3] ^ w[round - 8] ^ w[round - 14] ^ w[round - 16]), 1);
|
||||
sha1macro((b & c) | (b & d) | (c & d), 0x8f1bbcdc)
|
||||
++round;
|
||||
}
|
||||
while (round < 80)
|
||||
{
|
||||
w[round] = rol((w[round - 3] ^ w[round - 8] ^ w[round - 14] ^ w[round - 16]), 1);
|
||||
sha1macro(b ^ c ^ d, 0xca62c1d6)
|
||||
++round;
|
||||
}
|
||||
|
||||
#undef sha1macro
|
||||
|
||||
result[0] += a;
|
||||
result[1] += b;
|
||||
result[2] += c;
|
||||
result[3] += d;
|
||||
result[4] += e;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void calc(const void* src, const int bytelength, unsigned char* hash)
|
||||
{
|
||||
// Init the result array.
|
||||
unsigned int result[5] = { 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0 };
|
||||
|
||||
// Cast the void src pointer to be the byte array we can work with.
|
||||
const unsigned char* sarray = static_cast<const unsigned char*>(src);
|
||||
|
||||
// The reusable round buffer
|
||||
unsigned int w[80];
|
||||
|
||||
// Loop through all complete 64byte blocks.
|
||||
const int endOfFullBlocks = bytelength - 64;
|
||||
int endCurrentBlock;
|
||||
int currentBlock = 0;
|
||||
|
||||
while (currentBlock <= endOfFullBlocks)
|
||||
{
|
||||
endCurrentBlock = currentBlock + 64;
|
||||
|
||||
// Init the round buffer with the 64 byte block data.
|
||||
for (int roundPos = 0; currentBlock < endCurrentBlock; currentBlock += 4)
|
||||
{
|
||||
// This line will swap endian on big endian and keep endian on little endian.
|
||||
w[roundPos++] = static_cast<unsigned int>(sarray[currentBlock + 3])
|
||||
| (static_cast<unsigned int>(sarray[currentBlock + 2]) << 8)
|
||||
| (static_cast<unsigned int>(sarray[currentBlock + 1]) << 16)
|
||||
| (static_cast<unsigned int>(sarray[currentBlock]) << 24);
|
||||
}
|
||||
innerHash(result, w);
|
||||
}
|
||||
|
||||
// Handle the last and not full 64 byte block if existing.
|
||||
endCurrentBlock = bytelength - currentBlock;
|
||||
clearWBuffert(w);
|
||||
int lastBlockBytes = 0;
|
||||
for (;lastBlockBytes < endCurrentBlock; ++lastBlockBytes)
|
||||
{
|
||||
w[lastBlockBytes >> 2] |= static_cast<unsigned int>(sarray[lastBlockBytes + currentBlock]) << ((3 - (lastBlockBytes & 3)) << 3);
|
||||
}
|
||||
w[lastBlockBytes >> 2] |= 0x80 << ((3 - (lastBlockBytes & 3)) << 3);
|
||||
if (endCurrentBlock >= 56)
|
||||
{
|
||||
innerHash(result, w);
|
||||
clearWBuffert(w);
|
||||
}
|
||||
w[15] = bytelength << 3;
|
||||
innerHash(result, w);
|
||||
|
||||
// Store hash in result pointer, and make sure we get in in the correct order on both endian models.
|
||||
for (int hashByte = 20; --hashByte >= 0;)
|
||||
{
|
||||
hash[hashByte] = (result[hashByte >> 2] >> (((3 - hashByte) & 0x3) << 3)) & 0xff;
|
||||
}
|
||||
}
|
||||
|
||||
void toHexString(const unsigned char* hash, char* hexstring)
|
||||
{
|
||||
const char hexDigits[] = { "0123456789abcdef" };
|
||||
|
||||
for (int hashByte = 20; --hashByte >= 0;)
|
||||
{
|
||||
hexstring[hashByte << 1] = hexDigits[(hash[hashByte] >> 4) & 0xf];
|
||||
hexstring[(hashByte << 1) + 1] = hexDigits[hash[hashByte] & 0xf];
|
||||
}
|
||||
hexstring[40] = 0;
|
||||
}
|
||||
} // namespace sha1
|
@ -1,49 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2011, Micael Hildenborg
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of Micael Hildenborg nor the
|
||||
names of its contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY Micael Hildenborg ''AS IS'' AND ANY
|
||||
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL Micael Hildenborg BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef SHA1_DEFINED
|
||||
#define SHA1_DEFINED
|
||||
|
||||
namespace sha1
|
||||
{
|
||||
|
||||
/**
|
||||
@param src points to any kind of data to be hashed.
|
||||
@param bytelength the number of bytes to hash from the src pointer.
|
||||
@param hash should point to a buffer of at least 20 bytes of size for storing the sha1 result in.
|
||||
*/
|
||||
void calc(const void* src, const int bytelength, unsigned char* hash);
|
||||
|
||||
/**
|
||||
@param hash is 20 bytes of sha1 hash. This is the same data that is the result from the calc function.
|
||||
@param hexstring should point to a buffer of at least 41 bytes of size for storing the hexadecimal representation of the hash. A zero will be written at position 40, so the buffer will be a valid zero ended string.
|
||||
*/
|
||||
void toHexString(const unsigned char* hash, char* hexstring);
|
||||
|
||||
} // namespace sha1
|
||||
|
||||
#endif // SHA1_DEFINED
|
5
configure
vendored
5
configure
vendored
@ -944,7 +944,6 @@ Optional Features:
|
||||
--enable-profiling enable profiling
|
||||
--enable-plugins enable the support for dynamic plugins
|
||||
--default-dynamic make plugins dynamic by default
|
||||
--disable-mt32emu don't enable the integrated MT-32 emulator
|
||||
--disable-savegame-timestamp don't use timestamps for blank savegame descriptions
|
||||
--disable-translation don't build support for translated messages
|
||||
--disable-taskbar don't build support for taskbar and launcher integration
|
||||
@ -1139,8 +1138,8 @@ for ac_option in $@; do
|
||||
--enable-verbose-build) _verbose_build=yes ;;
|
||||
--enable-plugins) _dynamic_modules=yes ;;
|
||||
--default-dynamic) _plugins_default=dynamic ;;
|
||||
--enable-mt32emu) _mt32emu=yes ;;
|
||||
--disable-mt32emu) _mt32emu=no ;;
|
||||
# --enable-mt32emu) _mt32emu=yes ;; #ResidualVM: not supported
|
||||
# --disable-mt32emu) _mt32emu=no ;; #ResidualVM: not supported
|
||||
--enable-translation) _translation=yes ;;
|
||||
--disable-translation) _translation=no ;;
|
||||
--enable-vkeybd) _vkeybd=yes ;;
|
||||
|
@ -1045,7 +1045,7 @@ const Feature s_features[] = {
|
||||
{ "scalers", "USE_SCALERS", "", true, "Scalers" },
|
||||
{ "hqscalers", "USE_HQ_SCALERS", "", true, "HQ scalers" },
|
||||
{ "16bit", "USE_RGB_COLOR", "", true, "16bit color support" },
|
||||
{ "mt32emu", "USE_MT32EMU", "", false, "integrated MT-32 emulator" }, // ResidualVM change
|
||||
// { "mt32emu", "USE_MT32EMU", "", true, "integrated MT-32 emulator" }, // ResidualVM change
|
||||
{ "nasm", "USE_NASM", "", true, "IA-32 assembly support" }, // This feature is special in the regard, that it needs additional handling.
|
||||
{ "opengl", "USE_OPENGL", "", true, "OpenGL support" },
|
||||
{ "openglshaders", "USE_OPENGL_SHADERS", "", true, "OpenGL support (shaders)" }, // ResidualVM specific
|
||||
@ -1430,7 +1430,7 @@ void ProjectProvider::createProject(BuildSetup &setup) {
|
||||
createModuleList(setup.srcDir + "/graphics", setup.defines, setup.testDirs, in, ex);
|
||||
createModuleList(setup.srcDir + "/gui", setup.defines, setup.testDirs, in, ex);
|
||||
createModuleList(setup.srcDir + "/audio", setup.defines, setup.testDirs, in, ex);
|
||||
createModuleList(setup.srcDir + "/audio/softsynth/mt32", setup.defines, setup.testDirs, in, ex);
|
||||
// createModuleList(setup.srcDir + "/audio/softsynth/mt32", setup.defines, setup.testDirs, in, ex); // ResidualVM
|
||||
createModuleList(setup.srcDir + "/video", setup.defines, setup.testDirs, in, ex);
|
||||
createModuleList(setup.srcDir + "/image", setup.defines, setup.testDirs, in, ex);
|
||||
//ResidualVM specific:
|
||||
|
Loading…
Reference in New Issue
Block a user