From 077d19f5008e5cb911b1291f59b63fc46da18cc8 Mon Sep 17 00:00:00 2001 From: Jerome Fisher Date: Sun, 28 Nov 2004 05:35:07 +0000 Subject: - Added graphical representation of initialisation progress. This is quite hacky. - Initialisation is now interruptible. - All data is now loaded from MT32_CONTROL.ROM. drumpat.rom, Preset1.syx, Preset2.syx and patchlog.cfg are no longer used. - Major cleanup. In particular, separated Rhythm part into a new class, instead of dealing with it as a special case everywhere. - Improved accuracy of pitch key-follow. - Recaching now happens lazily. - Changed some right-shifts to divs, due to the former not being arithmetic on some architectures. - Setting "MT32EMU_ACCURATENOTES" to 1 will generate lookup tables for the exact frequency of every note played. Not recommended. - Several small bugs fixed. svn-id: r15929 --- backends/midi/mt32.cpp | 232 +++++++++- backends/midi/mt32/mt32_file.h | 2 +- backends/midi/mt32/mt32emu.h | 4 - backends/midi/mt32/part.cpp | 505 ++++++++++----------- backends/midi/mt32/part.h | 66 ++- backends/midi/mt32/partial.cpp | 287 ++++++------ backends/midi/mt32/partial.h | 35 +- backends/midi/mt32/partialManager.cpp | 49 +-- backends/midi/mt32/partialManager.h | 20 +- backends/midi/mt32/structures.h | 179 ++++---- backends/midi/mt32/synth.cpp | 800 ++++++++++++---------------------- backends/midi/mt32/synth.h | 84 ++-- backends/midi/mt32/tables.cpp | 126 +++--- backends/midi/mt32/tables.h | 28 +- 14 files changed, 1184 insertions(+), 1233 deletions(-) diff --git a/backends/midi/mt32.cpp b/backends/midi/mt32.cpp index 73775639f1..921141d4ea 100644 --- a/backends/midi/mt32.cpp +++ b/backends/midi/mt32.cpp @@ -1,10 +1,6 @@ /* ScummVM - Scumm Interpreter * Copyright (C) 2001-2004 The ScummVM project * - * YM2612 tone generation code written by Tomoaki Hayasaka. - * Used under the terms of the GNU General Public License. - * Adpated to ScummVM by Jamieson Christian. - * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 @@ -36,6 +32,9 @@ #include "common/file.h" #include "common/config-manager.h" +#include "graphics/font.h" +#include "graphics/surface.h" + class MidiChannel_MT32 : public MidiChannel_MPU401 { void effectLevel(byte value) { } void chorusLevel(byte value) { } @@ -54,6 +53,8 @@ protected: void generateSamples(int16 *buf, int len); public: + bool _initialising; + MidiDriver_MT32(SoundMixer *mixer); virtual ~MidiDriver_MT32(); @@ -109,6 +110,54 @@ public: } }; +static int eatSystemEvents() { + OSystem::Event event; + while (g_system->pollEvent(event)) { + switch (event.event_code) { + case OSystem::EVENT_QUIT: + return 1; + } + } + return 0; +} + +static void drawProgress(float progress) { + Graphics::Surface surf; + uint32 borderColor = 0x2; + uint32 fillColor = 0x4; + surf.w = g_system->getWidth() / 7 * 5; + surf.h = Graphics::g_sysfont.getFontHeight(); + int x = g_system->getWidth() / 7; + int y = g_system->getHeight() / 2 - surf.h / 2; + surf.pitch = surf.w; + surf.bytesPerPixel = 1; + surf.pixels = calloc(surf.w, surf.h); + Common::Rect r(surf.w, surf.h); + surf.frameRect(r, borderColor); + r.grow(-1); + r.right = r.left + (uint16)(r.width() * progress); + surf.fillRect(r, fillColor); + g_system->copyRectToScreen((byte *)surf.pixels, surf.pitch, x, y, surf.w, surf.h); + g_system->updateScreen(); + free(surf.pixels); +} + +static void drawMessage(int offset, const Common::String &text) { + const Graphics::Font &font(Graphics::g_sysfont); + Graphics::Surface surf; + uint32 color = 0x2; + surf.w = g_system->getWidth(); + surf.h = font.getFontHeight(); + surf.pitch = surf.w; + surf.bytesPerPixel = 1; + surf.pixels = calloc(surf.w, surf.h); + font.drawString(&surf, text, 0, 0, surf.w, color, Graphics::kTextAlignCenter); + int y = g_system->getHeight() / 2 - font.getFontHeight() / 2 + offset * (font.getFontHeight() + 1); + g_system->copyRectToScreen((byte *)surf.pixels, surf.pitch, 0, y, surf.w, surf.h); + g_system->updateScreen(); + free(surf.pixels); +} + static MT32Emu::File *MT32_OpenFile(void *userData, const char *filename, MT32Emu::File::OpenMode mode) { MT32File *file = new MT32File(); if (!file->open(filename, mode)) { @@ -119,32 +168,36 @@ static MT32Emu::File *MT32_OpenFile(void *userData, const char *filename, MT32Em } static void MT32_PrintDebug(void *userData, const char *fmt, va_list list) { + char buf[512]; + if (((MidiDriver_MT32 *)userData)->_initialising) { + vsprintf(buf, fmt, list); + buf[70] = 0; // Truncate to a reasonable length + drawMessage(1, buf); + } //vdebug(0, fmt, list); // FIXME: Use a higher debug level } -static void MT32_Report(void *userData, MT32Emu::ReportType type, const void *reportData) { +static int MT32_Report(void *userData, MT32Emu::ReportType type, const void *reportData) { switch(type) { case MT32Emu::ReportType_lcdMessage: g_system->displayMessageOnOSD((const char *)reportData); break; - case MT32Emu::ReportType_errorPreset1: - error("Couldn't open Preset1.syx file"); + case MT32Emu::ReportType_errorControlROM: + error("Failed to load MT32_CONTROL.ROM"); break; - case MT32Emu::ReportType_errorPreset2: - error("Couldn't open Preset2.syx file"); + case MT32Emu::ReportType_errorPCMROM: + error("Failed to load MT32_PCM.ROM"); break; - case MT32Emu::ReportType_errorDrumpat: - error("Couldn't open drumpat.rom file"); - break; - case MT32Emu::ReportType_errorPatchlog: - error("Couldn't open patchlog.cfg file"); - break; - case MT32Emu::ReportType_errorMT32ROM: - error("Couldn't open MT32_PCM.ROM file"); + case MT32Emu::ReportType_progressInit: + if (((MidiDriver_MT32 *)userData)->_initialising) { + drawProgress(*((float *)reportData)); + return eatSystemEvents(); + } break; default: break; } + return 0; } //////////////////////////////////////// @@ -160,10 +213,15 @@ MidiDriver_MT32::MidiDriver_MT32(SoundMixer *mixer) : MidiDriver_Emulated(mixer) _midiChannels[i].init(this, i); } _synth = NULL; - - _baseFreq = 1000; - + // A higher baseFreq reduces the length used in generateSamples(), + // and means that the timer callback will be called more often. + // That results in more accurate timing. + _baseFreq = 10000; + // Unfortunately bugs in the emulator cause inaccurate tuning + // at rates other than 32KHz, thus we produce data at 32KHz and + // rely on SoundMixer to convert. _outputRate = 32000; //_mixer->getOutputRate(); + _initialising = false; } MidiDriver_MT32::~MidiDriver_MT32() { @@ -186,15 +244,19 @@ int MidiDriver_MT32::open() { prop.reverbType = 0; prop.reverbTime = 5; prop.reverbLevel = 3; + prop.userData = this; prop.printDebug = MT32_PrintDebug; prop.report = MT32_Report; prop.openFile = MT32_OpenFile; _synth = new MT32Emu::Synth(); + _initialising = true; + drawMessage(-1, "Initialising MT-32 Emulator"); if (!_synth->open(prop)) return MERR_DEVICE_NOT_AVAILABLE; - + _initialising = false; + g_system->clearScreen(); + g_system->updateScreen(); _mixer->playInputStream(&_handle, this, false, 255, 0, -1, false); - return 0; } @@ -232,7 +294,9 @@ void MidiDriver_MT32::close() { return; _isOpen = false; - // Detach the premix callback handler + // Detach the player callback handler + setTimerCallback(NULL, NULL); + // Detach the mixer callback handler _mixer->stopHandle(_handle); _synth->close(); @@ -273,6 +337,128 @@ MidiChannel *MidiDriver_MT32::getPercussionChannel() { return &_midiChannels[9]; } +// This code should be used when calling the timer callback from the mixer thread is undesirable. +// Note that it results in less accurate timing. +#if 0 +class MidiEvent_MT32 { +public: + MidiEvent_MT32 *_next; + uint32 _msg; // 0xFFFFFFFF indicates a sysex message + byte *_data; + uint32 _len; + + MidiEvent_MT32(uint32 msg, byte *data, uint32 len) { + _msg = msg; + if (len > 0) { + _data = new byte[len]; + memcpy(_data, data, len); + } + _len = len; + _next = NULL; + } + + MidiEvent_MT32() { + if (_len > 0) + delete _data; + } +}; + +class MidiDriver_ThreadedMT32 : public MidiDriver_MT32 { +private: + OSystem::MutexRef _eventMutex; + MidiEvent_MT32 *_events; + Timer::TimerProc _timer_proc; + + void pushMidiEvent(MidiEvent_MT32 *event); + MidiEvent_MT32 *popMidiEvent(); + +protected: + void send(uint32 b); + void sysEx(byte *msg, uint16 length); + +public: + MidiDriver_ThreadedMT32(SoundMixer *mixer); + virtual ~MidiDriver_ThreadedMT32(); + + void onTimer(); + void close(); + void setTimerCallback(void *timer_param, Timer::TimerProc timer_proc); +}; + + +MidiDriver_ThreadedMT32::MidiDriver_ThreadedMT32(SoundMixer *mixer) : MidiDriver_MT32(mixer) { + _eventMutex = g_system->createMutex(); + _events = NULL; + _timer_proc = NULL; +} + +MidiDriver_ThreadedMT32::~MidiDriver_ThreadedMT32() { + g_system->deleteMutex(_eventMutex); +} + +void MidiDriver_ThreadedMT32::close() { + MidiDriver_MT32::close(); + while ((popMidiEvent() != NULL)) { + // Just eat any leftover events + } +} + +void MidiDriver_ThreadedMT32::setTimerCallback(void *timer_param, Timer::TimerProc timer_proc) { + if (!_timer_proc || !timer_proc) { + if (_timer_proc) + g_timer->removeTimerProc(_timer_proc); + _timer_proc = timer_proc; + if (timer_proc) + g_timer->installTimerProc(timer_proc, getBaseTempo(), timer_param); + } +} + +void MidiDriver_ThreadedMT32::pushMidiEvent(MidiEvent_MT32 *event) { + g_system->lockMutex(_eventMutex); + if (_events == NULL) { + _events = event; + } else { + MidiEvent_MT32 *last = _events; + while (last->_next != NULL) + last = last->_next; + last->_next = event; + } + g_system->unlockMutex(_eventMutex); +} + +MidiEvent_MT32 *MidiDriver_ThreadedMT32::popMidiEvent() { + MidiEvent_MT32 *event; + g_system->lockMutex(_eventMutex); + event = _events; + if (event != NULL) + _events = event->_next; + g_system->unlockMutex(_eventMutex); + return event; +} + +void MidiDriver_ThreadedMT32::send(uint32 b) { + MidiEvent_MT32 *event = new MidiEvent_MT32(b, NULL, 0); + pushMidiEvent(event); +} + +void MidiDriver_ThreadedMT32::sysEx(byte *msg, uint16 length) { + MidiEvent_MT32 *event = new MidiEvent_MT32(0xFFFFFFFF, msg, length); + pushMidiEvent(event); +} + +void MidiDriver_ThreadedMT32::onTimer() { + MidiEvent_MT32 *event; + while ((event = popMidiEvent()) != NULL) { + if (event->_msg == 0xFFFFFFFF) { + MidiDriver_MT32::sysEx(event->_data, event->_len); + } else { + MidiDriver_MT32::send(event->_msg); + } + delete event; + } +} +#endif + //////////////////////////////////////// // // MidiDriver_MT32 factory diff --git a/backends/midi/mt32/mt32_file.h b/backends/midi/mt32/mt32_file.h index f5888db5e3..7ebb6449b6 100644 --- a/backends/midi/mt32/mt32_file.h +++ b/backends/midi/mt32/mt32_file.h @@ -41,7 +41,7 @@ public: virtual bool readBit32u(Bit32u *in); virtual size_t write(const void *out, size_t size) = 0; virtual bool writeBit8u(Bit8u out) = 0; - // Note: May write some a single byte to the file before failing + // Note: May write a single byte to the file before failing virtual bool writeBit16u(Bit16u out); // Note: May write some (<4) bytes to the file before failing virtual bool writeBit32u(Bit32u out); diff --git a/backends/midi/mt32/mt32emu.h b/backends/midi/mt32/mt32emu.h index 48c3d903fd..9fffa721f5 100644 --- a/backends/midi/mt32/mt32emu.h +++ b/backends/midi/mt32/mt32emu.h @@ -27,10 +27,6 @@ #define MT32EMU_MONITOR_INSTRUMENTS 1 // Shows number of partials MT-32 is playing, and on which parts #define MT32EMU_MONITOR_PARTIALS 0 -// Dump drum patches to syx file for viewing -#define MT32EMU_DUMP_DRUMS 0 -// Output benchmarks for the filter implementations -#define MT32EMU_BENCHMARK_FILTERS 0 // Determines how the waveform cache file is handled (must be regenerated after sampling rate change) #define MT32EMU_WAVECACHEMODE 0 // Load existing cache if possible, otherwise generate and save cache //#define MT32EMU_WAVECACHEMODE 1 // Load existing cache if possible, otherwise generage but don't save cache diff --git a/backends/midi/mt32/part.cpp b/backends/midi/mt32/part.cpp index 6fe783e1b6..c240874143 100644 --- a/backends/midi/mt32/part.cpp +++ b/backends/midi/mt32/part.cpp @@ -34,16 +34,19 @@ static const Bit8u PartialMixStruct[13] = { 0, 1, 0, 1, 1, 0, 1, 3, 3, 2, 2, 2, 2 }; -// This caches the timbres/settings in use by the rhythm part -static PatchCache drumCache[94][4]; -static StereoVolume drumPan[64]; +static const float floatKeyfollow[17] = { + -1.0f, -1.0f/2.0f, -1.0f/4.0f, 0.0f, + 1.0f/8.0f, 1.0f/4.0f, 3.0f/8.0f, 1.0f/2.0f, 5.0f/8.0f, 3.0f/4.0f, 7.0f/8.0f, 1.0f, + 5.0f/4.0f, 3.0f/2.0f, 2.0f, + 1.0009765625f, 1.0048828125f +}; //FIXME:KG: Put this dpoly stuff somewhere better -bool dpoly::isActive() { +bool dpoly::isActive() const { return partials[0] != NULL || partials[1] != NULL || partials[2] != NULL || partials[3] != NULL; } -Bit64s dpoly::getAge() { +Bit64s dpoly::getAge() const { for (int i = 0; i < 4; i++) { if (partials[i] != NULL) { return partials[i]->age; @@ -52,36 +55,34 @@ Bit64s dpoly::getAge() { return 0; } -Part::Part(Synth *useSynth, int usePartNum) { +RhythmPart::RhythmPart(Synth *useSynth, unsigned int usePartNum): Part(useSynth, usePartNum) { + strcpy(name, "Rhythm"); + rhythmTemp = &synth->mt32ram.rhythmSettings[0]; + refresh(); +} + +Part::Part(Synth *useSynth, unsigned int usePartNum) { this->synth = useSynth; this->partNum = usePartNum; - isRhythm = (usePartNum == 8); + patchCache[0].dirty = true; holdpedal = false; - if (isRhythm) { - strcpy(name, "Rhythm"); + if (usePartNum == 8) { + // Nasty hack for rhythm patchTemp = NULL; timbreTemp = NULL; - rhythmTemp = &synth->mt32ram.params.rhythmSettings[0]; } else { sprintf(name, "Part %d", partNum + 1); - patchTemp = &synth->mt32ram.params.patchSettings[partNum]; - timbreTemp = &synth->mt32ram.params.timbreSettings[partNum]; - rhythmTemp = NULL; + patchTemp = &synth->mt32ram.patchSettings[partNum]; + timbreTemp = &synth->mt32ram.timbreSettings[partNum]; } currentInstr[0] = 0; currentInstr[10] = 0; - volume = 102; + volume = voltable[102]; //FIXME:KG: Original was just volume=102; I assume this is intended volumesetting.leftvol = 32767; volumesetting.rightvol = 32767; bend = 0.0f; memset(polyTable,0,sizeof(polyTable)); memset(patchCache, 0, sizeof(patchCache)); - - if (isRhythm) { - init = true; - refreshDrumCache(); - } - init = false; } void Part::setHoldPedal(bool pedalval) { @@ -90,16 +91,17 @@ void Part::setHoldPedal(bool pedalval) { holdpedal = pedalval; } -void Part::setBend(int midiBend) { - if (isRhythm) { - synth->printDebug("%s: Setting bend (%d) not supported on rhythm", name, midiBend); - return; - } - // FIXME:KG: Slightly uneven increments, but I wanted min -1.0, centre 0.0 and max 1.0 +void RhythmPart::setBend(unsigned int midiBend) { + synth->printDebug("%s: Setting bend (%d) not supported on rhythm", name, midiBend); + return; +} + +void Part::setBend(unsigned int midiBend) { + // FIXME:KG: Slightly unbalanced increments, but I wanted min -1.0, centre 0.0 and max 1.0 if (midiBend <= 0x2000) { - bend = (midiBend - 0x2000) / (float)0x2000; + bend = ((signed int)midiBend - 0x2000) / (float)0x2000; } else { - bend = (midiBend - 0x2000) / (float)0x1FFF; + bend = ((signed int)midiBend - 0x2000) / (float)0x1FFF; } // Loop through all partials to update their bend for (int i = 0; i < MT32EMU_MAX_POLY; i++) { @@ -111,15 +113,15 @@ void Part::setBend(int midiBend) { } } -void Part::setModulation(int vol) { - if (isRhythm) { - synth->printDebug("%s: Setting modulation (%d) not supported on rhythm", name, vol); - return; - } +void RhythmPart::setModulation(unsigned int midiModulation) { + synth->printDebug("%s: Setting modulation (%d) not supported on rhythm", name, midiModulation); +} + +void Part::setModulation(unsigned int midiModulation) { // Just a bloody guess, as always, before I get things figured out for (int t = 0; t < 4; t++) { if (patchCache[t].playPartial) { - int newrate = (patchCache[t].modsense * vol) >> 7; + int newrate = (patchCache[t].modsense * midiModulation) >> 7; //patchCache[t].lfoperiod = lfotable[newrate]; patchCache[t].lfodepth = newrate; //FIXME:KG: timbreTemp->partial[t].lfo.depth = @@ -127,52 +129,78 @@ void Part::setModulation(int vol) { } } -void Part::refreshDrumCache() { - if (!isRhythm) { - synth->printDebug("ERROR: RefreshDrumCache() called on non-rhythm part"); - } - // Cache drum patches - for (int m = 0; m < 64; m++) { - int drumTimbre = rhythmTemp[m].timbre; - if (drumTimbre >= 94) +void RhythmPart::refresh() { + // (Re-)cache all the mapped timbres ahead of time + for (unsigned int drumNum = 0; drumNum < 64; drumNum++) { + int drumTimbreNum = rhythmTemp[drumNum].timbre; + if (drumTimbreNum >= 94) continue; - setPatch(drumTimbre + 128); // This is to cache all the mapped drum timbres ahead of time - Bit16s pan = rhythmTemp[m].panpot; // They use R-L 0-14... + Bit16s pan = rhythmTemp[drumNum].panpot; // They use R-L 0-14... + // FIXME:KG: Panning cache should be backed up to partials using it, too // FIXME:KG: If I don't have left/right mixed up here, it's pure luck if (pan < 7) { - drumPan[m].leftvol = 32767; - drumPan[m].rightvol = pan * 4681; + drumPan[drumNum].leftvol = 32767; + drumPan[drumNum].rightvol = pan * 4681; } else { - drumPan[m].rightvol = 32767; - drumPan[m].leftvol = (14 - pan) * 4681; + drumPan[drumNum].rightvol = 32767; + drumPan[drumNum].leftvol = (14 - pan) * 4681; } + PatchCache *cache = drumCache[drumNum]; + backupCacheToPartials(cache); + for (int t = 0; t < 4; t++) { + // Common parameters, stored redundantly + cache[t].dirty = true; + cache[t].pitchShift = 0.0f; + cache[t].benderRange = 0.0f; + cache[t].pansetptr = &drumPan[drumNum]; + cache[t].reverb = rhythmTemp[drumNum].reverbSwitch > 0; + } + } +} + +void Part::refresh() { + backupCacheToPartials(patchCache); + for (int t = 0; t < 4; t++) { + // Common parameters, stored redundantly + patchCache[t].dirty = true; + patchCache[t].pitchShift = (patchTemp->patch.keyShift - 24) + (patchTemp->patch.fineTune - 50) / 100.0f; + patchCache[t].benderRange = patchTemp->patch.benderRange; + patchCache[t].pansetptr = &volumesetting; + patchCache[t].reverb = patchTemp->patch.reverbSwitch > 0; + } + memcpy(currentInstr, timbreTemp->common.name, 10); +} + +void RhythmPart::refreshTimbre(unsigned int absTimbreNum) { + for (int m = 0; m < 64; m++) { + if (rhythmTemp[m].timbre == absTimbreNum - 128) + drumCache[m][0].dirty = true; + } +} + +void Part::refreshTimbre(unsigned int absTimbreNum) { + if (getAbsTimbreNum() == absTimbreNum) { + memcpy(currentInstr, timbreTemp->common.name, 10); + patchCache[0].dirty = true; } } int Part::fixBiaslevel(int srcpnt, int *dir) { - int noteat = srcpnt & 63; + int noteat = srcpnt & 0x3F; int outnote; - *dir = 1; if (srcpnt < 64) *dir = 0; + else + *dir = 1; outnote = 33 + noteat; //synth->printDebug("Bias note %d, dir %d", outnote, *dir); return outnote; } -int Part::fixKeyfollow(int srckey, int *dir) { +int Part::fixKeyfollow(int srckey) { if (srckey>=0 && srckey<=16) { - //int keyfix[17] = { 256, 128, 64, 0, 32, 64, 96, 128, 128+32, 192, 192+32, 256, 256+64, 256+128, 512, 259, 269 }; - int keyfix[17] = { 256*16, 128*16, 64*16, 0, 32*16, 64*16, 96*16, 128*16, (128+32)*16, 192*16, (192+32)*16, 256*16, (256+64)*16, (256+128)*16, (512)*16, 4100, 4116}; - - if (srckey<3) - *dir = -1; - else if (srckey==3) - *dir = 0; - else - *dir = 1; - + int keyfix[17] = { -256*16, -128*16, -64*16, 0, 32*16, 64*16, 96*16, 128*16, (128+32)*16, 192*16, (192+32)*16, 256*16, (256+64)*16, (256+128)*16, (512)*16, 4100, 4116}; return keyfix[srckey]; } else { //LOG(LOG_ERROR|LOG_MISC,"Missed key: %d", srckey); @@ -180,10 +208,6 @@ int Part::fixKeyfollow(int srckey, int *dir) { } } -void Part::refreshPatch() { - setPatch(-1); -} - void Part::abortPoly(dpoly *poly) { if (!poly->isPlaying) { return; @@ -197,7 +221,7 @@ void Part::abortPoly(dpoly *poly) { poly->isPlaying = false; } -void Part::setPatch(PatchParam *patch) { +void Part::setPatch(const PatchParam *patch) { patchTemp->patch = *patch; } @@ -205,187 +229,165 @@ void Part::setTimbre(TimbreParam *timbre) { *timbreTemp = *timbre; } -unsigned int Part::getAbsTimbreNum() { - if (isRhythm) { - synth->printDebug("%s: Attempted to call getAbsTimbreNum() - doesn't make sense for rhythm"); - return 0; - } +unsigned int RhythmPart::getAbsTimbreNum() const { + synth->printDebug("%s: Attempted to call getAbsTimbreNum() - doesn't make sense for rhythm"); + return 0; +} + +unsigned int Part::getAbsTimbreNum() const { return (patchTemp->patch.timbreGroup * 64) + patchTemp->patch.timbreNum; } -void Part::setPatch(int patchNum) { - int absTimbreNum = -1; // Initialised to please compiler - const TimbreParam *timbre; - if (isRhythm) { - // "patchNum" is treated as "timbreNum" for rhythm part - if (patchNum < 128) { - synth->printDebug("%s: Patch #%d is not valid for rhythm (must be >= 128)", name, patchNum); - return; - } - absTimbreNum = patchNum; - timbre = &synth->mt32ram.params.timbres[absTimbreNum].timbre; - } else { - if (patchNum >= 0) { - setPatch(&synth->mt32ram.params.patches[patchNum]); - } - if (patchNum >= 0) { - setTimbre(&synth->mt32ram.params.timbres[getAbsTimbreNum()].timbre); - } - timbre = timbreTemp; +void RhythmPart::setProgram(unsigned int patchNum) { + synth->printDebug("%s: Attempt to set program (%d) on rhythm is invalid", name, patchNum); +} + +void Part::setProgram(unsigned int patchNum) { + setPatch(&synth->mt32ram.patches[patchNum]); + setTimbre(&synth->mt32ram.timbres[getAbsTimbreNum()].timbre); #if 0 - // Immediately stop all partials on this part (this is apparently *not* the correct behaviour) - for (int m = 0; m < MT32EMU_MAX_POLY; m++) { - AbortPoly(poly); - } -#else - // check if any partials are still playing with the old patch cache - // if so then duplicate the cached data from the part to the partial so that - // we can change the part's cache without affecting the partial. - // We delay this until now to avoid a copy operation with every note played - for (int m = 0; m < MT32EMU_MAX_POLY; m++) { - for (int i = 0; i < 4; i++) { - Partial *partial = polyTable[m].partials[i]; - if (partial != NULL && partial->patchCache == &patchCache[i]) { - // copy cache data - partial->cachebackup = patchCache[i]; - // update pointers - partial->patchCache = &partial->cachebackup; - } + // Immediately stop all partials on this part (this is apparently *not* the correct behaviour) + for (int m = 0; m < MT32EMU_MAX_POLY; m++) { + AbortPoly(poly); + } +#endif + + refresh(); + + allStop(); //FIXME:KG: Is this correct? +} + +void Part::backupCacheToPartials(PatchCache cache[4]) { + // check if any partials are still playing with the old patch cache + // if so then duplicate the cached data from the part to the partial so that + // we can change the part's cache without affecting the partial. + // We delay this until now to avoid a copy operation with every note played + for (int m = 0; m < MT32EMU_MAX_POLY; m++) { + for (int i = 0; i < 4; i++) { + Partial *partial = polyTable[m].partials[i]; + if (partial != NULL && partial->patchCache == &cache[i]) { + partial->cachebackup = cache[i]; + partial->patchCache = &partial->cachebackup; } } -#endif } +} - memcpy(currentInstr, timbre->common.name, 10); - +void Part::cacheTimbre(PatchCache cache[4], const TimbreParam *timbre) { + backupCacheToPartials(cache); int partialCount = 0; for (int t = 0; t < 4; t++) { if (((timbre->common.pmute >> t) & 0x1) == 1) { - patchCache[t].playPartial = true; + cache[t].playPartial = true; partialCount++; } else { - patchCache[t].playPartial = false; + cache[t].playPartial = false; continue; } // Calculate and cache common parameters - patchCache[t].pcm = timbre->partial[t].wg.pcmwave; - patchCache[t].useBender = (timbre->partial[t].wg.bender == 1); + cache[t].pcm = timbre->partial[t].wg.pcmwave; + cache[t].useBender = (timbre->partial[t].wg.bender == 1); switch (t) { case 0: - patchCache[t].PCMPartial = (PartialStruct[(int)timbre->common.pstruct12] & 0x2) ? true : false; - patchCache[t].structureMix = PartialMixStruct[(int)timbre->common.pstruct12]; - patchCache[t].structurePosition = 0; - patchCache[t].structurePair = 1; + cache[t].PCMPartial = (PartialStruct[(int)timbre->common.pstruct12] & 0x2) ? true : false; + cache[t].structureMix = PartialMixStruct[(int)timbre->common.pstruct12]; + cache[t].structurePosition = 0; + cache[t].structurePair = 1; break; case 1: - patchCache[t].PCMPartial = (PartialStruct[(int)timbre->common.pstruct12] & 0x1) ? true : false; - patchCache[t].structureMix = PartialMixStruct[(int)timbre->common.pstruct12]; - patchCache[t].structurePosition = 1; - patchCache[t].structurePair = 0; + cache[t].PCMPartial = (PartialStruct[(int)timbre->common.pstruct12] & 0x1) ? true : false; + cache[t].structureMix = PartialMixStruct[(int)timbre->common.pstruct12]; + cache[t].structurePosition = 1; + cache[t].structurePair = 0; break; case 2: - patchCache[t].PCMPartial = (PartialStruct[(int)timbre->common.pstruct34] & 0x2) ? true : false; - patchCache[t].structureMix = PartialMixStruct[(int)timbre->common.pstruct34]; - patchCache[t].structurePosition = 0; - patchCache[t].structurePair = 3; + cache[t].PCMPartial = (PartialStruct[(int)timbre->common.pstruct34] & 0x2) ? true : false; + cache[t].structureMix = PartialMixStruct[(int)timbre->common.pstruct34]; + cache[t].structurePosition = 0; + cache[t].structurePair = 3; break; case 3: - patchCache[t].PCMPartial = (PartialStruct[(int)timbre->common.pstruct34] & 0x1) ? true : false; - patchCache[t].structureMix = PartialMixStruct[(int)timbre->common.pstruct34]; - patchCache[t].structurePosition = 1; - patchCache[t].structurePair = 2; + cache[t].PCMPartial = (PartialStruct[(int)timbre->common.pstruct34] & 0x1) ? true : false; + cache[t].structureMix = PartialMixStruct[(int)timbre->common.pstruct34]; + cache[t].structurePosition = 1; + cache[t].structurePair = 2; break; default: break; } - patchCache[t].waveform = timbre->partial[t].wg.waveform; - patchCache[t].pulsewidth = timbre->partial[t].wg.pulsewid; - patchCache[t].pwsens = timbre->partial[t].wg.pwvelo; - patchCache[t].pitchkeyfollow = fixKeyfollow(timbre->partial[t].wg.keyfollow, &patchCache[t].pitchkeydir); - - // Calculate and cache pitch stuff - patchCache[t].pitchshift = timbre->partial[t].wg.coarse; - Bit32s pFine, fShift; - pFine = (Bit32s)timbre->partial[t].wg.fine; - if (isRhythm) { - patchCache[t].pitchshift += 24; - fShift = pFine + 50; + cache[t].waveform = timbre->partial[t].wg.waveform; + cache[t].pulsewidth = timbre->partial[t].wg.pulsewid; + cache[t].pwsens = timbre->partial[t].wg.pwvelo; + if (timbre->partial[t].wg.keyfollow > 16) { + synth->printDebug("Bad keyfollow value in timbre!"); + cache[t].pitchKeyfollow = 1.0f; } else { - patchCache[t].pitchshift += patchTemp->patch.keyShift; - fShift = pFine + (Bit32s)patchTemp->patch.fineTune; + cache[t].pitchKeyfollow = floatKeyfollow[timbre->partial[t].wg.keyfollow]; } - patchCache[t].fineshift = finetable[fShift]; - patchCache[t].pitchEnv = timbre->partial[t].env; - patchCache[t].pitchEnv.sensitivity = (char)((float)patchCache[t].pitchEnv.sensitivity*1.27); - patchCache[t].pitchsustain = patchCache[t].pitchEnv.level[3]; + cache[t].pitch = timbre->partial[t].wg.coarse + (timbre->partial[t].wg.fine - 50) / 100.0f + 24.0f; + cache[t].pitchEnv = timbre->partial[t].env; + cache[t].pitchEnv.sensitivity = (char)((float)cache[t].pitchEnv.sensitivity * 1.27f); + cache[t].pitchsustain = cache[t].pitchEnv.level[3]; // Calculate and cache TVA envelope stuff - patchCache[t].ampEnv = timbre->partial[t].tva; + cache[t].ampEnv = timbre->partial[t].tva; for (int i = 0; i < 4; i++) - patchCache[t].ampEnv.envlevel[i] = (char)((float)patchCache[t].ampEnv.envlevel[i] * 1.27f); - patchCache[t].ampEnv.level = (char)((float)patchCache[t].ampEnv.level * 1.27f); + cache[t].ampEnv.envlevel[i] = (char)((float)cache[t].ampEnv.envlevel[i] * 1.27f); + cache[t].ampEnv.level = (char)((float)cache[t].ampEnv.level * 1.27f); float tvelo = ((float)timbre->partial[t].tva.velosens / 100.0f); float velo = fabs(tvelo-0.5f) * 2.0f; velo *= 63.0f; - patchCache[t].ampEnv.velosens = (char)velo; + cache[t].ampEnv.velosens = (char)velo; if (tvelo<0.5f) - patchCache[t].ampenvdir = 1; + cache[t].ampenvdir = 1; else - patchCache[t].ampenvdir = 0; + cache[t].ampenvdir = 0; - patchCache[t].ampbias[0] = fixBiaslevel(patchCache[t].ampEnv.biaspoint1, &patchCache[t].ampdir[0]); - patchCache[t].ampblevel[0] = 12 - patchCache[t].ampEnv.biaslevel1; - patchCache[t].ampbias[1] = fixBiaslevel(patchCache[t].ampEnv.biaspoint2, &patchCache[t].ampdir[1]); - patchCache[t].ampblevel[1] = 12 - patchCache[t].ampEnv.biaslevel2; - patchCache[t].ampdepth = patchCache[t].ampEnv.envvkf * patchCache[t].ampEnv.envvkf; - patchCache[t].ampsustain = patchCache[t].ampEnv.envlevel[3]; - patchCache[t].amplevel = patchCache[t].ampEnv.level; + cache[t].ampbias[0] = fixBiaslevel(cache[t].ampEnv.biaspoint1, &cache[t].ampdir[0]); + cache[t].ampblevel[0] = 12 - cache[t].ampEnv.biaslevel1; + cache[t].ampbias[1] = fixBiaslevel(cache[t].ampEnv.biaspoint2, &cache[t].ampdir[1]); + cache[t].ampblevel[1] = 12 - cache[t].ampEnv.biaslevel2; + cache[t].ampdepth = cache[t].ampEnv.envvkf * cache[t].ampEnv.envvkf; + cache[t].ampsustain = cache[t].ampEnv.envlevel[3]; + cache[t].amplevel = cache[t].ampEnv.level; // Calculate and cache filter stuff - patchCache[t].filtEnv = timbre->partial[t].tvf; - patchCache[t].tvfdepth = patchCache[t].filtEnv.envdkf; - patchCache[t].filtkeyfollow = fixKeyfollow(patchCache[t].filtEnv.keyfollow, &patchCache[t].keydir); - patchCache[t].filtEnv.envdepth = (char)((float)patchCache[t].filtEnv.envdepth * 1.27); - patchCache[t].tvfbias = fixBiaslevel(patchCache[t].filtEnv.biaspoint, &patchCache[t].tvfdir); - patchCache[t].tvfblevel = patchCache[t].filtEnv.biaslevel; - patchCache[t].filtsustain = patchCache[t].filtEnv.envlevel[3]; + cache[t].filtEnv = timbre->partial[t].tvf; + cache[t].tvfdepth = cache[t].filtEnv.envdkf; + cache[t].filtkeyfollow = fixKeyfollow(cache[t].filtEnv.keyfollow); + cache[t].filtEnv.envdepth = (char)((float)cache[t].filtEnv.envdepth * 1.27); + cache[t].tvfbias = fixBiaslevel(cache[t].filtEnv.biaspoint, &cache[t].tvfdir); + cache[t].tvfblevel = cache[t].filtEnv.biaslevel; + cache[t].filtsustain = cache[t].filtEnv.envlevel[3]; // Calculate and cache LFO stuff - patchCache[t].lfodepth = timbre->partial[t].lfo.depth; - patchCache[t].lfoperiod = lfotable[(int)timbre->partial[t].lfo.rate]; - patchCache[t].lforate = timbre->partial[t].lfo.rate; - patchCache[t].modsense = timbre->partial[t].lfo.modsense; + cache[t].lfodepth = timbre->partial[t].lfo.depth; + cache[t].lfoperiod = lfotable[(int)timbre->partial[t].lfo.rate]; + cache[t].lforate = timbre->partial[t].lfo.rate; + cache[t].modsense = timbre->partial[t].lfo.modsense; } for (int t = 0; t < 4; t++) { // Common parameters, stored redundantly - patchCache[t].partialCount = partialCount; - patchCache[t].sustain = (timbre->common.nosustain == 0); - if (isRhythm) { - patchCache[t].benderRange = 0; - } else { - patchCache[t].benderRange = patchTemp->patch.benderRange; - } + cache[t].dirty = false; + cache[t].partialCount = partialCount; + cache[t].sustain = (timbre->common.nosustain == 0); } - //synth->printDebug("Res 1: %d 2: %d 3: %d 4: %d", patchCache[0].waveform, patchCache[1].waveform, patchCache[2].waveform, patchCache[3].waveform); + //synth->printDebug("Res 1: %d 2: %d 3: %d 4: %d", cache[0].waveform, cache[1].waveform, cache[2].waveform, cache[3].waveform); - if (isRhythm) - memcpy(drumCache[absTimbreNum - 128], patchCache, sizeof(patchCache)); - else - allStop(); #if MT32EMU_MONITOR_INSTRUMENTS == 1 - synth->printDebug("%s: Recache, param %d (timbre: %s)", name, patchNum, currentInstr); + synth->printDebug("%s (%s): Recached timbre", name, currentInstr); for (int i = 0; i < 4; i++) { - synth->printDebug(" %d: play=%s, pcm=%s (%d), wave=%d", i, patchCache[i].playPartial ? "YES" : "NO", patchCache[i].PCMPartial ? "YES" : "NO", timbre->partial[i].wg.pcmwave, timbre->partial[i].wg.waveform); + synth->printDebug(" %d: play=%s, pcm=%s (%d), wave=%d", i, cache[i].playPartial ? "YES" : "NO", cache[i].PCMPartial ? "YES" : "NO", timbre->partial[i].wg.pcmwave, timbre->partial[i].wg.waveform); } #endif } -char *Part::getName() { +const char *Part::getName() const { return name; } @@ -393,72 +395,88 @@ void Part::setVolume(int vol) { volume = voltable[vol]; } -void Part::setPan(int pan) { +void RhythmPart::setPan(unsigned int midiPan) +{ // FIXME:KG: This is unchangeable for drums (they always use drumPan), is that correct? + synth->printDebug("%s: Setting pan (%d) not supported on rhythm", name, midiPan); +} + +void Part::setPan(unsigned int midiPan) { // FIXME:KG: Tweaked this a bit so that we have a left 100%, centre and right 100% // (But this makes the range somewhat skewed) - if (pan < 64) { + if (midiPan < 64) { volumesetting.leftvol = 32767; - volumesetting.rightvol = (Bit16s)(pan * 512); - } else if (pan == 64) { + volumesetting.rightvol = (Bit16s)(midiPan * 512); + } else if (midiPan == 64) { volumesetting.leftvol = 32767; volumesetting.rightvol = 32767; } else { volumesetting.rightvol = 32767; - volumesetting.leftvol = (Bit16s)((127 - pan) * 520); + volumesetting.leftvol = (Bit16s)((127 - midiPan) * 520); } //synth->printDebug("%s (%s): Set pan to %d", name, currentInstr, panpot); } -void Part::playNote(PartialManager *partialManager, unsigned int key, int vel) { - int drumNum = -1; // Initialised to please compiler - int drumTimbre = -1; // As above - int freqNum; +void RhythmPart::playNote(unsigned int key, int vel) { + if (key < 24 || key > 87) { + synth->printDebug("%s: Attempted to play invalid key %d", name, key); + return; + } + int drumNum = key - 24; + int drumTimbreNum = rhythmTemp[drumNum].timbre; + if (drumTimbreNum >= 94) { + synth->printDebug("%s: Attempted to play unmapped key %d", name, key); + return; + } + int absTimbreNum = drumTimbreNum + 128; + TimbreParam *timbre = &synth->mt32ram.timbres[absTimbreNum].timbre; + memcpy(currentInstr, timbre->common.name, 10); +#if MT32EMU_MONITOR_INSTRUMENTS == 1 + synth->printDebug("%s (%s): starting poly (drum %d, timbre %d) - Vel %d Key %d Vol %d", name, currentInstr, drumNum, absTimbreNum, vel, key, volume); +#endif + if (drumCache[drumNum][0].dirty) { + cacheTimbre(drumCache[drumNum], timbre); + } + playPoly(drumCache[drumNum], key, MIDDLEC, vel); +} - if (isRhythm) { - if (key < 24 || key > 87) { - synth->printDebug("%s: Attempted to play invalid key %d", name, key); - return; - } - drumNum = key - 24; - drumTimbre = rhythmTemp[drumNum].timbre; - if (drumTimbre >= 94) { - synth->printDebug("%s: Attempted to play unmapped key %d", name, key); - return; +void Part::playNote(unsigned int key, int vel) { + int freqNum = key; + if (freqNum < 12) { + synth->printDebug("%s (%s): Attempted to play invalid key %d < 12; moving up by octave", name, currentInstr, key); + freqNum += 12; + } else if (freqNum > 108) { + synth->printDebug("%s (%s): Attempted to play invalid key %d > 108; moving down by octave", name, currentInstr, key); + while (freqNum > 108) { + freqNum -= 12; } - memcpy(patchCache, drumCache[drumTimbre], sizeof(patchCache)); - memcpy(¤tInstr, synth->mt32ram.params.timbres[128 + drumTimbre].timbre.common.name, 10); - freqNum = MIDDLEC; - } else { - if (key < 12) { - synth->printDebug("%s (%s): Attempted to play invalid key %d < 12; moving up by octave", name, currentInstr, key); - key += 12; - } else if (key > 108) { - synth->printDebug("%s (%s): Attempted to play invalid key %d > 108; moving down by octave", name, currentInstr, key); - while (key > 108) { - key -= 12; - } - } - freqNum = key; } // POLY1 mode, Single Assign // Haven't found any software that uses any of the other poly modes // FIXME:KG: Should this also apply to rhythm? - if (!isRhythm) { - for (unsigned int i = 0; i < MT32EMU_MAX_POLY; i++) { - if (polyTable[i].isActive() && (polyTable[i].key == key)) { - //AbortPoly(&polyTable[i]); - stopNote(key); - break; - } + for (unsigned int i = 0; i < MT32EMU_MAX_POLY; i++) { + if (polyTable[i].isActive() && (polyTable[i].key == key)) { + //AbortPoly(&polyTable[i]); + stopNote(key); + break; } } +#if MT32EMU_MONITOR_INSTRUMENTS == 1 + synth->printDebug("%s (%s): starting poly - Vel %d Key %d Vol %d", name, currentInstr, vel, key, volume); +#endif + if (patchCache[0].dirty) { + cacheTimbre(patchCache, timbreTemp); + } + playPoly(patchCache, key, freqNum, vel); +} - unsigned int needPartials = patchCache[0].partialCount; +void Part::playPoly(const PatchCache cache[4], unsigned int key, int freqNum, int vel) { + unsigned int needPartials = cache[0].partialCount; + unsigned int freePartials = synth->partialManager->getFreePartialCount(); - if (needPartials > partialManager->GetFreePartialCount()) { - if (!partialManager->FreePartials(needPartials, partNum)) { - synth->printDebug("%s (%s): Insufficient free partials to play key %d (vel=%d)", name, currentInstr, key, vel); + if (freePartials < needPartials) { + if (!synth->partialManager->freePartials(needPartials - freePartials, partNum)) { + synth->printDebug("%s (%s): Insufficient free partials to play key %d (vel=%d); needed=%d, free=%d", name, currentInstr, key, vel, needPartials, synth->partialManager->getFreePartialCount()); return; } } @@ -485,8 +503,8 @@ void Part::playNote(PartialManager *partialManager, unsigned int key, int vel) { bool allnull = true; for (int x = 0; x < 4; x++) { - if (patchCache[x].playPartial) { - tpoly->partials[x] = partialManager->AllocPartial(partNum); + if (cache[x].playPartial) { + tpoly->partials[x] = synth->partialManager->allocPartial(partNum); allnull = false; } else { tpoly->partials[x] = NULL; @@ -496,27 +514,12 @@ void Part::playNote(PartialManager *partialManager, unsigned int key, int vel) { if (allnull) synth->printDebug("%s (%s): No partials to play for this instrument", name, this->currentInstr); - if (isRhythm) { - tpoly->pansetptr = &drumPan[drumNum]; - tpoly->reverb = rhythmTemp[drumNum].reverbSwitch > 0; - } else { - tpoly->pansetptr = &volumesetting; - tpoly->reverb = patchTemp->patch.reverbSwitch > 0; - } - tpoly->sustain = patchCache[0].sustain; + tpoly->sustain = cache[0].sustain; tpoly->volumeptr = &volume; -#if MT32EMU_MONITOR_INSTRUMENTS == 1 - if (isRhythm) { - synth->printDebug("%s (%s): starting poly %d (drum %d, timbre %d) - Vel %d Key %d Vol %d", name, currentInstr, m, drumNum, drumTimbre, vel, key, volume); - } else { - synth->printDebug("%s (%s): starting poly %d - Vel %d Key %d Vol %d", name, currentInstr, m, vel, key, volume); - } -#endif - for (int x = 0; x < 4; x++) { if (tpoly->partials[x] != NULL) { - tpoly->partials[x]->startPartial(tpoly, &patchCache[x], tpoly->partials[patchCache[x].structurePair]); + tpoly->partials[x]->startPartial(tpoly, &cache[x], tpoly->partials[cache[x].structurePair]); tpoly->partials[x]->setBend(bend); } } diff --git a/backends/midi/mt32/part.h b/backends/midi/mt32/part.h index e9b8e923e3..1214ec52f9 100644 --- a/backends/midi/mt32/part.h +++ b/backends/midi/mt32/part.h @@ -29,20 +29,12 @@ class Synth; class Part { private: - Synth *synth; // Only used for sending debug output - // Pointers to the areas of the MT-32's memory dedicated to this part (for parts 1-8) MemParams::PatchTemp *patchTemp; TimbreParam *timbreTemp; - //... and for rhythm - MemParams::RhythmTemp *rhythmTemp; - - bool isRhythm; - bool init; - int partNum; - char name[8]; // "Part 1".."Part 8", "Rhythm" - char currentInstr[11]; + // 0=Part 1, .. 7=Part 8, 8=Rhythm + unsigned int partNum; bool holdpedal; @@ -51,33 +43,61 @@ private: PatchCache patchCache[4]; float bend; // -1.0 .. +1.0 - Bit32s volume; dpoly polyTable[MT32EMU_MAX_POLY]; void abortPoly(dpoly *poly); - static int fixKeyfollow(int srckey, int *dir); + static int fixKeyfollow(int srckey); static int fixBiaslevel(int srcpnt, int *dir); + void setPatch(const PatchParam *patch); + +protected: + Synth *synth; + char name[8]; // "Part 1".."Part 8", "Rhythm" + char currentInstr[11]; + Bit32u volume; + void backupCacheToPartials(PatchCache cache[4]); + void cacheTimbre(PatchCache cache[4], const TimbreParam *timbre); + void playPoly(const PatchCache cache[4], unsigned int key, int freqNum, int vel); + const char *getName() const; + public: - Part(Synth *synth, int usePartNum); - char *getName(); - void playNote(PartialManager *partialManager, unsigned int key, int vel); + Part(Synth *synth, unsigned int usePartNum); + virtual void playNote(unsigned int key, int vel); void stopNote(unsigned int key); void allStop(); void setVolume(int vol); - void setPan(int vol); - void setBend(int vol); - void setModulation(int vol); - void setPatch(int patchnum); + virtual void setPan(unsigned int midiPan); + virtual void setBend(unsigned int midiBend); + virtual void setModulation(unsigned int midiModulation); + virtual void setProgram(unsigned int patchNum); void setHoldPedal(bool pedalval); void stopPedalHold(); - void refreshPatch(); - void refreshDrumCache(); - void setPatch(PatchParam *patch); + virtual void refresh(); + virtual void refreshTimbre(unsigned int absTimbreNum); void setTimbre(TimbreParam *timbre); - unsigned int getAbsTimbreNum(); + virtual unsigned int getAbsTimbreNum() const; +}; + +class RhythmPart: public Part { + // Pointer to the area of the MT-32's memory dedicated to rhythm + const MemParams::RhythmTemp *rhythmTemp; + + // This caches the timbres/settings in use by the rhythm part + PatchCache drumCache[64][4]; + StereoVolume drumPan[64]; +public: + RhythmPart(Synth *synth, unsigned int usePartNum); + void refreshTimbre(unsigned int timbreNum); + void refresh(); + void playNote(unsigned int key, int vel); + unsigned int getAbsTimbreNum() const; + void setPan(unsigned int midiPan); + void setBend(unsigned int midiBend); + void setModulation(unsigned int midiModulation); + void setProgram(unsigned int patchNum); }; } diff --git a/backends/midi/mt32/partial.cpp b/backends/midi/mt32/partial.cpp index f785f22d67..97158ed396 100644 --- a/backends/midi/mt32/partial.cpp +++ b/backends/midi/mt32/partial.cpp @@ -25,6 +25,11 @@ #include "mt32emu.h" +#define FIXEDPOINT_UDIV(x, y, point) (((x) << (point)) / ((y))) +#define FIXEDPOINT_SDIV(x, y, point) (((x) * (1 << point)) / ((y))) +#define FIXEDPOINT_UMULT(x, y, point) (((x) * (y)) >> point) +#define FIXEDPOINT_SMULT(x, y, point) (((x) * (y)) / (1 << point)) + using namespace MT32Emu; Partial::Partial(Synth *useSynth) { @@ -32,9 +37,20 @@ Partial::Partial(Synth *useSynth) { ownerPart = -1; poly = NULL; pair = NULL; +#if MT32EMU_ACCURATENOTES == 1 + for (int i = 0; i < 3; i++) { + noteLookupStorage.waveforms[i] = new Bit16s[65536]; + } + noteLookup = ¬eLookupStorage; +#endif } Partial::~Partial() { +#if MT32EMU_ACCURATENOTES == 1 + for (int i = 0; i < 3; i++) { + delete[] noteLookupStorage.waveforms[i]; + } +#endif } int Partial::getOwnerPart() { @@ -68,55 +84,49 @@ void Partial::deactivate() { void Partial::initKeyFollow(int key) { // Setup partial keyfollow // Note follow relative to middle C - int keyfollow; - int realfol = (key * 2 - MIDDLEC * 2) / 2; - int antirealfol = (MIDDLEC * 2 - key * 2) / 2; + // Calculate keyfollow for pitch - switch(patchCache->pitchkeydir) { - case -1: - keyfollow = (antirealfol * patchCache->pitchkeyfollow) >> 12; - break; - case 0: - keyfollow = 0; - break; - case 1: - keyfollow = (realfol * patchCache->pitchkeyfollow) >> 12; - break; - default: - keyfollow = 0; // Please the compiler - } - if ((patchCache->pitchkeyfollow>4096) && (patchCache->pitchkeyfollow<4200)) { - // Be sure to round up on keys below MIDDLEC - if (realfol < 0) - keyfollow++; +#if 1 + float rel = key == -1 ? 0.0f : (key - MIDDLEC); + float newPitch = rel * patchCache->pitchKeyfollow + patchCache->pitch + patchCache->pitchShift; + //FIXME:KG: Does it truncate the keyfollowed pitch to a semitone (towards MIDDLEC)? + //int newKey = (int)(rel * patchCache->pitchKeyfollow); + //float newPitch = newKey + patchCache->pitch + patchCache->pitchShift; +#else + float rel = key == -1 ? 0.0f : (key + patchCache->pitchShift - MIDDLEC); + float newPitch = rel * patchCache->pitchKeyfollow + patchCache->pitch; +#endif +#if MT32EMU_ACCURATENOTES == 1 + noteVal = newPitch; + synth->printDebug("key=%d, pitch=%f, pitchKeyfollow=%f, pitchShift=%f, newPitch=%f", key, patchCache->pitch, patchCache->pitchKeyfollow, patchCache->pitchShift, newPitch); +#else + float newPitchInt; + float newPitchFract = modff(newPitch, &newPitchInt); + synth->printDebug("Really: newPitch=%f, newPitchInt=%f, newPitchFract=%f", newPitch, newPitchInt, newPitchFract); + if (newPitchFract > 0.5f) { + newPitchInt += 1.0f; + newPitchFract -= 1.0f; } - noteVal = (keyfollow + patchCache->pitchshift); - if (noteVal > 108) - noteVal = 108; - if (noteVal < 12) - noteVal = 12; - + noteVal = (int)newPitchInt; + fineShift = (int)(powf(2.0f, newPitchFract / 12.0f) * 4096.0f); + synth->printDebug("key=%d, pitch=%f, pitchKeyfollow=%f, pitchShift=%f, newPitch=%f, noteVal=%d, fineShift=%d", key, patchCache->pitch, patchCache->pitchKeyfollow, patchCache->pitchShift, newPitch, noteVal, fineShift); +#endif + // FIXME:KG: Raise/lower by octaves until in the supported range. + while (noteVal > HIGHEST_NOTE) // FIXME:KG: see tables.cpp: >108? + noteVal -= 12; + while (noteVal < LOWEST_NOTE) // FIXME:KG: see tables.cpp: <12? + noteVal += 12; // Calculate keyfollow for filter - switch(patchCache->keydir) { - case -1: - keyfollow = (antirealfol * patchCache->filtkeyfollow) >> 12; - break; - case 0: - keyfollow = key; - break; - case 1: - keyfollow = (realfol * patchCache->filtkeyfollow) >> 12; - break; - } + int keyfollow = ((key - MIDDLEC) * patchCache->filtkeyfollow) / 4096; if (keyfollow > 108) keyfollow = 108; if (keyfollow < -108) keyfollow = -108; filtVal = keytable[keyfollow + 108]; - realVal = keytable[realfol + 108]; + realVal = keytable[(key - MIDDLEC) + 108]; } -void Partial::startPartial(dpoly *usePoly, PatchCache *useCache, Partial *pairPartial) { +void Partial::startPartial(dpoly *usePoly, const PatchCache *useCache, Partial *pairPartial) { if (usePoly == NULL || useCache == NULL) { synth->printDebug("*** Error: Starting partial for owner %d, usePoly=%s, useCache=%s", ownerPart, usePoly == NULL ? "*** NULL ***" : "OK", useCache == NULL ? "*** NULL ***" : "OK"); return; @@ -128,11 +138,11 @@ void Partial::startPartial(dpoly *usePoly, PatchCache *useCache, Partial *pairPa play = true; initKeyFollow(poly->freqnum); // Initialises noteVal, filtVal and realVal - noteLookup = ¬eLookups[noteVal]; - - // FIXME:KG: I think most places should refer to noteVal/noteLookup instead of these. - keyVal = poly->freqnum; - keyLookup = ¬eLookups[poly->freqnum]; +#if MT32EMU_ACCURATENOTES == 0 + noteLookup = ¬eLookups[noteVal - LOWEST_NOTE]; +#else + TableInitialiser::initNote(synth, ¬eLookupStorage, noteVal, (float)synth->myProp.sampleRate, synth->masterTune, synth->PCMList, NULL); +#endif lfoPos = 0; pulsewidth = patchCache->pulsewidth + pwveltable[patchCache->pwsens][poly->vel]; @@ -154,8 +164,8 @@ void Partial::startPartial(dpoly *usePoly, PatchCache *useCache, Partial *pairPa envs[e].counter = 0; envs[e].count = 0; } - ampEnvCache = 0; - pitchEnvCache = 0; + ampEnvVal = 0; + pitchEnvVal = 0; pitchSustain = false; loopPos = 0; partialOff.pcmoffset = partialOff.pcmplace = 0; @@ -181,22 +191,16 @@ Bit16s *Partial::generateSamples(long length) { Bit16s *partialBuf = &myBuffer[0]; while (length--) { - Bit32s envval, ampval; - Bit32s ptemp = 0; - if (envs[EnvelopeType_amp].sustaining) - ampval = ampEnvCache; - else { + Bit32s envval; + Bit32s sample = 0; + if (!envs[EnvelopeType_amp].sustaining) { if (envs[EnvelopeType_amp].count <= 0) { - ampval = getAmpEnvelope(); + Bit32u ampval = getAmpEnvelope(); if (!play) { deactivate(); break; } - if (ampval < 0) { - //TODO: check what is going on here - synth->printDebug("ampval<0! ampval=%ld, active=%d", ampval, isActive()); - ampval = 0; - } else if (ampval > 127) { + if (ampval > 127) { ampval = 127; } @@ -208,15 +212,12 @@ Bit16s *Partial::generateSamples(long length) { tmpvel = poly->vel; ampval = (ampval * ampveltable[tmpvel][(int)patchCache->ampEnv.velosens]) >> 8; //if (envs[EnvelopeType_amp].sustaining) - ampEnvCache = ampval; - } else - ampval = ampEnvCache; + ampEnvVal = ampval; + } --envs[EnvelopeType_amp].count; } - // Calculate Pitch envelope int lfoat = 0x1000; - int pdep; if (pitchSustain) { // Calculate LFO position // LFO does not kick in completely until pitch envelope sustains @@ -224,17 +225,15 @@ Bit16s *Partial::generateSamples(long length) { lfoPos++; if (lfoPos >= patchCache->lfoperiod) lfoPos = 0; - int lfoatm = (lfoPos << 16) / patchCache->lfoperiod; + int lfoatm = FIXEDPOINT_UDIV(lfoPos, patchCache->lfoperiod, 16); int lfoatr = sintable[lfoatm]; lfoat = lfoptable[patchCache->lfodepth][lfoatr]; } - pdep = pitchEnvCache; } else { + // Calculate Pitch envelope envval = getPitchEnvelope(); int pd = patchCache->pitchEnv.depth; - pdep = penvtable[pd][envval]; - if (pitchSustain) - pitchEnvCache = pdep; + pitchEnvVal = penvtable[pd][envval]; } int delta; @@ -242,36 +241,23 @@ Bit16s *Partial::generateSamples(long length) { PCMWaveEntry *pcmWave = NULL; // Initialise to please compiler int pcmAddr = 0; // Initialise to please compiler - // Get waveform - either PCM or synthesized sawtooth or square + // Wrap positions or end if necessary if (patchCache->PCMPartial) { // PCM partial int len; pcmWave = &synth->PCMList[patchCache->pcm]; - if (pcmWave->aggSound == -1) { - delta = noteLookup->wavTable[pcmWave->pcmnum]; - pcmAddr = pcmWave->addr; - len = pcmWave->len; - if (partialOff.pcmplace >= len) { - if (pcmWave->loop) { - partialOff.pcmplace = partialOff.pcmoffset = 0; - // FIXME:KG: Use this?: partialOff.pcmplace %= len; - } else { - play = false; - deactivate(); - break; - } - } - } else { - int tmppcm = LoopPatterns[pcmWave->aggSound][loopPos]; - delta = noteLookup->loopTable[pcmWave->aggSound][loopPos]; - pcmAddr = synth->PCM[tmppcm].addr; - len = synth->PCM[tmppcm].len; - if (partialOff.pcmplace >= len) { - loopPos++; - if (LoopPatterns[pcmWave->aggSound][loopPos] == -1) - loopPos = 0; - partialOff.pcmplace = partialOff.pcmoffset = 0; + delta = noteLookup->wavTable[patchCache->pcm]; + pcmAddr = pcmWave->addr; + len = pcmWave->len; + if (partialOff.pcmplace >= len) { + if (pcmWave->loop) { + //partialOff.pcmplace = partialOff.pcmoffset = 0; + partialOff.pcmplace %= len; + } else { + play = false; + deactivate(); + break; } } } else { @@ -282,14 +268,18 @@ Bit16s *Partial::generateSamples(long length) { // Build delta for position of next sample // Fix delta code - Bit64s tdelta = (Bit64s)delta; - tdelta = (tdelta * patchCache->fineshift) >> 12; - tdelta = (tdelta * pdep) >> 12; + Bit64u tdelta = (Bit64u)delta; +#if MT32EMU_ACCURATENOTES == 0 + tdelta = (tdelta * fineShift) >> 12; +#endif + tdelta = (tdelta * pitchEnvVal) >> 12; tdelta = (tdelta * lfoat) >> 12; tdelta = (tdelta * bendShift) >> 12; delta = (int)tdelta; + Bit32u volume = *poly->volumeptr; - if (ampval > 0) { + // Get waveform - either PCM or synthesized sawtooth or square + if (ampEnvVal > 0) { if (patchCache->PCMPartial) { // Render PCM sample int ra, rb, dist; @@ -301,7 +291,7 @@ Bit16s *Partial::generateSamples(long length) { //FIXME:KG: Deal with condition that taddr + 1 is past PCM length rb = synth->romfile[taddr + 1]; dist = rb - ra; - ptemp = (ra + ((dist * (Bit32s)(partialOff.pcmoffset >> 8)) >> 8)); + sample = (ra + ((dist * (Bit32s)(partialOff.pcmoffset >> 8)) >> 8)); } else { // Sound decimation // The right way to do it is to use a lowpass filter on the waveform before selecting @@ -311,11 +301,11 @@ Bit16s *Partial::generateSamples(long length) { ra = 0; for (int ix = 0; ix < idelta; ix++) ra += synth->romfile[taddr++]; - ptemp = ra / idelta; + sample = ra / idelta; } } else { // Render synthesised sample - int div = noteLookup->div; + Bit32u div = noteLookup->div; int wf = patchCache->waveform; int toff = partialOff.pcmplace; int minorplace = partialOff.pcmoffset >> 14; @@ -328,26 +318,23 @@ Bit16s *Partial::generateSamples(long length) { // Square waveform. Made by combining two pregenerated bandlimited // sawtooth waveforms // Pulse width is not yet correct - int pa, pb; - int divmark = div << 8; - if (div == 0) { synth->printDebug("ERROR: div=0 generating square wave, this should never happen!"); div = 1; } - int ofsA = toff % div; - int ofsB = toff + ((divmark * pulsetable[pulsewidth]) >> 16); + Bit32u ofsA = toff % div; + Bit32u ofsB = toff + FIXEDPOINT_UMULT(div, pulsetable[pulsewidth], 8); ofsB = ofsB % div; - pa = noteLookup->waveforms[0][(ofsA << 2) + minorplace]; - pb = noteLookup->waveforms[0][(ofsB << 2) + minorplace]; - ptemp = (pa - pb) * 4; + Bit16s pa = noteLookup->waveforms[0][(ofsA << 2) + minorplace]; + Bit16s pb = noteLookup->waveforms[0][(ofsB << 2) + minorplace]; + sample = (pa - pb) * 4; // Non-bandlimited squarewave /* ofs = ((div << 1) * pulsetable[patchCache->pulsewidth]) >> 8; if (toff < ofs) - ptemp = 1 * WGAMP; + sample = 1 * WGAMP; else - ptemp = -1 * WGAMP; + sample = -1 * WGAMP; */ } else { // Sawtooth. Made by combining the full cosine and half cosine according @@ -355,33 +342,32 @@ Bit16s *Partial::generateSamples(long length) { // square wave and multiplies it by a full cosine int waveoff = (toff << 2) + minorplace; if (toff < noteLookup->sawTable[pulsewidth]) - ptemp = noteLookup->waveforms[1][waveoff % noteLookup->waveformSize[1]]; + sample = noteLookup->waveforms[1][waveoff % noteLookup->waveformSize[1]]; else - ptemp = noteLookup->waveforms[2][waveoff % noteLookup->waveformSize[2]]; - ptemp = ptemp * 4; + sample = noteLookup->waveforms[2][waveoff % noteLookup->waveformSize[2]]; + sample = sample * 4; // This is the correct way // Seems slow to me (though bandlimited) -- doesn't seem to // sound any better though /* - int divmark = noteLookup->div << 8; //int pw = (patchCache->pulsewidth * pulsemod[filtval]) >> 8; - int ofs = toff % div; + Bit32u ofs = toff % div; - int ofs3 = toff + ((divmark * pulsetable[patchCache->pulsewidth]) >> 16); + Bit32u ofs3 = toff + FIXEDPOINT_UMULT(div, pulsetable[patchCache->pulsewidth], 8); ofs3 = ofs3 % div; pa = noteLookup->waveforms[0][ofs]; pb = noteLookup->waveforms[0][ofs3]; - ptemp = ((pa - pb) * noteLookup->waveforms[2][toff]) / WGAMP; - ptemp = ptemp *4; + sample = ((pa - pb) * noteLookup->waveforms[2][toff]) / WGAMP; + sample = sample *4; */ } //Very exact filter if (filtval > ((FILTERGRAN * 15) / 16)) filtval = ((FILTERGRAN * 15) / 16); - ptemp = (Bit32s)floor((usefilter)((float)ptemp, &history[0], filtcoeff[filtval][(int)patchCache->filtEnv.resonance], patchCache->filtEnv.resonance)); + sample = (Bit32s)floor((synth->iirFilter)((float)sample, &history[0], filtcoeff[filtval][(int)patchCache->filtEnv.resonance], patchCache->filtEnv.resonance)); } } @@ -392,14 +378,13 @@ Bit16s *Partial::generateSamples(long length) { partialOff.pcmoffset = (Bit16u)(absOff & 0xFFFF); // Put volume envelope over generated sample - ptemp = (ptemp * ampval) >> 9; - ptemp = (ptemp * *poly->volumeptr) >> 7; - + sample = FIXEDPOINT_SMULT(sample, ampEnvVal, 9); + sample = FIXEDPOINT_SMULT(sample, volume, 7); envs[EnvelopeType_amp].envpos++; envs[EnvelopeType_pitch].envpos++; envs[EnvelopeType_filt].envpos++; - *partialBuf++ = (Bit16s)ptemp; + *partialBuf++ = (Bit16s)sample; } // We may have deactivated and broken out of the loop before the end of the buffer, // if so then fill the remainder with 0s. @@ -414,9 +399,10 @@ void Partial::setBend(float factor) { return; } // NOTE:KG: We can't do this smoothly with lookup tables, unless we use several MB. + // FIXME:KG: Bend should be influenced by pitch key-follow too, according to docs. float bendSemitones = factor * patchCache->benderRange; // -24 .. 24 float mult = powf(2.0f, bendSemitones / 12.0f); - synth->printDebug("setBend(): semitones=%f, mult=%f, factor=%f, benderRange=%d\n", bendSemitones, mult, factor, patchCache->benderRange); + synth->printDebug("setBend(): factor=%f, benderRange=%f, semitones=%f, mult=%f\n", factor, patchCache->benderRange, bendSemitones, mult); bendShift = (int)(mult * 4096.0f); } @@ -601,8 +587,8 @@ bool Partial::produceOutput(Bit16s *partialBuf, long length) { return false; Bit16s leftvol, rightvol; - leftvol = poly->pansetptr->leftvol; - rightvol = poly->pansetptr->rightvol; + leftvol = patchCache->pansetptr->leftvol; + rightvol = patchCache->pansetptr->rightvol; #if MT32EMU_USE_MMX >= 2 // FIXME:KG: This appears to introduce crackle @@ -624,7 +610,7 @@ Bit32s Partial::getFiltEnvelope() { int cutoff,depth,keyfollow, realfollow; - envstatus *tStat = &envs[EnvelopeType_filt]; + EnvelopeStatus *tStat = &envs[EnvelopeType_filt]; keyfollow = filtVal; realfollow = realVal; @@ -651,7 +637,7 @@ Bit32s Partial::getFiltEnvelope() { if (tStat->envstat == 3) { tStat->envsize = lasttimetable[(int)patchCache->filtEnv.envtime[tStat->envstat]]; } else { - tStat->envsize = (envtimetable[(int)patchCache->filtEnv.envtime[tStat->envstat]] * keyLookup->timekeyTable[(int)patchCache->filtEnv.envtkf]) >> 8; + tStat->envsize = (envtimetable[(int)patchCache->filtEnv.envtime[tStat->envstat]] * noteLookup->timekeyTable[(int)patchCache->filtEnv.envtkf]) >> 8; } tStat->envsize++; @@ -678,16 +664,17 @@ Bit32s Partial::getFiltEnvelope() { int dist; if (bias!=0) { + //FIXME:KG: Is this really based on pitch (as now), or key pressed? //synth->printDebug("Cutoff before %d", cutoff); if (patchCache->tvfdir == 0) { - if (keyVal < bias) { - dist = bias - keyVal; + if (noteVal < bias) { + dist = bias - noteVal; cutoff = (cutoff * fbiastable[patchCache->tvfblevel][dist]) >> 8; } } else { // > Bias - if (keyVal > bias) { - dist = keyVal - bias; + if (noteVal > bias) { + dist = noteVal - bias; cutoff = (cutoff * fbiastable[patchCache->tvfblevel][dist]) >> 8; } @@ -695,7 +682,7 @@ Bit32s Partial::getFiltEnvelope() { //synth->printDebug("Cutoff after %d", cutoff); } - depth = (depth * keyLookup->fildepTable[patchCache->tvfdepth]) >> 8; + depth = (depth * noteLookup->fildepTable[patchCache->tvfdepth]) >> 8; reshigh = (reshigh * depth) >> 7; Bit32s tmp; @@ -714,7 +701,7 @@ Bit32s Partial::getFiltEnvelope() { reshigh = 100; else if (reshigh<0) reshigh = 0; - tmp = keyLookup->nfiltTable[cutoff][reshigh]; + tmp = noteLookup->nfiltTable[cutoff][reshigh]; //tmp *= keyfollow; //tmp /= realfollow; @@ -725,13 +712,13 @@ Bit32s Partial::getFiltEnvelope() { bool Partial::shouldReverb() { if (!isActive()) return false; - return poly->reverb; + return patchCache->reverb; } -Bit32s Partial::getAmpEnvelope() { - Bit32s tc; +Bit32u Partial::getAmpEnvelope() { + Bit32u tc; - envstatus *tStat = &envs[EnvelopeType_amp]; + EnvelopeStatus *tStat = &envs[EnvelopeType_amp]; if (!play) return 0; @@ -780,7 +767,7 @@ Bit32s Partial::getAmpEnvelope() { default: //Spot for timekey follow //Only used in subsquent envelope parameters, including the decay - tStat->envsize = (envtimetable[(int)patchCache->ampEnv.envtime[tStat->envstat]] * keyLookup->timekeyTable[(int)patchCache->ampEnv.envtkf]) >> 8; + tStat->envsize = (envtimetable[(int)patchCache->ampEnv.envtime[tStat->envstat]] * noteLookup->timekeyTable[(int)patchCache->ampEnv.envtkf]) >> 8; //synth->printDebug("Envstat %d, size %d", tStat->envstat, tStat->envsize); break; @@ -809,21 +796,19 @@ PastCalc: //Bias level crap stuff now - int dist, bias; - for (int i = 0; i < 2; i++) { if (patchCache->ampblevel[i]!=0) { - bias = patchCache->ampbias[i]; + int bias = patchCache->ampbias[i]; if (patchCache->ampdir[i]==0) { // < Bias - if (keyVal < bias) { - dist = bias - keyVal; + if (noteVal < bias) { + int dist = bias - noteVal; tc = (tc * ampbiastable[patchCache->ampblevel[i]][dist]) >> 8; } } else { // > Bias - if (keyVal > bias) { - dist = keyVal - bias; + if (noteVal > bias) { + int dist = noteVal - bias; tc = (tc * ampbiastable[patchCache->ampblevel[i]][dist]) >> 8; } } @@ -833,7 +818,7 @@ PastCalc: } Bit32s Partial::getPitchEnvelope() { - envstatus *tStat = &envs[EnvelopeType_pitch]; + EnvelopeStatus *tStat = &envs[EnvelopeType_pitch]; Bit32s tc; pitchSustain = false; @@ -856,7 +841,7 @@ Bit32s Partial::getPitchEnvelope() { tStat->envstat++; tStat->envbase = patchCache->pitchEnv.level[tStat->envstat]; - tStat->envsize = (envtimetable[(int)patchCache->pitchEnv.time[tStat->envstat]] * keyLookup->timekeyTable[(int)patchCache->pitchEnv.timekeyfollow]) >> 8; + tStat->envsize = (envtimetable[(int)patchCache->pitchEnv.time[tStat->envstat]] * noteLookup->timekeyTable[(int)patchCache->pitchEnv.timekeyfollow]) >> 8; tStat->envpos = 0; tStat->envsize++; @@ -878,7 +863,7 @@ void Partial::startDecayAll() { } void Partial::startDecay(EnvelopeType envnum, Bit32s startval) { - envstatus *tStat = &envs[envnum]; + EnvelopeStatus *tStat = &envs[envnum]; tStat->sustaining = false; tStat->decaying = true; @@ -887,15 +872,15 @@ void Partial::startDecay(EnvelopeType envnum, Bit32s startval) { switch(envnum) { case EnvelopeType_amp: - tStat->envsize = (decaytimetable[(int)patchCache->ampEnv.envtime[4]] * keyLookup->timekeyTable[(int)patchCache->ampEnv.envtkf]) >> 8; + tStat->envsize = (decaytimetable[(int)patchCache->ampEnv.envtime[4]] * noteLookup->timekeyTable[(int)patchCache->ampEnv.envtkf]) >> 8; tStat->envdist = -startval; break; case EnvelopeType_filt: - tStat->envsize = (decaytimetable[(int)patchCache->filtEnv.envtime[4]] * keyLookup->timekeyTable[(int)patchCache->filtEnv.envtkf]) >> 8; + tStat->envsize = (decaytimetable[(int)patchCache->filtEnv.envtime[4]] * noteLookup->timekeyTable[(int)patchCache->filtEnv.envtkf]) >> 8; tStat->envdist = -startval; break; case EnvelopeType_pitch: - tStat->envsize = (decaytimetable[(int)patchCache->pitchEnv.time[3]] * keyLookup->timekeyTable[(int)patchCache->pitchEnv.timekeyfollow]) >> 8 ; + tStat->envsize = (decaytimetable[(int)patchCache->pitchEnv.time[3]] * noteLookup->timekeyTable[(int)patchCache->pitchEnv.timekeyfollow]) >> 8 ; tStat->envdist = patchCache->pitchEnv.level[4] - startval; break; default: diff --git a/backends/midi/mt32/partial.h b/backends/midi/mt32/partial.h index 2760b8fa2a..52b07bfeb1 100644 --- a/backends/midi/mt32/partial.h +++ b/backends/midi/mt32/partial.h @@ -33,7 +33,7 @@ enum EnvelopeType { EnvelopeType_pitch = 2 }; -struct envstatus { +struct EnvelopeStatus { Bit32s envpos; Bit32s envstat; Bit32s envbase; @@ -51,7 +51,7 @@ struct envstatus { // Class definition of MT-32 partials. 32 in all. class Partial { private: - Synth *synth; // Only used for sending debug output + Synth *synth; int ownerPart; // -1 if unassigned int mixType; @@ -63,25 +63,28 @@ private: bool play; // Keyfollowed note value +#if MT32EMU_ACCURATENOTES == 1 + NoteLookup noteLookupStorage; + float noteVal; +#else int noteVal; - NoteLookup *noteLookup; // Lookup stuff for this noteVal - - int keyVal; - NoteLookup *keyLookup; + int fineShift; +#endif + const NoteLookup *noteLookup; // LUTs for this noteVal // Keyfollowed filter values int realVal; int filtVal; - envstatus envs[3]; + EnvelopeStatus envs[3]; int pulsewidth; Bit32u lfoPos; soundaddr partialOff; - Bit32u ampEnvCache; - Bit32u pitchEnvCache; + Bit32u ampEnvVal; + Bit32u pitchEnvVal; float history[32]; @@ -93,19 +96,19 @@ private: int bendShift; - Bit16s *mixBuffers(Bit16s * buf1, Bit16s * buf2, int len); - Bit16s *mixBuffersRingMix(Bit16s * buf1, Bit16s * buf2, int len); - Bit16s *mixBuffersRing(Bit16s * buf1, Bit16s * buf2, int len); - void mixBuffersStereo(Bit16s * buf1, Bit16s * buf2, Bit16s * outBuf, int len); + Bit16s *mixBuffers(Bit16s *buf1, Bit16s *buf2, int len); + Bit16s *mixBuffersRingMix(Bit16s *buf1, Bit16s *buf2, int len); + Bit16s *mixBuffersRing(Bit16s *buf1, Bit16s *buf2, int len); + void mixBuffersStereo(Bit16s *buf1, Bit16s *buf2, Bit16s *outBuf, int len); Bit32s getFiltEnvelope(); - Bit32s getAmpEnvelope(); + Bit32u getAmpEnvelope(); Bit32s getPitchEnvelope(); void initKeyFollow(int freqNum); public: - PatchCache *patchCache; + const PatchCache *patchCache; PatchCache cachebackup; Partial *pair; @@ -119,7 +122,7 @@ public: bool isActive(); void activate(int part); void deactivate(void); - void startPartial(dpoly *usePoly, PatchCache *useCache, Partial *pairPartial); + void startPartial(dpoly *usePoly, const PatchCache *useCache, Partial *pairPartial); void startDecay(EnvelopeType envnum, Bit32s startval); void startDecayAll(); void setBend(float factor); diff --git a/backends/midi/mt32/partialManager.cpp b/backends/midi/mt32/partialManager.cpp index 4c9d264128..01fb57b026 100644 --- a/backends/midi/mt32/partialManager.cpp +++ b/backends/midi/mt32/partialManager.cpp @@ -36,7 +36,7 @@ PartialManager::~PartialManager(void) { delete partialTable[i]; } -void PartialManager::GetPerPartPartialUsage(int usage[9]) { +void PartialManager::getPerPartPartialUsage(int usage[9]) { memset(usage, 0, 9 * sizeof (int)); for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) { if (partialTable[i]->isActive()) @@ -44,12 +44,12 @@ void PartialManager::GetPerPartPartialUsage(int usage[9]) { } } -void PartialManager::ClearAlreadyOutputed() { +void PartialManager::clearAlreadyOutputed() { for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) partialTable[i]->alreadyOutputed = false; } -void PartialManager::AgeAll() { +void PartialManager::ageAll() { for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) partialTable[i]->age++; } @@ -58,28 +58,28 @@ bool PartialManager::shouldReverb(int i) { return partialTable[i]->shouldReverb(); } -bool PartialManager::ProduceOutput(int i, Bit16s *buffer, Bit32u bufferLength) { +bool PartialManager::produceOutput(int i, Bit16s *buffer, Bit32u bufferLength) { return partialTable[i]->produceOutput(buffer, bufferLength); } -void PartialManager::DeactivateAll() { +void PartialManager::deactivateAll() { for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) { partialTable[i]->deactivate(); } } -unsigned int PartialManager::SetReserve(char *rset) { +unsigned int PartialManager::setReserve(Bit8u *rset) { unsigned int pr = 0; for (int x = 0; x < 9; x++) { for (int y = 0; y < rset[x]; y++) { - PartialReserveTable[pr] = x; + partialReserveTable[pr] = x; pr++; } } return pr; } -Partial * PartialManager::AllocPartial(int partNum) { +Partial *PartialManager::allocPartial(int partNum) { Partial *outPartial = NULL; // Use the first inactive partial reserved for the specified part (if there are any) @@ -87,7 +87,7 @@ Partial * PartialManager::AllocPartial(int partNum) { for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) { if (!partialTable[i]->isActive()) { outPartial = partialTable[i]; - if (PartialReserveTable[i] == partNum) + if (partialReserveTable[i] == partNum) break; } } @@ -98,7 +98,7 @@ Partial * PartialManager::AllocPartial(int partNum) { return outPartial; } -unsigned int PartialManager::GetFreePartialCount(void) { +unsigned int PartialManager::getFreePartialCount(void) { int count = 0; memset(partialPart, 0, sizeof(partialPart)); for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) { @@ -111,9 +111,9 @@ unsigned int PartialManager::GetFreePartialCount(void) { } /* -bool PartialManager::FreePartials(unsigned int needed, int partNum) { +bool PartialManager::freePartials(unsigned int needed, int partNum) { int i; - int myPartPrior = (int)mt32ram.params.system.reserveSettings[partNum]; + int myPartPrior = (int)mt32ram.system.reserveSettings[partNum]; if (myPartPrior0) { if (diff>most) { @@ -143,7 +143,7 @@ bool PartialManager::FreePartials(unsigned int needed, int partNum) { bool found; int oldest; int oldnum; - while (partialPart[selectPart] > (int)mt32ram.params.system.reserveSettings[selectPart]) { + while (partialPart[selectPart] > (int)mt32ram.system.reserveSettings[selectPart]) { oldest = -1; oldnum = -1; found = false; @@ -198,7 +198,7 @@ bool PartialManager::FreePartials(unsigned int needed, int partNum) { } */ -bool PartialManager::FreePartials(unsigned int needed, int partNum) { +bool PartialManager::freePartials(unsigned int needed, int partNum) { if (needed == 0) { return true; } @@ -206,7 +206,7 @@ bool PartialManager::FreePartials(unsigned int needed, int partNum) { // Kill those that are already decaying first /* for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) { - if (PartialReserveTable[i] == partNum) { + if (partialReserveTable[i] == partNum) { if (partialTable[i]->ownerPart != partNum) { if (partialTable[i]->partCache->envs[AMPENV].decaying) { partialTable[i]->isActive = false; @@ -223,10 +223,10 @@ bool PartialManager::FreePartials(unsigned int needed, int partNum) { int priornum = -1; for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) { - if (PartialReserveTable[i] == partNum && partialTable[i]->isActive() && partialTable[i]->getOwnerPart() != partNum) { + if (partialReserveTable[i] == partNum && partialTable[i]->isActive() && partialTable[i]->getOwnerPart() != partNum) { /* - if (mt32ram.params.system.reserveSettings[partialTable[i]->ownerPart] < prior) { - prior = mt32ram.params.system.reserveSettings[partialTable[i]->ownerPart]; + if (mt32ram.system.reserveSettings[partialTable[i]->ownerPart] < prior) { + prior = mt32ram.system.reserveSettings[partialTable[i]->ownerPart]; priornum = i; }*/ if (partialTable[i]->age > prior) { @@ -244,17 +244,14 @@ bool PartialManager::FreePartials(unsigned int needed, int partNum) { } // Kill off the oldest partials within this part - while (needed > 0) { Bit64s oldest = -1; Bit64s oldlist = -1; for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) { - if (partialTable[i]->isActive()) { - if (partialTable[i]->getOwnerPart() == partNum) { - if (partialTable[i]->age > oldest) { - oldest = partialTable[i]->age; - oldlist = i; - } + if (partialTable[i]->getOwnerPart() == partNum && partialTable[i]->isActive()) { + if (partialTable[i]->age > oldest) { + oldest = partialTable[i]->age; + oldlist = i; } } } diff --git a/backends/midi/mt32/partialManager.h b/backends/midi/mt32/partialManager.h index 7902a3cdc5..ee145d4c9f 100644 --- a/backends/midi/mt32/partialManager.h +++ b/backends/midi/mt32/partialManager.h @@ -31,22 +31,22 @@ private: Synth *synth; // Only used for sending debug output Partial *partialTable[MT32EMU_MAX_PARTIALS]; - Bit32s PartialReserveTable[MT32EMU_MAX_PARTIALS]; + Bit32s partialReserveTable[MT32EMU_MAX_PARTIALS]; Bit32s partialPart[9]; // The count of partials played per part public: PartialManager(Synth *synth); ~PartialManager(); - Partial *AllocPartial(int partNum); - unsigned int GetFreePartialCount(void); - bool FreePartials(unsigned int needed, int partNum); - unsigned int SetReserve(char *rset); - void DeactivateAll(); - void AgeAll(); - bool ProduceOutput(int i, Bit16s *buffer, Bit32u bufferLength); + Partial *allocPartial(int partNum); + unsigned int getFreePartialCount(void); + bool freePartials(unsigned int needed, int partNum); + unsigned int setReserve(Bit8u *rset); + void deactivateAll(); + void ageAll(); + bool produceOutput(int i, Bit16s *buffer, Bit32u bufferLength); bool shouldReverb(int i); - void ClearAlreadyOutputed(); - void GetPerPartPartialUsage(int usage[9]); + void clearAlreadyOutputed(); + void getPerPartPartialUsage(int usage[9]); }; } diff --git a/backends/midi/mt32/structures.h b/backends/midi/mt32/structures.h index d64f0a44f2..047f69f8bd 100644 --- a/backends/midi/mt32/structures.h +++ b/backends/midi/mt32/structures.h @@ -56,92 +56,92 @@ typedef signed char Bit8s; struct TimbreParam { struct commonParam { char name[10]; - char pstruct12; // 1&2 0-12 (1-13) - char pstruct34; // #3&4 0-12 (1-13) - char pmute; // 0-15 (0000-1111) - char nosustain; // 0-1(Normal, No sustain) + Bit8u pstruct12; // 1&2 0-12 (1-13) + Bit8u pstruct34; // #3&4 0-12 (1-13) + Bit8u pmute; // 0-15 (0000-1111) + Bit8u nosustain; // 0-1(Normal, No sustain) } MT32EMU_ALIGN_PACKED common; struct partialParam { struct wgParam { - char coarse; // 0-96 (C1,C#1-C9) - char fine; // 0-100 (-50 to +50 (cents?)) - char keyfollow; // 0-16 (-1,-1/2,0,1,1/8,1/4,3/8,1/2,5/8,3/4,7/8,1,5/4,3/2,2.s1,s2) - char bender; // 0,1 (ON/OFF) - char waveform; // 0-1 (SQU/SAW) - char pcmwave; // 0-127 (1-128) - char pulsewid; // 0-100 - char pwvelo; // 0-14 (-7 - +7) + Bit8u coarse; // 0-96 (C1,C#1-C9) + Bit8u fine; // 0-100 (-50 to +50 (cents?)) + Bit8u keyfollow; // 0-16 (-1,-1/2,0,1,1/8,1/4,3/8,1/2,5/8,3/4,7/8,1,5/4,3/2,2.s1,s2) + Bit8u bender; // 0,1 (ON/OFF) + Bit8u waveform; // 0-1 (SQU/SAW) + Bit8u pcmwave; // 0-127 (1-128) + Bit8u pulsewid; // 0-100 + Bit8u pwvelo; // 0-14 (-7 - +7) } MT32EMU_ALIGN_PACKED wg; struct envParam { - char depth; // 0-10 - char sensitivity; // 1-100 - char timekeyfollow; // 0-4 - char time[4]; // 1-100 - char level[5]; // 1-100 (-50 - +50) + Bit8u depth; // 0-10 + Bit8u sensitivity; // 1-100 + Bit8u timekeyfollow; // 0-4 + Bit8u time[4]; // 1-100 + Bit8u level[5]; // 1-100 (-50 - +50) } MT32EMU_ALIGN_PACKED env; struct lfoParam { - char rate; // 0-100 - char depth; // 0-100 - char modsense; // 0-100 + Bit8u rate; // 0-100 + Bit8u depth; // 0-100 + Bit8u modsense; // 0-100 } MT32EMU_ALIGN_PACKED lfo; struct tvfParam { - char cutoff; // 0-100 - char resonance; // 0-30 - char keyfollow; // 0-16 (-1,-1/2,1/4,0,1,1/8,1/4,3/8,1/2,5/8,3/2,7/8,1,5/4,3/2,2,s1,s2) - char biaspoint; // 0-127 (<1A-<7C >1A-7C) - char biaslevel; // 0-14 (-7 - +7) - char envdepth; // 0-100 - char envsense; // 0-100 - char envdkf; // DEPTH KEY FOLL0W 0-4 - char envtkf; // TIME KEY FOLLOW 0-4 - char envtime[5]; // 1-100 - char envlevel[4]; // 1-100 + Bit8u cutoff; // 0-100 + Bit8u resonance; // 0-30 + Bit8u keyfollow; // 0-16 (-1,-1/2,1/4,0,1,1/8,1/4,3/8,1/2,5/8,3/2,7/8,1,5/4,3/2,2,s1,s2) + Bit8u biaspoint; // 0-127 (<1A-<7C >1A-7C) + Bit8u biaslevel; // 0-14 (-7 - +7) + Bit8u envdepth; // 0-100 + Bit8u envsense; // 0-100 + Bit8u envdkf; // DEPTH KEY FOLL0W 0-4 + Bit8u envtkf; // TIME KEY FOLLOW 0-4 + Bit8u envtime[5]; // 1-100 + Bit8u envlevel[4]; // 1-100 } MT32EMU_ALIGN_PACKED tvf; struct tvaParam { - char level; // 0-100 - char velosens; // 0-100 - char biaspoint1; // 0-127 (<1A-<7C >1A-7C) - char biaslevel1; // 0-12 (-12 - 0) - char biaspoint2; // 0-127 (<1A-<7C >1A-7C) - char biaslevel2; // 0-12 (-12 - 0) - char envtkf; // TIME KEY FOLLOW 0-4 - char envvkf; // VELOS KEY FOLL0W 0-4 - char envtime[5]; // 1-100 - char envlevel[4]; // 1-100 + Bit8u level; // 0-100 + Bit8u velosens; // 0-100 + Bit8u biaspoint1; // 0-127 (<1A-<7C >1A-7C) + Bit8u biaslevel1; // 0-12 (-12 - 0) + Bit8u biaspoint2; // 0-127 (<1A-<7C >1A-7C) + Bit8u biaslevel2; // 0-12 (-12 - 0) + Bit8u envtkf; // TIME KEY FOLLOW 0-4 + Bit8u envvkf; // VELOS KEY FOLL0W 0-4 + Bit8u envtime[5]; // 1-100 + Bit8u envlevel[4]; // 1-100 } MT32EMU_ALIGN_PACKED tva; } MT32EMU_ALIGN_PACKED partial[4]; } MT32EMU_ALIGN_PACKED; struct PatchParam { - char timbreGroup; // TIMBRE GROUP 0-3 (group A, group B, Memory, Rhythm) - char timbreNum; // TIMBRE NUMBER 0-63 - char keyShift; // KEY SHIFT 0-48 (-24 - +24 semitones) - char fineTune; // FINE TUNE 0-100 (-50 - +50 cents) - char benderRange; // BENDER RANGE 0-24 - char assignMode; // ASSIGN MODE 0-3 (POLY1, POLY2, POLY3, POLY4) - char reverbSwitch; // REVERB SWITCH 0-1 (OFF,ON) - char dummy; // (DUMMY) + Bit8u timbreGroup; // TIMBRE GROUP 0-3 (group A, group B, Memory, Rhythm) + Bit8u timbreNum; // TIMBRE NUMBER 0-63 + Bit8u keyShift; // KEY SHIFT 0-48 (-24 - +24 semitones) + Bit8u fineTune; // FINE TUNE 0-100 (-50 - +50 cents) + Bit8u benderRange; // BENDER RANGE 0-24 + Bit8u assignMode; // ASSIGN MODE 0-3 (POLY1, POLY2, POLY3, POLY4) + Bit8u reverbSwitch; // REVERB SWITCH 0-1 (OFF,ON) + Bit8u dummy; // (DUMMY) } MT32EMU_ALIGN_PACKED; struct MemParams { struct PatchTemp { PatchParam patch; - char outlevel; // OUTPUT LEVEL 0-100 - char panpot; // PANPOT 0-14 (R-L) - char dummyv[6]; + Bit8u outlevel; // OUTPUT LEVEL 0-100 + Bit8u panpot; // PANPOT 0-14 (R-L) + Bit8u dummyv[6]; } MT32EMU_ALIGN_PACKED patchSettings[8]; struct RhythmTemp { - char timbre; // TIMBRE 0-94 (M1-M64,R1-30,OFF) - char outlevel; // OUTPUT LEVEL 0-100 - char panpot; // PANPOT 0-14 (R-L) - char reverbSwitch; // REVERB SWITCH 0-1 (OFF,ON) - } MT32EMU_ALIGN_PACKED rhythmSettings[64]; + Bit8u timbre; // TIMBRE 0-94 (M1-M64,R1-30,OFF) + Bit8u outlevel; // OUTPUT LEVEL 0-100 + Bit8u panpot; // PANPOT 0-14 (R-L) + Bit8u reverbSwitch; // REVERB SWITCH 0-1 (OFF,ON) + } MT32EMU_ALIGN_PACKED rhythmSettings[86]; // FIXME: Was 64, but let's support the next model... TimbreParam MT32EMU_ALIGN_PACKED timbreSettings[8]; @@ -149,58 +149,43 @@ struct MemParams { struct PaddedTimbre { TimbreParam timbre; - char padding[10]; + Bit8u padding[10]; } MT32EMU_ALIGN_PACKED timbres[64 + 64 + 64 + 30]; // Group A, Group B, Memory, Rhythm struct SystemArea { - char masterTune; // MASTER TUNE 0-127 432.1-457.6Hz - char reverbMode; // REVERB MODE 0-3 (room, hall, plate, tap delay) - char reverbTime; // REVERB TIME 0-7 (1-8) - char reverbLevel; // REVERB LEVEL 0-7 (1-8) - char reserveSettings[9]; // PARTIAL RESERVE (PART 1) 0-32 - char chanAssign[9]; // MIDI CHANNEL (PART1) 0-16 (1-16,OFF) - char masterVol; // MASTER VOLUME 0-100 + Bit8u masterTune; // MASTER TUNE 0-127 432.1-457.6Hz + Bit8u reverbMode; // REVERB MODE 0-3 (room, hall, plate, tap delay) + Bit8u reverbTime; // REVERB TIME 0-7 (1-8) + Bit8u reverbLevel; // REVERB LEVEL 0-7 (1-8) + Bit8u reserveSettings[9]; // PARTIAL RESERVE (PART 1) 0-32 + Bit8u chanAssign[9]; // MIDI CHANNEL (PART1) 0-16 (1-16,OFF) + Bit8u masterVol; // MASTER VOLUME 0-100 } MT32EMU_ALIGN_PACKED system; }; struct MemBanks { - char pTemp[8][sizeof(MemParams::PatchTemp)]; - char rTemp[64][sizeof(MemParams::RhythmTemp)]; - char tTemp[8][sizeof(TimbreParam)]; - char patchBank[128][sizeof(PatchParam)]; - char timbreBank[64 + 64 + 64 + 30][sizeof(MemParams::PaddedTimbre)]; - char systemBank[sizeof(MemParams::SystemArea)]; + Bit8u pTemp[8][sizeof(MemParams::PatchTemp)]; + Bit8u rTemp[86][sizeof(MemParams::RhythmTemp)]; + Bit8u tTemp[8][sizeof(TimbreParam)]; + Bit8u patchBank[128][sizeof(PatchParam)]; + Bit8u timbreBank[64 + 64 + 64 + 30][sizeof(MemParams::PaddedTimbre)]; + Bit8u systemBank[sizeof(MemParams::SystemArea)]; // System memory 0x100000 // Display 0x200000 // Reset 0x7F0000 }; -union MT32RAMFormat { - MemParams params; - MemBanks banks; -} MT32EMU_ALIGN_PACKED; - #if defined(_MSC_VER) || defined (__MINGW32__) #pragma pack(pop) #else #pragma pack() #endif -struct PCMWave { - char name[16]; - Bit32u addr; - Bit32u len; - bool loop; - float tune; - Bit32s ampval; -}; - struct PCMWaveEntry { Bit32u addr; Bit32u len; - Bit32u pcmnum; + double tune; bool loop; - Bit32s aggSound; // This variable is for the last 9 PCM samples, which are actually loop combinations }; struct soundaddr { @@ -221,17 +206,15 @@ struct PatchCache { char waveform; int pulsewidth; int pwsens; - int pitchshift; - int fineshift; + + float pitch; int lfodepth; int lforate; Bit32u lfoperiod; int modsense; - int keydir; - int pitchkeyfollow; - int pitchkeydir; + float pitchKeyfollow; int filtkeyfollow; @@ -264,8 +247,12 @@ struct PatchCache { int structurePair; // The following fields are actually common to all partials in the timbre + bool dirty; Bit32u partialCount; bool sustain; + float pitchShift; + bool reverb; + const StereoVolume *pansetptr; }; class Partial; // Forward reference for class defined in partial.h @@ -277,19 +264,17 @@ struct dpoly { int freqnum; int vel; - bool reverb; bool isDecay; - const Bit32s *volumeptr; - const StereoVolume *pansetptr; + const Bit32u *volumeptr; Partial *partials[4]; bool pedalhold; // This marks keys that have been released on the keyboard, but are being held by the pedal bool sustain; - bool isActive(); - Bit64s getAge(); + bool isActive() const; + Bit64s getAge() const; }; } diff --git a/backends/midi/mt32/synth.cpp b/backends/midi/mt32/synth.cpp index 9361c3bda9..28d09f4d4d 100644 --- a/backends/midi/mt32/synth.cpp +++ b/backends/midi/mt32/synth.cpp @@ -26,20 +26,10 @@ #include "mt32emu.h" -#if MT32EMU_BENCHMARK_FILTERS > 0 -#include -#endif - namespace MT32Emu { const int MAX_SYSEX_SIZE = 512; -iir_filter_type usefilter; - -static const Bit8u InitPatches[8] = { - 68, 48, 95, 78, 41, 3, 110, 122 -}; - // Maps MIDI channel numbers to MT-32 parts (not to be confused with "partials") // This is the default (FIXME: the mapping from 11->9 is undocumented, is this correct?): static const Bit8s InitChanTable[16] = { @@ -51,23 +41,6 @@ static const Bit8s InitChanTable[16] = { // 0, 1, 2, 3, 4, 5, 6, 7, -1, 8, -1, -1, -1, -1, -1, -1 //}; -static int axtoi(char *str) { - int result = 0; - while (*str) { - char ch = *str++; - if (ch >= '0' && ch <= '9') - ch -= '0'; - else if (ch >= 'a' && ch <= 'f') - ch = ch + 10 - 'a'; - else if (ch >= 'A' && ch <= 'F') - ch = ch + 10 - 'A'; - else - break; - result = (result << 4) | ch; - } - return result; -} - float iir_filter_normal(float input,float *hist1_ptr, float *coef_ptr, int revLevel) { float *hist2_ptr; float output,new_hist; @@ -103,7 +76,7 @@ float iir_filter_normal(float input,float *hist1_ptr, float *coef_ptr, int revLe return(output); } -Bit8u Synth::calcSysexChecksum(Bit8u *data, Bit32u len, Bit8u checksum) { +Bit8u Synth::calcSysexChecksum(const Bit8u *data, Bit32u len, Bit8u checksum) { for (unsigned int i = 0; i < len; i++) { checksum = checksum + data[i]; } @@ -125,10 +98,11 @@ Synth::~Synth() { close(); // Make sure we're closed and everything is freed } -void Synth::report(ReportType type, const void *data) { +int Synth::report(ReportType type, const void *data) { if (myProp.report != NULL) { - myProp.report(myProp.userData, type, data); + return myProp.report(myProp.userData, type, data); } + return 0; } void Synth::printDebug(const char *fmt, ...) { @@ -143,15 +117,12 @@ void Synth::printDebug(const char *fmt, ...) { va_end(ap); } -void Synth::initReverb(char newRevMode, char newRevTime) { +void Synth::initReverb(Bit8u newRevMode, Bit8u newRevTime, Bit8u newRevLevel) { // FIXME:KG: I don't think it's necessary to recreate the reverbModel... Just set the parameters if (reverbModel != NULL) delete reverbModel; reverbModel = new revmodel(); - curRevTime = newRevTime; - curRevMode = newRevMode; - switch(newRevMode) { case 0: reverbModel->setroomsize(.1f); @@ -175,8 +146,8 @@ void Synth::initReverb(char newRevMode, char newRevTime) { break; } reverbModel->setdry(1); - reverbModel->setwet((float)mt32ram.params.system.reverbLevel / 8.0f); - reverbModel->setwidth((float)curRevTime / 8.0f); + reverbModel->setwet((float)newRevLevel / 8.0f); + reverbModel->setwidth((float)newRevTime / 8.0f); } File *Synth::openFile(const char *filename, File::OpenMode mode) { @@ -228,7 +199,7 @@ bool Synth::loadPreset(const char *filename) { if (inSys) { syslen++; if (c == 0xF7) { - playSysex(&sysexBuf[0],syslen); + playSysex(&sysexBuf[0], syslen); inSys = false; syslen = 0; } else if (syslen == MAX_SYSEX_SIZE) { @@ -245,231 +216,22 @@ bool Synth::loadPreset(const char *filename) { return rc; } -bool Synth::loadDrums(const char *filename) { - File *file = openFile(filename, File::OpenMode_read); - if (file == NULL) { - return false; - } - int drumnum = 0; - for (;;) { - //Read common area - TimbreParam *timbre = &mt32ram.params.timbres[drumnum + 192].timbre; - if (file->read(&timbre->common, 14) != 14) - break; - char drumname[11]; - strncpy(drumname, timbre->common.name, 10); - drumname[10] = 0; - bool breakout = false; - for (int t=0;t<4;t++) { - if (((timbre->common.pmute >> t) & 0x1) == 0x1) { - if (file->read(&timbre->partial[t], 58) != 58) { - breakout = true; - break; - } - //printDebug("Loaded drum #%d (%s) - t %d", drumnum, drumname, t); - } - } - if (breakout) { - break; - } - //printDebug("Loaded drum #%d (%s)", drumnum, drumname); - drumnum++; - } - closeFile(file); - return true; -} - -void Synth::dumpDrums(const char *filename) { - File *file = openFile(filename, File::OpenMode_write); - if (file == NULL) - return; - char dumbtext[10]; - memset(dumbtext,0,10); - for (int drumnum=0;drumnum<30;drumnum++) { - // Sysex header - if (!file->writeBit8u(0xf0)) - break; - if (!file->writeBit8u(0x41)) - break; - if (!file->writeBit8u(0x10)) - break; - if (!file->writeBit8u(0x16)) - break; - if (!file->writeBit8u(0x12)) - break; - - int useaddr = drumnum * 256; - char lsb = (char)(useaddr & 0x7f); - char isb = (char)((useaddr >> 7) & 0x7f); - char msb = (char)(((useaddr >> 14) & 0x7f) | 0x08); - //Address - if (!file->writeBit8u(msb)) - break; - if (!file->writeBit8u(isb)) - break; - if (!file->writeBit8u(lsb)) - break; - - TimbreParam *timbre = &mt32ram.params.timbres[192 + drumnum].timbre; - //Data - if (file->write(&timbre->common,0xE) != 0xE) - break; - if (file->write(&timbre->partial[0],0x3A) != 0x3A) - break; - if (file->write(&timbre->partial[1],0x3A) != 0x3A) - break; - if (file->write(&timbre->partial[2],0x3A) != 0x3A) - break; - if (file->write(&timbre->partial[3],0x3A) != 0x3A) - break; - //Checksum - unsigned char *dat = (unsigned char *)timbre; - unsigned char checksum = calcSysexChecksum(dat, 246, msb + isb + lsb); - if (!file->writeBit8u(checksum)) - break; - - //End of sysex - if (!file->writeBit8u(0xf7)) - break; - } - closeFile(file); -} - -bool Synth::loadPCMToROMMap(const char *filename) { - File *file = openFile(filename, File::OpenMode_read); // Original specified text mode - +bool Synth::loadControlROM(const char *filename) { + File *file = openFile(filename, File::OpenMode_read); // ROM File if (file == NULL) { return false; } + bool rc = (file->read(controlROMData, sizeof(controlROMData)) == sizeof(controlROMData)); - Bit32u PCMReassign[54]; - for (int i = 0; i < 54; i++) { - PCMReassign[i] = i; - PCM[i].tune = 220.0f; - PCM[i].ampval = 256; - } - //PCM[53].ampval = 128; - - char tbuf[512]; - char *cp; - if (!file->readLine(tbuf,sizeof(tbuf))) { - return false; - } - Bit32u patchstart = 0; //axtoi(tbuf); - Bit32u patchend = 0; - Bit32u patchcount = 0; - bool rc = true; - for (;;) { - if (!file->readLine(tbuf,sizeof(tbuf))) { - if (!file->isEOF()) { - rc = false; - } - break; - } - cp = strtok(tbuf," \n\r"); - PCM[patchcount].loop = false; - if (cp != NULL) { - patchend = axtoi(cp); - cp = strtok(NULL," \n\r"); - if (cp != NULL) { - cp = strtok(NULL," \n\r"); - if (cp != NULL) { - strncpy(PCM[patchcount].name, cp, 15); - PCM[patchcount].name[15] = 0; - cp = strtok(NULL," \n\r"); - if (cp !=NULL) { - int newpcm = atoi(cp); - PCMReassign[newpcm] = patchcount; - cp = strtok(NULL," \n\r"); - if (cp != NULL) { - if (atoi(cp)==1) - PCM[patchcount].loop = true; - cp = strtok(NULL," \n\r"); - if (cp != NULL) { - PCM[patchcount].tune = (float)atoi(cp) / 100.0f; - //printDebug("PCM %d tuning at %f", patchcount, PCM[patchcount].tune); - } - } - } - } - } - } - if (patchend==0) - break; - - PCM[patchcount].addr = patchstart; - PCM[patchcount].len = patchend - patchstart; - patchcount++; - //printf("Patch %d %d %d %d", patchcount, patchstart, patchend, mt32ram.PCM[patchcount].len); - patchstart = patchend; - } closeFile(file); - if (!rc) - return rc; - - PCM[53].len = 1950; - - // Generate official PCM list - - // Normal sounds - int pat = 0; - for (int p = 0; p < 54; p++) { - PCMList[pat].addr = PCM[PCMReassign[p]].addr; - PCMList[pat].len = PCM[PCMReassign[p]].len; - PCMList[pat].loop = PCM[PCMReassign[p]].loop; - PCMList[pat].aggSound = -1; - PCMList[pat].pcmnum = PCMReassign[p]; - pat++; - } - - // Drum specific sounds. Not exactly sure yet how these are different - for (int p = 0; p < 20; p++) { - PCMList[pat] = PCMList[p]; - pat++; - } - - // Looped PCM sounds. The last remaining 9 are aggregate sounds; - // FIXME:KG: I hope this is correct; the original was heavily broken, - // and it was hard to determine the author's intention. - for (int p = 0; p < 54; p++) { - if (p < 45) { - int pcmNum = p > 7 ? p - 1 : p; - PCMList[pat].addr = PCM[PCMReassign[pcmNum]].addr; - PCMList[pat].len = PCM[PCMReassign[pcmNum]].len; - PCMList[pat].pcmnum = PCMReassign[pcmNum]; - PCMList[pat].loop = true; - PCMList[pat].aggSound = -1; - } else { - //Calculate aggregate length - int aggsnd = p - 45; - int tmplen = 0; - int sndpos = 0; - while (LoopPatterns[aggsnd][sndpos] != -1) { - tmplen += PCM[LoopPatterns[aggsnd][sndpos]].len; - sndpos++; - } - PCMList[pat].addr = 0; - PCMList[pat].len = tmplen; - PCMList[pat].loop = true; - PCMList[pat].aggSound = aggsnd; - } - pat++; - } - - //for (p=0;p<128;p++) - // printDebug("PCM #%d addr 0x%x len %d loop %d aggSound %d pcmnum %d", p, PCMList[p].addr, PCMList[p].len, PCMList[p].loop, PCMList[p].aggSound, PCMList[p].pcmnum); - return true; + return rc; } -bool Synth::loadROM(const char *filename) { +bool Synth::loadPCMROM(const char *filename) { File *file = openFile(filename, File::OpenMode_read); // ROM File if (file == NULL) { return false; } -#ifdef MT32OUT - File *outFile = openFile("mt32out.raw", File::OpenMode_write); - File *outFileB = openFile("mt32out2.raw", File::OpenMode_write); -#endif bool rc = true; for (int i = 0; ; i++) { Bit8u s; @@ -495,12 +257,12 @@ bool Synth::loadROM(const char *filename) { int order[16] = {0, 9, 1 ,2, 3, 4, 5, 6, 7, 10, 11, 12, 13, 14, 15, 8}; - e=0; - for (u=0;u<15;u++) { - if (order[u]<8) - bit = (s >> (7-order[u])) & 0x1; + e = 0; + for (u = 0; u < 15; u++) { + if (order[u] < 8) + bit = (s >> (7 - order[u])) & 0x1; else - bit = (c >> (7-(order[u]-8))) & 0x1; + bit = (c >> (7 - (order[u] - 8))) & 0x1; e = e | (short)(bit << (15 - u)); } @@ -514,10 +276,6 @@ bool Synth::loadROM(const char *filename) { e = (int)((float)e * (x/3200)); */ -#ifdef MT32OUT - outFile->writeBit8u(e & 0xff); - outFile->writeBit8u(((e >> 8) & 0x7f)); -#endif // File is encoded in dB, convert to PCM // MINDB = -96 // MAXDB = -15 @@ -525,107 +283,169 @@ bool Synth::loadROM(const char *filename) { testval = (float)((~e) & 0x7fff); testval = -(testval / 400.00f); //testval = -(testval / 341.32291666666666666666666666667); - float vol = powf(8,(testval / 20)) * 32767.0f; + float vol = powf(8, testval / 20) * 32767.0f; - if (e>0) + if (e > 0) vol = -vol; romfile[i] = (Bit16s)vol; -#ifdef MT32OUT - outFileB->writeBit8u(romfile[i] & 0xff); - outFileB->writeBit8u(romfile[i] >> 8); -#endif } -#ifdef MT32OUT - closeFile(outFileB); - closeFile(outFile); -#endif closeFile(file); return rc; } +struct TempPCMStruct +{ + Bit8u pos; + Bit8u len; + Bit8u pitchLSB; + Bit8u pitchMSB; +}; + +void Synth::initPCMList() { + TempPCMStruct *tps = (TempPCMStruct *)&controlROMData[0x3000]; + printDebug("********************************"); + for (int i = 0; i < 128; i++) { + int rAddr = tps[i].pos * 0x800; + int rLenExp = (tps[i].len & 0x70) >> 4; + int rLen = 0x800 << rLenExp; + bool rLoop = (tps[i].len & 0x80) != 0; + Bit8u rFlag = tps[i].len & 0x0F; + Bit16u rTuneOffset = (tps[i].pitchMSB << 8) | tps[i].pitchLSB; + //FIXME:KG: Pick a number, any number. 260.1f sounded best to me in listening tests, but needs to be confirmed. + //double STANDARDFREQ = 261.6255653005986346778499935233; // A below Middle C of 440Hz + double STANDARDFREQ = 260.1f; + float rTune = (float)(STANDARDFREQ * pow(2.0, (0x5000 - rTuneOffset) / 4096.0)); + //printDebug("%f,%d,%d", pTune, tps[i].pitchCoarse, tps[i].pitchFine); + PCMList[i].addr = rAddr; + PCMList[i].len = rLen; + PCMList[i].loop = rLoop; + PCMList[i].tune = rTune; + } + printDebug("********************************"); +} + +void Synth::initRhythmTimbre(int timbreNum, const Bit8u *mem) { + TimbreParam *timbre = &mt32ram.timbres[timbreNum].timbre; + memcpy(&timbre->common, mem, 14); + mem += 14; + char drumname[11]; + strncpy(drumname, timbre->common.name, 10); + drumname[10] = 0; + bool breakout = false; + for (int t = 0; t < 4; t++) { + if (((timbre->common.pmute >> t) & 0x1) == 0x1) { + memcpy(&timbre->partial[t], mem, 58); + mem += 58; + } + } +} + +void Synth::initRhythmTimbres() { + TempPCMStruct *tps = (TempPCMStruct *)&controlROMData[0x3000]; + const Bit8u *drumMap = &controlROMData[0x3200]; + int timbreNum = 192; + for (Bit16u i = 0x3200; i < 0x323C; i += 2) { + Bit16u address = (controlROMData[i + 1] << 8) | controlROMData[i]; + initRhythmTimbre(timbreNum++, &controlROMData[address]); + } +} + +void Synth::initTimbres(Bit16u mapAddress, int startTimbre) { + for (Bit16u i = mapAddress; i < mapAddress + 0x80; i += 2) { + Bit16u address = (controlROMData[i + 1] << 8) | controlROMData[i]; + address = address + (mapAddress - 0x8000); + TimbreParam *timbre = &mt32ram.timbres[startTimbre++].timbre; + memcpy(timbre, &controlROMData[address], sizeof(TimbreParam)); + } +} + bool Synth::open(SynthProperties &useProp) { if (isOpen) return false; - // Initalise patch information - myProp = useProp; - usefilter = &iir_filter_normal; - - partialManager = new PartialManager(this); - - // This is so that names won't be garbage during early setup debug output, but we can detect bugs + // This is to help detect bugs memset(&mt32ram, '?', sizeof(mt32ram)); - printDebug("Initialising patch banks"); - initmode = 0; - if (!loadPreset("Preset1.syx")) { - report(ReportType_errorPreset1, &errno); + printDebug("Loading Control ROM"); + if (!loadControlROM("MT32_CONTROL.ROM")) { + printDebug("Init Error - Missing or invalid MT32_CONTROL.ROM"); + report(ReportType_errorControlROM, &errno); return false; } - initmode = 1; - if (!loadPreset("Preset2.syx")) { - report(ReportType_errorPreset2, &errno); - return false; - } - initmode = 2; - printDebug("Initialising Drums"); - if (!loadDrums("drumpat.rom")) { - report(ReportType_errorDrumpat, &errno); + printDebug("Loading PCM ROM"); + if (!loadPCMROM("MT32_PCM.ROM")) { + printDebug("Init Error - Missing MT32_PCM.ROM"); + report(ReportType_errorPCMROM, &errno); return false; } -#if MT32EMU_DUMP_DRUMS == 1 - strcpy(&pathBuf[0], baseDir); - dumpDrums(strcat(&pathBuf[0],"drumsys.syx")); -#endif + partialManager = new PartialManager(this); - printDebug("Initialising PCM-to-ROM map"); - if (!loadPCMToROMMap("patchlog.cfg")) { - printDebug("Init Error - Missing patchlog.cfg"); - report(ReportType_errorPatchlog, &errno); - return false; - } + printDebug("Initialising PCM List"); + initPCMList(); - printDebug("Initialising ROM"); - if (!loadROM("MT32_PCM.ROM")) { - printDebug("Init Error - Missing MT32_PCM.ROM"); - report(ReportType_errorMT32ROM, &errno); - return false; - } - memcpy(chantable, InitChanTable, sizeof (chantable)); - for (unsigned char i = 0; i < 128; i++) { - mt32ram.params.patches[i].timbreGroup = i >> 6; - mt32ram.params.patches[i].timbreNum = i & 63; + printDebug("Initialising Timbre Bank A"); + initTimbres(0x8000, 0); + + printDebug("Initialising Timbre Bank B"); + initTimbres(0xC000, 64); + + printDebug("Initialising Timbre Bank R"); + initRhythmTimbres(); + + printDebug("Initialising Rhythm Temp"); + memcpy(mt32ram.rhythmSettings, &controlROMData[0x741C], 344); + + printDebug("Initialising Patches"); + 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 (!TableInitialiser::initMT32Tables(this, PCM, (float)myProp.sampleRate)) { - report(ReportType_errorSampleRate, NULL); - return false; + printDebug("Initialising System"); + //FIXME: Confirm that these are all correct + // The MT-32 manual claims that "Standard pitch" is 442.0. + // I assume they mean this is the MT-32 default pitch, and not concert pitch, + // since the latter has been internationally defined as 440Hz for decades. + // Regardless, I'm setting the default masterTune to 440Hz + mt32ram.system.masterTune = 0x40; + mt32ram.system.reverbMode = 0; + mt32ram.system.reverbTime = 5; + mt32ram.system.reverbTime = 3; + memcpy(mt32ram.system.reserveSettings, &controlROMData[0x57E5], 9); + for (Bit8u i = 0; i < 9; i++) { + mt32ram.system.chanAssign[i] = i + 1; } - if (myProp.useDefaultReverb) - initReverb(0, 5); - else - initReverb(myProp.reverbType, myProp.reverbTime); + mt32ram.system.masterVol = 100; + if (!refreshSystem()) + return false; - for (int i = 0; i < 9; i++) { + for (int i = 0; i < 8; i++) { + mt32ram.patchSettings[i].outlevel = 80; + mt32ram.patchSettings[i].panpot = controlROMData[0x5800 + i]; + memset(mt32ram.patchSettings[i].dummyv, 0, sizeof(mt32ram.patchSettings[i].dummyv)); parts[i] = new Part(this, i); - - if (i<8) { - // The patch is already set by the presets, now set the timbre it wants - parts[i]->setTimbre(&mt32ram.params.timbres[parts[i]->getAbsTimbreNum()].timbre); - // And refresh the part's cache - parts[i]->refreshPatch(); - } + parts[i]->setProgram(controlROMData[0x57EE + i]); } + parts[8] = new RhythmPart(this, 8); // For resetting mt32 mid-execution mt32default = mt32ram; + iirFilter = &iir_filter_normal; + #ifdef MT32EMU_HAVE_X86 bool availableSSE = DetectSIMD(); bool available3DNow = Detect3DNow(); @@ -637,52 +457,19 @@ bool Synth::open(SynthProperties &useProp) { if (available3DNow) { printDebug("Detected and using SIMD (AMD 3DNow) extensions"); - usefilter = &iir_filter_3dnow; + iirFilter = &iir_filter_3dnow; report(ReportType_using3DNow, NULL); } else if (availableSSE) { printDebug("Detected and using SIMD (Intel SSE) extensions"); - usefilter = &iir_filter_sse; + iirFilter = &iir_filter_sse; report(ReportType_usingSSE, NULL); } #endif -#if MT32EMU_BENCHMARK_FILTERS > 1 - // Benchmark 3DNow, Floating point, and SSE filters - clock_t start, end; - float histval[50]; - - for (int bench = 0; bench < 3; bench++) { - start = clock(); - for (int benchcnt=0;benchcnt<2000000;benchcnt++) { - switch (bench) { - case 0: - iir_filter_normal(0,&histval[0],filtcoeff[0][0],0); - break; - case 1: - if (!availableSSE) { - printDebug("Skipping SSE benchmark, SSE not available"); - continue; - } - iir_filter_sse(0,&histval[0],filtcoeff[0][0],0); - break; - case 2: - if (!available3DNow) { - printDebug("Skipping 3DNow benchmark, 3DNow not available"); - continue; - } - iir_filter_3dnow(0,&histval[0],filtcoeff[0][0],0); - break; - } - } - end = clock(); - printDebug("Bench %ld completed in %ld milliseconds", bench, (end - start) * 1000 / CLOCKS_PER_SEC); - } -#endif - - isOpen=true; - isEnabled=false; + isOpen = true; + isEnabled = false; - printDebug("**************** Initialisation complete ****************"); + printDebug("*** Initialisation complete ***"); return true; } @@ -690,15 +477,7 @@ void Synth::close(void) { if (!isOpen) return; - for (int t = 0; t < 3; t++) { - for (int m = 0; m < NUM_NOTES; m++) { - if (noteLookups[m].waveforms[t] != NULL) { - delete[] noteLookups[m].waveforms[t]; - noteLookups[m].waveforms[t] = NULL; - noteLookups[m].waveformSize[t] = 0; - } - } - } + TableInitialiser::freeNotes(); if (partialManager != NULL) { delete partialManager; partialManager = NULL; @@ -752,7 +531,7 @@ void Synth::playMsgOnPart(unsigned char part, unsigned char code, unsigned char // MIDI defines note-on with velocity 0 as being the same as note-off with velocity 40 parts[part]->stopNote(note); } else { - parts[part]->playNote(partialManager, note, velocity); + parts[part]->playNote(note, velocity); } break; case 0xB: // Control change @@ -796,11 +575,7 @@ void Synth::playMsgOnPart(unsigned char part, unsigned char code, unsigned char break; case 0xC: // Program change //printDebug("Program change %01x", note); - if (part < 8) { - parts[part]->setPatch(note); - } else { - printDebug("Program change attempted on rhythm part"); - } + parts[part]->setProgram(note); break; case 0xE: // Pitch bender bend = (velocity << 7) | (note); @@ -815,7 +590,7 @@ void Synth::playMsgOnPart(unsigned char part, unsigned char code, unsigned char //midiOutShortMsg(m_out, msg); } -void Synth::playSysex(Bit8u * sysex,Bit32u len) { +void Synth::playSysex(const Bit8u * sysex,Bit32u len) { if (len < 3) { printDebug("playSysex: Message is too short for sysex (%d bytes)", len); } @@ -830,7 +605,7 @@ void Synth::playSysex(Bit8u * sysex,Bit32u len) { playSysexWithoutFraming(sysex + 1, len - 2); } -void Synth::playSysexWithoutFraming(Bit8u * sysex, Bit32u len) { +void Synth::playSysexWithoutFraming(const Bit8u * sysex, Bit32u len) { if (len < 4) { printDebug("playSysexWithoutFraming: Message is too short (%d bytes)!", len); return; @@ -861,7 +636,7 @@ void Synth::playSysexWithoutFraming(Bit8u * sysex, Bit32u len) { #define NUMTOUCHED(x,y) (((x) + sizeof(y) - 1) / sizeof(y)) -void Synth::playSysexWithoutHeader(unsigned char device, Bit8u *sysex, Bit32u len) { +void Synth::playSysexWithoutHeader(unsigned char device, 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); @@ -920,21 +695,21 @@ void Synth::playSysexWithoutHeader(unsigned char device, Bit8u *sysex, Bit32u le } if (addr >= MEMADDR(0x030000) && addr < MEMADDR(0x030110)) { int off = addr - MEMADDR(0x030000); - if (off + len > sizeof(mt32ram.banks.pTemp)) { + if (off + len > sizeof(mt32ram.patchSettings)) { printDebug("playSysexWithoutHeader: Message goes beyond bounds of memory region (addr=0x%06x, len=%d)!", SYSEXMEMADDR(addr), len); return; } int firstPart = off / sizeof(MemParams::PatchTemp); off %= sizeof(MemParams::PatchTemp); for (unsigned int m = 0; m < len; m++) - mt32ram.banks.pTemp[firstPart][off + m] = sysex[m]; + ((Bit8u *)&mt32ram.patchSettings[firstPart])[off + m] = sysex[m]; //printDebug("Patch temp: Patch %d, offset %x, len %d", off/16, off % 16, len); int lastPart = firstPart + NUMTOUCHED(off + len, MemParams::PatchTemp) - 1; for (int i = firstPart; i <= lastPart; i++) { - int absTimbreNum = mt32ram.params.patchSettings[i].patch.timbreGroup * 64 + mt32ram.params.patchSettings[i].patch.timbreNum; + int absTimbreNum = mt32ram.patchSettings[i].patch.timbreGroup * 64 + mt32ram.patchSettings[i].patch.timbreNum; char timbreName[11]; - memcpy(timbreName, mt32ram.params.timbres[absTimbreNum].timbre.common.name, 10); + memcpy(timbreName, mt32ram.timbres[absTimbreNum].timbre.common.name, 10); timbreName[10] = 0; printDebug("WRITE-PARTPATCH (%d-%d@%d..%d): %d; timbre=%d (%s)", firstPart, lastPart, off, off + len, i, absTimbreNum, timbreName); if (parts[i] != NULL) { @@ -942,83 +717,84 @@ void Synth::playSysexWithoutHeader(unsigned char device, Bit8u *sysex, Bit32u le printDebug(" (Not updating timbre, since those values weren't touched)"); } else { // Not sure whether we should do this at all, really. - parts[i]->setTimbre(&mt32ram.params.timbres[parts[i]->getAbsTimbreNum()].timbre); + parts[i]->setTimbre(&mt32ram.timbres[parts[i]->getAbsTimbreNum()].timbre); } - parts[i]->refreshPatch(); + parts[i]->refresh(); } } } else if (addr >= MEMADDR(0x030110) && addr < MEMADDR(0x040000)) { int off = addr - MEMADDR(0x030110); - if (off + len > sizeof(mt32ram.banks.rTemp)) { + if (off + len > sizeof(mt32ram.rhythmSettings)) { printDebug("playSysexWithoutHeader: Message goes beyond bounds of memory region (addr=0x%06x, len=%d)!", SYSEXMEMADDR(addr), len); return; } int firstDrum = off / sizeof(MemParams::RhythmTemp); off %= sizeof(MemParams::RhythmTemp); for (unsigned int m = 0; m < len; m++) - mt32ram.banks.rTemp[firstDrum][off + m] = sysex[m]; + ((Bit8u *)&mt32ram.rhythmSettings[firstDrum])[off + m] = sysex[m]; int lastDrum = firstDrum + NUMTOUCHED(off + len, MemParams::RhythmTemp) - 1; for (int i = firstDrum; i <= lastDrum; i++) { - int timbreNum = mt32ram.params.rhythmSettings[i].timbre; + int timbreNum = mt32ram.rhythmSettings[i].timbre; char timbreName[11]; if (timbreNum < 94) { - memcpy(timbreName, mt32ram.params.timbres[128 + timbreNum].timbre.common.name, 10); + memcpy(timbreName, mt32ram.timbres[128 + timbreNum].timbre.common.name, 10); timbreName[10] = 0; } else { strcpy(timbreName, "[None]"); } - printDebug("WRITE-RHYTHM (%d-%d@%d..%d): %d; level=%02x, panpot=%02x, reverb=%02x, timbre=%d (%s)", firstDrum, lastDrum, off, off + len, i, mt32ram.params.rhythmSettings[i].outlevel, mt32ram.params.rhythmSettings[i].panpot, mt32ram.params.rhythmSettings[i].reverbSwitch, mt32ram.params.rhythmSettings[i].timbre, timbreName); + printDebug("WRITE-RHYTHM (%d-%d@%d..%d): %d; level=%02x, panpot=%02x, reverb=%02x, timbre=%d (%s)", firstDrum, lastDrum, off, off + len, i, mt32ram.rhythmSettings[i].outlevel, mt32ram.rhythmSettings[i].panpot, mt32ram.rhythmSettings[i].reverbSwitch, mt32ram.rhythmSettings[i].timbre, timbreName); } if (parts[8] != NULL) { - parts[8]->refreshDrumCache(); + parts[8]->refresh(); } } else if (addr >= MEMADDR(0x040000) && addr < MEMADDR(0x050000)) { int off = addr - MEMADDR(0x040000); - if (off + len > sizeof(mt32ram.banks.tTemp)) { + if (off + len > sizeof(mt32ram.timbreSettings)) { printDebug("playSysexWithoutHeader: Message goes beyond bounds of memory region (addr=0x%06x, len=%d)!", SYSEXMEMADDR(addr), len); return; } int firstPart = off / sizeof(TimbreParam); off %= sizeof(TimbreParam); for (unsigned int m = 0; m < len; m++) - mt32ram.banks.tTemp[firstPart][off + m] = sysex[m]; + ((Bit8u *)&mt32ram.timbreSettings[firstPart])[off + m] = sysex[m]; int lastPart = firstPart + NUMTOUCHED(off + len, TimbreParam) - 1; for (int i = firstPart; i <= lastPart; i++) { char instrumentName[11]; - memcpy(instrumentName, mt32ram.params.timbreSettings[i].common.name, 10); + memcpy(instrumentName, mt32ram.timbreSettings[i].common.name, 10); instrumentName[10] = 0; printDebug("WRITE-PARTTIMBRE (%d-%d@%d..%d): timbre=%d (%s)", firstPart, lastPart, off, off + len, i, instrumentName); if (parts[i] != NULL) { - parts[i]->refreshPatch(); + parts[i]->refresh(); } } } else if (addr >= MEMADDR(0x050000) && addr < MEMADDR(0x060000)) { int off = addr - MEMADDR(0x050000); - if (off + len > sizeof(mt32ram.banks.patchBank)) { + if (off + len > sizeof(mt32ram.patches)) { printDebug("playSysexWithoutHeader: Message goes beyond bounds of memory region (addr=0x%06x, len=%d)!", SYSEXMEMADDR(addr), len); return; } int firstPatch = off / sizeof(PatchParam); off %= sizeof(PatchParam); for (unsigned int m = 0; m < len; m++) - mt32ram.banks.patchBank[firstPatch][off + m] = sysex[m]; + ((Bit8u *)&mt32ram.patches[firstPatch])[off + m] = sysex[m]; int lastPatch = firstPatch + NUMTOUCHED(off + len, PatchParam) - 1; for (int i = firstPatch; i <= lastPatch; i++) { - PatchParam *patch = &mt32ram.params.patches[i]; + PatchParam *patch = &mt32ram.patches[i]; int patchAbsTimbreNum = patch->timbreGroup * 64 + patch->timbreNum; char instrumentName[11]; - memcpy(instrumentName, mt32ram.params.timbres[patchAbsTimbreNum].timbre.common.name, 10); + memcpy(instrumentName, mt32ram.timbres[patchAbsTimbreNum].timbre.common.name, 10); instrumentName[10] = 0; - printDebug("WRITE-PATCH (%d-%d@%d..%d): %d; timbre=%d (%s)", firstPatch, lastPatch, off, off + len, i, patchAbsTimbreNum, instrumentName); + Bit8u *n = (Bit8u *)patch; + printDebug("WRITE-PATCH (%d-%d@%d..%d): %d; timbre=%d (%s) %02X%02X%02X%02X%02X%02X%02X%02X", firstPatch, lastPatch, off, off + len, i, patchAbsTimbreNum, instrumentName, n[0], n[1], n[2], n[3], n[4], n[5], n[6], n[7]); // FIXME:KG: The below is definitely dodgy. We just guess that this is the patch that the part was using // based on a timbre match (but many patches could have the same timbre!) // If this refresh is really correct, we should store the patch number in use by each part. /* for (int part = 0; part < 8; part++) { if (parts[part] != NULL) { - int partPatchAbsTimbreNum = mt32ram.params.patchSettings[part].patch.timbreGroup * 64 + mt32ram.params.patchSettings[part].patch.timbreNum; - if (partPatchAbsTimbreNum == patchAbsTimbreNum) { + int partPatchAbsTimbreNum = mt32ram.patchSettings[part].patch.timbreGroup * 64 + mt32ram.patchSettings[part].patch.timbreNum; + if (parts[part]->getAbsTimbreNum() == patchAbsTimbreNum) { parts[part]->setPatch(patch); parts[part]->RefreshPatch(); } @@ -1027,7 +803,7 @@ void Synth::playSysexWithoutHeader(unsigned char device, Bit8u *sysex, Bit32u le */ } } else if (addr >= MEMADDR(0x080000) && addr < MEMADDR(0x090000)) { - // Timbre patches + // Timbres int off = addr - MEMADDR(0x080000); if (off + len > sizeof(MemParams::PaddedTimbre) * 64) { // You can only write to one group at a time @@ -1049,75 +825,34 @@ void Synth::playSysexWithoutHeader(unsigned char device, Bit8u *sysex, Bit32u le // Write into user timbre group } for (unsigned int m = 0; m < len; m++) - mt32ram.banks.timbreBank[firstTimbre][off + m] = sysex[m]; + ((Bit8u *)&mt32ram.timbres[firstTimbre])[off + m] = sysex[m]; unsigned int lastTimbre = firstTimbre + NUMTOUCHED(len + off, MemParams::PaddedTimbre) - 1; for (unsigned int i = firstTimbre; i <= lastTimbre; i++) { char instrumentName[11]; - memcpy(instrumentName, mt32ram.params.timbres[i].timbre.common.name, 10); + memcpy(instrumentName, mt32ram.timbres[i].timbre.common.name, 10); instrumentName[10] = 0; printDebug("WRITE-TIMBRE (%d-%d@%d..%d): %d; name=\"%s\"", firstTimbre, lastTimbre, off, off + len, i, instrumentName); // FIXME:KG: Not sure if the stuff below should be done (for rhythm and/or parts)... // Does the real MT-32 automatically do this? - if (i >= 128 && parts[8] != NULL) { - // FIXME:KG: Only bother to re-cache when this timbre's actually in the rhythm map - parts[8]->setPatch(i); // Re-cache this timbre - } - for (unsigned int part = 0; part < 8; part++) { + for (unsigned int part = 0; part < 9; part++) { if (parts[part] != NULL) { - if (parts[part]->getAbsTimbreNum() == i) { - parts[part]->refreshPatch(); - } + parts[part]->refreshTimbre(i); } } } } else if (addr >= MEMADDR(0x100000) && addr < MEMADDR(0x200000)) { int off = addr - MEMADDR(0x100000); - if (off + len > sizeof(mt32ram.banks.systemBank)) { + if (off + len > sizeof(mt32ram.system)) { printDebug("playSysexWithoutHeader: Message goes beyond bounds of memory region (addr=0x%06x, len=%d)!", SYSEXMEMADDR(addr), len); return; } for (unsigned int m = 0; m < len; m++) - mt32ram.banks.systemBank[m + off] = sysex[m]; + ((Bit8u *)&mt32ram.system)[m + off] = sysex[m]; report(ReportType_devReconfig, NULL); - printDebug("System Reconfiguration:"); - memset(chantable,-1,sizeof(chantable)); - - for (unsigned int i = 0; i < 9; i++) { - //LOG(LOG_MISC|LOG_ERROR,"Part %d set to MIDI channel %d",i,mt32ram.params.system.chanAssign[i]); - if (mt32ram.params.system.chanAssign[i] == 16) { - parts[i]->allStop(); - } else { - chantable[(int)mt32ram.params.system.chanAssign[i]] = (char)i; - } - } - - printDebug(" Master Tune: %f", ((float)mt32ram.params.system.masterTune)*0.2+432.1); - printDebug(" Reverb: mode=%d, time=%d, level=%d", mt32ram.params.system.reverbMode, mt32ram.params.system.reverbTime, mt32ram.params.system.reverbLevel); - report(ReportType_newReverbMode, &mt32ram.params.system.reverbMode); - report(ReportType_newReverbTime, &mt32ram.params.system.reverbTime); - report(ReportType_newReverbLevel, &mt32ram.params.system.reverbLevel); - - if ((mt32ram.params.system.reverbMode != curRevMode) || (mt32ram.params.system.reverbTime != curRevTime)) { - if (myProp.useDefaultReverb) { - initReverb(mt32ram.params.system.reverbMode, mt32ram.params.system.reverbTime); - curRevLevel = mt32ram.params.system.reverbLevel; - } else { - initReverb(myProp.reverbType, myProp.reverbTime); - curRevLevel = myProp.reverbLevel; - } - } - - char *rset = mt32ram.params.system.reserveSettings; - 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]); - int pr = partialManager->SetReserve(rset); - if (pr != 32) - printDebug(" (Partial Reserve Table with less than 32 partials reserved!)"); - rset = mt32ram.params.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]); - printDebug(" Master volume: %d", mt32ram.params.system.masterVol); - mastervolume = (Bit16s)((float)mt32ram.params.system.masterVol * 327.0); + printDebug("WRITE-SYSTEM:"); + refreshSystem(); } else if (addr == MEMADDR(0x200000)) { char buf[MAX_SYSEX_SIZE]; if (len > MAX_SYSEX_SIZE - 1) { @@ -1131,73 +866,112 @@ void Synth::playSysexWithoutHeader(unsigned char device, Bit8u *sysex, Bit32u le } else if (addr >= MEMADDR(0x7f0000)) { printDebug("Reset"); report(ReportType_devReset, NULL); - partialManager->DeactivateAll(); + partialManager->deactivateAll(); mt32ram = mt32default; - for (int i = 0; i < 8; i++) { - parts[i]->refreshPatch(); + for (int i = 0; i < 9; i++) { + parts[i]->refresh(); } - parts[8]->refreshDrumCache(); isEnabled = false; } else { printDebug("Sysex write to unrecognised address %06x", SYSEXMEMADDR(addr)); } } -int Synth::dumpSysex(char *filename) { - File *file = openFile(filename, File::OpenMode_write); - if (file == NULL) - return -1; +bool Synth::refreshSystem() { + memset(chantable,-1,sizeof(chantable)); - int patchnum; - for (patchnum=0;patchnum<64;patchnum++) { - // Sysex header - if (!file->writeBit8u(0xF0)) - break; - if (!file->writeBit8u(0x41)) - break; - if (!file->writeBit8u(0x10)) - break; - if (!file->writeBit8u(0x16)) - break; - if (!file->writeBit8u(0x12)) - break; + for (unsigned int i = 0; i < 9; i++) { + //LOG(LOG_MISC|LOG_ERROR,"Part %d set to MIDI channel %d",i,mt32ram.system.chanAssign[i]); + if (mt32ram.system.chanAssign[i] == 16 && parts[i] != NULL) { + parts[i]->allStop(); + } else { + chantable[(int)mt32ram.system.chanAssign[i]] = (char)i; + } + } + //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 + masterTune = 440.0f * powf(2.0f, (mt32ram.system.masterTune - 64.0f) / (128.0f * 12.0f)); + printDebug(" Master Tune: %f", masterTune); + printDebug(" Reverb: mode=%d, time=%d, level=%d", mt32ram.system.reverbMode, mt32ram.system.reverbTime, mt32ram.system.reverbLevel); + report(ReportType_newReverbMode, &mt32ram.system.reverbMode); + report(ReportType_newReverbTime, &mt32ram.system.reverbTime); + report(ReportType_newReverbLevel, &mt32ram.system.reverbLevel); + + if (myProp.useDefaultReverb) { + initReverb(mt32ram.system.reverbMode, mt32ram.system.reverbTime, mt32ram.system.reverbLevel); + } else { + initReverb(myProp.reverbType, myProp.reverbTime, mt32ram.system.reverbLevel); + } - int useaddr = patchnum * 256; - char lsb = (char)(useaddr & 0x7f); - char isb = (char)((useaddr >> 7) & 0x7f); - char msb = (char)(((useaddr >> 14) & 0x7f) | 0x08); - //Address - if (!file->writeBit8u(msb)) - break; - if (!file->writeBit8u(isb)) - break; - if (!file->writeBit8u(lsb)) - break; + Bit8u *rset = mt32ram.system.reserveSettings; + 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]); + int pr = partialManager->setReserve(rset); + if (pr != 32) + printDebug(" (Partial Reserve Table with less than 32 partials reserved!)"); + 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]); + printDebug(" Master volume: %d", mt32ram.system.masterVol); + masterVolume = (Bit16u)(mt32ram.system.masterVol * 327); + if (!TableInitialiser::initMT32Tables(this, PCMList, (float)myProp.sampleRate, masterTune)) { + report(ReportType_errorSampleRate, NULL); + return false; + } + return true; +} - //Data - if (file->write(&mt32ram.params.timbres[patchnum + 128].timbre.common,0xE) != 0xE) - break; - if (file->write(&mt32ram.params.timbres[patchnum + 128].timbre.partial[0],0x3A) != 0x3A) - break; - if (file->write(&mt32ram.params.timbres[patchnum + 128].timbre.partial[1],0x3A) != 0x3A) - break; - if (file->write(&mt32ram.params.timbres[patchnum + 128].timbre.partial[2],0x3A) != 0x3A) - break; - if (file->write(&mt32ram.params.timbres[patchnum + 128].timbre.partial[3],0x3A) != 0x3A) - break; - //Checksum - unsigned char *dat = (unsigned char *)&mt32ram.params.timbres[patchnum + 128].timbre; - unsigned char checksum = calcSysexChecksum(dat, 246, msb + isb + lsb); +bool Synth::dumpTimbre(File *file, const TimbreParam *timbre, Bit32u address) { + // Sysex header + if (!file->writeBit8u(0xF0)) + return false; + if (!file->writeBit8u(0x41)) + return false; + if (!file->writeBit8u(0x10)) + return false; + if (!file->writeBit8u(0x16)) + return false; + if (!file->writeBit8u(0x12)) + return false; - if (!file->writeBit8u(checksum)) - break; + char lsb = (char)(address & 0x7f); + char isb = (char)((address >> 7) & 0x7f); + char msb = (char)(((address >> 14) & 0x7f) | 0x08); + + //Address + if (!file->writeBit8u(msb)) + return false; + if (!file->writeBit8u(isb)) + return false; + if (!file->writeBit8u(lsb)) + return false; + + //Data + if (file->write(timbre, 246) != 246) + return false; + + //Checksum + unsigned char checksum = calcSysexChecksum((unsigned char *)timbre, 246, msb + isb + lsb); + if (!file->writeBit8u(checksum)) + return false; + + //End of sysex + if (!file->writeBit8u(0xF7)) + return false; + return true; +} + +int Synth::dumpTimbres(const char *filename, int start, int len) { + File *file = openFile(filename, File::OpenMode_write); + if (file == NULL) + return -1; - //End of sysex - if (!file->writeBit8u(0xF7)) + for (int timbreNum = start; timbreNum < start + len; timbreNum++) { + int useaddr = (timbreNum - start) * 256; + TimbreParam *timbre = &mt32ram.timbres[timbreNum].timbre; + if (!dumpTimbre(file, timbre, useaddr)) break; } closeFile(file); - printDebug("Wrote temp patches to %s", filename); return 0; } @@ -1231,14 +1005,14 @@ void Synth::render(Bit16s *stream, Bit32u len) { void Synth::doRender(Bit16s * stream,Bit32u len) { Bit32u m; - partialManager->AgeAll(); + partialManager->ageAll(); if (myProp.useReverb) { bool hasOutput = false; for (unsigned int i = 0; i < MT32EMU_MAX_PARTIALS; i++) { if (partialManager->shouldReverb(i)) { - if (partialManager->ProduceOutput(i, &tmpBuffer[0], len)) { - ProduceOutput1(&tmpBuffer[0], stream, len, mastervolume); + if (partialManager->produceOutput(i, &tmpBuffer[0], len)) { + ProduceOutput1(&tmpBuffer[0], stream, len, masterVolume); hasOutput = true; } } @@ -1263,19 +1037,19 @@ void Synth::doRender(Bit16s * stream,Bit32u len) { } for (unsigned int i = 0; i < MT32EMU_MAX_PARTIALS; i++) { if (!partialManager->shouldReverb(i)) { - if (partialManager->ProduceOutput(i, &tmpBuffer[0], len)) { - ProduceOutput1(&tmpBuffer[0], stream, len, mastervolume); + if (partialManager->produceOutput(i, &tmpBuffer[0], len)) { + ProduceOutput1(&tmpBuffer[0], stream, len, masterVolume); } } } } else { for (unsigned int i = 0; i < MT32EMU_MAX_PARTIALS; i++) { - if (partialManager->ProduceOutput(i, &tmpBuffer[0], len)) - ProduceOutput1(&tmpBuffer[0], stream, len, mastervolume); + if (partialManager->produceOutput(i, &tmpBuffer[0], len)) + ProduceOutput1(&tmpBuffer[0], stream, len, masterVolume); } } - partialManager->ClearAlreadyOutputed(); + partialManager->clearAlreadyOutputed(); #if MT32EMU_MONITOR_PARTIALS == 1 samplepos += len; diff --git a/backends/midi/mt32/synth.h b/backends/midi/mt32/synth.h index dbd77c1dfd..c93ff01803 100644 --- a/backends/midi/mt32/synth.h +++ b/backends/midi/mt32/synth.h @@ -39,27 +39,27 @@ class PartialManager; class Part; enum ReportType { - // Files missing - ReportType_errorPreset1 = 1, - ReportType_errorPreset2 = 2, - ReportType_errorDrumpat = 3, - ReportType_errorPatchlog = 4, - ReportType_errorMT32ROM = 5, - ReportType_errorSampleRate = 6, + // Errors + ReportType_errorControlROM = 1, + ReportType_errorPCMROM, + ReportType_errorSampleRate, + + // Progress + ReportType_progressInit, // HW spec - ReportType_availableSSE = 7, - ReportType_available3DNow = 8, - ReportType_usingSSE = 9, - ReportType_using3DNow = 10, + ReportType_availableSSE, + ReportType_available3DNow, + ReportType_usingSSE, + ReportType_using3DNow, // General info - ReportType_lcdMessage = 11, - ReportType_devReset = 12, - ReportType_devReconfig = 13, - ReportType_newReverbMode = 14, - ReportType_newReverbTime = 15, - ReportType_newReverbLevel = 16 + ReportType_lcdMessage, + ReportType_devReset, + ReportType_devReconfig, + ReportType_newReverbMode, + ReportType_newReverbTime, + ReportType_newReverbLevel }; struct SynthProperties { @@ -84,7 +84,7 @@ struct SynthProperties { // This is used as the first argument to all callbacks void *userData; // Callback for reporting various errors and information. May be NULL - void (*report)(void *userData, ReportType type, const void *reportData); + int (*report)(void *userData, ReportType type, const void *reportData); // Callback for debug messages, in vprintf() format void (*printDebug)(void *userData, const char *fmt, va_list list); // Callback for providing an implementation of File, opened and ready for use @@ -105,19 +105,20 @@ typedef void (*recalcStatusCallback)(int percDone); bool RecalcWaveforms(char * baseDir, int sampRate, recalcStatusCallback callBack); typedef float (*iir_filter_type)(float input,float *hist1_ptr, float *coef_ptr, int revLevel); -extern iir_filter_type usefilter; class Synth { friend class Part; +friend class RhythmPart; friend class Partial; friend class TableInitialiser; private: bool isEnabled; - PCMWave PCM[54]; + iir_filter_type iirFilter; + PCMWaveEntry PCMList[128]; - Bit32s PCMLoopTable[54]; + Bit8u controlROMData[64 * 1024]; Bit16s romfile[PCMSIZE + GRAN]; Bit8s chantable[32]; @@ -125,15 +126,12 @@ private: static Bit32s samplepos = 0; #endif - MT32RAMFormat mt32ram, mt32default; + MemParams mt32ram, mt32default; revmodel *reverbModel; - Bit16s mastervolume; - - char curRevMode; - char curRevTime; - Bit32u curRevLevel; + float masterTune; + Bit16u masterVolume; unsigned char initmode; bool isOpen; @@ -150,25 +148,30 @@ private: SynthProperties myProp; bool loadPreset(const char *filename); - void initReverb(char newRevMode, char newRevTime); + void initReverb(Bit8u newRevMode, Bit8u newRevTime, Bit8u newRevLevel); void doRender(Bit16s * stream, Bit32u len); void playMsgOnPart(unsigned char part, unsigned char code, unsigned char note, unsigned char velocity); - void playSysexWithoutHeader(unsigned char channel, Bit8u *sysex, Bit32u len); - - bool loadDrums(const char *filename); - bool loadPCMToROMMap(const char *filename); - bool loadROM(const char *filename); - void dumpDrums(const char *filename); - // Save the system state to a sysex file specified by filename - int dumpSysex(char *filename); - + void playSysexWithoutHeader(unsigned char channel, const Bit8u *sysex, Bit32u len); + + bool loadControlROM(const char *filename); + bool loadPCMROM(const char *filename); + bool dumpTimbre(File *file, const TimbreParam *timbre, Bit32u addr); + int dumpTimbres(const char *filename, int start, int len); + + void initPCMList(); + void initRhythmTimbres(); + void initTimbres(Bit16u mapAddress, int startTimbre); + void initRhythmTimbre(int drumNum, const Bit8u *mem); + bool refreshSystem(); protected: - void report(ReportType type, const void *reportData); + int report(ReportType type, const void *reportData); File *openFile(const char *filename, File::OpenMode mode); void closeFile(File *file); void printDebug(const char *fmt, ...); public: + static Bit8u calcSysexChecksum(const Bit8u *data, Bit32u len, Bit8u checksum); + Synth(); ~Synth(); @@ -182,11 +185,10 @@ public: // Sends a 4-byte MIDI message to the MT-32 for immediate playback void playMsg(Bit32u msg); - static Bit8u calcSysexChecksum(Bit8u *data, Bit32u len, Bit8u checksum); // Sends a string of Sysex commands to the MT-32 for immediate interpretation // The length is in bytes - void playSysex(Bit8u *sysex, Bit32u len); - void playSysexWithoutFraming(Bit8u *sysex, Bit32u len); + void playSysex(const Bit8u *sysex, Bit32u len); + void playSysexWithoutFraming(const Bit8u *sysex, Bit32u len); // This callback routine is used to have the MT-32 generate samples to the specified // output stream. The length is in whole samples, not bytes. (I.E. in 16-bit stereo, diff --git a/backends/midi/mt32/tables.cpp b/backends/midi/mt32/tables.cpp index 2f710e8e0e..3d0ab5ea45 100644 --- a/backends/midi/mt32/tables.cpp +++ b/backends/midi/mt32/tables.cpp @@ -25,36 +25,14 @@ #include "mt32emu.h" +#define FIXEDPOINT_MAKE(x, point) ((Bit32u)((1 << point) * x)) + namespace MT32Emu { //Amplitude time velocity follow exponential coefficients const double tvcatconst[5] = {0.0, 0.002791309, 0.005942882, 0.012652792, 0.026938637}; const double tvcatmult[5] = {1.0, 1.072662811, 1.169129367, 1.288579123, 1.229630539}; -const Bit8s LoopPatterns[9][10] = { - { 2, 3, 4, 5, 6, 7, -1, -1, -1, -1}, - { 8, 9, 10, 11, 12, 13, 14, 15, 16, -1}, - {17, 18, 19, 20, 21, -1, -1, -1, -1, -1}, - {22, 23, 24, 25, 26, 27, 28, 29, -1, -1}, - {30, 31, 32, 33, 34, 35, 36, 37, -1, -1}, - {45, 46, 47, 48, 49, 50, 51, 52, 53, -1}, - {15, 11, 12, 13, 14, 15, 16, -1, -1, -1}, - {30, 35, 32, 33, 34, -1, -1, -1, -1, -1}, - { 2, 3, -1, -1, -1, -1, -1, -1, -1, -1}, -}; - -static const Bit32s LoopPatternTuning[9][10] = { - {0x1294A, 0x1294A, 0x1294A, 0x1294A, 0x1294A, 0x1294A, -1, -1, -1, -1}, - {0x1294A, 0x1294A, 0x1294A, 0x1294A, 0x1294A, 0x1294A, 0x1294A, 0x1294A, 0x1294A, -1}, - {0x1294A, 0x1294A, 0x1294A, 0x1294A, 0x1294A, -1, -1, -1, -1, -1}, - {0x1294A, 0x1294A, 0x1294A, 0x1294A, 0x1294A, 0x1294A, 0x1294A, 0x1294A, -1, -1}, - {0x1294A, 0x1294A, 0x1294A, 0x1294A, 0x1294A, 0x1294A, 0x1294A, 0x1294A, -1, -1}, - {0x1294A, 0x1294A, 0x1294A, 0x1294A, 0x1294A, 0x1294A, 0x1294A, 0x1294A, 0x1294A, -1}, - {0x2590B, 0x1294A, 0x1294A, 0x1294A, 0x1294A, 0x1294A, 0x1294A, -1, -1, -1}, - {0x1294A, 0x1294A, 0x1294A, 0x1294A, 0x1294A, -1, -1, -1, -1, -1}, - {0x1294A, 0x1294A, -1, -1, -1, -1, -1, -1, -1, -1}, -}; - // These are division constants for the TVF depth key follow static const Bit32u depexp[5] = {3000, 950, 485, 255, 138}; @@ -63,6 +41,7 @@ static const double tkcatconst[5] = {0.0, 0.005853144, 0.011148054, 0.019086143, static const double tkcatmult[5] = {1.0, 1.058245688, 1.048488989, 1.016049301, 1.097538067}; static float initialisedSampleRate = 0.0f; +static float initialisedMasterTune = 0.0f; Bit16s smallnoise[MAX_SAMPLE_OUTPUT]; @@ -215,7 +194,7 @@ static void initEnvelopes(float samplerate) { //lasttimetable[lf] = (int)((exp(logtime)/(312.12*6)) * (float)samplerate); float mv = (float)lf / 100.0f; - float pt = mv-0.5f; + float pt = mv - 0.5f; if (pt < 0) pt = 0; @@ -319,7 +298,7 @@ void TableInitialiser::initMT32ConstantTables(Synth *synth) { for (lf = 0; lf < 128; lf++) { // Converts MIDI velocity to volume. - voltable[lf] = (int)(127.0f * powf((float)lf / 127.0f, FLOAT_LN)); + voltable[lf] = FIXEDPOINT_MAKE(powf((float)lf / 127.0f, FLOAT_LN), 7); } for (unsigned int i = 0; i < MAX_SAMPLE_OUTPUT; i++) { int myRand; @@ -523,7 +502,7 @@ File *TableInitialiser::initWave(Synth *synth, NoteLookup *noteLookup, float amp #if 0 //FIXME:KG: Credit Timo Strunk (bastardo on #scummvm) for help with this! - float saw = 0.5f * FLOAT_PI - sa / 2; + double saw = 0.5 * DOUBLE_PI - sa / 2; #else double saw = 0.0; for (int sinus = 1; sinus < div; sinus++) { @@ -581,12 +560,15 @@ static void initNFiltTable(NoteLookup *noteLookup, float freq, float rate) { } } -File *TableInitialiser::initNote(Synth *synth, NoteLookup *noteLookup, float note, float rate, float tuning, PCMWave pcmWaves[54], File *file) { +File *TableInitialiser::initNote(Synth *synth, NoteLookup *noteLookup, float note, float rate, float masterTune, PCMWaveEntry pcmWaves[128], File *file) { float ampsize = WGAMP; - float freq = (float)(tuning * pow(2.0, ((double)note - MIDDLEA) / 12.0)); + float freq = (float)(masterTune * pow(2.0, ((double)note - MIDDLEA) / 12.0)); float div = rate / freq; noteLookup->div = (int)div; + if (noteLookup->div == 0) + noteLookup->div = 1; + initSaw(noteLookup, noteLookup->div); initDep(noteLookup, note); @@ -595,18 +577,10 @@ File *TableInitialiser::initNote(Synth *synth, NoteLookup *noteLookup, float not // Create the pitch tables - float rateMult = 32000.0f / rate; - float tuner = rateMult * freq * 65536.0f; - for (int pc = 0; pc < 54; pc++) { - noteLookup->wavTable[pc] = (int)(tuner / pcmWaves[pc].tune); - } - for (int lp = 0; lp < 9; lp++) { - for (int ln = 0; ln < 10; ln++) { - // FIXME:KG: Surely this needs to be adjusted for the rate? - // If not, remove rateMult * from below - // (Note: I'm assuming the LoopPatternTuning constants were intended for 32k rate) - noteLookup->loopTable[lp][ln] = (int)(rateMult * (float)LoopPatternTuning[lp][ln] * (freq / 220.0f)); - } + double rateMult = 32000.0 / rate; + double tuner = freq * 65536.0f; + for (int pc = 0; pc < 128; pc++) { + noteLookup->wavTable[pc] = (int)(tuner / pcmWaves[pc].tune * rateMult); } initFiltTable(noteLookup, freq, rate); @@ -614,11 +588,14 @@ File *TableInitialiser::initNote(Synth *synth, NoteLookup *noteLookup, float not return file; } -void TableInitialiser::initNotes(Synth *synth, PCMWave pcmWaves[54], float rate, float tuning) { +bool TableInitialiser::initNotes(Synth *synth, PCMWaveEntry pcmWaves[128], float rate, float masterTune) { + const char *NoteNames[12] = { + "C ", "C#", "D ", "D#", "E ", "F ", "F#", "G ", "G#", "A ", "A#", "B " + }; char filename[64]; int intRate = (int)rate; - char version[4] = {0, 0, 0, 1}; - sprintf(filename, "waveformcache-%d-%.1f.raw", intRate, tuning); + char version[4] = {0, 0, 0, 2}; + sprintf(filename, "waveformcache-%d-%.2f.raw", intRate, masterTune); File *file = NULL; char header[20]; @@ -631,11 +608,11 @@ void TableInitialiser::initNotes(Synth *synth, PCMWave pcmWaves[54], float rate, header[pos++] = (char)((intRate >> 16) & 0xFF); header[pos++] = (char)((intRate >> 8) & 0xFF); header[pos++] = (char)(intRate & 0xFF); - int intTuning = (int)tuning; + int intTuning = (int)masterTune; header[pos++] = (char)((intTuning >> 8) & 0xFF); header[pos++] = (char)(intTuning & 0xFF); header[pos++] = 0; - header[pos] = (char)((tuning - intTuning) * 10); + header[pos] = (char)((masterTune - intTuning) * 10); #if MT32EMU_WAVECACHEMODE < 2 bool reading = false; file = synth->openFile(filename, File::OpenMode_read); @@ -648,33 +625,37 @@ void TableInitialiser::initNotes(Synth *synth, PCMWave pcmWaves[54], float rate, if (endianCheck == 1) { reading = true; } else { - synth->printDebug("Endian check in %s does not match expected - will generate", filename); + synth->printDebug("Endian check in %s does not match expected", filename); } } else { - synth->printDebug("Unable to read endian check in %s - will generate", filename); + synth->printDebug("Unable to read endian check in %s", filename); } } else { - synth->printDebug("Header of %s does not match expected - will generate", filename); + synth->printDebug("Header of %s does not match expected", filename); } } else { - synth->printDebug("Error reading 16 bytes of %s - will generate", filename); + synth->printDebug("Error reading 16 bytes of %s", filename); } if (!reading) { file->close(); file = NULL; } } else { - synth->printDebug("Unable to open %s for reading - will generate", filename); + synth->printDebug("Unable to open %s for reading", filename); } #endif - //FIXME:KG: may only need to do 12 to 108 - //12..108 is the range allowed by note on commands, but the key can be modified by pitch keyfollow - //and adjustment for timbre pitch, so the results can be outside that range. Do move it (by octave) into - // the 12..108 range, or keep it in 0..127 range, or something else altogether? - for (int f = 12; f < 109; f++) { - NoteLookup *noteLookup = ¬eLookups[f]; - file = initNote(synth, noteLookup, (float)f, rate, tuning, pcmWaves, file); + float progress = 0.0f; + bool abort = false; + synth->report(ReportType_progressInit, &progress); + for (int f = LOWEST_NOTE; f <= HIGHEST_NOTE; f++) { + synth->printDebug("Initialising note %s%d", NoteNames[f % 12], (f / 12) - 1); + NoteLookup *noteLookup = ¬eLookups[f - LOWEST_NOTE]; + file = initNote(synth, noteLookup, (float)f, rate, masterTune, pcmWaves, file); + progress = (f - LOWEST_NOTE + 1) / (float)NUM_NOTES; + abort = synth->report(ReportType_progressInit, &progress) != 0; + if (abort) + break; } #if MT32EMU_WAVECACHEMODE == 0 || MT32EMU_WAVECACHEMODE == 2 @@ -682,7 +663,7 @@ void TableInitialiser::initNotes(Synth *synth, PCMWave pcmWaves[54], float rate, file = synth->openFile(filename, File::OpenMode_write); if (file != NULL) { if (file->write(header, 16) == 16 && file->writeBit16u(1)) { - for (int f = 12; f < 109 && file != NULL; f++) { + for (int f = 0; f < NUM_NOTES; f++) { for (int i = 0; i < 3 && file != NULL; i++) { int len = noteLookups[f].waveformSize[i]; for (int j = 0; j < len; j++) { @@ -706,21 +687,42 @@ void TableInitialiser::initNotes(Synth *synth, PCMWave pcmWaves[54], float rate, if (file != NULL) synth->closeFile(file); + return !abort; +} + +void TableInitialiser::freeNotes() { + for (int t = 0; t < 3; t++) { + for (int m = 0; m < NUM_NOTES; m++) { + if (noteLookups[m].waveforms[t] != NULL) { + delete[] noteLookups[m].waveforms[t]; + noteLookups[m].waveforms[t] = NULL; + noteLookups[m].waveformSize[t] = 0; + } + } + } + initialisedMasterTune = 0.0f; } -bool TableInitialiser::initMT32Tables(Synth *synth, PCMWave pcms[54], float sampleRate) { +bool TableInitialiser::initMT32Tables(Synth *synth, PCMWaveEntry pcmWaves[128], float sampleRate, float masterTune) { if (sampleRate <= 0.0f) { synth->printDebug("Bad sampleRate (%d <= 0.0f)", sampleRate); return false; } if (initialisedSampleRate == 0.0f) { initMT32ConstantTables(synth); + } + if (initialisedSampleRate != sampleRate) { initFiltCoeff(sampleRate); initEnvelopes(sampleRate); + } + if (initialisedSampleRate != sampleRate || initialisedMasterTune != masterTune) { + freeNotes(); + if (!initNotes(synth, pcmWaves, sampleRate, masterTune)) { + return false; + } initialisedSampleRate = sampleRate; + initialisedMasterTune = masterTune; } - // This always needs to be done, to allocate the waveforms - initNotes(synth, pcms, sampleRate, TUNING); return true; } diff --git a/backends/midi/mt32/tables.h b/backends/midi/mt32/tables.h index 46be3f1548..0c85796f4f 100644 --- a/backends/midi/mt32/tables.h +++ b/backends/midi/mt32/tables.h @@ -36,21 +36,19 @@ const int FILTERGRAN = 512; const int MIDDLEC = 60; const int MIDDLEA = 69; // By this I mean "A above middle C" -// Constant tuning for now. The manual claims "Standard pitch" is 442.0. -// I assume they mean this is the MT-32 default pitch, and not concert pitch, -// since the latter has been internationally defined as 440Hz for decades. -// FIXME:KG: Keeping it at 440.0f for now, as in original. Check with CC -const float TUNING = 440.0f; - -const int NUM_NOTES = 128; // Number of slots for note LUT (we actually only use 12..108) +//FIXME:KG: may only need to do 12 to 108 +//12..108 is the range allowed by note on commands, but the key can be modified by pitch keyfollow +//and adjustment for timbre pitch, so the results can be outside that range. Do move it (by octave) into +// the 12..108 range, or keep it in 0..127 range, or something else altogether? +const int LOWEST_NOTE = 12; +const int HIGHEST_NOTE = 127; +const int NUM_NOTES = HIGHEST_NOTE - LOWEST_NOTE + 1; // Number of slots for note LUT // Amplitude of waveform generator const int WGAMP = 7168; // 8192? class Synth; -extern const Bit8s LoopPatterns[9][10]; - extern Bit16s smallnoise[MAX_SAMPLE_OUTPUT]; // Some optimization stuff @@ -75,9 +73,8 @@ extern Bit32s voltable[128]; extern float ResonInv[31]; struct NoteLookup { - Bit32s div; - Bit32u wavTable[54]; - Bit32u loopTable[9][10]; + Bit32u div; + Bit32u wavTable[128]; Bit32s sawTable[101]; Bit32s fildepTable[5]; Bit32s timekeyTable[5]; @@ -92,10 +89,11 @@ extern NoteLookup noteLookups[NUM_NOTES]; class TableInitialiser { static void initMT32ConstantTables(Synth *synth); static File *initWave(Synth *synth, NoteLookup *noteLookup, float ampsize, float div, File *file); - static void initNotes(Synth *synth, PCMWave pcms[54], float rate, float tuning); + static bool initNotes(Synth *synth, PCMWaveEntry pcmWaves[128], float rate, float tuning); public: - static bool initMT32Tables(Synth *synth, PCMWave pcms[54], float sampleRate); - static File *initNote(Synth *synth, NoteLookup *noteLookup, float note, float rate, float tuning, PCMWave pcmWaves[54], File *file); + static bool initMT32Tables(Synth *synth, PCMWaveEntry pcmWaves[128], float sampleRate, float masterTune); + static File *initNote(Synth *synth, NoteLookup *noteLookup, float note, float rate, float tuning, PCMWaveEntry pcmWaves[128], File *file); + static void freeNotes(); }; } -- cgit v1.2.3