diff options
Diffstat (limited to 'audio/softsynth/mt32/Synth.cpp')
-rw-r--r-- | audio/softsynth/mt32/Synth.cpp | 1620 |
1 files changed, 1620 insertions, 0 deletions
diff --git a/audio/softsynth/mt32/Synth.cpp b/audio/softsynth/mt32/Synth.cpp new file mode 100644 index 0000000000..0861053b5c --- /dev/null +++ b/audio/softsynth/mt32/Synth.cpp @@ -0,0 +1,1620 @@ +/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher + * Copyright (C) 2011 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 <cerrno> +//#include <cmath> +//#include <cstdlib> +//#include <cstring> + +#define FORBIDDEN_SYMBOL_EXCEPTION_printf +#define FORBIDDEN_SYMBOL_EXCEPTION_vprintf + +#include "mt32emu.h" +#include "mmath.h" +#include "PartialManager.h" + +#if MT32EMU_USE_AREVERBMODEL == 1 +#include "AReverbModel.h" +#else +#include "FreeverbModel.h" +#endif +#include "DelayReverb.h" + +namespace MT32Emu { + +const ControlROMMap ControlROMMaps[7] = { + // ID IDc IDbytes PCMmap PCMc tmbrA tmbrAO, tmbrAC tmbrB tmbrBO, tmbrBC tmbrR trC rhythm rhyC rsrv panpot prog rhyMax patMax sysMax timMax + {0x4014, 22, "\000 ver1.04 14 July 87 ", 0x3000, 128, 0x8000, 0x0000, false, 0xC000, 0x4000, false, 0x3200, 30, 0x73A6, 85, 0x57C7, 0x57E2, 0x57D0, 0x5252, 0x525E, 0x526E, 0x520A}, + {0x4014, 22, "\000 ver1.05 06 Aug, 87 ", 0x3000, 128, 0x8000, 0x0000, false, 0xC000, 0x4000, false, 0x3200, 30, 0x7414, 85, 0x57C7, 0x57E2, 0x57D0, 0x5252, 0x525E, 0x526E, 0x520A}, + {0x4014, 22, "\000 ver1.06 31 Aug, 87 ", 0x3000, 128, 0x8000, 0x0000, false, 0xC000, 0x4000, false, 0x3200, 30, 0x7414, 85, 0x57D9, 0x57F4, 0x57E2, 0x5264, 0x5270, 0x5280, 0x521C}, + {0x4010, 22, "\000 ver1.07 10 Oct, 87 ", 0x3000, 128, 0x8000, 0x0000, false, 0xC000, 0x4000, false, 0x3200, 30, 0x73fe, 85, 0x57B1, 0x57CC, 0x57BA, 0x523C, 0x5248, 0x5258, 0x51F4}, // MT-32 revision 1 + {0x4010, 22, "\000verX.XX 30 Sep, 88 ", 0x3000, 128, 0x8000, 0x0000, false, 0xC000, 0x4000, false, 0x3200, 30, 0x741C, 85, 0x57E5, 0x5800, 0x57EE, 0x5270, 0x527C, 0x528C, 0x5228}, // MT-32 Blue Ridge mod + {0x2205, 22, "\000CM32/LAPC1.00 890404", 0x8100, 256, 0x8000, 0x8000, false, 0x8080, 0x8000, false, 0x8500, 64, 0x8580, 85, 0x4F65, 0x4F80, 0x4F6E, 0x48A1, 0x48A5, 0x48BE, 0x48D5}, + {0x2205, 22, "\000CM32/LAPC1.02 891205", 0x8100, 256, 0x8000, 0x8000, true, 0x8080, 0x8000, true, 0x8500, 64, 0x8580, 85, 0x4F93, 0x4FAE, 0x4F9C, 0x48CB, 0x48CF, 0x48E8, 0x48FF} // CM-32L + // (Note that all but CM-32L ROM actually have 86 entries for rhythmTemp) +}; + +static inline Bit16s *streamOffset(Bit16s *stream, Bit32u pos) { + return stream == NULL ? NULL : stream + pos; +} + +static inline void clearIfNonNull(Bit16s *stream, Bit32u len) { + if (stream != NULL) { + memset(stream, 0, len * sizeof(Bit16s)); + } +} + +static inline void mix(float *target, const float *stream, Bit32u len) { + while (len--) { + *target += *stream; + stream++; + target++; + } +} + +static inline void clearFloats(float *leftBuf, float *rightBuf, Bit32u len) { + // FIXME: Use memset() where compatibility is guaranteed (if this turns out to be a win) + while (len--) { + *leftBuf++ = 0.0f; + *rightBuf++ = 0.0f; + } +} + +static inline Bit16s clipBit16s(Bit32s a) { + // Clamp values above 32767 to 32767, and values below -32768 to -32768 + if ((a + 32768) & ~65535) { + return (a >> 31) ^ 32767; + } + return a; +} + +static void floatToBit16s_nice(Bit16s *target, const float *source, Bit32u len, float outputGain) { + float gain = outputGain * 16384.0f; + while (len--) { + // Since we're not shooting for accuracy here, don't worry about the rounding mode. + *target = clipBit16s((Bit32s)(*source * gain)); + source++; + target++; + } +} + +static void floatToBit16s_pure(Bit16s *target, const float *source, Bit32u len, float /*outputGain*/) { + while (len--) { + *target = clipBit16s((Bit32s)floor(*source * 8192.0f)); + source++; + target++; + } +} + +static void floatToBit16s_reverb(Bit16s *target, const float *source, Bit32u len, float outputGain) { + float gain = outputGain * 8192.0f; + while (len--) { + *target = clipBit16s((Bit32s)floor(*source * gain)); + source++; + target++; + } +} + +static void floatToBit16s_generation1(Bit16s *target, const float *source, Bit32u len, float outputGain) { + float gain = outputGain * 8192.0f; + while (len--) { + *target = clipBit16s((Bit32s)floor(*source * gain)); + *target = (*target & 0x8000) | ((*target << 1) & 0x7FFE); + source++; + target++; + } +} + +static void floatToBit16s_generation2(Bit16s *target, const float *source, Bit32u len, float outputGain) { + float gain = outputGain * 8192.0f; + while (len--) { + *target = clipBit16s((Bit32s)floor(*source * gain)); + *target = (*target & 0x8000) | ((*target << 1) & 0x7FFE) | ((*target >> 14) & 0x0001); + source++; + target++; + } +} + +Bit8u Synth::calcSysexChecksum(const Bit8u *data, Bit32u len, Bit8u checksum) { + for (unsigned int i = 0; i < len; i++) { + checksum = checksum + data[i]; + } + checksum = checksum & 0x7f; + if (checksum) { + checksum = 0x80 - checksum; + } + return checksum; +} + +Synth::Synth() { + isOpen = false; + reverbEnabled = true; + reverbOverridden = false; + +#if MT32EMU_USE_AREVERBMODEL == 1 + reverbModels[0] = new AReverbModel(&AReverbModel::REVERB_MODE_0_SETTINGS); + reverbModels[1] = new AReverbModel(&AReverbModel::REVERB_MODE_1_SETTINGS); + reverbModels[2] = new AReverbModel(&AReverbModel::REVERB_MODE_2_SETTINGS); +#else + reverbModels[0] = new FreeverbModel(0.76f, 0.687770909f, 0.63f, 0, 0.5f); + reverbModels[1] = new FreeverbModel(2.0f, 0.712025098f, 0.86f, 1, 0.5f); + reverbModels[2] = new FreeverbModel(0.4f, 0.939522749f, 0.38f, 2, 0.05f); +#endif + + reverbModels[3] = new DelayReverb(); + reverbModel = NULL; + setDACInputMode(DACInputMode_NICE); + setOutputGain(1.0f); + setReverbOutputGain(0.68f); + partialManager = NULL; + memset(parts, 0, sizeof(parts)); + renderedSampleCount = 0; +} + +Synth::~Synth() { + close(); // Make sure we're closed and everything is freed + for (int i = 0; i < 4; i++) { + delete reverbModels[i]; + } +} + +int Synth::report(ReportType type, const void *data) { + if (myProp.report != NULL) { + return myProp.report(myProp.userData, type, data); + } + return 0; +} + +unsigned int Synth::getSampleRate() const { + return myProp.sampleRate; +} + +void Synth::printDebug(const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + if (myProp.printDebug != NULL) { + myProp.printDebug(myProp.userData, fmt, ap); + } else { +#if MT32EMU_DEBUG_SAMPLESTAMPS > 0 + printf("[%u] ", renderedSampleCount); +#endif + vprintf(fmt, ap); + printf("\n"); + } + va_end(ap); +} + +void Synth::setReverbEnabled(bool newReverbEnabled) { + reverbEnabled = newReverbEnabled; +} + +bool Synth::isReverbEnabled() const { + return reverbEnabled; +} + +void Synth::setReverbOverridden(bool newReverbOverridden) { + reverbOverridden = newReverbOverridden; +} + +bool Synth::isReverbOverridden() const { + return reverbOverridden; +} + +void Synth::setDACInputMode(DACInputMode mode) { + switch(mode) { + case DACInputMode_GENERATION1: + la32FloatToBit16sFunc = floatToBit16s_generation1; + reverbFloatToBit16sFunc = floatToBit16s_reverb; + break; + case DACInputMode_GENERATION2: + la32FloatToBit16sFunc = floatToBit16s_generation2; + reverbFloatToBit16sFunc = floatToBit16s_reverb; + break; + case DACInputMode_PURE: + la32FloatToBit16sFunc = floatToBit16s_pure; + reverbFloatToBit16sFunc = floatToBit16s_pure; + break; + case DACInputMode_NICE: + default: + la32FloatToBit16sFunc = floatToBit16s_nice; + reverbFloatToBit16sFunc = floatToBit16s_reverb; + break; + } +} + +void Synth::setOutputGain(float newOutputGain) { + outputGain = newOutputGain; +} + +void Synth::setReverbOutputGain(float newReverbOutputGain) { + reverbOutputGain = newReverbOutputGain; +} + +Common::File *Synth::openFile(const char *filename) { + if (myProp.openFile != NULL) { + return myProp.openFile(myProp.userData, filename); + } + char pathBuf[2048]; + if (myProp.baseDir != NULL) { + strcpy(&pathBuf[0], myProp.baseDir); + strcat(&pathBuf[0], filename); + filename = pathBuf; + } + Common::File *file = new Common::File(); + if (!file->open(filename)) { + delete file; + return NULL; + } + return file; +} + +void Synth::closeFile(Common::File *file) { + if (myProp.closeFile != NULL) { + myProp.closeFile(myProp.userData, file); + } else { + file->close(); + delete file; + } +} + +LoadResult Synth::loadControlROM(const char *filename) { + Common::File *file = openFile(filename); // ROM File + if (file == NULL) { + return LoadResult_NotFound; + } + size_t fileSize = file->size(); + if (fileSize != CONTROL_ROM_SIZE) { + printDebug("Control ROM file %s size mismatch: %i", filename, fileSize); + } + file->read(controlROMData, CONTROL_ROM_SIZE); + if (file->err()) { + closeFile(file); + return LoadResult_Unreadable; + } + closeFile(file); + + // Control ROM successfully loaded, now check whether it's a known type + controlROMMap = NULL; + for (unsigned int i = 0; i < sizeof(ControlROMMaps) / sizeof(ControlROMMaps[0]); i++) { + if (memcmp(&controlROMData[ControlROMMaps[i].idPos], ControlROMMaps[i].idBytes, ControlROMMaps[i].idLen) == 0) { + controlROMMap = &ControlROMMaps[i]; + return LoadResult_OK; + } + } + printDebug("%s does not match a known control ROM type", filename); + return LoadResult_Invalid; +} + +LoadResult Synth::loadPCMROM(const char *filename) { + Common::File *file = openFile(filename); // ROM File + if (file == NULL) { + return LoadResult_NotFound; + } + size_t fileSize = file->size(); + if (fileSize < (size_t)(2 * pcmROMSize)) { + printDebug("PCM ROM file is too short (expected %d, got %d)", 2 * pcmROMSize, fileSize); + closeFile(file); + return LoadResult_Invalid; + } + if (file->err()) { + closeFile(file); + return LoadResult_Unreadable; + } + LoadResult rc = LoadResult_OK; + for (int i = 0; i < pcmROMSize; i++) { + Bit8u s = file->readByte(); + Bit8u c = file->readByte(); + + int order[16] = {0, 9, 1, 2, 3, 4, 5, 6, 7, 10, 11, 12, 13, 14, 15, 8}; + + signed short 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 | (short)(bit << (15 - u)); + } + bool negative = log < 0; + log &= 0x7FFF; + + // CONFIRMED from sample analysis to be 99.99%+ accurate with current TVA multiplier + float lin = EXP2F((32787 - log) / -2048.0f); + + if (negative) { + lin = -lin; + } + + pcmROMData[i] = lin; + } + closeFile(file); + return rc; +} + +bool Synth::initPCMList(Bit16u mapAddress, Bit16u count) { + ControlROMPCMStruct *tps = (ControlROMPCMStruct *)&controlROMData[mapAddress]; + for (int i = 0; i < count; i++) { + int rAddr = tps[i].pos * 0x800; + int rLenExp = (tps[i].len & 0x70) >> 4; + int 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(int timbreNum, const Bit8u *src, unsigned int 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, int count, int 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; +} + +bool Synth::open(SynthProperties &useProp) { + if (isOpen) { + return false; + } + prerenderReadIx = prerenderWriteIx = 0; + myProp = useProp; +#if MT32EMU_MONITOR_INIT + printDebug("Initialising Constant Tables"); +#endif + tables.init(); +#if !MT32EMU_REDUCE_REVERB_MEMORY + for (int i = 0; i < 4; i++) { + reverbModels[i]->open(useProp.sampleRate); + } +#endif + if (useProp.baseDir != NULL) { + char *baseDirCopy = new char[strlen(useProp.baseDir) + 1]; + strcpy(baseDirCopy, useProp.baseDir); + myProp.baseDir = baseDirCopy; + } + + // This is to help detect bugs + memset(&mt32ram, '?', sizeof(mt32ram)); + +#if MT32EMU_MONITOR_INIT + printDebug("Loading Control ROM"); +#endif + if (loadControlROM("CM32L_CONTROL.ROM") != LoadResult_OK) { + if (loadControlROM("MT32_CONTROL.ROM") != LoadResult_OK) { + printDebug("Init Error - Missing or invalid MT32_CONTROL.ROM"); + //report(ReportType_errorControlROM, &errno); + 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 float[pcmROMSize]; + +#if MT32EMU_MONITOR_INIT + printDebug("Loading PCM ROM"); +#endif + if (loadPCMROM("CM32L_PCM.ROM") != LoadResult_OK) { + if (loadPCMROM("MT32_PCM.ROM") != LoadResult_OK) { + printDebug("Init Error - Missing MT32_PCM.ROM"); + //report(ReportType_errorPCMROM, &errno); + return false; + } + } + +#if MT32EMU_MONITOR_INIT + printDebug("Initialising Timbre Bank A"); +#endif + if (!initTimbres(controlROMMap->timbreAMap, controlROMMap->timbreAOffset, 0x40, 0, controlROMMap->timbreACompressed)) { + return false; + } + +#if MT32EMU_MONITOR_INIT + printDebug("Initialising Timbre Bank B"); +#endif + if (!initTimbres(controlROMMap->timbreBMap, controlROMMap->timbreBOffset, 0x40, 64, controlROMMap->timbreBCompressed)) { + return false; + } + +#if MT32EMU_MONITOR_INIT + printDebug("Initialising Timbre Bank R"); +#endif + if (!initTimbres(controlROMMap->timbreRMap, 0, controlROMMap->timbreRCount, 192, true)) { + 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 + refreshSystem(); + + 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; + + isOpen = true; + isEnabled = false; + +#if MT32EMU_MONITOR_INIT + printDebug("*** Initialisation complete ***"); +#endif + return true; +} + +void Synth::close() { + if (!isOpen) { + return; + } + + delete partialManager; + partialManager = NULL; + + for (int i = 0; i < 9; i++) { + delete parts[i]; + parts[i] = NULL; + } + + delete[] myProp.baseDir; + myProp.baseDir = NULL; + + delete[] pcmWaves; + delete[] pcmROMData; + + deleteMemoryRegions(); + + for (int i = 0; i < 4; i++) { + reverbModels[i]->close(); + } + reverbModel = NULL; + isOpen = false; +} + +void Synth::playMsg(Bit32u msg) { + // FIXME: Implement active sensing + unsigned char code = (unsigned char)((msg & 0x0000F0) >> 4); + unsigned char chan = (unsigned char)(msg & 0x00000F); + unsigned char note = (unsigned char)((msg & 0x00FF00) >> 8); + unsigned char velocity = (unsigned char)((msg & 0xFF0000) >> 16); + isEnabled = true; + + //printDebug("Playing chan %d, code 0x%01x note: 0x%02x", chan, code, note); + + char part = chantable[chan]; + if (part < 0 || 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(unsigned char part, unsigned char code, unsigned char note, unsigned char velocity) { + Bit32u bend; + + //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 + break; + } + + 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 + break; + } + + //midiOutShortMsg(m_out, msg); +} + +void Synth::playSysex(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(unsigned char device, unsigned char 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 (len < 4) { + printDebug("playSysexWithoutHeader: Message is too short (%d bytes)!", len); + return; + } + unsigned char checksum = calcSysexChecksum(sysex, len - 1, 0); + 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_DAT: + 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(unsigned char /*device*/, const Bit8u * /*sysex*/, Bit32u /*len*/) const { + // NYI +} + +void Synth::writeSysex(unsigned char device, const Bit8u *sysex, Bit32u len) { + 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] == -1) { +#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] == -1) { +#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) { + 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, (Bit8u *)&mt32ram.patchTemp[0], &controlROMData[controlROMMap->patchMaxTable]); + rhythmTempMemoryRegion = new RhythmTempMemoryRegion(this, (Bit8u *)&mt32ram.rhythmTemp[0], &controlROMData[controlROMMap->rhythmMaxTable]); + timbreTempMemoryRegion = new TimbreTempMemoryRegion(this, (Bit8u *)&mt32ram.timbreTemp[0], paddedTimbreMaxTable); + patchesMemoryRegion = new PatchesMemoryRegion(this, (Bit8u *)&mt32ram.patches[0], &controlROMData[controlROMMap->patchMaxTable]); + timbresMemoryRegion = new TimbresMemoryRegion(this, (Bit8u *)&mt32ram.timbres[0], paddedTimbreMaxTable); + systemMemoryRegion = new SystemMemoryRegion(this, (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); + + report(ReportType_devReconfig, NULL); + // 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 > 9) + lastPart = 9; + refreshSystemChanAssign(firstPart, lastPart); + } + if (off <= SYSTEM_MASTER_VOL_OFF && off + len > SYSTEM_MASTER_VOL_OFF) { + refreshSystemMasterVol(); + } + break; + case MR_Display: + char buf[MAX_SYSEX_SIZE]; + memcpy(&buf, &data[0], len); + buf[len] = 0; +#if MT32EMU_MONITOR_SYSEX > 0 + printDebug("WRITE-LCD: %s", buf); +#endif + report(ReportType_lcdMessage, 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 && reverbModel != NULL) { +#if MT32EMU_MONITOR_SYSEX > 0 + printDebug(" (Reverb overridden - ignoring)"); +#endif + return; + } + report(ReportType_newReverbMode, &mt32ram.system.reverbMode); + report(ReportType_newReverbTime, &mt32ram.system.reverbTime); + report(ReportType_newReverbLevel, &mt32ram.system.reverbLevel); + + ReverbModel *newReverbModel = reverbModels[mt32ram.system.reverbMode]; +#if MT32EMU_REDUCE_REVERB_MEMORY + if (reverbModel != newReverbModel) { + if (reverbModel != NULL) { + reverbModel->close(); + } + newReverbModel->open(myProp.sampleRate); + } +#endif + reverbModel = newReverbModel; + 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(unsigned int firstPart, unsigned int lastPart) { + memset(chantable, -1, sizeof(chantable)); + + // CONFIRMED: In the case of assigning a channel to multiple parts, the lower part wins. + for (unsigned int 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(); + } + int chan = mt32ram.system.chanAssign[i]; + if (chan != 16 && chantable[chan] == -1) { + chantable[chan] = 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 MT32EMU_MONITOR_SYSEX > 0 + printDebug("RESET"); +#endif + report(ReportType_devReset, NULL); + 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(); + isEnabled = false; +} + +void Synth::render(Bit16s *stream, Bit32u len) { + if (!isEnabled) { + memset(stream, 0, len * sizeof(Bit16s) * 2); + return; + } + while (len > 0) { + Bit32u thisLen = len > MAX_SAMPLES_PER_RUN ? MAX_SAMPLES_PER_RUN : len; + renderStreams(tmpNonReverbLeft, tmpNonReverbRight, tmpReverbDryLeft, tmpReverbDryRight, tmpReverbWetLeft, tmpReverbWetRight, thisLen); + for (Bit32u i = 0; i < thisLen; i++) { + stream[0] = clipBit16s((Bit32s)tmpNonReverbLeft[i] + (Bit32s)tmpReverbDryLeft[i] + (Bit32s)tmpReverbWetLeft[i]); + stream[1] = clipBit16s((Bit32s)tmpNonReverbRight[i] + (Bit32s)tmpReverbDryRight[i] + (Bit32s)tmpReverbWetRight[i]); + stream += 2; + } + len -= thisLen; + } +} + +bool Synth::prerender() { + int newPrerenderWriteIx = (prerenderWriteIx + 1) % MAX_PRERENDER_SAMPLES; + if (newPrerenderWriteIx == prerenderReadIx) { + // The prerender buffer is full + return false; + } + doRenderStreams( + prerenderNonReverbLeft + prerenderWriteIx, + prerenderNonReverbRight + prerenderWriteIx, + prerenderReverbDryLeft + prerenderWriteIx, + prerenderReverbDryRight + prerenderWriteIx, + prerenderReverbWetLeft + prerenderWriteIx, + prerenderReverbWetRight + prerenderWriteIx, + 1); + prerenderWriteIx = newPrerenderWriteIx; + return true; +} + +static inline void maybeCopy(Bit16s *out, Bit32u outPos, Bit16s *in, Bit32u inPos, Bit32u len) { + if (out == NULL) { + return; + } + memcpy(out + outPos, in + inPos, len * sizeof(Bit16s)); +} + +void Synth::copyPrerender(Bit16s *nonReverbLeft, Bit16s *nonReverbRight, Bit16s *reverbDryLeft, Bit16s *reverbDryRight, Bit16s *reverbWetLeft, Bit16s *reverbWetRight, Bit32u pos, Bit32u len) { + maybeCopy(nonReverbLeft, pos, prerenderNonReverbLeft, prerenderReadIx, len); + maybeCopy(nonReverbRight, pos, prerenderNonReverbRight, prerenderReadIx, len); + maybeCopy(reverbDryLeft, pos, prerenderReverbDryLeft, prerenderReadIx, len); + maybeCopy(reverbDryRight, pos, prerenderReverbDryRight, prerenderReadIx, len); + maybeCopy(reverbWetLeft, pos, prerenderReverbWetLeft, prerenderReadIx, len); + maybeCopy(reverbWetRight, pos, prerenderReverbWetRight, prerenderReadIx, len); +} + +void Synth::checkPrerender(Bit16s *nonReverbLeft, Bit16s *nonReverbRight, Bit16s *reverbDryLeft, Bit16s *reverbDryRight, Bit16s *reverbWetLeft, Bit16s *reverbWetRight, Bit32u &pos, Bit32u &len) { + if (prerenderReadIx > prerenderWriteIx) { + // There's data in the prerender buffer, and the write index has wrapped. + Bit32u prerenderCopyLen = MAX_PRERENDER_SAMPLES - prerenderReadIx; + if (prerenderCopyLen > len) { + prerenderCopyLen = len; + } + copyPrerender(nonReverbLeft, nonReverbRight, reverbDryLeft, reverbDryRight, reverbWetLeft, reverbWetRight, pos, prerenderCopyLen); + len -= prerenderCopyLen; + pos += prerenderCopyLen; + prerenderReadIx = (prerenderReadIx + prerenderCopyLen) % MAX_PRERENDER_SAMPLES; + } + if (prerenderReadIx < prerenderWriteIx) { + // There's data in the prerender buffer, and the write index is ahead of the read index. + Bit32u prerenderCopyLen = prerenderWriteIx - prerenderReadIx; + if (prerenderCopyLen > len) { + prerenderCopyLen = len; + } + copyPrerender(nonReverbLeft, nonReverbRight, reverbDryLeft, reverbDryRight, reverbWetLeft, reverbWetRight, pos, prerenderCopyLen); + len -= prerenderCopyLen; + pos += prerenderCopyLen; + prerenderReadIx += prerenderCopyLen; + } + if (prerenderReadIx == prerenderWriteIx) { + // If the ring buffer's empty, reset it to start at 0 to minimise wrapping, + // which requires two writes instead of one. + prerenderReadIx = prerenderWriteIx = 0; + } +} + +void Synth::renderStreams(Bit16s *nonReverbLeft, Bit16s *nonReverbRight, Bit16s *reverbDryLeft, Bit16s *reverbDryRight, Bit16s *reverbWetLeft, Bit16s *reverbWetRight, Bit32u len) { + if (!isEnabled) { + clearIfNonNull(nonReverbLeft, len); + clearIfNonNull(nonReverbRight, len); + clearIfNonNull(reverbDryLeft, len); + clearIfNonNull(reverbDryRight, len); + clearIfNonNull(reverbWetLeft, len); + clearIfNonNull(reverbWetRight, len); + return; + } + Bit32u pos = 0; + + // First, check for data in the prerender buffer and spit that out before generating anything new. + // Note that the prerender buffer is rarely used - see comments elsewhere for details. + checkPrerender(nonReverbLeft, nonReverbRight, reverbDryLeft, reverbDryRight, reverbWetLeft, reverbWetRight, pos, len); + + while (len > 0) { + Bit32u thisLen = len > MAX_SAMPLES_PER_RUN ? MAX_SAMPLES_PER_RUN : len; + doRenderStreams( + streamOffset(nonReverbLeft, pos), + streamOffset(nonReverbRight, pos), + streamOffset(reverbDryLeft, pos), + streamOffset(reverbDryRight, pos), + streamOffset(reverbWetLeft, pos), + streamOffset(reverbWetRight, pos), + thisLen); + len -= thisLen; + pos += thisLen; + } +} + +// FIXME: Using more temporary buffers than we need to +void Synth::doRenderStreams(Bit16s *nonReverbLeft, Bit16s *nonReverbRight, Bit16s *reverbDryLeft, Bit16s *reverbDryRight, Bit16s *reverbWetLeft, Bit16s *reverbWetRight, Bit32u len) { + clearFloats(&tmpBufMixLeft[0], &tmpBufMixRight[0], len); + if (!reverbEnabled) { + for (unsigned int i = 0; i < MT32EMU_MAX_PARTIALS; i++) { + if (partialManager->produceOutput(i, &tmpBufPartialLeft[0], &tmpBufPartialRight[0], len)) { + mix(&tmpBufMixLeft[0], &tmpBufPartialLeft[0], len); + mix(&tmpBufMixRight[0], &tmpBufPartialRight[0], len); + } + } + if (nonReverbLeft != NULL) { + la32FloatToBit16sFunc(nonReverbLeft, &tmpBufMixLeft[0], len, outputGain); + } + if (nonReverbRight != NULL) { + la32FloatToBit16sFunc(nonReverbRight, &tmpBufMixRight[0], len, outputGain); + } + clearIfNonNull(reverbDryLeft, len); + clearIfNonNull(reverbDryRight, len); + clearIfNonNull(reverbWetLeft, len); + clearIfNonNull(reverbWetRight, len); + } else { + for (unsigned int i = 0; i < MT32EMU_MAX_PARTIALS; i++) { + if (!partialManager->shouldReverb(i)) { + if (partialManager->produceOutput(i, &tmpBufPartialLeft[0], &tmpBufPartialRight[0], len)) { + mix(&tmpBufMixLeft[0], &tmpBufPartialLeft[0], len); + mix(&tmpBufMixRight[0], &tmpBufPartialRight[0], len); + } + } + } + if (nonReverbLeft != NULL) { + la32FloatToBit16sFunc(nonReverbLeft, &tmpBufMixLeft[0], len, outputGain); + } + if (nonReverbRight != NULL) { + la32FloatToBit16sFunc(nonReverbRight, &tmpBufMixRight[0], len, outputGain); + } + + clearFloats(&tmpBufMixLeft[0], &tmpBufMixRight[0], len); + for (unsigned int i = 0; i < MT32EMU_MAX_PARTIALS; i++) { + if (partialManager->shouldReverb(i)) { + if (partialManager->produceOutput(i, &tmpBufPartialLeft[0], &tmpBufPartialRight[0], len)) { + mix(&tmpBufMixLeft[0], &tmpBufPartialLeft[0], len); + mix(&tmpBufMixRight[0], &tmpBufPartialRight[0], len); + } + } + } + if (reverbDryLeft != NULL) { + la32FloatToBit16sFunc(reverbDryLeft, &tmpBufMixLeft[0], len, outputGain); + } + if (reverbDryRight != NULL) { + la32FloatToBit16sFunc(reverbDryRight, &tmpBufMixRight[0], len, outputGain); + } + + // FIXME: Note that on the real devices, reverb input and output are signed linear 16-bit (well, kinda, there's some fudging) PCM, not float. + reverbModel->process(&tmpBufMixLeft[0], &tmpBufMixRight[0], &tmpBufReverbOutLeft[0], &tmpBufReverbOutRight[0], len); + if (reverbWetLeft != NULL) { + reverbFloatToBit16sFunc(reverbWetLeft, &tmpBufReverbOutLeft[0], len, reverbOutputGain); + } + if (reverbWetRight != NULL) { + reverbFloatToBit16sFunc(reverbWetRight, &tmpBufReverbOutRight[0], len, reverbOutputGain); + } + } + partialManager->clearAlreadyOutputed(); + renderedSampleCount += len; +} + +void Synth::printPartialUsage(unsigned long sampleOffset) { + unsigned int partialUsage[9]; + partialManager->getPerPartPartialUsage(partialUsage); + if (sampleOffset > 0) { + printDebug("[+%lu] 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], MT32EMU_MAX_PARTIALS - 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], MT32EMU_MAX_PARTIALS - partialManager->getFreePartialCount()); + } +} + +bool Synth::hasActivePartials() const { + if (prerenderReadIx != prerenderWriteIx) { + // Data in the prerender buffer means that the current isActive() states are "in the future". + // It also means that partials are definitely active at this render point. + return true; + } + for (int partialNum = 0; partialNum < MT32EMU_MAX_PARTIALS; partialNum++) { + if (partialManager->getPartial(partialNum)->isActive()) { + return true; + } + } + return false; +} + +bool Synth::isActive() const { + if (hasActivePartials()) { + return true; + } + if (reverbEnabled) { + return reverbModel->isActive(); + } + return false; +} + +const Partial *Synth::getPartial(unsigned int partialNum) const { + return partialManager->getPartial(partialNum); +} + +const Part *Synth::getPart(unsigned int 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 + } + + 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++; + } +} + +} |