mirror of
https://github.com/scummvm/scummvm.git
synced 2026-06-20 05:45:29 +00:00
This update uses upstream commit 777c51cdb4dbb4e02a53c23edea9086f0b600e26. The new SampleRateConverter is added, but not built as we don't use it. Also, building it without source changes will need additional include directories. This update of Munt reduces the stack size, and thus fixes bug #9630.
2111 lines
69 KiB
C++
Executable File
2111 lines
69 KiB
C++
Executable File
/* 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 "internals.h"
|
|
|
|
#include "Synth.h"
|
|
#include "Analog.h"
|
|
#include "BReverbModel.h"
|
|
#include "File.h"
|
|
#include "MemoryRegion.h"
|
|
#include "MidiEventQueue.h"
|
|
#include "Part.h"
|
|
#include "Partial.h"
|
|
#include "PartialManager.h"
|
|
#include "Poly.h"
|
|
#include "ROMInfo.h"
|
|
#include "TVA.h"
|
|
|
|
namespace MT32Emu {
|
|
|
|
// MIDI interface data transfer rate in samples. Used to simulate the transfer delay.
|
|
static const double MIDI_DATA_TRANSFER_RATE = double(SAMPLE_RATE) / 31250.0 * 8.0;
|
|
|
|
// FIXME: there should be more specific feature sets for various MT-32 control ROM versions
|
|
static const ControlROMFeatureSet OLD_MT32_COMPATIBLE = { true, true, true };
|
|
static const ControlROMFeatureSet CM32L_COMPATIBLE = { false, false, false };
|
|
|
|
static const ControlROMMap ControlROMMaps[7] = {
|
|
// ID Features PCMmap PCMc tmbrA tmbrAO, tmbrAC tmbrB tmbrBO tmbrBC tmbrR trC rhythm rhyC rsrv panpot prog rhyMax patMax sysMax timMax sndGrp sGC
|
|
{ "ctrl_mt32_1_04", OLD_MT32_COMPATIBLE, 0x3000, 128, 0x8000, 0x0000, false, 0xC000, 0x4000, false, 0x3200, 30, 0x73A6, 85, 0x57C7, 0x57E2, 0x57D0, 0x5252, 0x525E, 0x526E, 0x520A, 0x7064, 19 },
|
|
{ "ctrl_mt32_1_05", OLD_MT32_COMPATIBLE, 0x3000, 128, 0x8000, 0x0000, false, 0xC000, 0x4000, false, 0x3200, 30, 0x7414, 85, 0x57C7, 0x57E2, 0x57D0, 0x5252, 0x525E, 0x526E, 0x520A, 0x70CA, 19 },
|
|
{ "ctrl_mt32_1_06", OLD_MT32_COMPATIBLE, 0x3000, 128, 0x8000, 0x0000, false, 0xC000, 0x4000, false, 0x3200, 30, 0x7414, 85, 0x57D9, 0x57F4, 0x57E2, 0x5264, 0x5270, 0x5280, 0x521C, 0x70CA, 19 },
|
|
{ "ctrl_mt32_1_07", OLD_MT32_COMPATIBLE, 0x3000, 128, 0x8000, 0x0000, false, 0xC000, 0x4000, false, 0x3200, 30, 0x73fe, 85, 0x57B1, 0x57CC, 0x57BA, 0x523C, 0x5248, 0x5258, 0x51F4, 0x70B0, 19 }, // MT-32 revision 1
|
|
{"ctrl_mt32_bluer", OLD_MT32_COMPATIBLE, 0x3000, 128, 0x8000, 0x0000, false, 0xC000, 0x4000, false, 0x3200, 30, 0x741C, 85, 0x57E5, 0x5800, 0x57EE, 0x5270, 0x527C, 0x528C, 0x5228, 0x70CE, 19 }, // MT-32 Blue Ridge mod
|
|
{"ctrl_cm32l_1_00", CM32L_COMPATIBLE, 0x8100, 256, 0x8000, 0x8000, true, 0x8080, 0x8000, true, 0x8500, 64, 0x8580, 85, 0x4F65, 0x4F80, 0x4F6E, 0x48A1, 0x48A5, 0x48BE, 0x48D5, 0x5A6C, 19 },
|
|
{"ctrl_cm32l_1_02", CM32L_COMPATIBLE, 0x8100, 256, 0x8000, 0x8000, true, 0x8080, 0x8000, true, 0x8500, 64, 0x8580, 85, 0x4F93, 0x4FAE, 0x4F9C, 0x48CB, 0x48CF, 0x48E8, 0x48FF, 0x5A96, 19 } // CM-32L
|
|
// (Note that all but CM-32L ROM actually have 86 entries for rhythmTemp)
|
|
};
|
|
|
|
static const PartialState PARTIAL_PHASE_TO_STATE[8] = {
|
|
PartialState_ATTACK, PartialState_ATTACK, PartialState_ATTACK, PartialState_ATTACK,
|
|
PartialState_SUSTAIN, PartialState_SUSTAIN, PartialState_RELEASE, PartialState_INACTIVE
|
|
};
|
|
|
|
static inline PartialState getPartialState(PartialManager *partialManager, unsigned int partialNum) {
|
|
const Partial *partial = partialManager->getPartial(partialNum);
|
|
return partial->isActive() ? PARTIAL_PHASE_TO_STATE[partial->getTVA()->getPhase()] : PartialState_INACTIVE;
|
|
}
|
|
|
|
class SampleFormatConverter {
|
|
protected:
|
|
#if MT32EMU_USE_FLOAT_SAMPLES
|
|
Bit16s *outBuffer;
|
|
#else
|
|
float *outBuffer;
|
|
#endif
|
|
|
|
public:
|
|
Sample *sampleBuffer;
|
|
|
|
SampleFormatConverter(Sample *buffer) : outBuffer(NULL), sampleBuffer(buffer) {}
|
|
|
|
inline bool isConversionNeeded() {
|
|
return outBuffer != NULL;
|
|
}
|
|
|
|
inline void convert(Bit32u len) {
|
|
if (sampleBuffer == NULL) return;
|
|
if (outBuffer == NULL) {
|
|
sampleBuffer += len;
|
|
return;
|
|
}
|
|
Sample *inBuffer = sampleBuffer;
|
|
while (len--) {
|
|
*(outBuffer++) = Synth::convertSample(*(inBuffer++));
|
|
}
|
|
}
|
|
|
|
inline void addSilence(Bit32u len) {
|
|
if (outBuffer != NULL) {
|
|
Synth::muteSampleBuffer(outBuffer, len);
|
|
outBuffer += len;
|
|
} else if (sampleBuffer != NULL) {
|
|
Synth::muteSampleBuffer(sampleBuffer, len);
|
|
sampleBuffer += len;
|
|
}
|
|
}
|
|
};
|
|
|
|
template <int BUFFER_SIZE_MULTIPLIER = 1>
|
|
class BufferedSampleFormatConverter : public SampleFormatConverter {
|
|
Sample renderingBuffer[BUFFER_SIZE_MULTIPLIER * MAX_SAMPLES_PER_RUN];
|
|
|
|
public:
|
|
#if MT32EMU_USE_FLOAT_SAMPLES
|
|
BufferedSampleFormatConverter(Bit16s *buffer)
|
|
#else
|
|
BufferedSampleFormatConverter(float *buffer)
|
|
#endif
|
|
: SampleFormatConverter(renderingBuffer)
|
|
{
|
|
outBuffer = buffer;
|
|
if (buffer == NULL) sampleBuffer = NULL;
|
|
}
|
|
};
|
|
|
|
class Renderer {
|
|
Synth &synth;
|
|
|
|
// These buffers are used for building the output streams as they are found at the DAC entrance.
|
|
// The output is mixed down to stereo interleaved further in the analog circuitry emulation.
|
|
Sample tmpNonReverbLeft[MAX_SAMPLES_PER_RUN], tmpNonReverbRight[MAX_SAMPLES_PER_RUN];
|
|
Sample tmpReverbDryLeft[MAX_SAMPLES_PER_RUN], tmpReverbDryRight[MAX_SAMPLES_PER_RUN];
|
|
Sample tmpReverbWetLeft[MAX_SAMPLES_PER_RUN], tmpReverbWetRight[MAX_SAMPLES_PER_RUN];
|
|
|
|
public:
|
|
Renderer(Synth &useSynth) : synth(useSynth) {}
|
|
|
|
void render(SampleFormatConverter &converter, Bit32u len);
|
|
void renderStreams(SampleFormatConverter &nonReverbLeft, SampleFormatConverter &nonReverbRight, SampleFormatConverter &reverbDryLeft, SampleFormatConverter &reverbDryRight, SampleFormatConverter &reverbWetLeft, SampleFormatConverter &reverbWetRight, Bit32u len);
|
|
void produceLA32Output(Sample *buffer, Bit32u len);
|
|
void convertSamplesToOutput(Sample *buffer, Bit32u len);
|
|
void doRenderStreams(DACOutputStreams<Sample> &streams, Bit32u len);
|
|
};
|
|
|
|
Bit32u Synth::getLibraryVersionInt() {
|
|
return (MT32EMU_VERSION_MAJOR << 16) | (MT32EMU_VERSION_MINOR << 8) | (MT32EMU_VERSION_PATCH);
|
|
}
|
|
|
|
const char *Synth::getLibraryVersionString() {
|
|
return MT32EMU_VERSION;
|
|
}
|
|
|
|
Bit8u Synth::calcSysexChecksum(const Bit8u *data, const Bit32u len, const Bit8u initChecksum) {
|
|
unsigned int checksum = -initChecksum;
|
|
for (unsigned int i = 0; i < len; i++) {
|
|
checksum -= data[i];
|
|
}
|
|
return Bit8u(checksum & 0x7f);
|
|
}
|
|
|
|
Bit32u Synth::getStereoOutputSampleRate(AnalogOutputMode analogOutputMode) {
|
|
static const unsigned int SAMPLE_RATES[] = {SAMPLE_RATE, SAMPLE_RATE, SAMPLE_RATE * 3 / 2, SAMPLE_RATE * 3};
|
|
|
|
return SAMPLE_RATES[analogOutputMode];
|
|
}
|
|
|
|
Synth::Synth(ReportHandler *useReportHandler) : mt32ram(*new MemParams), mt32default(*new MemParams), renderer(*new Renderer(*this)) {
|
|
opened = false;
|
|
reverbOverridden = false;
|
|
partialCount = DEFAULT_MAX_PARTIALS;
|
|
controlROMMap = NULL;
|
|
controlROMFeatures = NULL;
|
|
|
|
if (useReportHandler == NULL) {
|
|
reportHandler = new ReportHandler;
|
|
isDefaultReportHandler = true;
|
|
} else {
|
|
reportHandler = useReportHandler;
|
|
isDefaultReportHandler = false;
|
|
}
|
|
|
|
for (int i = 0; i < 4; i++) {
|
|
reverbModels[i] = NULL;
|
|
}
|
|
reverbModel = NULL;
|
|
analog = NULL;
|
|
setDACInputMode(DACInputMode_NICE);
|
|
setMIDIDelayMode(MIDIDelayMode_DELAY_SHORT_MESSAGES_ONLY);
|
|
setOutputGain(1.0f);
|
|
setReverbOutputGain(1.0f);
|
|
setReversedStereoEnabled(false);
|
|
|
|
patchTempMemoryRegion = NULL;
|
|
rhythmTempMemoryRegion = NULL;
|
|
timbreTempMemoryRegion = NULL;
|
|
patchesMemoryRegion = NULL;
|
|
timbresMemoryRegion = NULL;
|
|
systemMemoryRegion = NULL;
|
|
displayMemoryRegion = NULL;
|
|
resetMemoryRegion = NULL;
|
|
paddedTimbreMaxTable = NULL;
|
|
|
|
partialManager = NULL;
|
|
pcmWaves = NULL;
|
|
pcmROMData = NULL;
|
|
soundGroupNames = NULL;
|
|
midiQueue = NULL;
|
|
lastReceivedMIDIEventTimestamp = 0;
|
|
memset(parts, 0, sizeof(parts));
|
|
renderedSampleCount = 0;
|
|
|
|
reserved = NULL;
|
|
}
|
|
|
|
Synth::~Synth() {
|
|
close(); // Make sure we're closed and everything is freed
|
|
if (isDefaultReportHandler) {
|
|
delete reportHandler;
|
|
}
|
|
delete &mt32ram;
|
|
delete &mt32default;
|
|
delete &renderer;
|
|
}
|
|
|
|
void ReportHandler::showLCDMessage(const char *data) {
|
|
printf("WRITE-LCD: %s\n", data);
|
|
}
|
|
|
|
void ReportHandler::printDebug(const char *fmt, va_list list) {
|
|
vprintf(fmt, list);
|
|
printf("\n");
|
|
}
|
|
|
|
void Synth::newTimbreSet(Bit8u partNum, Bit8u timbreGroup, Bit8u timbreNumber, const char patchName[]) {
|
|
const char *soundGroupName;
|
|
switch (timbreGroup) {
|
|
case 1:
|
|
timbreNumber += 64;
|
|
// Fall-through
|
|
case 0:
|
|
soundGroupName = soundGroupNames[soundGroupIx[timbreNumber]];
|
|
break;
|
|
case 2:
|
|
soundGroupName = soundGroupNames[controlROMMap->soundGroupsCount - 2];
|
|
break;
|
|
case 3:
|
|
soundGroupName = soundGroupNames[controlROMMap->soundGroupsCount - 1];
|
|
break;
|
|
default:
|
|
soundGroupName = NULL;
|
|
break;
|
|
}
|
|
reportHandler->onProgramChanged(partNum, soundGroupName, patchName);
|
|
}
|
|
|
|
void Synth::printDebug(const char *fmt, ...) {
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
#if MT32EMU_DEBUG_SAMPLESTAMPS > 0
|
|
reportHandler->printDebug("[%u]", (va_list)&renderedSampleCount);
|
|
#endif
|
|
reportHandler->printDebug(fmt, ap);
|
|
va_end(ap);
|
|
}
|
|
|
|
void Synth::setReverbEnabled(bool newReverbEnabled) {
|
|
if (!opened) return;
|
|
if (isReverbEnabled() == newReverbEnabled) return;
|
|
if (newReverbEnabled) {
|
|
bool oldReverbOverridden = reverbOverridden;
|
|
reverbOverridden = false;
|
|
refreshSystemReverbParameters();
|
|
reverbOverridden = oldReverbOverridden;
|
|
} else {
|
|
#if MT32EMU_REDUCE_REVERB_MEMORY
|
|
reverbModel->close();
|
|
#endif
|
|
reverbModel = NULL;
|
|
}
|
|
}
|
|
|
|
bool Synth::isReverbEnabled() const {
|
|
return reverbModel != NULL;
|
|
}
|
|
|
|
void Synth::setReverbOverridden(bool newReverbOverridden) {
|
|
reverbOverridden = newReverbOverridden;
|
|
}
|
|
|
|
bool Synth::isReverbOverridden() const {
|
|
return reverbOverridden;
|
|
}
|
|
|
|
void Synth::setReverbCompatibilityMode(bool mt32CompatibleMode) {
|
|
if (!opened || (isMT32ReverbCompatibilityMode() == mt32CompatibleMode)) return;
|
|
bool oldReverbEnabled = isReverbEnabled();
|
|
setReverbEnabled(false);
|
|
for (int i = 0; i < 4; i++) {
|
|
delete reverbModels[i];
|
|
}
|
|
initReverbModels(mt32CompatibleMode);
|
|
setReverbEnabled(oldReverbEnabled);
|
|
setReverbOutputGain(reverbOutputGain);
|
|
}
|
|
|
|
bool Synth::isMT32ReverbCompatibilityMode() const {
|
|
return opened && (reverbModels[REVERB_MODE_ROOM]->isMT32Compatible(REVERB_MODE_ROOM));
|
|
}
|
|
|
|
bool Synth::isDefaultReverbMT32Compatible() const {
|
|
return opened && controlROMFeatures->defaultReverbMT32Compatible;
|
|
}
|
|
|
|
void Synth::setDACInputMode(DACInputMode mode) {
|
|
#if MT32EMU_USE_FLOAT_SAMPLES
|
|
// We aren't emulating these in float mode, so better to inform the invoker
|
|
if ((mode == DACInputMode_GENERATION1) || (mode == DACInputMode_GENERATION2)) {
|
|
mode = DACInputMode_NICE;
|
|
}
|
|
#endif
|
|
dacInputMode = mode;
|
|
}
|
|
|
|
DACInputMode Synth::getDACInputMode() const {
|
|
return dacInputMode;
|
|
}
|
|
|
|
void Synth::setMIDIDelayMode(MIDIDelayMode mode) {
|
|
midiDelayMode = mode;
|
|
}
|
|
|
|
MIDIDelayMode Synth::getMIDIDelayMode() const {
|
|
return midiDelayMode;
|
|
}
|
|
|
|
void Synth::setOutputGain(float newOutputGain) {
|
|
if (newOutputGain < 0.0f) newOutputGain = -newOutputGain;
|
|
outputGain = newOutputGain;
|
|
if (analog != NULL) analog->setSynthOutputGain(newOutputGain);
|
|
}
|
|
|
|
float Synth::getOutputGain() const {
|
|
return outputGain;
|
|
}
|
|
|
|
void Synth::setReverbOutputGain(float newReverbOutputGain) {
|
|
if (newReverbOutputGain < 0.0f) newReverbOutputGain = -newReverbOutputGain;
|
|
reverbOutputGain = newReverbOutputGain;
|
|
if (analog != NULL) analog->setReverbOutputGain(newReverbOutputGain, isMT32ReverbCompatibilityMode());
|
|
}
|
|
|
|
float Synth::getReverbOutputGain() const {
|
|
return reverbOutputGain;
|
|
}
|
|
|
|
void Synth::setReversedStereoEnabled(bool enabled) {
|
|
reversedStereoEnabled = enabled;
|
|
}
|
|
|
|
bool Synth::isReversedStereoEnabled() const {
|
|
return reversedStereoEnabled;
|
|
}
|
|
|
|
bool Synth::loadControlROM(const ROMImage &controlROMImage) {
|
|
File *file = controlROMImage.getFile();
|
|
const ROMInfo *controlROMInfo = controlROMImage.getROMInfo();
|
|
if ((controlROMInfo == NULL)
|
|
|| (controlROMInfo->type != ROMInfo::Control)
|
|
|| (controlROMInfo->pairType != ROMInfo::Full)) {
|
|
#if MT32EMU_MONITOR_INIT
|
|
printDebug("Invalid Control ROM Info provided");
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
#if MT32EMU_MONITOR_INIT
|
|
printDebug("Found Control ROM: %s, %s", controlROMInfo->shortName, controlROMInfo->description);
|
|
#endif
|
|
const Bit8u *fileData = file->getData();
|
|
memcpy(controlROMData, fileData, CONTROL_ROM_SIZE);
|
|
|
|
// Control ROM successfully loaded, now check whether it's a known type
|
|
controlROMMap = NULL;
|
|
controlROMFeatures = NULL;
|
|
for (unsigned int i = 0; i < sizeof(ControlROMMaps) / sizeof(ControlROMMaps[0]); i++) {
|
|
if (strcmp(controlROMInfo->shortName, ControlROMMaps[i].shortName) == 0) {
|
|
controlROMMap = &ControlROMMaps[i];
|
|
controlROMFeatures = &controlROMMap->featureSet;
|
|
return true;
|
|
}
|
|
}
|
|
#if MT32EMU_MONITOR_INIT
|
|
printDebug("Control ROM failed to load");
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
bool Synth::loadPCMROM(const ROMImage &pcmROMImage) {
|
|
File *file = pcmROMImage.getFile();
|
|
const ROMInfo *pcmROMInfo = pcmROMImage.getROMInfo();
|
|
if ((pcmROMInfo == NULL)
|
|
|| (pcmROMInfo->type != ROMInfo::PCM)
|
|
|| (pcmROMInfo->pairType != ROMInfo::Full)) {
|
|
return false;
|
|
}
|
|
#if MT32EMU_MONITOR_INIT
|
|
printDebug("Found PCM ROM: %s, %s", pcmROMInfo->shortName, pcmROMInfo->description);
|
|
#endif
|
|
size_t fileSize = file->getSize();
|
|
if (fileSize != (2 * pcmROMSize)) {
|
|
#if MT32EMU_MONITOR_INIT
|
|
printDebug("PCM ROM file has wrong size (expected %d, got %d)", 2 * pcmROMSize, fileSize);
|
|
#endif
|
|
return false;
|
|
}
|
|
const Bit8u *fileData = file->getData();
|
|
for (size_t i = 0; i < pcmROMSize; i++) {
|
|
Bit8u s = *(fileData++);
|
|
Bit8u c = *(fileData++);
|
|
|
|
int order[16] = {0, 9, 1, 2, 3, 4, 5, 6, 7, 10, 11, 12, 13, 14, 15, 8};
|
|
|
|
Bit16s log = 0;
|
|
for (int u = 0; u < 15; u++) {
|
|
int bit;
|
|
if (order[u] < 8) {
|
|
bit = (s >> (7 - order[u])) & 0x1;
|
|
} else {
|
|
bit = (c >> (7 - (order[u] - 8))) & 0x1;
|
|
}
|
|
log = log | Bit16s(bit << (15 - u));
|
|
}
|
|
pcmROMData[i] = log;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool Synth::initPCMList(Bit16u mapAddress, Bit16u count) {
|
|
ControlROMPCMStruct *tps = reinterpret_cast<ControlROMPCMStruct *>(&controlROMData[mapAddress]);
|
|
for (int i = 0; i < count; i++) {
|
|
Bit32u rAddr = tps[i].pos * 0x800;
|
|
Bit32u rLenExp = (tps[i].len & 0x70) >> 4;
|
|
Bit32u rLen = 0x800 << rLenExp;
|
|
if (rAddr + rLen > pcmROMSize) {
|
|
printDebug("Control ROM error: Wave map entry %d points to invalid PCM address 0x%04X, length 0x%04X", i, rAddr, rLen);
|
|
return false;
|
|
}
|
|
pcmWaves[i].addr = rAddr;
|
|
pcmWaves[i].len = rLen;
|
|
pcmWaves[i].loop = (tps[i].len & 0x80) != 0;
|
|
pcmWaves[i].controlROMPCMStruct = &tps[i];
|
|
//int pitch = (tps[i].pitchMSB << 8) | tps[i].pitchLSB;
|
|
//bool unaffectedByMasterTune = (tps[i].len & 0x01) == 0;
|
|
//printDebug("PCM %d: pos=%d, len=%d, pitch=%d, loop=%s, unaffectedByMasterTune=%s", i, rAddr, rLen, pitch, pcmWaves[i].loop ? "YES" : "NO", unaffectedByMasterTune ? "YES" : "NO");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool Synth::initCompressedTimbre(Bit16u timbreNum, const Bit8u *src, Bit32u srcLen) {
|
|
// "Compressed" here means that muted partials aren't present in ROM (except in the case of partial 0 being muted).
|
|
// Instead the data from the previous unmuted partial is used.
|
|
if (srcLen < sizeof(TimbreParam::CommonParam)) {
|
|
return false;
|
|
}
|
|
TimbreParam *timbre = &mt32ram.timbres[timbreNum].timbre;
|
|
timbresMemoryRegion->write(timbreNum, 0, src, sizeof(TimbreParam::CommonParam), true);
|
|
unsigned int srcPos = sizeof(TimbreParam::CommonParam);
|
|
unsigned int memPos = sizeof(TimbreParam::CommonParam);
|
|
for (int t = 0; t < 4; t++) {
|
|
if (t != 0 && ((timbre->common.partialMute >> t) & 0x1) == 0x00) {
|
|
// This partial is muted - we'll copy the previously copied partial, then
|
|
srcPos -= sizeof(TimbreParam::PartialParam);
|
|
} else if (srcPos + sizeof(TimbreParam::PartialParam) >= srcLen) {
|
|
return false;
|
|
}
|
|
timbresMemoryRegion->write(timbreNum, memPos, src + srcPos, sizeof(TimbreParam::PartialParam));
|
|
srcPos += sizeof(TimbreParam::PartialParam);
|
|
memPos += sizeof(TimbreParam::PartialParam);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool Synth::initTimbres(Bit16u mapAddress, Bit16u offset, Bit16u count, Bit16u startTimbre, bool compressed) {
|
|
const Bit8u *timbreMap = &controlROMData[mapAddress];
|
|
for (Bit16u i = 0; i < count * 2; i += 2) {
|
|
Bit16u address = (timbreMap[i + 1] << 8) | timbreMap[i];
|
|
if (!compressed && (address + offset + sizeof(TimbreParam) > CONTROL_ROM_SIZE)) {
|
|
printDebug("Control ROM error: Timbre map entry 0x%04x for timbre %d points to invalid timbre address 0x%04x", i, startTimbre, address);
|
|
return false;
|
|
}
|
|
address += offset;
|
|
if (compressed) {
|
|
if (!initCompressedTimbre(startTimbre, &controlROMData[address], CONTROL_ROM_SIZE - address)) {
|
|
printDebug("Control ROM error: Timbre map entry 0x%04x for timbre %d points to invalid timbre at 0x%04x", i, startTimbre, address);
|
|
return false;
|
|
}
|
|
} else {
|
|
timbresMemoryRegion->write(startTimbre, 0, &controlROMData[address], sizeof(TimbreParam), true);
|
|
}
|
|
startTimbre++;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void Synth::initReverbModels(bool mt32CompatibleMode) {
|
|
reverbModels[REVERB_MODE_ROOM] = new BReverbModel(REVERB_MODE_ROOM, mt32CompatibleMode);
|
|
reverbModels[REVERB_MODE_HALL] = new BReverbModel(REVERB_MODE_HALL, mt32CompatibleMode);
|
|
reverbModels[REVERB_MODE_PLATE] = new BReverbModel(REVERB_MODE_PLATE, mt32CompatibleMode);
|
|
reverbModels[REVERB_MODE_TAP_DELAY] = new BReverbModel(REVERB_MODE_TAP_DELAY, mt32CompatibleMode);
|
|
#if !MT32EMU_REDUCE_REVERB_MEMORY
|
|
for (int i = REVERB_MODE_ROOM; i <= REVERB_MODE_TAP_DELAY; i++) {
|
|
reverbModels[i]->open();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void Synth::initSoundGroups(char newSoundGroupNames[][9]) {
|
|
memcpy(soundGroupIx, &controlROMData[controlROMMap->soundGroupsTable - sizeof(soundGroupIx)], sizeof(soundGroupIx));
|
|
const SoundGroup *table = reinterpret_cast<SoundGroup *>(&controlROMData[controlROMMap->soundGroupsTable]);
|
|
for (unsigned int i = 0; i < controlROMMap->soundGroupsCount; i++) {
|
|
memcpy(&newSoundGroupNames[i][0], table[i].name, sizeof(table[i].name));
|
|
}
|
|
}
|
|
|
|
bool Synth::open(const ROMImage &controlROMImage, const ROMImage &pcmROMImage, AnalogOutputMode analogOutputMode) {
|
|
return open(controlROMImage, pcmROMImage, DEFAULT_MAX_PARTIALS, analogOutputMode);
|
|
}
|
|
|
|
bool Synth::open(const ROMImage &controlROMImage, const ROMImage &pcmROMImage, Bit32u usePartialCount, AnalogOutputMode analogOutputMode) {
|
|
if (opened) {
|
|
return false;
|
|
}
|
|
partialCount = usePartialCount;
|
|
abortingPoly = NULL;
|
|
|
|
// This is to help detect bugs
|
|
memset(&mt32ram, '?', sizeof(mt32ram));
|
|
|
|
#if MT32EMU_MONITOR_INIT
|
|
printDebug("Loading Control ROM");
|
|
#endif
|
|
if (!loadControlROM(controlROMImage)) {
|
|
printDebug("Init Error - Missing or invalid Control ROM image");
|
|
reportHandler->onErrorControlROM();
|
|
dispose();
|
|
return false;
|
|
}
|
|
|
|
initMemoryRegions();
|
|
|
|
// 512KB PCM ROM for MT-32, etc.
|
|
// 1MB PCM ROM for CM-32L, LAPC-I, CM-64, CM-500
|
|
// Note that the size below is given in samples (16-bit), not bytes
|
|
pcmROMSize = controlROMMap->pcmCount == 256 ? 512 * 1024 : 256 * 1024;
|
|
pcmROMData = new Bit16s[pcmROMSize];
|
|
|
|
#if MT32EMU_MONITOR_INIT
|
|
printDebug("Loading PCM ROM");
|
|
#endif
|
|
if (!loadPCMROM(pcmROMImage)) {
|
|
printDebug("Init Error - Missing PCM ROM image");
|
|
reportHandler->onErrorPCMROM();
|
|
dispose();
|
|
return false;
|
|
}
|
|
|
|
#if MT32EMU_MONITOR_INIT
|
|
printDebug("Initialising Reverb Models");
|
|
#endif
|
|
bool mt32CompatibleReverb = controlROMFeatures->defaultReverbMT32Compatible;
|
|
#if MT32EMU_MONITOR_INIT
|
|
printDebug("Using %s Compatible Reverb Models", mt32CompatibleReverb ? "MT-32" : "CM-32L");
|
|
#endif
|
|
initReverbModels(mt32CompatibleReverb);
|
|
|
|
#if MT32EMU_MONITOR_INIT
|
|
printDebug("Initialising Timbre Bank A");
|
|
#endif
|
|
if (!initTimbres(controlROMMap->timbreAMap, controlROMMap->timbreAOffset, 0x40, 0, controlROMMap->timbreACompressed)) {
|
|
dispose();
|
|
return false;
|
|
}
|
|
|
|
#if MT32EMU_MONITOR_INIT
|
|
printDebug("Initialising Timbre Bank B");
|
|
#endif
|
|
if (!initTimbres(controlROMMap->timbreBMap, controlROMMap->timbreBOffset, 0x40, 64, controlROMMap->timbreBCompressed)) {
|
|
dispose();
|
|
return false;
|
|
}
|
|
|
|
#if MT32EMU_MONITOR_INIT
|
|
printDebug("Initialising Timbre Bank R");
|
|
#endif
|
|
if (!initTimbres(controlROMMap->timbreRMap, 0, controlROMMap->timbreRCount, 192, true)) {
|
|
dispose();
|
|
return false;
|
|
}
|
|
|
|
#if MT32EMU_MONITOR_INIT
|
|
printDebug("Initialising Timbre Bank M");
|
|
#endif
|
|
// CM-64 seems to initialise all bytes in this bank to 0.
|
|
memset(&mt32ram.timbres[128], 0, sizeof(mt32ram.timbres[128]) * 64);
|
|
|
|
partialManager = new PartialManager(this, parts);
|
|
|
|
pcmWaves = new PCMWaveEntry[controlROMMap->pcmCount];
|
|
|
|
#if MT32EMU_MONITOR_INIT
|
|
printDebug("Initialising PCM List");
|
|
#endif
|
|
initPCMList(controlROMMap->pcmTable, controlROMMap->pcmCount);
|
|
|
|
#if MT32EMU_MONITOR_INIT
|
|
printDebug("Initialising Rhythm Temp");
|
|
#endif
|
|
memcpy(mt32ram.rhythmTemp, &controlROMData[controlROMMap->rhythmSettings], controlROMMap->rhythmSettingsCount * 4);
|
|
|
|
#if MT32EMU_MONITOR_INIT
|
|
printDebug("Initialising Patches");
|
|
#endif
|
|
for (Bit8u i = 0; i < 128; i++) {
|
|
PatchParam *patch = &mt32ram.patches[i];
|
|
patch->timbreGroup = i / 64;
|
|
patch->timbreNum = i % 64;
|
|
patch->keyShift = 24;
|
|
patch->fineTune = 50;
|
|
patch->benderRange = 12;
|
|
patch->assignMode = 0;
|
|
patch->reverbSwitch = 1;
|
|
patch->dummy = 0;
|
|
}
|
|
|
|
#if MT32EMU_MONITOR_INIT
|
|
printDebug("Initialising System");
|
|
#endif
|
|
// The MT-32 manual claims that "Standard pitch" is 442Hz.
|
|
mt32ram.system.masterTune = 0x4A; // Confirmed on CM-64
|
|
mt32ram.system.reverbMode = 0; // Confirmed
|
|
mt32ram.system.reverbTime = 5; // Confirmed
|
|
mt32ram.system.reverbLevel = 3; // Confirmed
|
|
memcpy(mt32ram.system.reserveSettings, &controlROMData[controlROMMap->reserveSettings], 9); // Confirmed
|
|
for (Bit8u i = 0; i < 9; i++) {
|
|
// This is the default: {1, 2, 3, 4, 5, 6, 7, 8, 9}
|
|
// An alternative configuration can be selected by holding "Master Volume"
|
|
// and pressing "PART button 1" on the real MT-32's frontpanel.
|
|
// The channel assignment is then {0, 1, 2, 3, 4, 5, 6, 7, 9}
|
|
mt32ram.system.chanAssign[i] = i + 1;
|
|
}
|
|
mt32ram.system.masterVol = 100; // Confirmed
|
|
|
|
bool oldReverbOverridden = reverbOverridden;
|
|
reverbOverridden = false;
|
|
refreshSystem();
|
|
reverbOverridden = oldReverbOverridden;
|
|
|
|
char(*writableSoundGroupNames)[9] = new char[controlROMMap->soundGroupsCount][9];
|
|
soundGroupNames = writableSoundGroupNames;
|
|
initSoundGroups(writableSoundGroupNames);
|
|
|
|
for (int i = 0; i < 9; i++) {
|
|
MemParams::PatchTemp *patchTemp = &mt32ram.patchTemp[i];
|
|
|
|
// Note that except for the rhythm part, these patch fields will be set in setProgram() below anyway.
|
|
patchTemp->patch.timbreGroup = 0;
|
|
patchTemp->patch.timbreNum = 0;
|
|
patchTemp->patch.keyShift = 24;
|
|
patchTemp->patch.fineTune = 50;
|
|
patchTemp->patch.benderRange = 12;
|
|
patchTemp->patch.assignMode = 0;
|
|
patchTemp->patch.reverbSwitch = 1;
|
|
patchTemp->patch.dummy = 0;
|
|
|
|
patchTemp->outputLevel = 80;
|
|
patchTemp->panpot = controlROMData[controlROMMap->panSettings + i];
|
|
memset(patchTemp->dummyv, 0, sizeof(patchTemp->dummyv));
|
|
patchTemp->dummyv[1] = 127;
|
|
|
|
if (i < 8) {
|
|
parts[i] = new Part(this, i);
|
|
parts[i]->setProgram(controlROMData[controlROMMap->programSettings + i]);
|
|
} else {
|
|
parts[i] = new RhythmPart(this, i);
|
|
}
|
|
}
|
|
|
|
// For resetting mt32 mid-execution
|
|
mt32default = mt32ram;
|
|
|
|
midiQueue = new MidiEventQueue();
|
|
|
|
analog = new Analog(analogOutputMode, controlROMFeatures->oldMT32AnalogLPF);
|
|
setOutputGain(outputGain);
|
|
setReverbOutputGain(reverbOutputGain);
|
|
|
|
opened = true;
|
|
activated = false;
|
|
|
|
#if MT32EMU_MONITOR_INIT
|
|
printDebug("*** Initialisation complete ***");
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
void Synth::dispose() {
|
|
opened = false;
|
|
|
|
delete midiQueue;
|
|
midiQueue = NULL;
|
|
|
|
delete analog;
|
|
analog = NULL;
|
|
|
|
delete partialManager;
|
|
partialManager = NULL;
|
|
|
|
for (int i = 0; i < 9; i++) {
|
|
delete parts[i];
|
|
parts[i] = NULL;
|
|
}
|
|
|
|
delete[] soundGroupNames;
|
|
soundGroupNames = NULL;
|
|
|
|
delete[] pcmWaves;
|
|
pcmWaves = NULL;
|
|
|
|
delete[] pcmROMData;
|
|
pcmROMData = NULL;
|
|
|
|
deleteMemoryRegions();
|
|
|
|
for (int i = 0; i < 4; i++) {
|
|
delete reverbModels[i];
|
|
reverbModels[i] = NULL;
|
|
}
|
|
reverbModel = NULL;
|
|
controlROMFeatures = NULL;
|
|
controlROMMap = NULL;
|
|
}
|
|
|
|
void Synth::close() {
|
|
if (opened) {
|
|
dispose();
|
|
}
|
|
}
|
|
|
|
bool Synth::isOpen() const {
|
|
return opened;
|
|
}
|
|
|
|
void Synth::flushMIDIQueue() {
|
|
if (midiQueue != NULL) {
|
|
for (;;) {
|
|
const MidiEvent *midiEvent = midiQueue->peekMidiEvent();
|
|
if (midiEvent == NULL) break;
|
|
if (midiEvent->sysexData == NULL) {
|
|
playMsgNow(midiEvent->shortMessageData);
|
|
} else {
|
|
playSysexNow(midiEvent->sysexData, midiEvent->sysexLength);
|
|
}
|
|
midiQueue->dropMidiEvent();
|
|
}
|
|
lastReceivedMIDIEventTimestamp = renderedSampleCount;
|
|
}
|
|
}
|
|
|
|
Bit32u Synth::setMIDIEventQueueSize(Bit32u useSize) {
|
|
static const Bit32u MAX_QUEUE_SIZE = (1 << 24); // This results in about 256 Mb - much greater than any reasonable value
|
|
|
|
if (midiQueue == NULL) return 0;
|
|
flushMIDIQueue();
|
|
|
|
// Find a power of 2 that is >= useSize
|
|
Bit32u binarySize = 1;
|
|
if (useSize < MAX_QUEUE_SIZE) {
|
|
// Using simple linear search as this isn't time critical
|
|
while (binarySize < useSize) binarySize <<= 1;
|
|
} else {
|
|
binarySize = MAX_QUEUE_SIZE;
|
|
}
|
|
delete midiQueue;
|
|
midiQueue = new MidiEventQueue(binarySize);
|
|
return binarySize;
|
|
}
|
|
|
|
Bit32u Synth::getShortMessageLength(Bit32u msg) {
|
|
if ((msg & 0xF0) == 0xF0) {
|
|
switch (msg & 0xFF) {
|
|
case 0xF1:
|
|
case 0xF3:
|
|
return 2;
|
|
case 0xF2:
|
|
return 3;
|
|
default:
|
|
return 1;
|
|
}
|
|
}
|
|
// NOTE: This calculation isn't quite correct
|
|
// as it doesn't consider the running status byte
|
|
return ((msg & 0xE0) == 0xC0) ? 2 : 3;
|
|
}
|
|
|
|
Bit32u Synth::addMIDIInterfaceDelay(Bit32u len, Bit32u timestamp) {
|
|
Bit32u transferTime = Bit32u(double(len) * MIDI_DATA_TRANSFER_RATE);
|
|
// Dealing with wrapping
|
|
if (Bit32s(timestamp - lastReceivedMIDIEventTimestamp) < 0) {
|
|
timestamp = lastReceivedMIDIEventTimestamp;
|
|
}
|
|
timestamp += transferTime;
|
|
lastReceivedMIDIEventTimestamp = timestamp;
|
|
return timestamp;
|
|
}
|
|
|
|
bool Synth::playMsg(Bit32u msg) {
|
|
return playMsg(msg, renderedSampleCount);
|
|
}
|
|
|
|
bool Synth::playMsg(Bit32u msg, Bit32u timestamp) {
|
|
if ((msg & 0xF8) == 0xF8) {
|
|
reportHandler->onMIDISystemRealtime(Bit8u(msg));
|
|
return true;
|
|
}
|
|
if (midiQueue == NULL) return false;
|
|
if (midiDelayMode != MIDIDelayMode_IMMEDIATE) {
|
|
timestamp = addMIDIInterfaceDelay(getShortMessageLength(msg), timestamp);
|
|
}
|
|
if (!activated) activated = true;
|
|
do {
|
|
if (midiQueue->pushShortMessage(msg, timestamp)) return true;
|
|
} while (reportHandler->onMIDIQueueOverflow());
|
|
return false;
|
|
}
|
|
|
|
bool Synth::playSysex(const Bit8u *sysex, Bit32u len) {
|
|
return playSysex(sysex, len, renderedSampleCount);
|
|
}
|
|
|
|
bool Synth::playSysex(const Bit8u *sysex, Bit32u len, Bit32u timestamp) {
|
|
if (midiQueue == NULL) return false;
|
|
if (midiDelayMode == MIDIDelayMode_DELAY_ALL) {
|
|
timestamp = addMIDIInterfaceDelay(len, timestamp);
|
|
}
|
|
if (!activated) activated = true;
|
|
do {
|
|
if (midiQueue->pushSysex(sysex, len, timestamp)) return true;
|
|
} while (reportHandler->onMIDIQueueOverflow());
|
|
return false;
|
|
}
|
|
|
|
void Synth::playMsgNow(Bit32u msg) {
|
|
if (!opened) return;
|
|
|
|
// NOTE: Active sense IS implemented in real hardware. However, realtime processing is clearly out of the library scope.
|
|
// It is assumed that realtime consumers of the library respond to these MIDI events as appropriate.
|
|
|
|
Bit8u code = Bit8u((msg & 0x0000F0) >> 4);
|
|
Bit8u chan = Bit8u(msg & 0x00000F);
|
|
Bit8u note = Bit8u((msg & 0x007F00) >> 8);
|
|
Bit8u velocity = Bit8u((msg & 0x7F0000) >> 16);
|
|
|
|
//printDebug("Playing chan %d, code 0x%01x note: 0x%02x", chan, code, note);
|
|
|
|
Bit8u part = chantable[chan];
|
|
if (part > 8) {
|
|
#if MT32EMU_MONITOR_MIDI > 0
|
|
printDebug("Play msg on unreg chan %d (%d): code=0x%01x, vel=%d", chan, part, code, velocity);
|
|
#endif
|
|
return;
|
|
}
|
|
playMsgOnPart(part, code, note, velocity);
|
|
}
|
|
|
|
void Synth::playMsgOnPart(Bit8u part, Bit8u code, Bit8u note, Bit8u velocity) {
|
|
if (!opened) return;
|
|
|
|
Bit32u bend;
|
|
|
|
if (!activated) activated = true;
|
|
//printDebug("Synth::playMsgOnPart(%02x, %02x, %02x, %02x)", part, code, note, velocity);
|
|
switch (code) {
|
|
case 0x8:
|
|
//printDebug("Note OFF - Part %d", part);
|
|
// The MT-32 ignores velocity for note off
|
|
parts[part]->noteOff(note);
|
|
break;
|
|
case 0x9:
|
|
//printDebug("Note ON - Part %d, Note %d Vel %d", part, note, velocity);
|
|
if (velocity == 0) {
|
|
// MIDI defines note-on with velocity 0 as being the same as note-off with velocity 40
|
|
parts[part]->noteOff(note);
|
|
} else {
|
|
parts[part]->noteOn(note, velocity);
|
|
}
|
|
break;
|
|
case 0xB: // Control change
|
|
switch (note) {
|
|
case 0x01: // Modulation
|
|
//printDebug("Modulation: %d", velocity);
|
|
parts[part]->setModulation(velocity);
|
|
break;
|
|
case 0x06:
|
|
parts[part]->setDataEntryMSB(velocity);
|
|
break;
|
|
case 0x07: // Set volume
|
|
//printDebug("Volume set: %d", velocity);
|
|
parts[part]->setVolume(velocity);
|
|
break;
|
|
case 0x0A: // Pan
|
|
//printDebug("Pan set: %d", velocity);
|
|
parts[part]->setPan(velocity);
|
|
break;
|
|
case 0x0B:
|
|
//printDebug("Expression set: %d", velocity);
|
|
parts[part]->setExpression(velocity);
|
|
break;
|
|
case 0x40: // Hold (sustain) pedal
|
|
//printDebug("Hold pedal set: %d", velocity);
|
|
parts[part]->setHoldPedal(velocity >= 64);
|
|
break;
|
|
|
|
case 0x62:
|
|
case 0x63:
|
|
parts[part]->setNRPN();
|
|
break;
|
|
case 0x64:
|
|
parts[part]->setRPNLSB(velocity);
|
|
break;
|
|
case 0x65:
|
|
parts[part]->setRPNMSB(velocity);
|
|
break;
|
|
|
|
case 0x79: // Reset all controllers
|
|
//printDebug("Reset all controllers");
|
|
parts[part]->resetAllControllers();
|
|
break;
|
|
|
|
case 0x7B: // All notes off
|
|
//printDebug("All notes off");
|
|
parts[part]->allNotesOff();
|
|
break;
|
|
|
|
case 0x7C:
|
|
case 0x7D:
|
|
case 0x7E:
|
|
case 0x7F:
|
|
// CONFIRMED:Mok: A real LAPC-I responds to these controllers as follows:
|
|
parts[part]->setHoldPedal(false);
|
|
parts[part]->allNotesOff();
|
|
break;
|
|
|
|
default:
|
|
#if MT32EMU_MONITOR_MIDI > 0
|
|
printDebug("Unknown MIDI Control code: 0x%02x - vel 0x%02x", note, velocity);
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
break;
|
|
case 0xC: // Program change
|
|
//printDebug("Program change %01x", note);
|
|
parts[part]->setProgram(note);
|
|
break;
|
|
case 0xE: // Pitch bender
|
|
bend = (velocity << 7) | (note);
|
|
//printDebug("Pitch bender %02x", bend);
|
|
parts[part]->setBend(bend);
|
|
break;
|
|
default:
|
|
#if MT32EMU_MONITOR_MIDI > 0
|
|
printDebug("Unknown Midi code: 0x%01x - %02x - %02x", code, note, velocity);
|
|
#endif
|
|
return;
|
|
}
|
|
reportHandler->onMIDIMessagePlayed();
|
|
}
|
|
|
|
void Synth::playSysexNow(const Bit8u *sysex, Bit32u len) {
|
|
if (len < 2) {
|
|
printDebug("playSysex: Message is too short for sysex (%d bytes)", len);
|
|
}
|
|
if (sysex[0] != 0xF0) {
|
|
printDebug("playSysex: Message lacks start-of-sysex (0xF0)");
|
|
return;
|
|
}
|
|
// Due to some programs (e.g. Java) sending buffers with junk at the end, we have to go through and find the end marker rather than relying on len.
|
|
Bit32u endPos;
|
|
for (endPos = 1; endPos < len; endPos++) {
|
|
if (sysex[endPos] == 0xF7) {
|
|
break;
|
|
}
|
|
}
|
|
if (endPos == len) {
|
|
printDebug("playSysex: Message lacks end-of-sysex (0xf7)");
|
|
return;
|
|
}
|
|
playSysexWithoutFraming(sysex + 1, endPos - 1);
|
|
}
|
|
|
|
void Synth::playSysexWithoutFraming(const Bit8u *sysex, Bit32u len) {
|
|
if (len < 4) {
|
|
printDebug("playSysexWithoutFraming: Message is too short (%d bytes)!", len);
|
|
return;
|
|
}
|
|
if (sysex[0] != SYSEX_MANUFACTURER_ROLAND) {
|
|
printDebug("playSysexWithoutFraming: Header not intended for this device manufacturer: %02x %02x %02x %02x", int(sysex[0]), int(sysex[1]), int(sysex[2]), int(sysex[3]));
|
|
return;
|
|
}
|
|
if (sysex[2] == SYSEX_MDL_D50) {
|
|
printDebug("playSysexWithoutFraming: Header is intended for model D-50 (not yet supported): %02x %02x %02x %02x", int(sysex[0]), int(sysex[1]), int(sysex[2]), int(sysex[3]));
|
|
return;
|
|
} else if (sysex[2] != SYSEX_MDL_MT32) {
|
|
printDebug("playSysexWithoutFraming: Header not intended for model MT-32: %02x %02x %02x %02x", int(sysex[0]), int(sysex[1]), int(sysex[2]), int(sysex[3]));
|
|
return;
|
|
}
|
|
playSysexWithoutHeader(sysex[1], sysex[3], sysex + 4, len - 4);
|
|
}
|
|
|
|
void Synth::playSysexWithoutHeader(Bit8u device, Bit8u command, const Bit8u *sysex, Bit32u len) {
|
|
if (device > 0x10) {
|
|
// We have device ID 0x10 (default, but changeable, on real MT-32), < 0x10 is for channels
|
|
printDebug("playSysexWithoutHeader: Message is not intended for this device ID (provided: %02x, expected: 0x10 or channel)", int(device));
|
|
return;
|
|
}
|
|
// This is checked early in the real devices (before any sysex length checks or further processing)
|
|
// FIXME: Response to SYSEX_CMD_DAT reset with partials active (and in general) is untested.
|
|
if ((command == SYSEX_CMD_DT1 || command == SYSEX_CMD_DAT) && sysex[0] == 0x7F) {
|
|
reset();
|
|
return;
|
|
}
|
|
|
|
if (command == SYSEX_CMD_EOD) {
|
|
#if MT32EMU_MONITOR_SYSEX > 0
|
|
printDebug("playSysexWithoutHeader: Ignored unsupported command %02x", command);
|
|
#endif
|
|
return;
|
|
}
|
|
if (len < 4) {
|
|
printDebug("playSysexWithoutHeader: Message is too short (%d bytes)!", len);
|
|
return;
|
|
}
|
|
Bit8u checksum = calcSysexChecksum(sysex, len - 1);
|
|
if (checksum != sysex[len - 1]) {
|
|
printDebug("playSysexWithoutHeader: Message checksum is incorrect (provided: %02x, expected: %02x)!", sysex[len - 1], checksum);
|
|
return;
|
|
}
|
|
len -= 1; // Exclude checksum
|
|
switch (command) {
|
|
case SYSEX_CMD_WSD:
|
|
#if MT32EMU_MONITOR_SYSEX > 0
|
|
printDebug("playSysexWithoutHeader: Ignored unsupported command %02x", command);
|
|
#endif
|
|
break;
|
|
case SYSEX_CMD_DAT:
|
|
/* Outcommented until we (ever) actually implement handshake communication
|
|
if (hasActivePartials()) {
|
|
printDebug("playSysexWithoutHeader: Got SYSEX_CMD_DAT but partials are active - ignoring");
|
|
// FIXME: We should send SYSEX_CMD_RJC in this case
|
|
break;
|
|
}
|
|
*/
|
|
// Deliberate fall-through
|
|
case SYSEX_CMD_DT1:
|
|
writeSysex(device, sysex, len);
|
|
break;
|
|
case SYSEX_CMD_RQD:
|
|
if (hasActivePartials()) {
|
|
printDebug("playSysexWithoutHeader: Got SYSEX_CMD_RQD but partials are active - ignoring");
|
|
// FIXME: We should send SYSEX_CMD_RJC in this case
|
|
break;
|
|
}
|
|
// Deliberate fall-through
|
|
case SYSEX_CMD_RQ1:
|
|
readSysex(device, sysex, len);
|
|
break;
|
|
default:
|
|
printDebug("playSysexWithoutHeader: Unsupported command %02x", command);
|
|
return;
|
|
}
|
|
}
|
|
|
|
void Synth::readSysex(Bit8u /*device*/, const Bit8u * /*sysex*/, Bit32u /*len*/) const {
|
|
// NYI
|
|
}
|
|
|
|
void Synth::writeSysex(Bit8u device, const Bit8u *sysex, Bit32u len) {
|
|
if (!opened) return;
|
|
reportHandler->onMIDIMessagePlayed();
|
|
Bit32u addr = (sysex[0] << 16) | (sysex[1] << 8) | (sysex[2]);
|
|
addr = MT32EMU_MEMADDR(addr);
|
|
sysex += 3;
|
|
len -= 3;
|
|
//printDebug("Sysex addr: 0x%06x", MT32EMU_SYSEXMEMADDR(addr));
|
|
// NOTE: Please keep both lower and upper bounds in each check, for ease of reading
|
|
|
|
// Process channel-specific sysex by converting it to device-global
|
|
if (device < 0x10) {
|
|
#if MT32EMU_MONITOR_SYSEX > 0
|
|
printDebug("WRITE-CHANNEL: Channel %d temp area 0x%06x", device, MT32EMU_SYSEXMEMADDR(addr));
|
|
#endif
|
|
if (/*addr >= MT32EMU_MEMADDR(0x000000) && */addr < MT32EMU_MEMADDR(0x010000)) {
|
|
int offset;
|
|
if (chantable[device] > 8) {
|
|
#if MT32EMU_MONITOR_SYSEX > 0
|
|
printDebug(" (Channel not mapped to a part... 0 offset)");
|
|
#endif
|
|
offset = 0;
|
|
} else if (chantable[device] == 8) {
|
|
#if MT32EMU_MONITOR_SYSEX > 0
|
|
printDebug(" (Channel mapped to rhythm... 0 offset)");
|
|
#endif
|
|
offset = 0;
|
|
} else {
|
|
offset = chantable[device] * sizeof(MemParams::PatchTemp);
|
|
#if MT32EMU_MONITOR_SYSEX > 0
|
|
printDebug(" (Setting extra offset to %d)", offset);
|
|
#endif
|
|
}
|
|
addr += MT32EMU_MEMADDR(0x030000) + offset;
|
|
} else if (/*addr >= MT32EMU_MEMADDR(0x010000) && */ addr < MT32EMU_MEMADDR(0x020000)) {
|
|
addr += MT32EMU_MEMADDR(0x030110) - MT32EMU_MEMADDR(0x010000);
|
|
} else if (/*addr >= MT32EMU_MEMADDR(0x020000) && */ addr < MT32EMU_MEMADDR(0x030000)) {
|
|
int offset;
|
|
if (chantable[device] > 8) {
|
|
#if MT32EMU_MONITOR_SYSEX > 0
|
|
printDebug(" (Channel not mapped to a part... 0 offset)");
|
|
#endif
|
|
offset = 0;
|
|
} else if (chantable[device] == 8) {
|
|
#if MT32EMU_MONITOR_SYSEX > 0
|
|
printDebug(" (Channel mapped to rhythm... 0 offset)");
|
|
#endif
|
|
offset = 0;
|
|
} else {
|
|
offset = chantable[device] * sizeof(TimbreParam);
|
|
#if MT32EMU_MONITOR_SYSEX > 0
|
|
printDebug(" (Setting extra offset to %d)", offset);
|
|
#endif
|
|
}
|
|
addr += MT32EMU_MEMADDR(0x040000) - MT32EMU_MEMADDR(0x020000) + offset;
|
|
} else {
|
|
#if MT32EMU_MONITOR_SYSEX > 0
|
|
printDebug(" Invalid channel");
|
|
#endif
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Process device-global sysex (possibly converted from channel-specific sysex above)
|
|
for (;;) {
|
|
// Find the appropriate memory region
|
|
const MemoryRegion *region = findMemoryRegion(addr);
|
|
|
|
if (region == NULL) {
|
|
printDebug("Sysex write to unrecognised address %06x, len %d", MT32EMU_SYSEXMEMADDR(addr), len);
|
|
break;
|
|
}
|
|
writeMemoryRegion(region, addr, region->getClampedLen(addr, len), sysex);
|
|
|
|
Bit32u next = region->next(addr, len);
|
|
if (next == 0) {
|
|
break;
|
|
}
|
|
addr += next;
|
|
sysex += next;
|
|
len -= next;
|
|
}
|
|
}
|
|
|
|
void Synth::readMemory(Bit32u addr, Bit32u len, Bit8u *data) {
|
|
if (!opened) return;
|
|
const MemoryRegion *region = findMemoryRegion(addr);
|
|
if (region != NULL) {
|
|
readMemoryRegion(region, addr, len, data);
|
|
}
|
|
}
|
|
|
|
void Synth::initMemoryRegions() {
|
|
// Timbre max tables are slightly more complicated than the others, which are used directly from the ROM.
|
|
// The ROM (sensibly) just has maximums for TimbreParam.commonParam followed by just one TimbreParam.partialParam,
|
|
// so we produce a table with all partialParams filled out, as well as padding for PaddedTimbre, for quick lookup.
|
|
paddedTimbreMaxTable = new Bit8u[sizeof(MemParams::PaddedTimbre)];
|
|
memcpy(&paddedTimbreMaxTable[0], &controlROMData[controlROMMap->timbreMaxTable], sizeof(TimbreParam::CommonParam) + sizeof(TimbreParam::PartialParam)); // commonParam and one partialParam
|
|
int pos = sizeof(TimbreParam::CommonParam) + sizeof(TimbreParam::PartialParam);
|
|
for (int i = 0; i < 3; i++) {
|
|
memcpy(&paddedTimbreMaxTable[pos], &controlROMData[controlROMMap->timbreMaxTable + sizeof(TimbreParam::CommonParam)], sizeof(TimbreParam::PartialParam));
|
|
pos += sizeof(TimbreParam::PartialParam);
|
|
}
|
|
memset(&paddedTimbreMaxTable[pos], 0, 10); // Padding
|
|
patchTempMemoryRegion = new PatchTempMemoryRegion(this, reinterpret_cast<Bit8u *>(&mt32ram.patchTemp[0]), &controlROMData[controlROMMap->patchMaxTable]);
|
|
rhythmTempMemoryRegion = new RhythmTempMemoryRegion(this, reinterpret_cast<Bit8u *>(&mt32ram.rhythmTemp[0]), &controlROMData[controlROMMap->rhythmMaxTable]);
|
|
timbreTempMemoryRegion = new TimbreTempMemoryRegion(this, reinterpret_cast<Bit8u *>(&mt32ram.timbreTemp[0]), paddedTimbreMaxTable);
|
|
patchesMemoryRegion = new PatchesMemoryRegion(this, reinterpret_cast<Bit8u *>(&mt32ram.patches[0]), &controlROMData[controlROMMap->patchMaxTable]);
|
|
timbresMemoryRegion = new TimbresMemoryRegion(this, reinterpret_cast<Bit8u *>(&mt32ram.timbres[0]), paddedTimbreMaxTable);
|
|
systemMemoryRegion = new SystemMemoryRegion(this, reinterpret_cast<Bit8u *>(&mt32ram.system), &controlROMData[controlROMMap->systemMaxTable]);
|
|
displayMemoryRegion = new DisplayMemoryRegion(this);
|
|
resetMemoryRegion = new ResetMemoryRegion(this);
|
|
}
|
|
|
|
void Synth::deleteMemoryRegions() {
|
|
delete patchTempMemoryRegion;
|
|
patchTempMemoryRegion = NULL;
|
|
delete rhythmTempMemoryRegion;
|
|
rhythmTempMemoryRegion = NULL;
|
|
delete timbreTempMemoryRegion;
|
|
timbreTempMemoryRegion = NULL;
|
|
delete patchesMemoryRegion;
|
|
patchesMemoryRegion = NULL;
|
|
delete timbresMemoryRegion;
|
|
timbresMemoryRegion = NULL;
|
|
delete systemMemoryRegion;
|
|
systemMemoryRegion = NULL;
|
|
delete displayMemoryRegion;
|
|
displayMemoryRegion = NULL;
|
|
delete resetMemoryRegion;
|
|
resetMemoryRegion = NULL;
|
|
|
|
delete[] paddedTimbreMaxTable;
|
|
paddedTimbreMaxTable = NULL;
|
|
}
|
|
|
|
MemoryRegion *Synth::findMemoryRegion(Bit32u addr) {
|
|
MemoryRegion *regions[] = {
|
|
patchTempMemoryRegion,
|
|
rhythmTempMemoryRegion,
|
|
timbreTempMemoryRegion,
|
|
patchesMemoryRegion,
|
|
timbresMemoryRegion,
|
|
systemMemoryRegion,
|
|
displayMemoryRegion,
|
|
resetMemoryRegion,
|
|
NULL
|
|
};
|
|
for (int pos = 0; regions[pos] != NULL; pos++) {
|
|
if (regions[pos]->contains(addr)) {
|
|
return regions[pos];
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void Synth::readMemoryRegion(const MemoryRegion *region, Bit32u addr, Bit32u len, Bit8u *data) {
|
|
unsigned int first = region->firstTouched(addr);
|
|
//unsigned int last = region->lastTouched(addr, len);
|
|
unsigned int off = region->firstTouchedOffset(addr);
|
|
len = region->getClampedLen(addr, len);
|
|
|
|
unsigned int m;
|
|
|
|
if (region->isReadable()) {
|
|
region->read(first, off, data, len);
|
|
} else {
|
|
// FIXME: We might want to do these properly in future
|
|
for (m = 0; m < len; m += 2) {
|
|
data[m] = 0xff;
|
|
if (m + 1 < len) {
|
|
data[m+1] = Bit8u(region->type);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Synth::writeMemoryRegion(const MemoryRegion *region, Bit32u addr, Bit32u len, const Bit8u *data) {
|
|
unsigned int first = region->firstTouched(addr);
|
|
unsigned int last = region->lastTouched(addr, len);
|
|
unsigned int off = region->firstTouchedOffset(addr);
|
|
switch (region->type) {
|
|
case MR_PatchTemp:
|
|
region->write(first, off, data, len);
|
|
//printDebug("Patch temp: Patch %d, offset %x, len %d", off/16, off % 16, len);
|
|
|
|
for (unsigned int i = first; i <= last; i++) {
|
|
int absTimbreNum = mt32ram.patchTemp[i].patch.timbreGroup * 64 + mt32ram.patchTemp[i].patch.timbreNum;
|
|
char timbreName[11];
|
|
memcpy(timbreName, mt32ram.timbres[absTimbreNum].timbre.common.name, 10);
|
|
timbreName[10] = 0;
|
|
#if MT32EMU_MONITOR_SYSEX > 0
|
|
printDebug("WRITE-PARTPATCH (%d-%d@%d..%d): %d; timbre=%d (%s), outlevel=%d", first, last, off, off + len, i, absTimbreNum, timbreName, mt32ram.patchTemp[i].outputLevel);
|
|
#endif
|
|
if (parts[i] != NULL) {
|
|
if (i != 8) {
|
|
// Note: Confirmed on CM-64 that we definitely *should* update the timbre here,
|
|
// but only in the case that the sysex actually writes to those values
|
|
if (i == first && off > 2) {
|
|
#if MT32EMU_MONITOR_SYSEX > 0
|
|
printDebug(" (Not updating timbre, since those values weren't touched)");
|
|
#endif
|
|
} else {
|
|
parts[i]->setTimbre(&mt32ram.timbres[parts[i]->getAbsTimbreNum()].timbre);
|
|
}
|
|
}
|
|
parts[i]->refresh();
|
|
}
|
|
}
|
|
break;
|
|
case MR_RhythmTemp:
|
|
region->write(first, off, data, len);
|
|
for (unsigned int i = first; i <= last; i++) {
|
|
int timbreNum = mt32ram.rhythmTemp[i].timbre;
|
|
char timbreName[11];
|
|
if (timbreNum < 94) {
|
|
memcpy(timbreName, mt32ram.timbres[128 + timbreNum].timbre.common.name, 10);
|
|
timbreName[10] = 0;
|
|
} else {
|
|
strcpy(timbreName, "[None]");
|
|
}
|
|
#if MT32EMU_MONITOR_SYSEX > 0
|
|
printDebug("WRITE-RHYTHM (%d-%d@%d..%d): %d; level=%02x, panpot=%02x, reverb=%02x, timbre=%d (%s)", first, last, off, off + len, i, mt32ram.rhythmTemp[i].outputLevel, mt32ram.rhythmTemp[i].panpot, mt32ram.rhythmTemp[i].reverbSwitch, mt32ram.rhythmTemp[i].timbre, timbreName);
|
|
#endif
|
|
}
|
|
if (parts[8] != NULL) {
|
|
parts[8]->refresh();
|
|
}
|
|
break;
|
|
case MR_TimbreTemp:
|
|
region->write(first, off, data, len);
|
|
for (unsigned int i = first; i <= last; i++) {
|
|
char instrumentName[11];
|
|
memcpy(instrumentName, mt32ram.timbreTemp[i].common.name, 10);
|
|
instrumentName[10] = 0;
|
|
#if MT32EMU_MONITOR_SYSEX > 0
|
|
printDebug("WRITE-PARTTIMBRE (%d-%d@%d..%d): timbre=%d (%s)", first, last, off, off + len, i, instrumentName);
|
|
#endif
|
|
if (parts[i] != NULL) {
|
|
parts[i]->refresh();
|
|
}
|
|
}
|
|
break;
|
|
case MR_Patches:
|
|
region->write(first, off, data, len);
|
|
#if MT32EMU_MONITOR_SYSEX > 0
|
|
for (unsigned int i = first; i <= last; i++) {
|
|
PatchParam *patch = &mt32ram.patches[i];
|
|
int patchAbsTimbreNum = patch->timbreGroup * 64 + patch->timbreNum;
|
|
char instrumentName[11];
|
|
memcpy(instrumentName, mt32ram.timbres[patchAbsTimbreNum].timbre.common.name, 10);
|
|
instrumentName[10] = 0;
|
|
Bit8u *n = (Bit8u *)patch;
|
|
printDebug("WRITE-PATCH (%d-%d@%d..%d): %d; timbre=%d (%s) %02X%02X%02X%02X%02X%02X%02X%02X", first, last, off, off + len, i, patchAbsTimbreNum, instrumentName, n[0], n[1], n[2], n[3], n[4], n[5], n[6], n[7]);
|
|
}
|
|
#endif
|
|
break;
|
|
case MR_Timbres:
|
|
// Timbres
|
|
first += 128;
|
|
last += 128;
|
|
region->write(first, off, data, len);
|
|
for (unsigned int i = first; i <= last; i++) {
|
|
#if MT32EMU_MONITOR_TIMBRES >= 1
|
|
TimbreParam *timbre = &mt32ram.timbres[i].timbre;
|
|
char instrumentName[11];
|
|
memcpy(instrumentName, timbre->common.name, 10);
|
|
instrumentName[10] = 0;
|
|
printDebug("WRITE-TIMBRE (%d-%d@%d..%d): %d; name=\"%s\"", first, last, off, off + len, i, instrumentName);
|
|
#if MT32EMU_MONITOR_TIMBRES >= 2
|
|
#define DT(x) printDebug(" " #x ": %d", timbre->x)
|
|
DT(common.partialStructure12);
|
|
DT(common.partialStructure34);
|
|
DT(common.partialMute);
|
|
DT(common.noSustain);
|
|
|
|
#define DTP(x) \
|
|
DT(partial[x].wg.pitchCoarse); \
|
|
DT(partial[x].wg.pitchFine); \
|
|
DT(partial[x].wg.pitchKeyfollow); \
|
|
DT(partial[x].wg.pitchBenderEnabled); \
|
|
DT(partial[x].wg.waveform); \
|
|
DT(partial[x].wg.pcmWave); \
|
|
DT(partial[x].wg.pulseWidth); \
|
|
DT(partial[x].wg.pulseWidthVeloSensitivity); \
|
|
DT(partial[x].pitchEnv.depth); \
|
|
DT(partial[x].pitchEnv.veloSensitivity); \
|
|
DT(partial[x].pitchEnv.timeKeyfollow); \
|
|
DT(partial[x].pitchEnv.time[0]); \
|
|
DT(partial[x].pitchEnv.time[1]); \
|
|
DT(partial[x].pitchEnv.time[2]); \
|
|
DT(partial[x].pitchEnv.time[3]); \
|
|
DT(partial[x].pitchEnv.level[0]); \
|
|
DT(partial[x].pitchEnv.level[1]); \
|
|
DT(partial[x].pitchEnv.level[2]); \
|
|
DT(partial[x].pitchEnv.level[3]); \
|
|
DT(partial[x].pitchEnv.level[4]); \
|
|
DT(partial[x].pitchLFO.rate); \
|
|
DT(partial[x].pitchLFO.depth); \
|
|
DT(partial[x].pitchLFO.modSensitivity); \
|
|
DT(partial[x].tvf.cutoff); \
|
|
DT(partial[x].tvf.resonance); \
|
|
DT(partial[x].tvf.keyfollow); \
|
|
DT(partial[x].tvf.biasPoint); \
|
|
DT(partial[x].tvf.biasLevel); \
|
|
DT(partial[x].tvf.envDepth); \
|
|
DT(partial[x].tvf.envVeloSensitivity); \
|
|
DT(partial[x].tvf.envDepthKeyfollow); \
|
|
DT(partial[x].tvf.envTimeKeyfollow); \
|
|
DT(partial[x].tvf.envTime[0]); \
|
|
DT(partial[x].tvf.envTime[1]); \
|
|
DT(partial[x].tvf.envTime[2]); \
|
|
DT(partial[x].tvf.envTime[3]); \
|
|
DT(partial[x].tvf.envTime[4]); \
|
|
DT(partial[x].tvf.envLevel[0]); \
|
|
DT(partial[x].tvf.envLevel[1]); \
|
|
DT(partial[x].tvf.envLevel[2]); \
|
|
DT(partial[x].tvf.envLevel[3]); \
|
|
DT(partial[x].tva.level); \
|
|
DT(partial[x].tva.veloSensitivity); \
|
|
DT(partial[x].tva.biasPoint1); \
|
|
DT(partial[x].tva.biasLevel1); \
|
|
DT(partial[x].tva.biasPoint2); \
|
|
DT(partial[x].tva.biasLevel2); \
|
|
DT(partial[x].tva.envTimeKeyfollow); \
|
|
DT(partial[x].tva.envTimeVeloSensitivity); \
|
|
DT(partial[x].tva.envTime[0]); \
|
|
DT(partial[x].tva.envTime[1]); \
|
|
DT(partial[x].tva.envTime[2]); \
|
|
DT(partial[x].tva.envTime[3]); \
|
|
DT(partial[x].tva.envTime[4]); \
|
|
DT(partial[x].tva.envLevel[0]); \
|
|
DT(partial[x].tva.envLevel[1]); \
|
|
DT(partial[x].tva.envLevel[2]); \
|
|
DT(partial[x].tva.envLevel[3]);
|
|
|
|
DTP(0);
|
|
DTP(1);
|
|
DTP(2);
|
|
DTP(3);
|
|
#undef DTP
|
|
#undef DT
|
|
#endif
|
|
#endif
|
|
// FIXME:KG: Not sure if the stuff below should be done (for rhythm and/or parts)...
|
|
// Does the real MT-32 automatically do this?
|
|
for (unsigned int part = 0; part < 9; part++) {
|
|
if (parts[part] != NULL) {
|
|
parts[part]->refreshTimbre(i);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case MR_System:
|
|
region->write(0, off, data, len);
|
|
|
|
reportHandler->onDeviceReconfig();
|
|
// FIXME: We haven't properly confirmed any of this behaviour
|
|
// In particular, we tend to reset things such as reverb even if the write contained
|
|
// the same parameters as were already set, which may be wrong.
|
|
// On the other hand, the real thing could be resetting things even when they aren't touched
|
|
// by the write at all.
|
|
#if MT32EMU_MONITOR_SYSEX > 0
|
|
printDebug("WRITE-SYSTEM:");
|
|
#endif
|
|
if (off <= SYSTEM_MASTER_TUNE_OFF && off + len > SYSTEM_MASTER_TUNE_OFF) {
|
|
refreshSystemMasterTune();
|
|
}
|
|
if (off <= SYSTEM_REVERB_LEVEL_OFF && off + len > SYSTEM_REVERB_MODE_OFF) {
|
|
refreshSystemReverbParameters();
|
|
}
|
|
if (off <= SYSTEM_RESERVE_SETTINGS_END_OFF && off + len > SYSTEM_RESERVE_SETTINGS_START_OFF) {
|
|
refreshSystemReserveSettings();
|
|
}
|
|
if (off <= SYSTEM_CHAN_ASSIGN_END_OFF && off + len > SYSTEM_CHAN_ASSIGN_START_OFF) {
|
|
int firstPart = off - SYSTEM_CHAN_ASSIGN_START_OFF;
|
|
if(firstPart < 0)
|
|
firstPart = 0;
|
|
int lastPart = off + len - SYSTEM_CHAN_ASSIGN_START_OFF;
|
|
if(lastPart > 8)
|
|
lastPart = 8;
|
|
refreshSystemChanAssign(Bit8u(firstPart), Bit8u(lastPart));
|
|
}
|
|
if (off <= SYSTEM_MASTER_VOL_OFF && off + len > SYSTEM_MASTER_VOL_OFF) {
|
|
refreshSystemMasterVol();
|
|
}
|
|
break;
|
|
case MR_Display:
|
|
char buf[SYSEX_BUFFER_SIZE];
|
|
memcpy(&buf, &data[0], len);
|
|
buf[len] = 0;
|
|
#if MT32EMU_MONITOR_SYSEX > 0
|
|
printDebug("WRITE-LCD: %s", buf);
|
|
#endif
|
|
reportHandler->showLCDMessage(buf);
|
|
break;
|
|
case MR_Reset:
|
|
reset();
|
|
break;
|
|
}
|
|
}
|
|
|
|
void Synth::refreshSystemMasterTune() {
|
|
#if MT32EMU_MONITOR_SYSEX > 0
|
|
//FIXME:KG: This is just an educated guess.
|
|
// The LAPC-I documentation claims a range of 427.5Hz-452.6Hz (similar to what we have here)
|
|
// The MT-32 documentation claims a range of 432.1Hz-457.6Hz
|
|
float masterTune = 440.0f * EXP2F((mt32ram.system.masterTune - 64.0f) / (128.0f * 12.0f));
|
|
printDebug(" Master Tune: %f", masterTune);
|
|
#endif
|
|
}
|
|
|
|
void Synth::refreshSystemReverbParameters() {
|
|
#if MT32EMU_MONITOR_SYSEX > 0
|
|
printDebug(" Reverb: mode=%d, time=%d, level=%d", mt32ram.system.reverbMode, mt32ram.system.reverbTime, mt32ram.system.reverbLevel);
|
|
#endif
|
|
if (reverbOverridden) {
|
|
#if MT32EMU_MONITOR_SYSEX > 0
|
|
printDebug(" (Reverb overridden - ignoring)");
|
|
#endif
|
|
return;
|
|
}
|
|
reportHandler->onNewReverbMode(mt32ram.system.reverbMode);
|
|
reportHandler->onNewReverbTime(mt32ram.system.reverbTime);
|
|
reportHandler->onNewReverbLevel(mt32ram.system.reverbLevel);
|
|
|
|
BReverbModel *oldReverbModel = reverbModel;
|
|
if (mt32ram.system.reverbTime == 0 && mt32ram.system.reverbLevel == 0) {
|
|
// Setting both time and level to 0 effectively disables wet reverb output on real devices.
|
|
// Take a shortcut in this case to reduce CPU load.
|
|
reverbModel = NULL;
|
|
} else {
|
|
reverbModel = reverbModels[mt32ram.system.reverbMode];
|
|
}
|
|
if (reverbModel != oldReverbModel) {
|
|
#if MT32EMU_REDUCE_REVERB_MEMORY
|
|
if (oldReverbModel != NULL) {
|
|
oldReverbModel->close();
|
|
}
|
|
if (isReverbEnabled()) {
|
|
reverbModel->open();
|
|
}
|
|
#else
|
|
if (isReverbEnabled()) {
|
|
reverbModel->mute();
|
|
}
|
|
#endif
|
|
}
|
|
if (isReverbEnabled()) {
|
|
reverbModel->setParameters(mt32ram.system.reverbTime, mt32ram.system.reverbLevel);
|
|
}
|
|
}
|
|
|
|
void Synth::refreshSystemReserveSettings() {
|
|
Bit8u *rset = mt32ram.system.reserveSettings;
|
|
#if MT32EMU_MONITOR_SYSEX > 0
|
|
printDebug(" Partial reserve: 1=%02d 2=%02d 3=%02d 4=%02d 5=%02d 6=%02d 7=%02d 8=%02d Rhythm=%02d", rset[0], rset[1], rset[2], rset[3], rset[4], rset[5], rset[6], rset[7], rset[8]);
|
|
#endif
|
|
partialManager->setReserve(rset);
|
|
}
|
|
|
|
void Synth::refreshSystemChanAssign(Bit8u firstPart, Bit8u lastPart) {
|
|
memset(chantable, 0xFF, sizeof(chantable));
|
|
|
|
// CONFIRMED: In the case of assigning a channel to multiple parts, the lower part wins.
|
|
for (Bit32u i = 0; i <= 8; i++) {
|
|
if (parts[i] != NULL && i >= firstPart && i <= lastPart) {
|
|
// CONFIRMED: Decay is started for all polys, and all controllers are reset, for every part whose assignment was touched by the sysex write.
|
|
parts[i]->allSoundOff();
|
|
parts[i]->resetAllControllers();
|
|
}
|
|
Bit8u chan = mt32ram.system.chanAssign[i];
|
|
if (chan < 16 && chantable[chan] > 8) {
|
|
chantable[chan] = Bit8u(i);
|
|
}
|
|
}
|
|
|
|
#if MT32EMU_MONITOR_SYSEX > 0
|
|
Bit8u *rset = mt32ram.system.chanAssign;
|
|
printDebug(" Part assign: 1=%02d 2=%02d 3=%02d 4=%02d 5=%02d 6=%02d 7=%02d 8=%02d Rhythm=%02d", rset[0], rset[1], rset[2], rset[3], rset[4], rset[5], rset[6], rset[7], rset[8]);
|
|
#endif
|
|
}
|
|
|
|
void Synth::refreshSystemMasterVol() {
|
|
#if MT32EMU_MONITOR_SYSEX > 0
|
|
printDebug(" Master volume: %d", mt32ram.system.masterVol);
|
|
#endif
|
|
}
|
|
|
|
void Synth::refreshSystem() {
|
|
refreshSystemMasterTune();
|
|
refreshSystemReverbParameters();
|
|
refreshSystemReserveSettings();
|
|
refreshSystemChanAssign(0, 8);
|
|
refreshSystemMasterVol();
|
|
}
|
|
|
|
void Synth::reset() {
|
|
if (!opened) return;
|
|
#if MT32EMU_MONITOR_SYSEX > 0
|
|
printDebug("RESET");
|
|
#endif
|
|
reportHandler->onDeviceReset();
|
|
partialManager->deactivateAll();
|
|
mt32ram = mt32default;
|
|
for (int i = 0; i < 9; i++) {
|
|
parts[i]->reset();
|
|
if (i != 8) {
|
|
parts[i]->setProgram(controlROMData[controlROMMap->programSettings + i]);
|
|
} else {
|
|
parts[8]->refresh();
|
|
}
|
|
}
|
|
refreshSystem();
|
|
isActive();
|
|
}
|
|
|
|
MidiEvent::~MidiEvent() {
|
|
if (sysexData != NULL) {
|
|
delete[] sysexData;
|
|
}
|
|
}
|
|
|
|
void MidiEvent::setShortMessage(Bit32u useShortMessageData, Bit32u useTimestamp) {
|
|
if (sysexData != NULL) {
|
|
delete[] sysexData;
|
|
}
|
|
shortMessageData = useShortMessageData;
|
|
timestamp = useTimestamp;
|
|
sysexData = NULL;
|
|
sysexLength = 0;
|
|
}
|
|
|
|
void MidiEvent::setSysex(const Bit8u *useSysexData, Bit32u useSysexLength, Bit32u useTimestamp) {
|
|
if (sysexData != NULL) {
|
|
delete[] sysexData;
|
|
}
|
|
shortMessageData = 0;
|
|
timestamp = useTimestamp;
|
|
sysexLength = useSysexLength;
|
|
Bit8u *dstSysexData = new Bit8u[sysexLength];
|
|
sysexData = dstSysexData;
|
|
memcpy(dstSysexData, useSysexData, sysexLength);
|
|
}
|
|
|
|
MidiEventQueue::MidiEventQueue(Bit32u useRingBufferSize) : ringBuffer(new MidiEvent[useRingBufferSize]), ringBufferMask(useRingBufferSize - 1) {
|
|
memset(ringBuffer, 0, useRingBufferSize * sizeof(MidiEvent));
|
|
reset();
|
|
}
|
|
|
|
MidiEventQueue::~MidiEventQueue() {
|
|
delete[] ringBuffer;
|
|
}
|
|
|
|
void MidiEventQueue::reset() {
|
|
startPosition = 0;
|
|
endPosition = 0;
|
|
}
|
|
|
|
bool MidiEventQueue::pushShortMessage(Bit32u shortMessageData, Bit32u timestamp) {
|
|
Bit32u newEndPosition = (endPosition + 1) & ringBufferMask;
|
|
// Is ring buffer full?
|
|
if (startPosition == newEndPosition) return false;
|
|
ringBuffer[endPosition].setShortMessage(shortMessageData, timestamp);
|
|
endPosition = newEndPosition;
|
|
return true;
|
|
}
|
|
|
|
bool MidiEventQueue::pushSysex(const Bit8u *sysexData, Bit32u sysexLength, Bit32u timestamp) {
|
|
Bit32u newEndPosition = (endPosition + 1) & ringBufferMask;
|
|
// Is ring buffer full?
|
|
if (startPosition == newEndPosition) return false;
|
|
ringBuffer[endPosition].setSysex(sysexData, sysexLength, timestamp);
|
|
endPosition = newEndPosition;
|
|
return true;
|
|
}
|
|
|
|
const MidiEvent *MidiEventQueue::peekMidiEvent() {
|
|
return isEmpty() ? NULL : &ringBuffer[startPosition];
|
|
}
|
|
|
|
void MidiEventQueue::dropMidiEvent() {
|
|
// Is ring buffer empty?
|
|
if (startPosition != endPosition) {
|
|
startPosition = (startPosition + 1) & ringBufferMask;
|
|
}
|
|
}
|
|
|
|
bool MidiEventQueue::isFull() const {
|
|
return startPosition == ((endPosition + 1) & ringBufferMask);
|
|
}
|
|
|
|
bool MidiEventQueue::isEmpty() const {
|
|
return startPosition == endPosition;
|
|
}
|
|
|
|
Bit32u Synth::getStereoOutputSampleRate() const {
|
|
return (analog == NULL) ? SAMPLE_RATE : analog->getOutputSampleRate();
|
|
}
|
|
|
|
void Renderer::render(SampleFormatConverter &converter, Bit32u len) {
|
|
if (!synth.opened) {
|
|
converter.addSilence(len << 1);
|
|
return;
|
|
}
|
|
|
|
if (!synth.activated) {
|
|
synth.renderedSampleCount += synth.analog->getDACStreamsLength(len);
|
|
synth.analog->process(NULL, NULL, NULL, NULL, NULL, NULL, NULL, len);
|
|
converter.addSilence(len << 1);
|
|
return;
|
|
}
|
|
|
|
while (len > 0) {
|
|
// As in AnalogOutputMode_ACCURATE mode output is upsampled, MAX_SAMPLES_PER_RUN is more than enough for the temp buffers.
|
|
Bit32u thisPassLen = len > MAX_SAMPLES_PER_RUN ? MAX_SAMPLES_PER_RUN : len;
|
|
synth.renderStreams(tmpNonReverbLeft, tmpNonReverbRight, tmpReverbDryLeft, tmpReverbDryRight, tmpReverbWetLeft, tmpReverbWetRight, synth.analog->getDACStreamsLength(thisPassLen));
|
|
synth.analog->process(converter.sampleBuffer, tmpNonReverbLeft, tmpNonReverbRight, tmpReverbDryLeft, tmpReverbDryRight, tmpReverbWetLeft, tmpReverbWetRight, thisPassLen);
|
|
converter.convert(thisPassLen << 1);
|
|
len -= thisPassLen;
|
|
}
|
|
}
|
|
|
|
void Synth::render(Bit16s *stream, Bit32u len) {
|
|
#if MT32EMU_USE_FLOAT_SAMPLES
|
|
BufferedSampleFormatConverter<2> converter(stream);
|
|
#else
|
|
SampleFormatConverter converter(stream);
|
|
#endif
|
|
renderer.render(converter, len);
|
|
}
|
|
|
|
void Synth::render(float *stream, Bit32u len) {
|
|
#if MT32EMU_USE_FLOAT_SAMPLES
|
|
SampleFormatConverter converter(stream);
|
|
#else
|
|
BufferedSampleFormatConverter<2> converter(stream);
|
|
#endif
|
|
renderer.render(converter, len);
|
|
}
|
|
|
|
void Renderer::renderStreams(
|
|
SampleFormatConverter &nonReverbLeft, SampleFormatConverter &nonReverbRight,
|
|
SampleFormatConverter &reverbDryLeft, SampleFormatConverter &reverbDryRight,
|
|
SampleFormatConverter &reverbWetLeft, SampleFormatConverter &reverbWetRight,
|
|
Bit32u len)
|
|
{
|
|
if (!synth.opened) {
|
|
nonReverbLeft.addSilence(len);
|
|
nonReverbRight.addSilence(len);
|
|
reverbDryLeft.addSilence(len);
|
|
reverbDryRight.addSilence(len);
|
|
reverbWetLeft.addSilence(len);
|
|
reverbWetRight.addSilence(len);
|
|
return;
|
|
}
|
|
|
|
while (len > 0) {
|
|
// We need to ensure zero-duration notes will play so add minimum 1-sample delay.
|
|
Bit32u thisLen = 1;
|
|
if (!synth.isAbortingPoly()) {
|
|
const MidiEvent *nextEvent = synth.midiQueue->peekMidiEvent();
|
|
Bit32s samplesToNextEvent = (nextEvent != NULL) ? Bit32s(nextEvent->timestamp - synth.renderedSampleCount) : MAX_SAMPLES_PER_RUN;
|
|
if (samplesToNextEvent > 0) {
|
|
thisLen = len > MAX_SAMPLES_PER_RUN ? MAX_SAMPLES_PER_RUN : len;
|
|
if (thisLen > Bit32u(samplesToNextEvent)) {
|
|
thisLen = samplesToNextEvent;
|
|
}
|
|
} else {
|
|
if (nextEvent->sysexData == NULL) {
|
|
synth.playMsgNow(nextEvent->shortMessageData);
|
|
// If a poly is aborting we don't drop the event from the queue.
|
|
// Instead, we'll return to it again when the abortion is done.
|
|
if (!synth.isAbortingPoly()) {
|
|
synth.midiQueue->dropMidiEvent();
|
|
}
|
|
} else {
|
|
synth.playSysexNow(nextEvent->sysexData, nextEvent->sysexLength);
|
|
synth.midiQueue->dropMidiEvent();
|
|
}
|
|
}
|
|
}
|
|
DACOutputStreams<Sample> streams = {
|
|
nonReverbLeft.sampleBuffer, nonReverbRight.sampleBuffer,
|
|
reverbDryLeft.sampleBuffer, reverbDryRight.sampleBuffer,
|
|
reverbWetLeft.sampleBuffer, reverbWetRight.sampleBuffer
|
|
};
|
|
doRenderStreams(streams, thisLen);
|
|
nonReverbLeft.convert(thisLen);
|
|
nonReverbRight.convert(thisLen);
|
|
reverbDryLeft.convert(thisLen);
|
|
reverbDryRight.convert(thisLen);
|
|
reverbWetLeft.convert(thisLen);
|
|
reverbWetRight.convert(thisLen);
|
|
len -= thisLen;
|
|
}
|
|
}
|
|
|
|
void Synth::renderStreams(
|
|
Bit16s *nonReverbLeft, Bit16s *nonReverbRight,
|
|
Bit16s *reverbDryLeft, Bit16s *reverbDryRight,
|
|
Bit16s *reverbWetLeft, Bit16s *reverbWetRight,
|
|
Bit32u len)
|
|
{
|
|
#if MT32EMU_USE_FLOAT_SAMPLES
|
|
BufferedSampleFormatConverter<> convNonReverbLeft(nonReverbLeft), convNonReverbRight(nonReverbRight);
|
|
BufferedSampleFormatConverter<> convReverbDryLeft(reverbDryLeft), convReverbDryRight(reverbDryRight);
|
|
BufferedSampleFormatConverter<> convReverbWetLeft(reverbWetLeft), convReverbWetRight(reverbWetRight);
|
|
#else
|
|
SampleFormatConverter convNonReverbLeft(nonReverbLeft), convNonReverbRight(nonReverbRight);
|
|
SampleFormatConverter convReverbDryLeft(reverbDryLeft), convReverbDryRight(reverbDryRight);
|
|
SampleFormatConverter convReverbWetLeft(reverbWetLeft), convReverbWetRight(reverbWetRight);
|
|
#endif
|
|
renderer.renderStreams(
|
|
convNonReverbLeft, convNonReverbRight,
|
|
convReverbDryLeft, convReverbDryRight,
|
|
convReverbWetLeft, convReverbWetRight,
|
|
len);
|
|
}
|
|
|
|
void Synth::renderStreams(
|
|
float *nonReverbLeft, float *nonReverbRight,
|
|
float *reverbDryLeft, float *reverbDryRight,
|
|
float *reverbWetLeft, float *reverbWetRight,
|
|
Bit32u len)
|
|
{
|
|
#if MT32EMU_USE_FLOAT_SAMPLES
|
|
SampleFormatConverter convNonReverbLeft(nonReverbLeft), convNonReverbRight(nonReverbRight);
|
|
SampleFormatConverter convReverbDryLeft(reverbDryLeft), convReverbDryRight(reverbDryRight);
|
|
SampleFormatConverter convReverbWetLeft(reverbWetLeft), convReverbWetRight(reverbWetRight);
|
|
#else
|
|
BufferedSampleFormatConverter<> convNonReverbLeft(nonReverbLeft), convNonReverbRight(nonReverbRight);
|
|
BufferedSampleFormatConverter<> convReverbDryLeft(reverbDryLeft), convReverbDryRight(reverbDryRight);
|
|
BufferedSampleFormatConverter<> convReverbWetLeft(reverbWetLeft), convReverbWetRight(reverbWetRight);
|
|
#endif
|
|
renderer.renderStreams(
|
|
convNonReverbLeft, convNonReverbRight,
|
|
convReverbDryLeft, convReverbDryRight,
|
|
convReverbWetLeft, convReverbWetRight,
|
|
len);
|
|
}
|
|
|
|
// In GENERATION2 units, the output from LA32 goes to the Boss chip already bit-shifted.
|
|
// In NICE mode, it's also better to increase volume before the reverb processing to preserve accuracy.
|
|
void Renderer::produceLA32Output(Sample *buffer, Bit32u len) {
|
|
#if MT32EMU_USE_FLOAT_SAMPLES
|
|
(void)buffer;
|
|
(void)len;
|
|
#else
|
|
switch (synth.dacInputMode) {
|
|
case DACInputMode_GENERATION2:
|
|
while (len--) {
|
|
*buffer = (*buffer & 0x8000) | ((*buffer << 1) & 0x7FFE) | ((*buffer >> 14) & 0x0001);
|
|
++buffer;
|
|
}
|
|
break;
|
|
case DACInputMode_NICE:
|
|
while (len--) {
|
|
*buffer = Synth::clipSampleEx(SampleEx(*buffer) << 1);
|
|
++buffer;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void Renderer::convertSamplesToOutput(Sample *buffer, Bit32u len) {
|
|
#if MT32EMU_USE_FLOAT_SAMPLES
|
|
(void)buffer;
|
|
(void)len;
|
|
#else
|
|
if (synth.dacInputMode == DACInputMode_GENERATION1) {
|
|
while (len--) {
|
|
*buffer = Sample((*buffer & 0x8000) | ((*buffer << 1) & 0x7FFE));
|
|
++buffer;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void Renderer::doRenderStreams(DACOutputStreams<Sample> &streams, Bit32u len) {
|
|
if (synth.activated) {
|
|
// Even if LA32 output isn't desired, we proceed anyway with temp buffers
|
|
Sample *nonReverbLeft = streams.nonReverbLeft == NULL ? tmpNonReverbLeft : streams.nonReverbLeft;
|
|
Sample *nonReverbRight = streams.nonReverbRight == NULL ? tmpNonReverbRight : streams.nonReverbRight;
|
|
Sample *reverbDryLeft = streams.reverbDryLeft == NULL ? tmpReverbDryLeft : streams.reverbDryLeft;
|
|
Sample *reverbDryRight = streams.reverbDryRight == NULL ? tmpReverbDryRight : streams.reverbDryRight;
|
|
|
|
Synth::muteSampleBuffer(nonReverbLeft, len);
|
|
Synth::muteSampleBuffer(nonReverbRight, len);
|
|
Synth::muteSampleBuffer(reverbDryLeft, len);
|
|
Synth::muteSampleBuffer(reverbDryRight, len);
|
|
|
|
for (unsigned int i = 0; i < synth.getPartialCount(); i++) {
|
|
if (synth.partialManager->shouldReverb(i)) {
|
|
synth.partialManager->produceOutput(i, reverbDryLeft, reverbDryRight, len);
|
|
} else {
|
|
synth.partialManager->produceOutput(i, nonReverbLeft, nonReverbRight, len);
|
|
}
|
|
}
|
|
|
|
produceLA32Output(reverbDryLeft, len);
|
|
produceLA32Output(reverbDryRight, len);
|
|
|
|
if (synth.isReverbEnabled()) {
|
|
synth.reverbModel->process(reverbDryLeft, reverbDryRight, streams.reverbWetLeft, streams.reverbWetRight, len);
|
|
if (streams.reverbWetLeft != NULL) convertSamplesToOutput(streams.reverbWetLeft, len);
|
|
if (streams.reverbWetRight != NULL) convertSamplesToOutput(streams.reverbWetRight, len);
|
|
} else {
|
|
Synth::muteSampleBuffer(streams.reverbWetLeft, len);
|
|
Synth::muteSampleBuffer(streams.reverbWetRight, len);
|
|
}
|
|
|
|
// Don't bother with conversion if the output is going to be unused
|
|
if (streams.nonReverbLeft != NULL) {
|
|
produceLA32Output(nonReverbLeft, len);
|
|
convertSamplesToOutput(nonReverbLeft, len);
|
|
}
|
|
if (streams.nonReverbRight != NULL) {
|
|
produceLA32Output(nonReverbRight, len);
|
|
convertSamplesToOutput(nonReverbRight, len);
|
|
}
|
|
if (streams.reverbDryLeft != NULL) convertSamplesToOutput(reverbDryLeft, len);
|
|
if (streams.reverbDryRight != NULL) convertSamplesToOutput(reverbDryRight, len);
|
|
} else {
|
|
Synth::muteSampleBuffer(streams.nonReverbLeft, len);
|
|
Synth::muteSampleBuffer(streams.nonReverbRight, len);
|
|
Synth::muteSampleBuffer(streams.reverbDryLeft, len);
|
|
Synth::muteSampleBuffer(streams.reverbDryRight, len);
|
|
Synth::muteSampleBuffer(streams.reverbWetLeft, len);
|
|
Synth::muteSampleBuffer(streams.reverbWetRight, len);
|
|
}
|
|
|
|
synth.partialManager->clearAlreadyOutputed();
|
|
synth.renderedSampleCount += len;
|
|
}
|
|
|
|
void Synth::printPartialUsage(Bit32u sampleOffset) {
|
|
unsigned int partialUsage[9];
|
|
partialManager->getPerPartPartialUsage(partialUsage);
|
|
if (sampleOffset > 0) {
|
|
printDebug("[+%u] Partial Usage: 1:%02d 2:%02d 3:%02d 4:%02d 5:%02d 6:%02d 7:%02d 8:%02d R: %02d TOTAL: %02d", sampleOffset, partialUsage[0], partialUsage[1], partialUsage[2], partialUsage[3], partialUsage[4], partialUsage[5], partialUsage[6], partialUsage[7], partialUsage[8], getPartialCount() - partialManager->getFreePartialCount());
|
|
} else {
|
|
printDebug("Partial Usage: 1:%02d 2:%02d 3:%02d 4:%02d 5:%02d 6:%02d 7:%02d 8:%02d R: %02d TOTAL: %02d", partialUsage[0], partialUsage[1], partialUsage[2], partialUsage[3], partialUsage[4], partialUsage[5], partialUsage[6], partialUsage[7], partialUsage[8], getPartialCount() - partialManager->getFreePartialCount());
|
|
}
|
|
}
|
|
|
|
bool Synth::hasActivePartials() const {
|
|
if (!opened) {
|
|
return false;
|
|
}
|
|
for (unsigned int partialNum = 0; partialNum < getPartialCount(); partialNum++) {
|
|
if (partialManager->getPartial(partialNum)->isActive()) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool Synth::isActive() {
|
|
if (!opened) {
|
|
return false;
|
|
}
|
|
if (!midiQueue->isEmpty() || hasActivePartials()) {
|
|
return true;
|
|
}
|
|
if (isReverbEnabled() && reverbModel->isActive()) {
|
|
return true;
|
|
}
|
|
activated = false;
|
|
return false;
|
|
}
|
|
|
|
Bit32u Synth::getPartialCount() const {
|
|
return partialCount;
|
|
}
|
|
|
|
void Synth::getPartStates(bool *partStates) const {
|
|
if (!opened) {
|
|
memset(partStates, 0, 9 * sizeof(bool));
|
|
return;
|
|
}
|
|
for (int partNumber = 0; partNumber < 9; partNumber++) {
|
|
const Part *part = parts[partNumber];
|
|
partStates[partNumber] = part->getActiveNonReleasingPartialCount() > 0;
|
|
}
|
|
}
|
|
|
|
Bit32u Synth::getPartStates() const {
|
|
if (!opened) return 0;
|
|
bool partStates[9];
|
|
getPartStates(partStates);
|
|
Bit32u bitSet = 0;
|
|
for (int partNumber = 8; partNumber >= 0; partNumber--) {
|
|
bitSet = (bitSet << 1) | (partStates[partNumber] ? 1 : 0);
|
|
}
|
|
return bitSet;
|
|
}
|
|
|
|
void Synth::getPartialStates(PartialState *partialStates) const {
|
|
if (!opened) {
|
|
memset(partialStates, 0, partialCount * sizeof(PartialState));
|
|
return;
|
|
}
|
|
for (unsigned int partialNum = 0; partialNum < partialCount; partialNum++) {
|
|
partialStates[partialNum] = getPartialState(partialManager, partialNum);
|
|
}
|
|
}
|
|
|
|
void Synth::getPartialStates(Bit8u *partialStates) const {
|
|
if (!opened) {
|
|
memset(partialStates, 0, ((partialCount + 3) >> 2));
|
|
return;
|
|
}
|
|
for (unsigned int quartNum = 0; (4 * quartNum) < partialCount; quartNum++) {
|
|
Bit8u packedStates = 0;
|
|
for (unsigned int i = 0; i < 4; i++) {
|
|
unsigned int partialNum = (4 * quartNum) + i;
|
|
if (partialCount <= partialNum) break;
|
|
PartialState partialState = getPartialState(partialManager, partialNum);
|
|
packedStates |= (partialState & 3) << (2 * i);
|
|
}
|
|
partialStates[quartNum] = packedStates;
|
|
}
|
|
}
|
|
|
|
Bit32u Synth::getPlayingNotes(Bit8u partNumber, Bit8u *keys, Bit8u *velocities) const {
|
|
Bit32u playingNotes = 0;
|
|
if (opened && (partNumber < 9)) {
|
|
const Part *part = parts[partNumber];
|
|
const Poly *poly = part->getFirstActivePoly();
|
|
while (poly != NULL) {
|
|
keys[playingNotes] = Bit8u(poly->getKey());
|
|
velocities[playingNotes] = Bit8u(poly->getVelocity());
|
|
playingNotes++;
|
|
poly = poly->getNext();
|
|
}
|
|
}
|
|
return playingNotes;
|
|
}
|
|
|
|
const char *Synth::getPatchName(Bit8u partNumber) const {
|
|
return (!opened || partNumber > 8) ? NULL : parts[partNumber]->getCurrentInstr();
|
|
}
|
|
|
|
const Part *Synth::getPart(Bit8u partNum) const {
|
|
if (partNum > 8) {
|
|
return NULL;
|
|
}
|
|
return parts[partNum];
|
|
}
|
|
|
|
void MemoryRegion::read(unsigned int entry, unsigned int off, Bit8u *dst, unsigned int len) const {
|
|
off += entry * entrySize;
|
|
// This method should never be called with out-of-bounds parameters,
|
|
// or on an unsupported region - seeing any of this debug output indicates a bug in the emulator
|
|
if (off > entrySize * entries - 1) {
|
|
#if MT32EMU_MONITOR_SYSEX > 0
|
|
synth->printDebug("read[%d]: parameters start out of bounds: entry=%d, off=%d, len=%d", type, entry, off, len);
|
|
#endif
|
|
return;
|
|
}
|
|
if (off + len > entrySize * entries) {
|
|
#if MT32EMU_MONITOR_SYSEX > 0
|
|
synth->printDebug("read[%d]: parameters end out of bounds: entry=%d, off=%d, len=%d", type, entry, off, len);
|
|
#endif
|
|
len = entrySize * entries - off;
|
|
}
|
|
Bit8u *src = getRealMemory();
|
|
if (src == NULL) {
|
|
#if MT32EMU_MONITOR_SYSEX > 0
|
|
synth->printDebug("read[%d]: unreadable region: entry=%d, off=%d, len=%d", type, entry, off, len);
|
|
#endif
|
|
return;
|
|
}
|
|
memcpy(dst, src + off, len);
|
|
}
|
|
|
|
void MemoryRegion::write(unsigned int entry, unsigned int off, const Bit8u *src, unsigned int len, bool init) const {
|
|
unsigned int memOff = entry * entrySize + off;
|
|
// This method should never be called with out-of-bounds parameters,
|
|
// or on an unsupported region - seeing any of this debug output indicates a bug in the emulator
|
|
if (off > entrySize * entries - 1) {
|
|
#if MT32EMU_MONITOR_SYSEX > 0
|
|
synth->printDebug("write[%d]: parameters start out of bounds: entry=%d, off=%d, len=%d", type, entry, off, len);
|
|
#endif
|
|
return;
|
|
}
|
|
if (off + len > entrySize * entries) {
|
|
#if MT32EMU_MONITOR_SYSEX > 0
|
|
synth->printDebug("write[%d]: parameters end out of bounds: entry=%d, off=%d, len=%d", type, entry, off, len);
|
|
#endif
|
|
len = entrySize * entries - off;
|
|
}
|
|
Bit8u *dest = getRealMemory();
|
|
if (dest == NULL) {
|
|
#if MT32EMU_MONITOR_SYSEX > 0
|
|
synth->printDebug("write[%d]: unwritable region: entry=%d, off=%d, len=%d", type, entry, off, len);
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
for (unsigned int i = 0; i < len; i++) {
|
|
Bit8u desiredValue = src[i];
|
|
Bit8u maxValue = getMaxValue(memOff);
|
|
// maxValue == 0 means write-protected unless called from initialisation code, in which case it really means the maximum value is 0.
|
|
if (maxValue != 0 || init) {
|
|
if (desiredValue > maxValue) {
|
|
#if MT32EMU_MONITOR_SYSEX > 0
|
|
synth->printDebug("write[%d]: Wanted 0x%02x at %d, but max 0x%02x", type, desiredValue, memOff, maxValue);
|
|
#endif
|
|
desiredValue = maxValue;
|
|
}
|
|
dest[memOff] = desiredValue;
|
|
} else if (desiredValue != 0) {
|
|
#if MT32EMU_MONITOR_SYSEX > 0
|
|
// Only output debug info if they wanted to write non-zero, since a lot of things cause this to spit out a lot of debug info otherwise.
|
|
synth->printDebug("write[%d]: Wanted 0x%02x at %d, but write-protected", type, desiredValue, memOff);
|
|
#endif
|
|
}
|
|
memOff++;
|
|
}
|
|
}
|
|
|
|
} // namespace MT32Emu
|