Update internal MUNT to version 2.7.0
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
||||
* Copyright (C) 2011-2020 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
||||
* Copyright (C) 2011-2022 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
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "Synth.h"
|
||||
#include "Analog.h"
|
||||
#include "BReverbModel.h"
|
||||
#include "Display.h"
|
||||
#include "File.h"
|
||||
#include "MemoryRegion.h"
|
||||
#include "MidiEventQueue.h"
|
||||
@@ -41,19 +42,35 @@ 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, // quirkBasePitchOverflow
|
||||
true, // quirkPitchEnvelopeOverflow
|
||||
true, // quirkRingModulationNoMix
|
||||
true, // quirkTVAZeroEnvLevels
|
||||
true, // quirkPanMult
|
||||
true, // quirkKeyShift
|
||||
true, // quirkTVFBaseCutoffLimit
|
||||
true, // defaultReverbMT32Compatible
|
||||
true // oldMT32AnalogLPF
|
||||
static const ControlROMFeatureSet OLD_MT32_ELDER = {
|
||||
true, // quirkBasePitchOverflow
|
||||
true, // quirkPitchEnvelopeOverflow
|
||||
true, // quirkRingModulationNoMix
|
||||
true, // quirkTVAZeroEnvLevels
|
||||
true, // quirkPanMult
|
||||
true, // quirkKeyShift
|
||||
true, // quirkTVFBaseCutoffLimit
|
||||
false, // quirkFastPitchChanges
|
||||
true, // quirkDisplayCustomMessagePriority
|
||||
true, // oldMT32DisplayFeatures
|
||||
true, // defaultReverbMT32Compatible
|
||||
true // oldMT32AnalogLPF
|
||||
};
|
||||
static const ControlROMFeatureSet CM32L_COMPATIBLE = {
|
||||
static const ControlROMFeatureSet OLD_MT32_LATER = {
|
||||
true, // quirkBasePitchOverflow
|
||||
true, // quirkPitchEnvelopeOverflow
|
||||
true, // quirkRingModulationNoMix
|
||||
true, // quirkTVAZeroEnvLevels
|
||||
true, // quirkPanMult
|
||||
true, // quirkKeyShift
|
||||
true, // quirkTVFBaseCutoffLimit
|
||||
false, // quirkFastPitchChanges
|
||||
false, // quirkDisplayCustomMessagePriority
|
||||
true, // oldMT32DisplayFeatures
|
||||
true, // defaultReverbMT32Compatible
|
||||
true // oldMT32AnalogLPF
|
||||
};
|
||||
static const ControlROMFeatureSet NEW_MT32_COMPATIBLE = {
|
||||
false, // quirkBasePitchOverflow
|
||||
false, // quirkPitchEnvelopeOverflow
|
||||
false, // quirkRingModulationNoMix
|
||||
@@ -61,20 +78,41 @@ static const ControlROMFeatureSet CM32L_COMPATIBLE = {
|
||||
false, // quirkPanMult
|
||||
false, // quirkKeyShift
|
||||
false, // quirkTVFBaseCutoffLimit
|
||||
false, // quirkFastPitchChanges
|
||||
false, // quirkDisplayCustomMessagePriority
|
||||
false, // oldMT32DisplayFeatures
|
||||
false, // defaultReverbMT32Compatible
|
||||
false // oldMT32AnalogLPF
|
||||
false // oldMT32AnalogLPF
|
||||
};
|
||||
static const ControlROMFeatureSet CM32LN_COMPATIBLE = {
|
||||
false, // quirkBasePitchOverflow
|
||||
false, // quirkPitchEnvelopeOverflow
|
||||
false, // quirkRingModulationNoMix
|
||||
false, // quirkTVAZeroEnvLevels
|
||||
false, // quirkPanMult
|
||||
false, // quirkKeyShift
|
||||
false, // quirkTVFBaseCutoffLimit
|
||||
true, // quirkFastPitchChanges
|
||||
false, // quirkDisplayCustomMessagePriority
|
||||
false, // oldMT32DisplayFeatures
|
||||
false, // defaultReverbMT32Compatible
|
||||
false // oldMT32AnalogLPF
|
||||
};
|
||||
|
||||
static const ControlROMMap ControlROMMaps[8] = {
|
||||
// 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_mt32_2_04", CM32L_COMPATIBLE, 0x8100, 128, 0x8000, 0x8000, true, 0x8080, 0x8000, true, 0x8500, 30, 0x8580, 85, 0x4F5D, 0x4F78, 0x4F66, 0x4899, 0x489D, 0x48B6, 0x48CD, 0x5A58, 19 },
|
||||
{"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
|
||||
static const ControlROMMap ControlROMMaps[] = {
|
||||
// ID Features PCMmap PCMc tmbrA tmbrAO, tmbrAC tmbrB tmbrBO tmbrBC tmbrR trC rhythm rhyC rsrv panpot prog rhyMax patMax sysMax timMax sndGrp sGC stMsg sErMsg
|
||||
{"ctrl_mt32_1_04", OLD_MT32_ELDER, 0x3000, 128, 0x8000, 0x0000, false, 0xC000, 0x4000, false, 0x3200, 30, 0x73A6, 85, 0x57C7, 0x57E2, 0x57D0, 0x5252, 0x525E, 0x526E, 0x520A, 0x7064, 19, 0x217A, 0x4BB6},
|
||||
{"ctrl_mt32_1_05", OLD_MT32_ELDER, 0x3000, 128, 0x8000, 0x0000, false, 0xC000, 0x4000, false, 0x3200, 30, 0x7414, 85, 0x57C7, 0x57E2, 0x57D0, 0x5252, 0x525E, 0x526E, 0x520A, 0x70CA, 19, 0x217A, 0x4BB6},
|
||||
{"ctrl_mt32_1_06", OLD_MT32_LATER, 0x3000, 128, 0x8000, 0x0000, false, 0xC000, 0x4000, false, 0x3200, 30, 0x7414, 85, 0x57D9, 0x57F4, 0x57E2, 0x5264, 0x5270, 0x5280, 0x521C, 0x70CA, 19, 0x217A, 0x4BBA},
|
||||
{"ctrl_mt32_1_07", OLD_MT32_LATER, 0x3000, 128, 0x8000, 0x0000, false, 0xC000, 0x4000, false, 0x3200, 30, 0x73fe, 85, 0x57B1, 0x57CC, 0x57BA, 0x523C, 0x5248, 0x5258, 0x51F4, 0x70B0, 19, 0x217A, 0x4B92},
|
||||
{"ctrl_mt32_bluer", OLD_MT32_LATER, 0x3000, 128, 0x8000, 0x0000, false, 0xC000, 0x4000, false, 0x3200, 30, 0x741C, 85, 0x57E5, 0x5800, 0x57EE, 0x5270, 0x527C, 0x528C, 0x5228, 0x70CE, 19, 0x217A, 0x4BC6},
|
||||
{"ctrl_mt32_2_03", NEW_MT32_COMPATIBLE, 0x8100, 128, 0x8000, 0x8000, true, 0x8080, 0x8000, true, 0x8500, 64, 0x8580, 85, 0x4F49, 0x4F64, 0x4F52, 0x4885, 0x4889, 0x48A2, 0x48B9, 0x5A44, 19, 0x1EF0, 0x4066},
|
||||
{"ctrl_mt32_2_04", NEW_MT32_COMPATIBLE, 0x8100, 128, 0x8000, 0x8000, true, 0x8080, 0x8000, true, 0x8500, 64, 0x8580, 85, 0x4F5D, 0x4F78, 0x4F66, 0x4899, 0x489D, 0x48B6, 0x48CD, 0x5A58, 19, 0x1EF0, 0x406D},
|
||||
{"ctrl_mt32_2_06", NEW_MT32_COMPATIBLE, 0x8100, 128, 0x8000, 0x8000, true, 0x8080, 0x8000, true, 0x8500, 64, 0x8580, 85, 0x4F69, 0x4F84, 0x4F72, 0x48A5, 0x48A9, 0x48C2, 0x48D9, 0x5A64, 19, 0x1EF0, 0x4021},
|
||||
{"ctrl_mt32_2_07", NEW_MT32_COMPATIBLE, 0x8100, 128, 0x8000, 0x8000, true, 0x8080, 0x8000, true, 0x8500, 64, 0x8580, 85, 0x4F81, 0x4F9C, 0x4F8A, 0x48B9, 0x48BD, 0x48D6, 0x48ED, 0x5A78, 19, 0x1EE7, 0x4035},
|
||||
{"ctrl_cm32l_1_00", NEW_MT32_COMPATIBLE, 0x8100, 256, 0x8000, 0x8000, true, 0x8080, 0x8000, true, 0x8500, 64, 0x8580, 85, 0x4F65, 0x4F80, 0x4F6E, 0x48A1, 0x48A5, 0x48BE, 0x48D5, 0x5A6C, 19, 0x1EF0, 0x401D},
|
||||
{"ctrl_cm32l_1_02", NEW_MT32_COMPATIBLE, 0x8100, 256, 0x8000, 0x8000, true, 0x8080, 0x8000, true, 0x8500, 64, 0x8580, 85, 0x4F93, 0x4FAE, 0x4F9C, 0x48CB, 0x48CF, 0x48E8, 0x48FF, 0x5A96, 19, 0x1EE7, 0x4047},
|
||||
{"ctrl_cm32ln_1_00", CM32LN_COMPATIBLE, 0x8100, 256, 0x8000, 0x8000, true, 0x8080, 0x8000, true, 0x8500, 64, 0x8580, 85, 0x4EC7, 0x4EE2, 0x4ED0, 0x47FF, 0x4803, 0x481C, 0x4833, 0x55A2, 19, 0x1F59, 0x3F7C}
|
||||
// (Note that old MT-32 ROMs actually have 86 entries for rhythmTemp)
|
||||
};
|
||||
|
||||
@@ -138,6 +176,8 @@ protected:
|
||||
synth.renderedSampleCount += count;
|
||||
}
|
||||
|
||||
void updateDisplayState();
|
||||
|
||||
public:
|
||||
Renderer(Synth &useSynth) : synth(useSynth) {}
|
||||
|
||||
@@ -209,10 +249,16 @@ public:
|
||||
|
||||
Bit32u midiEventQueueSize;
|
||||
Bit32u midiEventQueueSysexStorageBufferSize;
|
||||
|
||||
Display *display;
|
||||
bool oldMT32DisplayFeatures;
|
||||
|
||||
ReportHandler2 defaultReportHandler;
|
||||
ReportHandler2 *reportHandler2;
|
||||
};
|
||||
|
||||
Bit32u Synth::getLibraryVersionInt() {
|
||||
return (MT32EMU_VERSION_MAJOR << 16) | (MT32EMU_VERSION_MINOR << 8) | (MT32EMU_VERSION_PATCH);
|
||||
return MT32EMU_CURRENT_VERSION_INT;
|
||||
}
|
||||
|
||||
const char *Synth::getLibraryVersionString() {
|
||||
@@ -244,13 +290,8 @@ Synth::Synth(ReportHandler *useReportHandler) :
|
||||
controlROMMap = NULL;
|
||||
controlROMFeatures = NULL;
|
||||
|
||||
if (useReportHandler == NULL) {
|
||||
reportHandler = new ReportHandler;
|
||||
isDefaultReportHandler = true;
|
||||
} else {
|
||||
reportHandler = useReportHandler;
|
||||
isDefaultReportHandler = false;
|
||||
}
|
||||
reportHandler = useReportHandler != NULL ? useReportHandler : &extensions.defaultReportHandler;
|
||||
extensions.reportHandler2 = &extensions.defaultReportHandler;
|
||||
|
||||
extensions.preallocatedReverbMemory = false;
|
||||
for (int i = REVERB_MODE_ROOM; i <= REVERB_MODE_TAP_DELAY; i++) {
|
||||
@@ -289,18 +330,27 @@ Synth::Synth(ReportHandler *useReportHandler) :
|
||||
lastReceivedMIDIEventTimestamp = 0;
|
||||
memset(parts, 0, sizeof(parts));
|
||||
renderedSampleCount = 0;
|
||||
extensions.display = NULL;
|
||||
extensions.oldMT32DisplayFeatures = false;
|
||||
}
|
||||
|
||||
Synth::~Synth() {
|
||||
close(); // Make sure we're closed and everything is freed
|
||||
if (isDefaultReportHandler) {
|
||||
delete reportHandler;
|
||||
}
|
||||
delete &mt32ram;
|
||||
delete &mt32default;
|
||||
delete &extensions;
|
||||
}
|
||||
|
||||
void Synth::setReportHandler2(ReportHandler2 *reportHandler2) {
|
||||
if (reportHandler2 != NULL) {
|
||||
reportHandler = reportHandler2;
|
||||
extensions.reportHandler2 = reportHandler2;
|
||||
} else {
|
||||
reportHandler = &extensions.defaultReportHandler;
|
||||
extensions.reportHandler2 = &extensions.defaultReportHandler;
|
||||
}
|
||||
}
|
||||
|
||||
void ReportHandler::showLCDMessage(const char *data) {
|
||||
printf("WRITE-LCD: %s\n", data);
|
||||
}
|
||||
@@ -310,26 +360,38 @@ void ReportHandler::printDebug(const char *fmt, va_list list) {
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
void Synth::newTimbreSet(Bit8u partNum, Bit8u timbreGroup, Bit8u timbreNumber, const char patchName[]) {
|
||||
const char *soundGroupName;
|
||||
void Synth::rhythmNotePlayed() const {
|
||||
extensions.display->rhythmNotePlayed();
|
||||
}
|
||||
|
||||
void Synth::voicePartStateChanged(Bit8u partNum, bool partActivated) const {
|
||||
extensions.display->voicePartStateChanged(partNum, partActivated);
|
||||
}
|
||||
|
||||
void Synth::newTimbreSet(Bit8u partNum) const {
|
||||
const Part *part = getPart(partNum);
|
||||
reportHandler->onProgramChanged(partNum, getSoundGroupName(part), part->getCurrentInstr());
|
||||
}
|
||||
|
||||
const char *Synth::getSoundGroupName(const Part *part) const {
|
||||
const PatchParam &patch = part->getPatchTemp()->patch;
|
||||
return getSoundGroupName(patch.timbreGroup, patch.timbreNum);
|
||||
}
|
||||
|
||||
const char *Synth::getSoundGroupName(Bit8u timbreGroup, Bit8u timbreNumber) const {
|
||||
switch (timbreGroup) {
|
||||
case 1:
|
||||
timbreNumber += 64;
|
||||
// Fall-through
|
||||
case 0:
|
||||
soundGroupName = soundGroupNames[soundGroupIx[timbreNumber]];
|
||||
break;
|
||||
return soundGroupNames[soundGroupIx[timbreNumber]];
|
||||
case 2:
|
||||
soundGroupName = soundGroupNames[controlROMMap->soundGroupsCount - 2];
|
||||
break;
|
||||
return soundGroupNames[controlROMMap->soundGroupsCount - 2];
|
||||
case 3:
|
||||
soundGroupName = soundGroupNames[controlROMMap->soundGroupsCount - 1];
|
||||
break;
|
||||
return soundGroupNames[controlROMMap->soundGroupsCount - 1];
|
||||
default:
|
||||
soundGroupName = NULL;
|
||||
break;
|
||||
return NULL;
|
||||
}
|
||||
reportHandler->onProgramChanged(partNum, soundGroupName, patchName);
|
||||
}
|
||||
|
||||
#define MT32EMU_PRINT_DEBUG \
|
||||
@@ -450,6 +512,16 @@ float Synth::getReverbOutputGain() const {
|
||||
return reverbOutputGain;
|
||||
}
|
||||
|
||||
void Synth::setPartVolumeOverride(Bit8u partNumber, Bit8u volumeOverride) {
|
||||
if (opened && partNumber < 9) {
|
||||
parts[partNumber]->setVolumeOverride(volumeOverride);
|
||||
}
|
||||
}
|
||||
|
||||
Bit8u Synth::getPartVolumeOverride(Bit8u partNumber) const {
|
||||
return (!opened || partNumber > 8) ? 255 : parts[partNumber]->getVolumeOverride();
|
||||
}
|
||||
|
||||
void Synth::setReversedStereoEnabled(bool enabled) {
|
||||
reversedStereoEnabled = enabled;
|
||||
}
|
||||
@@ -542,7 +614,7 @@ bool Synth::loadPCMROM(const ROMImage &pcmROMImage) {
|
||||
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++) {
|
||||
for (int u = 0; u < 16; u++) {
|
||||
int bit;
|
||||
if (order[u] < 8) {
|
||||
bit = (s >> (7 - order[u])) & 0x1;
|
||||
@@ -717,6 +789,16 @@ bool Synth::open(const ROMImage &controlROMImage, const ROMImage &pcmROMImage, B
|
||||
return false;
|
||||
}
|
||||
|
||||
if (controlROMMap->timbreRCount == 30) {
|
||||
// We must initialise all 64 rhythm timbres to avoid undefined behaviour.
|
||||
// SEMI-CONFIRMED: Old-gen MT-32 units likely map timbres 30..59 to 0..29.
|
||||
// Attempts to play rhythm timbres 60..63 exhibit undefined behaviour.
|
||||
// We want to emulate the wrap around, so merely copy the entire set of standard
|
||||
// timbres once more. The last 4 dangerous timbres are zeroed out.
|
||||
memcpy(&mt32ram.timbres[222], &mt32ram.timbres[192], sizeof(*mt32ram.timbres) * 30);
|
||||
memset(&mt32ram.timbres[252], 0, sizeof(*mt32ram.timbres) * 4);
|
||||
}
|
||||
|
||||
#if MT32EMU_MONITOR_INIT
|
||||
printDebug("Initialising Timbre Bank M");
|
||||
#endif
|
||||
@@ -838,6 +920,9 @@ bool Synth::open(const ROMImage &controlROMImage, const ROMImage &pcmROMImage, B
|
||||
return false;
|
||||
}
|
||||
|
||||
extensions.display = new Display(*this);
|
||||
extensions.oldMT32DisplayFeatures = controlROMFeatures->oldMT32DisplayFeatures;
|
||||
|
||||
opened = true;
|
||||
activated = false;
|
||||
|
||||
@@ -850,6 +935,9 @@ bool Synth::open(const ROMImage &controlROMImage, const ROMImage &pcmROMImage, B
|
||||
void Synth::dispose() {
|
||||
opened = false;
|
||||
|
||||
delete extensions.display;
|
||||
extensions.display = NULL;
|
||||
|
||||
delete midiQueue;
|
||||
midiQueue = NULL;
|
||||
|
||||
@@ -1064,7 +1152,7 @@ void Synth::playMsgOnPart(Bit8u part, Bit8u code, Bit8u note, Bit8u 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 {
|
||||
} else if (parts[part]->getVolumeOverride() > 0) {
|
||||
parts[part]->noteOn(note, velocity);
|
||||
}
|
||||
break;
|
||||
@@ -1130,16 +1218,21 @@ void Synth::playMsgOnPart(Bit8u part, Bit8u code, Bit8u note, Bit8u velocity) {
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
extensions.display->midiMessagePlayed();
|
||||
break;
|
||||
case 0xC: // Program change
|
||||
//printDebug("Program change %01x", note);
|
||||
parts[part]->setProgram(note);
|
||||
if (part < 8) {
|
||||
extensions.display->midiMessagePlayed();
|
||||
extensions.display->programChanged(part);
|
||||
}
|
||||
break;
|
||||
case 0xE: // Pitch bender
|
||||
bend = (velocity << 7) | (note);
|
||||
//printDebug("Pitch bender %02x", bend);
|
||||
parts[part]->setBend(bend);
|
||||
extensions.display->midiMessagePlayed();
|
||||
break;
|
||||
default:
|
||||
#if MT32EMU_MONITOR_MIDI > 0
|
||||
@@ -1197,12 +1290,19 @@ void Synth::playSysexWithoutHeader(Bit8u device, Bit8u command, const Bit8u *sys
|
||||
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();
|
||||
|
||||
// All models process the checksum before anything else and ignore messages lacking the checksum, or containing the checksum only.
|
||||
if (len < 2) {
|
||||
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);
|
||||
if (opened) extensions.display->checksumErrorOccurred();
|
||||
return;
|
||||
}
|
||||
len -= 1; // Exclude checksum
|
||||
|
||||
if (command == SYSEX_CMD_EOD) {
|
||||
#if MT32EMU_MONITOR_SYSEX > 0
|
||||
@@ -1210,16 +1310,6 @@ void Synth::playSysexWithoutHeader(Bit8u device, Bit8u command, const Bit8u *sys
|
||||
#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
|
||||
@@ -1259,12 +1349,34 @@ void Synth::readSysex(Bit8u /*device*/, const Bit8u * /*sysex*/, Bit32u /*len*/)
|
||||
}
|
||||
|
||||
void Synth::writeSysex(Bit8u device, const Bit8u *sysex, Bit32u len) {
|
||||
if (!opened) return;
|
||||
if (!opened || len < 1) return;
|
||||
|
||||
// This is checked early in the real devices (before any sysex length checks or further processing)
|
||||
if (sysex[0] == 0x7F) {
|
||||
if (!isDisplayOldMT32Compatible()) extensions.display->midiMessagePlayed();
|
||||
reset();
|
||||
return;
|
||||
}
|
||||
|
||||
extensions.display->midiMessagePlayed();
|
||||
reportHandler->onMIDIMessagePlayed();
|
||||
|
||||
if (len < 3) {
|
||||
// A short message of just 1 or 2 bytes may be written to the display area yet it may cause a user-visible effect,
|
||||
// similarly to the reset area.
|
||||
if (sysex[0] == 0x20) {
|
||||
extensions.display->displayControlMessageReceived(sysex, len);
|
||||
return;
|
||||
}
|
||||
printDebug("writeSysex: Message is too short (%d bytes)!", len);
|
||||
return;
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -1345,6 +1457,7 @@ void Synth::writeSysexGlobal(Bit32u addr, const Bit8u *sysex, Bit32u len) {
|
||||
|
||||
if (region == NULL) {
|
||||
printDebug("Sysex write to unrecognised address %06x, len %d", MT32EMU_SYSEXMEMADDR(addr), len);
|
||||
// FIXME: Real devices may respond differently to a long SysEx that covers adjacent regions.
|
||||
break;
|
||||
}
|
||||
writeMemoryRegion(region, addr, region->getClampedLen(addr, len), sysex);
|
||||
@@ -1663,7 +1776,10 @@ void Synth::writeMemoryRegion(const MemoryRegion *region, Bit32u addr, Bit32u le
|
||||
}
|
||||
break;
|
||||
case MR_Display:
|
||||
char buf[SYSEX_BUFFER_SIZE];
|
||||
if (len > Display::LCD_TEXT_SIZE) len = Display::LCD_TEXT_SIZE;
|
||||
if (!extensions.display->customDisplayMessageReceived(data, off, len)) break;
|
||||
// Holds zero-terminated string of the maximum length.
|
||||
char buf[Display::LCD_TEXT_SIZE + 1];
|
||||
memcpy(&buf, &data[0], len);
|
||||
buf[len] = 0;
|
||||
#if MT32EMU_MONITOR_SYSEX > 0
|
||||
@@ -1674,6 +1790,8 @@ void Synth::writeMemoryRegion(const MemoryRegion *region, Bit32u addr, Bit32u le
|
||||
case MR_Reset:
|
||||
reset();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1767,6 +1885,10 @@ void Synth::refreshSystemChanAssign(Bit8u firstPart, Bit8u lastPart) {
|
||||
}
|
||||
|
||||
void Synth::refreshSystemMasterVol() {
|
||||
// Note, this should only occur when the user turns the volume knob. When the master volume is set via a SysEx, display
|
||||
// doesn't actually update on all real devices. However, we anyway update the display, as we don't foresee a dedicated
|
||||
// API for setting the master volume yet it's rather dubious that one really needs this quirk to be fairly emulated.
|
||||
if (opened) extensions.display->masterVolumeChanged();
|
||||
#if MT32EMU_MONITOR_SYSEX > 0
|
||||
printDebug(" Master volume: %d", mt32ram.system.masterVol);
|
||||
#endif
|
||||
@@ -1816,6 +1938,32 @@ Bit32s Synth::getMasterTunePitchDelta() const {
|
||||
return extensions.masterTunePitchDelta;
|
||||
}
|
||||
|
||||
bool Synth::getDisplayState(char *targetBuffer, bool narrowLCD) const {
|
||||
if (!opened) {
|
||||
memset(targetBuffer, ' ', Display::LCD_TEXT_SIZE);
|
||||
targetBuffer[Display::LCD_TEXT_SIZE] = 0;
|
||||
return false;
|
||||
}
|
||||
return extensions.display->getDisplayState(targetBuffer, narrowLCD);
|
||||
}
|
||||
|
||||
void Synth::setMainDisplayMode() {
|
||||
if (opened) extensions.display->setMainDisplayMode();
|
||||
}
|
||||
|
||||
|
||||
void Synth::setDisplayCompatibility(bool oldMT32CompatibilityEnabled) {
|
||||
extensions.oldMT32DisplayFeatures = oldMT32CompatibilityEnabled;
|
||||
}
|
||||
|
||||
bool Synth::isDisplayOldMT32Compatible() const {
|
||||
return extensions.oldMT32DisplayFeatures;
|
||||
}
|
||||
|
||||
bool Synth::isDefaultDisplayOldMT32Compatible() const {
|
||||
return opened && controlROMFeatures->oldMT32DisplayFeatures;
|
||||
}
|
||||
|
||||
/** Defines an interface of a class that maintains storage of variable-sized data of SysEx messages. */
|
||||
class MidiEventQueue::SysexDataStorage {
|
||||
public:
|
||||
@@ -1994,6 +2142,15 @@ Bit32u Synth::getStereoOutputSampleRate() const {
|
||||
return (analog == NULL) ? SAMPLE_RATE : analog->getOutputSampleRate();
|
||||
}
|
||||
|
||||
void Renderer::updateDisplayState() {
|
||||
bool midiMessageLEDState;
|
||||
bool midiMessageLEDStateUpdated;
|
||||
bool lcdUpdated;
|
||||
synth.extensions.display->checkDisplayStateUpdated(midiMessageLEDState, midiMessageLEDStateUpdated, lcdUpdated);
|
||||
if (midiMessageLEDStateUpdated) synth.extensions.reportHandler2->onMidiMessageLEDStateUpdated(midiMessageLEDState);
|
||||
if (lcdUpdated) synth.extensions.reportHandler2->onLCDStateUpdated();
|
||||
}
|
||||
|
||||
template <class Sample>
|
||||
void RendererImpl<Sample>::doRender(Sample *stereoStream, Bit32u len) {
|
||||
if (!isActivated()) {
|
||||
@@ -2002,6 +2159,7 @@ void RendererImpl<Sample>::doRender(Sample *stereoStream, Bit32u len) {
|
||||
printDebug("RendererImpl: Invalid call to Analog::process()!\n");
|
||||
}
|
||||
Synth::muteSampleBuffer(stereoStream, len << 1);
|
||||
updateDisplayState();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2358,6 +2516,7 @@ void RendererImpl<Sample>::produceStreams(const DACOutputStreams<Sample> &stream
|
||||
|
||||
getPartialManager().clearAlreadyOutputed();
|
||||
incRenderedSampleCount(len);
|
||||
updateDisplayState();
|
||||
}
|
||||
|
||||
void Synth::printPartialUsage(Bit32u sampleOffset) {
|
||||
@@ -2468,6 +2627,26 @@ const char *Synth::getPatchName(Bit8u partNumber) const {
|
||||
return (!opened || partNumber > 8) ? NULL : parts[partNumber]->getCurrentInstr();
|
||||
}
|
||||
|
||||
bool Synth::getSoundGroupName(char *soundGroupName, Bit8u timbreGroup, Bit8u timbreNumber) const {
|
||||
if (!opened || 63 < timbreNumber) return false;
|
||||
const char *foundGroupName = getSoundGroupName(timbreGroup, timbreNumber);
|
||||
if (foundGroupName == NULL) return false;
|
||||
memcpy(soundGroupName, foundGroupName, 7);
|
||||
soundGroupName[7] = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Synth::getSoundName(char *soundName, Bit8u timbreGroup, Bit8u timbreNumber) const {
|
||||
if (!opened || 3 < timbreGroup) return false;
|
||||
Bit8u timbresInGroup = 3 == timbreGroup ? controlROMMap->timbreRCount : 64;
|
||||
if (timbresInGroup <= timbreNumber) return false;
|
||||
TimbreParam::CommonParam &timbreCommon = mt32ram.timbres[timbreGroup * 64 + timbreNumber].timbre.common;
|
||||
if (timbreCommon.partialMute == 0) return false;
|
||||
memcpy(soundName, timbreCommon.name, sizeof timbreCommon.name);
|
||||
soundName[sizeof timbreCommon.name] = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
const Part *Synth::getPart(Bit8u partNum) const {
|
||||
if (partNum > 8) {
|
||||
return NULL;
|
||||
|
||||
Reference in New Issue
Block a user