From 7f83c4786045bd438ff76cea2c6189a62774798b Mon Sep 17 00:00:00 2001 From: Jerome Fisher Date: Sun, 20 Mar 2005 16:24:55 +0000 Subject: Merged with latest Munt CVS. * Added support for most of the extended capabilities of the CM-32L/LAPC-I (more rhythm timbres, more rhythm key mappings, more PCM samples). * The control ROM is now identified by searching for matching ID strings at various locations in the file. There are now a lot of safety checks to help ensure that a bad ROM will not crash the emulator. * Three control ROM versions are now identified and mapped out - an original MT-32 control ROM, an original CM-32L ROM, and the Blue Ridge modified MT-32 ROM. * Now supports the expression controller properly. * Sustain is now turned off correctly again. * "All Notes Off" no longer stops notes held by the sustain controller. * Implemented "Reset all controllers". * Stereo pan positions are no longer inverted. * Volume, pitch, filter and envelopes are now more accurately calculated. Overall, the sound emulation is much more accurate. * Waveforms are now slightly more accurate (in terms of pitch), necessitating a regeneration. * Handling of unusual sysex messages has been improved (fixes sysex messages from Java's MIDI classes on Windows). * Fixed a reverb bug during periods of silence. svn-id: r17188 --- sound/softsynth/mt32/i386.cpp | 8 +- sound/softsynth/mt32/i386.h | 8 +- sound/softsynth/mt32/mt32emu.h | 2 +- sound/softsynth/mt32/part.cpp | 125 ++++--- sound/softsynth/mt32/part.h | 26 +- sound/softsynth/mt32/partial.cpp | 321 +++++++++-------- sound/softsynth/mt32/partial.h | 16 +- sound/softsynth/mt32/partialManager.cpp | 8 +- sound/softsynth/mt32/partialManager.h | 4 +- sound/softsynth/mt32/structures.h | 34 +- sound/softsynth/mt32/synth.cpp | 605 ++++++++++++++++++++------------ sound/softsynth/mt32/synth.h | 132 ++++++- sound/softsynth/mt32/tables.cpp | 311 ++++++++-------- sound/softsynth/mt32/tables.h | 101 +++--- 14 files changed, 1028 insertions(+), 673 deletions(-) (limited to 'sound') diff --git a/sound/softsynth/mt32/i386.cpp b/sound/softsynth/mt32/i386.cpp index e2e4b0f790..e45e8daab8 100644 --- a/sound/softsynth/mt32/i386.cpp +++ b/sound/softsynth/mt32/i386.cpp @@ -1,4 +1,4 @@ -/* Copyright (c) 2003-2004 Various contributors +/* Copyright (c) 2003-2005 Various contributors * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to @@ -465,7 +465,7 @@ NO_3DNOW: #endif } -float iir_filter_sse(float input,float *hist1_ptr, float *coef_ptr, int revLevel) { +float iir_filter_sse(float input,float *hist1_ptr, float *coef_ptr) { float output; // 1st number of coefficients array is overall input scale factor, or filter gain @@ -542,11 +542,10 @@ float iir_filter_sse(float input,float *hist1_ptr, float *coef_ptr, int revLevel #else output = atti386_iir_filter_sse(&output, hist1_ptr, coef_ptr); #endif - output *= ResonInv[revLevel]; return output; } -float iir_filter_3dnow(float input,float *hist1_ptr, float *coef_ptr, int revLevel) { +float iir_filter_3dnow(float input,float *hist1_ptr, float *coef_ptr) { float output; // 1st number of coefficients array is overall input scale factor, or filter gain @@ -624,7 +623,6 @@ float iir_filter_3dnow(float input,float *hist1_ptr, float *coef_ptr, int revLev #else output = atti386_iir_filter_3DNow(output, hist1_ptr, coef_ptr); #endif - output *= ResonInv[revLevel]; return output; } diff --git a/sound/softsynth/mt32/i386.h b/sound/softsynth/mt32/i386.h index c26fcb4110..e8644411cd 100644 --- a/sound/softsynth/mt32/i386.h +++ b/sound/softsynth/mt32/i386.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2003-2004 Various contributors +/* Copyright (c) 2003-2005 Various contributors * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to @@ -30,9 +30,9 @@ bool DetectSIMD(); // Function that detects the availablity of 3DNow instructions bool Detect3DNow(); -float iir_filter_sse(float input,float *hist1_ptr, float *coef_ptr, int revLevel); -float iir_filter_3dnow(float input,float *hist1_ptr, float *coef_ptr, int revLevel); -float iir_filter_normal(float input,float *hist1_ptr, float *coef_ptr, int revLevel); +float iir_filter_sse(float input,float *hist1_ptr, float *coef_ptr); +float iir_filter_3dnow(float input,float *hist1_ptr, float *coef_ptr); +float iir_filter_normal(float input,float *hist1_ptr, float *coef_ptr); #if MT32EMU_USE_MMX > 0 int i386_partialProductOutput(int len, Bit16s leftvol, Bit16s rightvol, Bit16s *partialBuf, Bit16s *mixedBuf); diff --git a/sound/softsynth/mt32/mt32emu.h b/sound/softsynth/mt32/mt32emu.h index 9fffa721f5..0aa4df7488 100644 --- a/sound/softsynth/mt32/mt32emu.h +++ b/sound/softsynth/mt32/mt32emu.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2003-2004 Various contributors +/* Copyright (c) 2003-2005 Various contributors * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to diff --git a/sound/softsynth/mt32/part.cpp b/sound/softsynth/mt32/part.cpp index 5aee1202b2..5c4c11fa27 100644 --- a/sound/softsynth/mt32/part.cpp +++ b/sound/softsynth/mt32/part.cpp @@ -1,4 +1,4 @@ -/* Copyright (c) 2003-2004 Various contributors +/* Copyright (c) 2003-2005 Various contributors * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to @@ -66,18 +66,18 @@ Part::Part(Synth *useSynth, unsigned int usePartNum) { this->partNum = usePartNum; patchCache[0].dirty = true; holdpedal = false; + patchTemp = &synth->mt32ram.patchSettings[partNum]; if (usePartNum == 8) { // Nasty hack for rhythm - patchTemp = NULL; timbreTemp = NULL; } else { sprintf(name, "Part %d", partNum + 1); - patchTemp = &synth->mt32ram.patchSettings[partNum]; timbreTemp = &synth->mt32ram.timbreSettings[partNum]; } currentInstr[0] = 0; currentInstr[10] = 0; - volume = voltable[102]; //FIXME:KG: Original was just volume=102; I assume this is intended + expression = 127; + volumeMult = 0; volumesetting.leftvol = 32767; volumesetting.rightvol = 32767; bend = 0.0f; @@ -86,9 +86,12 @@ Part::Part(Synth *useSynth, unsigned int usePartNum) { } void Part::setHoldPedal(bool pedalval) { - if (holdpedal && !pedalval) + if (holdpedal && !pedalval) { + holdpedal = false; stopPedalHold(); - holdpedal = pedalval; + } else { + holdpedal = pedalval; + } } void RhythmPart::setBend(unsigned int midiBend) { @@ -130,20 +133,20 @@ void Part::setModulation(unsigned int midiModulation) { } void RhythmPart::refresh() { + updateVolume(); // (Re-)cache all the mapped timbres ahead of time - for (unsigned int drumNum = 0; drumNum < 64; drumNum++) { + for (unsigned int drumNum = 0; drumNum < synth->controlROMMap->rhythmSettingsCount; drumNum++) { int drumTimbreNum = rhythmTemp[drumNum].timbre; - if (drumTimbreNum >= 94) + if (drumTimbreNum >= 127) // 94 on MT-32 continue; 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[drumNum].leftvol = 32767; - drumPan[drumNum].rightvol = pan * 4681; - } else { + drumPan[drumNum].leftvol = pan * 4681; drumPan[drumNum].rightvol = 32767; - drumPan[drumNum].leftvol = (14 - pan) * 4681; + } else { + drumPan[drumNum].rightvol = (14 - pan) * 4681; + drumPan[drumNum].leftvol = 32767; } PatchCache *cache = drumCache[drumNum]; backupCacheToPartials(cache); @@ -159,6 +162,7 @@ void RhythmPart::refresh() { } void Part::refresh() { + updateVolume(); backupCacheToPartials(patchCache); for (int t = 0; t < 4; t++) { // Common parameters, stored redundantly @@ -171,8 +175,12 @@ void Part::refresh() { memcpy(currentInstr, timbreTemp->common.name, 10); } +const char *Part::getCurrentInstr() const { + return ¤tInstr[0]; +} + void RhythmPart::refreshTimbre(unsigned int absTimbreNum) { - for (int m = 0; m < 64; m++) { + for (int m = 0; m < 85; m++) { if (rhythmTemp[m].timbre == absTimbreNum - 128) drumCache[m][0].dirty = true; } @@ -225,12 +233,16 @@ void Part::setPatch(const PatchParam *patch) { patchTemp->patch = *patch; } +void RhythmPart::setTimbre(TimbreParam *timbre) { + synth->printDebug("%s: Attempted to call setTimbre() - doesn't make sense for rhythm", name); +} + void Part::setTimbre(TimbreParam *timbre) { *timbreTemp = *timbre; } unsigned int RhythmPart::getAbsTimbreNum() const { - synth->printDebug("%s: Attempted to call getAbsTimbreNum() - doesn't make sense for rhythm"); + synth->printDebug("%s: Attempted to call getAbsTimbreNum() - doesn't make sense for rhythm", name); return 0; } @@ -245,16 +257,10 @@ void RhythmPart::setProgram(unsigned int 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); - } -#endif refresh(); - allStop(); //FIXME:KG: Is this correct? + allSoundOff(); //FIXME:KG: Is this correct? } void Part::backupCacheToPartials(PatchCache cache[4]) { @@ -336,29 +342,16 @@ void Part::cacheTimbre(PatchCache cache[4], const TimbreParam *timbre) { // Calculate and cache TVA envelope stuff cache[t].ampEnv = timbre->partial[t].tva; - for (int i = 0; i < 4; i++) - 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; - cache[t].ampEnv.velosens = (char)velo; - if (tvelo<0.5f) - cache[t].ampenvdir = 1; - else - cache[t].ampenvdir = 0; 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 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); @@ -367,7 +360,7 @@ void Part::cacheTimbre(PatchCache cache[4], const TimbreParam *timbre) { // Calculate and cache LFO stuff cache[t].lfodepth = timbre->partial[t].lfo.depth; - cache[t].lfoperiod = lfotable[(int)timbre->partial[t].lfo.rate]; + cache[t].lfoperiod = synth->tables.lfoPeriod[(int)timbre->partial[t].lfo.rate]; cache[t].lforate = timbre->partial[t].lfo.rate; cache[t].modsense = timbre->partial[t].lfo.modsense; } @@ -391,8 +384,25 @@ const char *Part::getName() const { return name; } -void Part::setVolume(int vol) { - volume = voltable[vol]; +void Part::updateVolume() { + volumeMult = synth->tables.volumeMult[patchTemp->outlevel * expression / 127]; +} + +int Part::getVolume() const { + // FIXME: Use the mappings for this in the control ROM + return patchTemp->outlevel * 127 / 100; +} + +void Part::setVolume(int midiVolume) { + // FIXME: Use the mappings for this in the control ROM + patchTemp->outlevel = (Bit8u)(midiVolume * 100 / 127); + updateVolume(); + synth->printDebug("%s (%s): Set volume to %d", name, currentInstr, midiVolume); +} + +void Part::setExpression(int midiExpression) { + expression = midiExpression; + updateVolume(); } void RhythmPart::setPan(unsigned int midiPan) @@ -404,27 +414,30 @@ void RhythmPart::setPan(unsigned int 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) + // Check against the real thing + // NOTE: Panning is inverted compared to GM. if (midiPan < 64) { - volumesetting.leftvol = 32767; - volumesetting.rightvol = (Bit16s)(midiPan * 512); + volumesetting.leftvol = (Bit16s)(midiPan * 512); + volumesetting.rightvol = 32767; } else if (midiPan == 64) { volumesetting.leftvol = 32767; volumesetting.rightvol = 32767; } else { - volumesetting.rightvol = 32767; - volumesetting.leftvol = (Bit16s)((127 - midiPan) * 520); + volumesetting.rightvol = (Bit16s)((127 - midiPan) * 520); + volumesetting.leftvol = 32767; } + patchTemp->panpot = (Bit8u)(midiPan * 14 / 127); //synth->printDebug("%s (%s): Set pan to %d", name, currentInstr, panpot); } void RhythmPart::playNote(unsigned int key, int vel) { - if (key < 24 || key > 87) { + if (key < 24 || key > 108)/*> 87 on MT-32)*/ { synth->printDebug("%s: Attempted to play invalid key %d", name, key); return; } int drumNum = key - 24; int drumTimbreNum = rhythmTemp[drumNum].timbre; - if (drumTimbreNum >= 94) { + if (drumTimbreNum >= 127) { // 94 on MT-32 synth->printDebug("%s: Attempted to play unmapped key %d", name, key); return; } @@ -432,7 +445,7 @@ void RhythmPart::playNote(unsigned int key, int vel) { 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); + synth->printDebug("%s (%s): starting poly (drum %d, timbre %d) - Vel %d Key %d", name, currentInstr, drumNum, absTimbreNum, vel, key); #endif if (drumCache[drumNum][0].dirty) { cacheTimbre(drumCache[drumNum], timbre); @@ -462,7 +475,7 @@ void Part::playNote(unsigned int key, int vel) { } } #if MT32EMU_MONITOR_INSTRUMENTS == 1 - synth->printDebug("%s (%s): starting poly - Vel %d Key %d Vol %d", name, currentInstr, vel, key, volume); + synth->printDebug("%s (%s): starting poly - Vel %d Key %d", name, currentInstr, vel, key); #endif if (patchCache[0].dirty) { cacheTimbre(patchCache, timbreTemp); @@ -515,7 +528,7 @@ void Part::playPoly(const PatchCache cache[4], unsigned int key, int freqNum, in synth->printDebug("%s (%s): No partials to play for this instrument", name, this->currentInstr); tpoly->sustain = cache[0].sustain; - tpoly->volumeptr = &volume; + tpoly->volumeptr = &volumeMult; for (int x = 0; x < 4; x++) { if (tpoly->partials[x] != NULL) { @@ -540,7 +553,23 @@ static void startDecayPoly(dpoly *tpoly) { tpoly->isPlaying = false; } -void Part::allStop() { +void Part::allNotesOff() { + // Note: Unchecked on real MT-32, but the MIDI specification states that all notes off (0x7B) + // should treat the hold pedal as usual. + // All *sound* off (0x78) should stop notes immediately regardless of the hold pedal. + // The latter controller is not implemented on the MT-32 (according to the docs). + for (int q = 0; q < MT32EMU_MAX_POLY; q++) { + dpoly *tpoly = &polyTable[q]; + if (tpoly->isPlaying) { + if (holdpedal) + tpoly->pedalhold = true; + else if (tpoly->sustain) + startDecayPoly(tpoly); + } + } +} + +void Part::allSoundOff() { for (int q = 0; q < MT32EMU_MAX_POLY; q++) { dpoly *tpoly = &polyTable[q]; if (tpoly->isPlaying) { diff --git a/sound/softsynth/mt32/part.h b/sound/softsynth/mt32/part.h index 1214ec52f9..1a32818ec0 100644 --- a/sound/softsynth/mt32/part.h +++ b/sound/softsynth/mt32/part.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2003-2004 Various contributors +/* Copyright (c) 2003-2005 Various contributors * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to @@ -57,7 +57,10 @@ protected: Synth *synth; char name[8]; // "Part 1".."Part 8", "Rhythm" char currentInstr[11]; - Bit32u volume; + int expression; + Bit32u volumeMult; + + void updateVolume(); 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); @@ -67,18 +70,22 @@ public: 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 allNotesOff(); + void allSoundOff(); + int getVolume() const; + void setVolume(int midiVolume); + void setExpression(int midiExpression); virtual void setPan(unsigned int midiPan); virtual void setBend(unsigned int midiBend); virtual void setModulation(unsigned int midiModulation); - virtual void setProgram(unsigned int patchNum); + virtual void setProgram(unsigned int midiProgram); void setHoldPedal(bool pedalval); void stopPedalHold(); virtual void refresh(); virtual void refreshTimbre(unsigned int absTimbreNum); - void setTimbre(TimbreParam *timbre); + virtual void setTimbre(TimbreParam *timbre); virtual unsigned int getAbsTimbreNum() const; + const char *getCurrentInstr() const; }; class RhythmPart: public Part { @@ -86,12 +93,13 @@ class RhythmPart: public Part { const MemParams::RhythmTemp *rhythmTemp; // This caches the timbres/settings in use by the rhythm part - PatchCache drumCache[64][4]; - StereoVolume drumPan[64]; + PatchCache drumCache[85][4]; + StereoVolume drumPan[85]; public: RhythmPart(Synth *synth, unsigned int usePartNum); - void refreshTimbre(unsigned int timbreNum); void refresh(); + void refreshTimbre(unsigned int timbreNum); + void setTimbre(TimbreParam *timbre); void playNote(unsigned int key, int vel); unsigned int getAbsTimbreNum() const; void setPan(unsigned int midiPan); diff --git a/sound/softsynth/mt32/partial.cpp b/sound/softsynth/mt32/partial.cpp index 32ff9e9af7..4cbe4e015c 100644 --- a/sound/softsynth/mt32/partial.cpp +++ b/sound/softsynth/mt32/partial.cpp @@ -1,4 +1,4 @@ -/* Copyright (c) 2003-2004 Various contributors +/* Copyright (c) 2003-2005 Various contributors * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to @@ -50,10 +50,11 @@ Partial::~Partial() { for (int i = 0; i < 3; i++) { delete[] noteLookupStorage.waveforms[i]; } + delete[] noteLookupStorage.wavTable; #endif } -int Partial::getOwnerPart() { +int Partial::getOwnerPart() const { return ownerPart; } @@ -61,6 +62,10 @@ bool Partial::isActive() { return ownerPart > -1; } +const dpoly *Partial::getDpoly() const { + return this->poly; +} + void Partial::activate(int part) { // This just marks the partial as being assigned to a part ownerPart = part; @@ -119,10 +124,18 @@ void Partial::initKeyFollow(int key) { int keyfollow = ((key - MIDDLEC) * patchCache->filtkeyfollow) / 4096; if (keyfollow > 108) keyfollow = 108; - if (keyfollow < -108) + else if (keyfollow < -108) keyfollow = -108; - filtVal = keytable[keyfollow + 108]; - realVal = keytable[(key - MIDDLEC) + 108]; + filtVal = synth->tables.tvfKeyfollowMult[keyfollow + 108]; + realVal = synth->tables.tvfKeyfollowMult[(noteVal - MIDDLEC) + 108]; +} + +int Partial::getKey() const { + if (poly == NULL) { + return -1; + } else { + return poly->key; + } } void Partial::startPartial(dpoly *usePoly, const PatchCache *useCache, Partial *pairPartial) { @@ -138,13 +151,27 @@ void Partial::startPartial(dpoly *usePoly, const PatchCache *useCache, Partial * play = true; initKeyFollow(poly->freqnum); // Initialises noteVal, filtVal and realVal #if MT32EMU_ACCURATENOTES == 0 - noteLookup = ¬eLookups[noteVal - LOWEST_NOTE]; + noteLookup = &synth->tables.noteLookups[noteVal - LOWEST_NOTE]; #else - TableInitialiser::initNote(synth, ¬eLookupStorage, noteVal, (float)synth->myProp.sampleRate, synth->masterTune, synth->PCMList, NULL); + Tables::initNote(synth, ¬eLookupStorage, noteVal, (float)synth->myProp.sampleRate, synth->masterTune, synth->pcmWaves, NULL); #endif + keyLookup = &synth->tables.keyLookups[poly->freqnum - 12]; + + if (patchCache->PCMPartial) { + pcmNum = patchCache->pcm; + if (synth->controlROMMap->pcmCount > 128) { + // CM-32L, etc. support two "banks" of PCMs, selectable by waveform type parameter. + if (patchCache->waveform > 1) { + pcmNum += 128; + } + } + pcmWave = &synth->pcmWaves[pcmNum]; + } else { + pcmWave = NULL; + } lfoPos = 0; - pulsewidth = patchCache->pulsewidth + pwveltable[patchCache->pwsens][poly->vel]; + pulsewidth = patchCache->pulsewidth + synth->tables.pwVelfollowAdd[patchCache->pwsens][poly->vel]; if (pulsewidth > 100) { pulsewidth = 100; } else if (pulsewidth < 0) { @@ -189,6 +216,7 @@ Bit16s *Partial::generateSamples(long length) { // Generate samples Bit16s *partialBuf = &myBuffer[0]; + Bit32u volume = *poly->volumeptr; while (length--) { Bit32s envval; Bit32s sample = 0; @@ -199,24 +227,19 @@ Bit16s *Partial::generateSamples(long length) { deactivate(); break; } - if (ampval > 127) { - ampval = 127; + if (ampval > 100) { + ampval = 100; } - ampval = voltable[ampval]; - int tmpvel; - if (patchCache->ampenvdir == 1) - tmpvel = 127 - poly->vel; - else - tmpvel = poly->vel; - ampval = (ampval * ampveltable[tmpvel][(int)patchCache->ampEnv.velosens]) >> 8; + ampval = synth->tables.volumeMult[ampval]; + ampval = FIXEDPOINT_UMULT(ampval, synth->tables.tvaVelfollowMult[poly->vel][(int)patchCache->ampEnv.velosens], 8); //if (envs[EnvelopeType_amp].sustaining) ampEnvVal = ampval; } --envs[EnvelopeType_amp].count; } - int lfoat = 0x1000; + unsigned int lfoShift = 0x1000; if (pitchSustain) { // Calculate LFO position // LFO does not kick in completely until pitch envelope sustains @@ -225,30 +248,24 @@ Bit16s *Partial::generateSamples(long length) { if (lfoPos >= patchCache->lfoperiod) lfoPos = 0; int lfoatm = FIXEDPOINT_UDIV(lfoPos, patchCache->lfoperiod, 16); - int lfoatr = sintable[lfoatm]; - lfoat = lfoptable[patchCache->lfodepth][lfoatr]; + int lfoatr = synth->tables.sintable[lfoatm]; + lfoShift = synth->tables.lfoShift[patchCache->lfodepth][lfoatr]; } } else { // Calculate Pitch envelope envval = getPitchEnvelope(); int pd = patchCache->pitchEnv.depth; - pitchEnvVal = penvtable[pd][envval]; + pitchEnvVal = synth->tables.pitchEnvVal[pd][envval]; } int delta; - // These two are only for PCM partials, obviously - PCMWaveEntry *pcmWave = NULL; // Initialise to please compiler - Bit32u pcmAddr = 0; // Initialise to please compiler // Wrap positions or end if necessary if (patchCache->PCMPartial) { // PCM partial - int len; - pcmWave = &synth->PCMList[patchCache->pcm]; - delta = noteLookup->wavTable[patchCache->pcm]; - pcmAddr = pcmWave->addr; - len = pcmWave->len; + delta = noteLookup->wavTable[pcmNum]; + int len = pcmWave->len; if (partialOff.pcmplace >= len) { if (pcmWave->loop) { //partialOff.pcmplace = partialOff.pcmoffset = 0; @@ -261,21 +278,20 @@ Bit16s *Partial::generateSamples(long length) { } } else { // Synthesis partial - delta = 0x10707; - partialOff.pcmplace %= (Bit16u)(noteLookup->div << 1); + delta = 0x10000; + partialOff.pcmplace %= (Bit16u)noteLookup->div2; } // Build delta for position of next sample // Fix delta code - Bit64u tdelta = (Bit64u)delta; + Bit32u tdelta = delta; #if MT32EMU_ACCURATENOTES == 0 - tdelta = (tdelta * fineShift) >> 12; + tdelta = FIXEDPOINT_UMULT(tdelta, fineShift, 12); #endif - tdelta = (tdelta * pitchEnvVal) >> 12; - tdelta = (tdelta * lfoat) >> 12; - tdelta = (tdelta * bendShift) >> 12; + tdelta = FIXEDPOINT_UMULT(tdelta, pitchEnvVal, 12); + tdelta = FIXEDPOINT_UMULT(tdelta, lfoShift, 12); + tdelta = FIXEDPOINT_UMULT(tdelta, bendShift, 12); delta = (int)tdelta; - Bit32u volume = *poly->volumeptr; // Get waveform - either PCM or synthesized sawtooth or square if (ampEnvVal > 0) { @@ -283,20 +299,21 @@ Bit16s *Partial::generateSamples(long length) { // Render PCM sample int ra, rb, dist; Bit32u taddr; + Bit32u pcmAddr = pcmWave->addr; if (delta < 0x10000) { // Linear sound interpolation taddr = pcmAddr + partialOff.pcmplace; - ra = synth->romfile[taddr]; + ra = synth->pcmROMData[taddr]; taddr++; if (taddr == pcmAddr + pcmWave->len) { // Past end of PCM if (pcmWave->loop) { - rb = synth->romfile[pcmAddr]; + rb = synth->pcmROMData[pcmAddr]; } else { rb = 0; } } else { - rb = synth->romfile[taddr]; + rb = synth->pcmROMData[taddr]; } dist = rb - ra; sample = (ra + ((dist * (Bit32s)(partialOff.pcmoffset >> 8)) >> 8)); @@ -306,7 +323,7 @@ Bit16s *Partial::generateSamples(long length) { // a point. This is too slow. The following approximates this as fast as possible int idelta = delta >> 16; taddr = pcmAddr + partialOff.pcmplace; - ra = synth->romfile[taddr++]; + ra = synth->pcmROMData[taddr++]; for (int ix = 0; ix < idelta - 1; ix++) { if (taddr == pcmAddr + pcmWave->len) { // Past end of PCM @@ -317,38 +334,31 @@ Bit16s *Partial::generateSamples(long length) { break; } } - ra += synth->romfile[taddr++]; + ra += synth->pcmROMData[taddr++]; } sample = ra / idelta; } } else { // Render synthesised sample - Bit32u div = noteLookup->div; - int wf = patchCache->waveform; int toff = partialOff.pcmplace; int minorplace = partialOff.pcmoffset >> 14; - + Bit32s filterInput; Bit32s filtval = getFiltEnvelope(); //synth->printDebug("Filtval: %d", filtval); - if (wf==0) { + if ((patchCache->waveform & 1) == 0) { // Square waveform. Made by combining two pregenerated bandlimited // sawtooth waveforms - // Pulse width is not yet correct - if (div == 0) { - synth->printDebug("ERROR: div=0 generating square wave, this should never happen!"); - div = 1; - } - Bit32u ofsA = toff % div; - Bit32u ofsB = toff + FIXEDPOINT_UMULT(div, pulsetable[pulsewidth], 8); - ofsB = ofsB % div; - Bit16s pa = noteLookup->waveforms[0][(ofsA << 2) + minorplace]; - Bit16s pb = noteLookup->waveforms[0][(ofsB << 2) + minorplace]; - sample = (pa - pb) * 4; + Bit32u ofsA = ((toff << 2) + minorplace) % noteLookup->waveformSize[0]; + int width = FIXEDPOINT_UMULT(noteLookup->div2, synth->tables.pwFactor[pulsewidth], 7); + Bit32u ofsB = (ofsA + width) % noteLookup->waveformSize[0]; + Bit16s pa = noteLookup->waveforms[0][ofsA]; + Bit16s pb = noteLookup->waveforms[0][ofsB]; + filterInput = pa - pb; // Non-bandlimited squarewave /* - ofs = ((div << 1) * pulsetable[patchCache->pulsewidth]) >> 8; + ofs = FIXEDPOINT_UMULT(noteLookup->div2, synth->tables.pwFactor[patchCache->pulsewidth], 8); if (toff < ofs) sample = 1 * WGAMP; else @@ -360,32 +370,38 @@ Bit16s *Partial::generateSamples(long length) { // square wave and multiplies it by a full cosine int waveoff = (toff << 2) + minorplace; if (toff < noteLookup->sawTable[pulsewidth]) - sample = noteLookup->waveforms[1][waveoff % noteLookup->waveformSize[1]]; + filterInput = noteLookup->waveforms[1][waveoff % noteLookup->waveformSize[1]]; else - sample = noteLookup->waveforms[2][waveoff % noteLookup->waveformSize[2]]; - sample = sample * 4; + filterInput = noteLookup->waveforms[2][waveoff % noteLookup->waveformSize[2]]; // This is the correct way // Seems slow to me (though bandlimited) -- doesn't seem to // sound any better though /* //int pw = (patchCache->pulsewidth * pulsemod[filtval]) >> 8; - Bit32u ofs = toff % div; + Bit32u ofs = toff % (noteLookup->div2 >> 1); - Bit32u ofs3 = toff + FIXEDPOINT_UMULT(div, pulsetable[patchCache->pulsewidth], 8); - ofs3 = ofs3 % div; + Bit32u ofs3 = toff + FIXEDPOINT_UMULT(noteLookup->div2, synth->tables.pwFactor[patchCache->pulsewidth], 9); + ofs3 = ofs3 % (noteLookup->div2 >> 1); pa = noteLookup->waveforms[0][ofs]; pb = noteLookup->waveforms[0][ofs3]; - sample = ((pa - pb) * noteLookup->waveforms[2][toff]) / WGAMP; - sample = sample *4; + sample = ((pa - pb) * noteLookup->waveforms[2][toff]) / 2; */ } //Very exact filter if (filtval > ((FILTERGRAN * 15) / 16)) filtval = ((FILTERGRAN * 15) / 16); - sample = (Bit32s)floor((synth->iirFilter)((float)sample, &history[0], filtcoeff[filtval][(int)patchCache->filtEnv.resonance], patchCache->filtEnv.resonance)); + sample = (Bit32s)(floorf((synth->iirFilter)((float)filterInput, &history[0], synth->tables.filtCoeff[filtval][(int)patchCache->filtEnv.resonance])) / synth->tables.resonanceFactor[patchCache->filtEnv.resonance]); + if (sample < -32768) { + synth->printDebug("Overdriven amplitude for %d: %d:=%d < -32768", patchCache->waveform, filterInput, sample); + sample = -32768; + } + else if (sample > 32767) { + synth->printDebug("Overdriven amplitude for %d: %d:=%d > 32767", patchCache->waveform, filterInput, sample); + sample = 32767; + } } } @@ -553,7 +569,7 @@ bool Partial::produceOutput(Bit16s *partialBuf, long length) { } } else if (useNoisePair) { // Generate noise for pairless ring mix - pairBuf = smallnoise; + pairBuf = synth->tables.noiseBuf; } Bit16s *myBuf = generateSamples(length); @@ -561,7 +577,7 @@ bool Partial::produceOutput(Bit16s *partialBuf, long length) { if (myBuf == NULL && pairBuf == NULL) return false; - Bit16s * p1buf, * p2buf; + Bit16s *p1buf, *p2buf; if (structurePosition == 0 || pairBuf == NULL) { p1buf = myBuf; @@ -574,31 +590,31 @@ bool Partial::produceOutput(Bit16s *partialBuf, long length) { //synth->printDebug("mixType: %d", mixType); Bit16s *mixedBuf; - switch(mixType) { - case 0: - // Standard sound mix - mixedBuf = mixBuffers(p1buf, p2buf, length); - break; + switch (mixType) { + case 0: + // Standard sound mix + mixedBuf = mixBuffers(p1buf, p2buf, length); + break; - case 1: - // Ring modulation with sound mix - mixedBuf = mixBuffersRingMix(p1buf, p2buf, length); - break; + case 1: + // Ring modulation with sound mix + mixedBuf = mixBuffersRingMix(p1buf, p2buf, length); + break; - case 2: - // Ring modulation alone - mixedBuf = mixBuffersRing(p1buf, p2buf, length); - break; + case 2: + // Ring modulation alone + mixedBuf = mixBuffersRing(p1buf, p2buf, length); + break; - case 3: - // Stereo mixing. One partial to one speaker channel, one to another. - // FIXME:KG: Surely we should be multiplying by the left/right volumes here? - mixBuffersStereo(p1buf, p2buf, partialBuf, length); - return true; + case 3: + // Stereo mixing. One partial to one speaker channel, one to another. + // FIXME:KG: Surely we should be multiplying by the left/right volumes here? + mixBuffersStereo(p1buf, p2buf, partialBuf, length); + return true; - default: - mixedBuf = mixBuffers(p1buf, p2buf, length); - break; + default: + mixedBuf = mixBuffers(p1buf, p2buf, length); + break; } if (mixedBuf == NULL) @@ -626,13 +642,10 @@ bool Partial::produceOutput(Bit16s *partialBuf, long length) { Bit32s Partial::getFiltEnvelope() { int reshigh; - int cutoff,depth,keyfollow, realfollow; + int cutoff, depth; EnvelopeStatus *tStat = &envs[EnvelopeType_filt]; - keyfollow = filtVal; - realfollow = realVal; - if (tStat->decaying) { reshigh = tStat->envbase; reshigh = (reshigh + ((tStat->envdist * tStat->envpos) / tStat->envsize)); @@ -653,9 +666,17 @@ Bit32s Partial::getFiltEnvelope() { tStat->envstat++; tStat->envpos = 0; if (tStat->envstat == 3) { - tStat->envsize = lasttimetable[(int)patchCache->filtEnv.envtime[tStat->envstat]]; + tStat->envsize = synth->tables.envTime[(int)patchCache->filtEnv.envtime[tStat->envstat]]; } else { - tStat->envsize = (envtimetable[(int)patchCache->filtEnv.envtime[tStat->envstat]] * noteLookup->timekeyTable[(int)patchCache->filtEnv.envtkf]) >> 8; + Bit32u envTime = (int)patchCache->filtEnv.envtime[tStat->envstat]; + if(tStat->envstat > 1) { + int envDiff = abs(patchCache->filtEnv.envlevel[tStat->envstat] - patchCache->filtEnv.envlevel[tStat->envstat - 1]); + if(envTime > synth->tables.envDeltaMaxTime[envDiff]) { + envTime = synth->tables.envDeltaMaxTime[envDiff]; + } + } + + tStat->envsize = (synth->tables.envTime[envTime] * keyLookup->envTimeMult[(int)patchCache->filtEnv.envtkf]) >> 8; } tStat->envsize++; @@ -676,48 +697,52 @@ Bit32s Partial::getFiltEnvelope() { depth = patchCache->filtEnv.envdepth; //int sensedep = (depth * 127-patchCache->filtEnv.envsense) >> 7; - depth = (depth * filveltable[poly->vel][(int)patchCache->filtEnv.envsense]) >> 8; + depth = FIXEDPOINT_UMULT(depth, synth->tables.tvfVelfollowMult[poly->vel][(int)patchCache->filtEnv.envsense], 8); int bias = patchCache->tvfbias; int dist; - if (bias!=0) { + 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 (noteVal < bias) { dist = bias - noteVal; - cutoff = (cutoff * fbiastable[patchCache->tvfblevel][dist]) >> 8; + cutoff = FIXEDPOINT_UMULT(cutoff, synth->tables.tvfBiasMult[patchCache->tvfblevel][dist], 8); } } else { // > Bias if (noteVal > bias) { dist = noteVal - bias; - cutoff = (cutoff * fbiastable[patchCache->tvfblevel][dist]) >> 8; + cutoff = FIXEDPOINT_UMULT(cutoff, synth->tables.tvfBiasMult[patchCache->tvfblevel][dist], 8); } } //synth->printDebug("Cutoff after %d", cutoff); } - depth = (depth * noteLookup->fildepTable[patchCache->tvfdepth]) >> 8; + depth = (depth * keyLookup->envDepthMult[patchCache->filtEnv.envdkf]) >> 8; reshigh = (reshigh * depth) >> 7; Bit32s tmp; - cutoff *= keyfollow; - cutoff /= realfollow; + cutoff *= filtVal; + cutoff /= realVal; //FIXME:KG: With filter keyfollow 0, this makes no sense. What's correct? - reshigh *= keyfollow; - reshigh /= realfollow; + reshigh *= filtVal; + reshigh /= realVal; //FIXME:KG: As above for cutoff - if (cutoff>100) + if (patchCache->waveform == 1) { + reshigh = (reshigh * 65) / 100; + } + + if (cutoff > 100) cutoff = 100; - else if (cutoff<0) + else if (cutoff < 0) cutoff = 0; - if (reshigh>100) + if (reshigh > 100) reshigh = 100; - else if (reshigh<0) + else if (reshigh < 0) reshigh = 0; tmp = noteLookup->nfiltTable[cutoff][reshigh]; //tmp *= keyfollow; @@ -743,7 +768,7 @@ Bit32u Partial::getAmpEnvelope() { if (tStat->decaying) { tc = tStat->envbase; - tc = (tc + ((tStat->envdist * tStat->envpos) / tStat->envsize)); + tc += (tStat->envdist * tStat->envpos) / tStat->envsize; if (tc < 0) tc = 0; if ((tStat->envpos >= tStat->envsize) || (tc == 0)) { @@ -752,47 +777,56 @@ Bit32u Partial::getAmpEnvelope() { return 0; } } else { - if ((tStat->envstat==-1) || (tStat->envpos >= tStat->envsize)) { - if (tStat->envstat==-1) + if ((tStat->envstat == -1) || (tStat->envpos >= tStat->envsize)) { + if (tStat->envstat == -1) tStat->envbase = 0; else tStat->envbase = patchCache->ampEnv.envlevel[tStat->envstat]; tStat->envstat++; tStat->envpos = 0; + if (tStat->envstat == 4) { + //synth->printDebug("Envstat %d, size %d", tStat->envstat, tStat->envsize); + tc = patchCache->ampEnv.envlevel[3]; + if (!poly->sustain) + startDecay(EnvelopeType_amp, tc); + else + tStat->sustaining = true; + goto PastCalc; + } + Bit8u targetLevel = patchCache->ampEnv.envlevel[tStat->envstat]; + tStat->envdist = targetLevel - tStat->envbase; + Bit32u envTime = patchCache->ampEnv.envtime[tStat->envstat]; + if (targetLevel == 0) { + tStat->envsize = synth->tables.envDecayTime[envTime]; + } else { + int envLevelDelta = abs(tStat->envdist); + if (envTime > synth->tables.envDeltaMaxTime[envLevelDelta]) { + envTime = synth->tables.envDeltaMaxTime[envLevelDelta]; + } + tStat->envsize = synth->tables.envTime[envTime]; + } + + // Time keyfollow is used by all sections of the envelope (confirmed on CM-32L) + tStat->envsize = FIXEDPOINT_UMULT(tStat->envsize, keyLookup->envTimeMult[(int)patchCache->ampEnv.envtkf], 8); - switch(tStat->envstat) { + switch (tStat->envstat) { case 0: //Spot for velocity time follow //Only used for first attack - tStat->envsize = (envtimetable[(int)patchCache->ampEnv.envtime[tStat->envstat]] * veltkeytable[(int)patchCache->ampEnv.envvkf][poly->vel]) >> 8; + tStat->envsize = FIXEDPOINT_UMULT(tStat->envsize, synth->tables.envTimeVelfollowMult[(int)patchCache->ampEnv.envvkf][poly->vel], 8); //synth->printDebug("Envstat %d, size %d", tStat->envstat, tStat->envsize); break; + case 1: + case 2: case 3: - // Final attack envelope uses same time table as the decay - //tStat->envsize = decaytimetable[patchCache->ampEnv.envtime[tStat->envstat]]; - tStat->envsize = lasttimetable[(int)patchCache->ampEnv.envtime[tStat->envstat]]; //synth->printDebug("Envstat %d, size %d", tStat->envstat, tStat->envsize); break; - case 4: - //synth->printDebug("Envstat %d, size %d", tStat->envstat, tStat->envsize); - tc = patchCache->ampsustain; - if (!poly->sustain) - startDecay(EnvelopeType_amp, tc); - else - tStat->sustaining = true; - - goto PastCalc; default: - //Spot for timekey follow - //Only used in subsquent envelope parameters, including the decay - 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); + synth->printDebug("Invalid TVA envelope number %d hit!", tStat->envstat); break; } tStat->envsize++; - tStat->envdist = patchCache->ampEnv.envlevel[tStat->envstat] - tStat->envbase; if (tStat->envdist != 0) { tStat->counter = abs(tStat->envsize / tStat->envdist); @@ -806,7 +840,7 @@ Bit32u Partial::getAmpEnvelope() { tc = (tc + ((tStat->envdist * tStat->envpos) / tStat->envsize)); tStat->count = tStat->counter; PastCalc: - tc = (tc * (Bit32s)patchCache->amplevel) >> 7; + tc = (tc * (Bit32s)patchCache->ampEnv.level) / 100; } // Prevlevel storage is bottle neck @@ -821,13 +855,13 @@ PastCalc: // < Bias if (noteVal < bias) { int dist = bias - noteVal; - tc = (tc * ampbiastable[patchCache->ampblevel[i]][dist]) >> 8; + tc = FIXEDPOINT_UMULT(tc, synth->tables.tvaBiasMult[patchCache->ampblevel[i]][dist], 8); } } else { // > Bias if (noteVal > bias) { int dist = noteVal - bias; - tc = (tc * ampbiastable[patchCache->ampblevel[i]][dist]) >> 8; + tc = FIXEDPOINT_UMULT(tc, synth->tables.tvaBiasMult[patchCache->ampblevel[i]][dist], 8); } } } @@ -863,7 +897,14 @@ Bit32s Partial::getPitchEnvelope() { tStat->envstat++; tStat->envbase = patchCache->pitchEnv.level[tStat->envstat]; - tStat->envsize = (envtimetable[(int)patchCache->pitchEnv.time[tStat->envstat]] * noteLookup->timekeyTable[(int)patchCache->pitchEnv.timekeyfollow]) >> 8; + + Bit32u envTime = patchCache->pitchEnv.time[tStat->envstat]; + int envDiff = abs(patchCache->pitchEnv.level[tStat->envstat] - patchCache->pitchEnv.level[tStat->envstat + 1]); + if (envTime > synth->tables.envDeltaMaxTime[envDiff]) { + envTime = synth->tables.envDeltaMaxTime[envDiff]; + } + + tStat->envsize = (synth->tables.envTime[envTime] * keyLookup->envTimeMult[(int)patchCache->pitchEnv.timekeyfollow]) >> 8; tStat->envpos = 0; tStat->envsize++; @@ -892,17 +933,17 @@ void Partial::startDecay(EnvelopeType envnum, Bit32s startval) { tStat->envpos = 0; tStat->envbase = startval; - switch(envnum) { + switch (envnum) { case EnvelopeType_amp: - tStat->envsize = (decaytimetable[(int)patchCache->ampEnv.envtime[4]] * noteLookup->timekeyTable[(int)patchCache->ampEnv.envtkf]) >> 8; + tStat->envsize = FIXEDPOINT_UMULT(synth->tables.envDecayTime[(int)patchCache->ampEnv.envtime[4]], keyLookup->envTimeMult[(int)patchCache->ampEnv.envtkf], 8); tStat->envdist = -startval; break; case EnvelopeType_filt: - tStat->envsize = (decaytimetable[(int)patchCache->filtEnv.envtime[4]] * noteLookup->timekeyTable[(int)patchCache->filtEnv.envtkf]) >> 8; + tStat->envsize = FIXEDPOINT_UMULT(synth->tables.envDecayTime[(int)patchCache->filtEnv.envtime[4]], keyLookup->envTimeMult[(int)patchCache->filtEnv.envtkf], 8); tStat->envdist = -startval; break; case EnvelopeType_pitch: - tStat->envsize = (decaytimetable[(int)patchCache->pitchEnv.time[3]] * noteLookup->timekeyTable[(int)patchCache->pitchEnv.timekeyfollow]) >> 8 ; + tStat->envsize = FIXEDPOINT_UMULT(synth->tables.envDecayTime[(int)patchCache->pitchEnv.time[3]], keyLookup->envTimeMult[(int)patchCache->pitchEnv.timekeyfollow], 8); tStat->envdist = patchCache->pitchEnv.level[4] - startval; break; default: diff --git a/sound/softsynth/mt32/partial.h b/sound/softsynth/mt32/partial.h index 0834a64c15..93d8bcd985 100644 --- a/sound/softsynth/mt32/partial.h +++ b/sound/softsynth/mt32/partial.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2003-2004 Various contributors +/* Copyright (c) 2003-2005 Various contributors * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to @@ -60,8 +60,6 @@ private: Bit16s myBuffer[MAX_SAMPLE_OUTPUT]; - bool play; - // Keyfollowed note value #if MT32EMU_ACCURATENOTES == 1 NoteLookup noteLookupStorage; @@ -71,12 +69,15 @@ private: int fineShift; #endif const NoteLookup *noteLookup; // LUTs for this noteVal + const KeyLookup *keyLookup; // LUTs for the clamped (12..108) key // Keyfollowed filter values int realVal; int filtVal; - EnvelopeStatus envs[3]; + // Only used for PCM partials + int pcmNum; + PCMWaveEntry *pcmWave; int pulsewidth; @@ -109,6 +110,9 @@ private: public: const PatchCache *patchCache; + EnvelopeStatus envs[3]; + bool play; + PatchCache cachebackup; Partial *pair; @@ -118,7 +122,9 @@ public: Partial(Synth *synth); ~Partial(); - int getOwnerPart(); + int getOwnerPart() const; + int getKey() const; + const dpoly *getDpoly() const; bool isActive(); void activate(int part); void deactivate(void); diff --git a/sound/softsynth/mt32/partialManager.cpp b/sound/softsynth/mt32/partialManager.cpp index 776276edda..51e55a7d69 100644 --- a/sound/softsynth/mt32/partialManager.cpp +++ b/sound/softsynth/mt32/partialManager.cpp @@ -1,4 +1,4 @@ -/* Copyright (c) 2003-2004 Various contributors +/* Copyright (c) 2003-2005 Various contributors * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to @@ -264,3 +264,9 @@ bool PartialManager::freePartials(unsigned int needed, int partNum) { } return needed == 0; } + +const Partial *PartialManager::getPartial(unsigned int partialNum) const { + if (partialNum > MT32EMU_MAX_PARTIALS - 1) + return NULL; + return partialTable[partialNum]; +} diff --git a/sound/softsynth/mt32/partialManager.h b/sound/softsynth/mt32/partialManager.h index ee145d4c9f..b10f93ff02 100644 --- a/sound/softsynth/mt32/partialManager.h +++ b/sound/softsynth/mt32/partialManager.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2003-2004 Various contributors +/* Copyright (c) 2003-2005 Various contributors * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to @@ -35,6 +35,7 @@ private: Bit32s partialPart[9]; // The count of partials played per part public: + PartialManager(Synth *synth); ~PartialManager(); Partial *allocPartial(int partNum); @@ -47,6 +48,7 @@ public: bool shouldReverb(int i); void clearAlreadyOutputed(); void getPerPartPartialUsage(int usage[9]); + const Partial *getPartial(unsigned int partialNum) const; }; } diff --git a/sound/softsynth/mt32/structures.h b/sound/softsynth/mt32/structures.h index b04d98b0fd..d231ccfb67 100644 --- a/sound/softsynth/mt32/structures.h +++ b/sound/softsynth/mt32/structures.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2003-2004 Various contributors +/* Copyright (c) 2003-2005 Various contributors * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to @@ -26,6 +26,11 @@ namespace MT32Emu { const unsigned int MAX_SAMPLE_OUTPUT = 4096; +// MT32EMU_MEMADDR() converts from sysex-padded, MT32EMU_SYSEXMEMADDR converts to it +// Roland provides documentation using the sysex-padded addresses, so we tend to use that in code and output +#define MT32EMU_MEMADDR(x) ((((x) & 0x7f0000) >> 2) | (((x) & 0x7f00) >> 1) | ((x) & 0x7f)) +#define MT32EMU_SYSEXMEMADDR(x) ((((x) & 0x1FC000) << 2) | (((x) & 0x3F80) << 1) | ((x) & 0x7f)) + #ifdef _MSC_VER #define MT32EMU_ALIGN_PACKED __declspec(align(1)) typedef unsigned __int64 Bit64u; @@ -68,7 +73,7 @@ struct TimbreParam { 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 waveform; // MT-32: 0-1 (SQU/SAW); LAPC-I: WG WAVEFORM/PCM BANK 0 - 3 (SQU/1, SAW/1, SQU/2, SAW/2) Bit8u pcmwave; // 0-127 (1-128) Bit8u pulsewid; // 0-100 Bit8u pwvelo; // 0-14 (-7 - +7) @@ -129,28 +134,32 @@ struct PatchParam { } MT32EMU_ALIGN_PACKED; struct MemParams { + // NOTE: The MT-32 documentation only specifies PatchTemp areas for parts 1-8. + // The LAPC-I documentation specified an additional area for rhythm at the end, + // where all parameters but fine tune, assign mode and output level are ignored struct PatchTemp { PatchParam patch; Bit8u outlevel; // OUTPUT LEVEL 0-100 Bit8u panpot; // PANPOT 0-14 (R-L) Bit8u dummyv[6]; - } MT32EMU_ALIGN_PACKED patchSettings[8]; + } MT32EMU_ALIGN_PACKED patchSettings[9]; struct RhythmTemp { 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... + } MT32EMU_ALIGN_PACKED rhythmSettings[85]; TimbreParam MT32EMU_ALIGN_PACKED timbreSettings[8]; PatchParam MT32EMU_ALIGN_PACKED patches[128]; + // NOTE: There are only 30 timbres in the "rhythm" bank for MT-32; the additional 34 are for LAPC-I and above struct PaddedTimbre { TimbreParam timbre; Bit8u padding[10]; - } MT32EMU_ALIGN_PACKED timbres[64 + 64 + 64 + 30]; // Group A, Group B, Memory, Rhythm + } MT32EMU_ALIGN_PACKED timbres[64 + 64 + 64 + 64]; // Group A, Group B, Memory, Rhythm struct SystemArea { Bit8u masterTune; // MASTER TUNE 0-127 432.1-457.6Hz @@ -163,18 +172,6 @@ struct MemParams { } MT32EMU_ALIGN_PACKED system; }; -struct MemBanks { - 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 -}; - #if defined(_MSC_VER) || defined (__MINGW32__) #pragma pack(pop) #else @@ -227,9 +224,7 @@ struct PatchCache { int ampdir[2]; int ampdepth; - int ampenvdir; int amplevel; - int tvfdepth; bool useBender; float benderRange; // 0.0, 1.0, .., 24.0 (semitones) @@ -238,7 +233,6 @@ struct PatchCache { TimbreParam::partialParam::tvaParam ampEnv; TimbreParam::partialParam::tvfParam filtEnv; - Bit32s ampsustain; Bit32s pitchsustain; Bit32s filtsustain; diff --git a/sound/softsynth/mt32/synth.cpp b/sound/softsynth/mt32/synth.cpp index 1ba543b581..289db62553 100644 --- a/sound/softsynth/mt32/synth.cpp +++ b/sound/softsynth/mt32/synth.cpp @@ -1,4 +1,4 @@ -/* Copyright (c) 2003-2004 Various contributors +/* Copyright (c) 2003-2005 Various contributors * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to @@ -30,7 +30,15 @@ namespace MT32Emu { const int MAX_SYSEX_SIZE = 512; -float iir_filter_normal(float input,float *hist1_ptr, float *coef_ptr, int revLevel) { +const ControlROMMap ControlROMMaps[3] = { + // ID IDc IDbytes PCMmap PCMc tmbrA tmbrAO, tmbrB tmbrBO, tmbrR trC rhythm rhyC rsrv panpot prog + {0x4010, 22, "\000 ver1.07 10 Oct, 87 ", 0x3000, 128, 0x8000, 0x0000, 0xC000, 0x4000, 0x3200, 30, 0x73fe, 85, 0x57B1, 0x57BA, 0x57CC}, // MT-32 revision 1 + {0x4010, 22, "\000verX.XX 30 Sep, 88 ", 0x3000, 128, 0x8000, 0x0000, 0xC000, 0x4000, 0x3200, 30, 0x741C, 85, 0x57E5, 0x57EE, 0x5800}, // MT-32 Blue Ridge mod + {0x2205, 22, "\000CM32/LAPC1.02 891205", 0x8100, 256, 0x8000, 0x8000, 0x8080, 0x8000, 0x8500, 64, 0x8580, 85, 0x4F93, 0x4F9C, 0x4FAE} // CM-32L + // (Note that all but CM-32L ROM actually have 86 entries for rhythmTemp) +}; + +float iir_filter_normal(float input, float *hist1_ptr, float *coef_ptr) { float *hist2_ptr; float output,new_hist; @@ -60,8 +68,6 @@ float iir_filter_normal(float input,float *hist1_ptr, float *coef_ptr, int revLe *hist2_ptr++ = *hist1_ptr; *hist1_ptr++ = new_hist; - output *= ResonInv[revLevel]; - return(output); } @@ -79,7 +85,6 @@ Synth::Synth() { isOpen = false; reverbModel = NULL; partialManager = NULL; - memset(noteLookups, 0, sizeof(noteLookups)); memset(parts, 0, sizeof(parts)); } @@ -112,7 +117,7 @@ void Synth::initReverb(Bit8u newRevMode, Bit8u newRevTime, Bit8u newRevLevel) { delete reverbModel; reverbModel = new revmodel(); - switch(newRevMode) { + switch (newRevMode) { case 0: reverbModel->setroomsize(.1f); reverbModel->setdamp(.75f); @@ -204,10 +209,21 @@ bool Synth::loadControlROM(const char *filename) { if (file == NULL) { return false; } - bool rc = (file->read(controlROMData, sizeof(controlROMData)) == sizeof(controlROMData)); + bool rc = (file->read(controlROMData, CONTROL_ROM_SIZE) == CONTROL_ROM_SIZE); closeFile(file); - return rc; + if (!rc) + return rc; + + // Control ROM successfully loaded, now check whether it's a known type + controlROMMap = NULL; + for (unsigned int i = 0; i < sizeof (ControlROMMaps) / sizeof (ControlROMMaps[0]); i++) { + if (memcmp(&controlROMData[ControlROMMaps[i].idPos], ControlROMMaps[i].idBytes, ControlROMMaps[i].idLen) == 0) { + controlROMMap = &ControlROMMaps[i]; + return true; + } + } + return false; } bool Synth::loadPCMROM(const char *filename) { @@ -216,7 +232,8 @@ bool Synth::loadPCMROM(const char *filename) { return false; } bool rc = true; - for (int i = 0; ; i++) { + int i; + for (i = 0; i < pcmROMSize; i++) { Bit8u s; if (!file->readBit8u(&s)) { if (!file->isEOF()) { @@ -229,7 +246,7 @@ bool Synth::loadPCMROM(const char *filename) { if (!file->isEOF()) { rc = false; } else { - printDebug("ROM file has an odd number of bytes! Ignoring last"); + printDebug("PCM ROM file has an odd number of bytes! Ignoring last"); } break; } @@ -237,7 +254,6 @@ bool Synth::loadPCMROM(const char *filename) { short e; int bit; int u; - int order[16] = {0, 9, 1 ,2, 3, 4, 5, 6, 7, 10, 11, 12, 13, 14, 15, 8}; e = 0; @@ -259,7 +275,7 @@ bool Synth::loadPCMROM(const char *filename) { e = (int)((float)e * (x/3200)); */ - // File is encoded in dB, convert to PCM + // File is companded (dB?), convert to linear PCM // MINDB = -96 // MAXDB = -15 float testval; @@ -271,72 +287,95 @@ bool Synth::loadPCMROM(const char *filename) { if (e > 0) vol = -vol; - romfile[i] = (Bit16s)vol; - + pcmROMData[i] = (Bit16s)vol; + } + if (i != pcmROMSize) { + printDebug("PCM ROM file is too short (expected %d, got %d)", pcmROMSize, i); + rc = false; } closeFile(file); return rc; } -struct ControlROMPCMStruct -{ - Bit8u pos; - Bit8u len; - Bit8u pitchLSB; - Bit8u pitchMSB; -}; - -void Synth::initPCMList() { - ControlROMPCMStruct *tps = (ControlROMPCMStruct *)&controlROMData[0x3000]; - for (int i = 0; i < 128; i++) { +bool Synth::initPCMList(Bit16u mapAddress, Bit16u count) { + ControlROMPCMStruct *tps = (ControlROMPCMStruct *)&controlROMData[mapAddress]; + for (int i = 0; i < count; i++) { int rAddr = tps[i].pos * 0x800; int rLenExp = (tps[i].len & 0x70) >> 4; int rLen = 0x800 << rLenExp; 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. The one below sounded best to me in listening tests, but needs to be confirmed. - double STANDARDFREQ = 432.1; - float rTune = (float)(STANDARDFREQ * pow(2.0, (0x5000 - rTuneOffset) / 4096.0 - 9.0 / 12.0)); + // The number below is confirmed to a reasonable degree of accuracy on CM-32L + double STANDARDFREQ = 442.0; + float rTune = (float)(STANDARDFREQ * pow(2.0, (0x5000 - rTuneOffset) / 4056.0 - 9.0 / 12.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; + if (rAddr + rLen > pcmROMSize) { + printDebug("Control ROM error: Wave map entry %d points to invalid PCM address 0x%04X, length 0x%04X", i, rAddr, rLen); + return false; + } + pcmWaves[i].addr = rAddr; + pcmWaves[i].len = rLen; + pcmWaves[i].loop = rLoop; + pcmWaves[i].tune = rTune; } + return false; } -void Synth::initRhythmTimbre(int timbreNum, const Bit8u *mem) { +bool Synth::initRhythmTimbre(int timbreNum, const Bit8u *mem, unsigned int memLen) { + if (memLen < sizeof(TimbreParam::commonParam)) { + return false; + } TimbreParam *timbre = &mt32ram.timbres[timbreNum].timbre; memcpy(&timbre->common, mem, 14); - mem += 14; + unsigned int memPos = 14; char drumname[11]; strncpy(drumname, timbre->common.name, 10); drumname[10] = 0; for (int t = 0; t < 4; t++) { if (((timbre->common.pmute >> t) & 0x1) == 0x1) { - memcpy(&timbre->partial[t], mem, 58); - mem += 58; + if (memPos + 58 >= memLen) { + return false; + } + memcpy(&timbre->partial[t], mem + memPos, 58); + memPos += 58; } } + return true; } -void Synth::initRhythmTimbres() { - const Bit8u *drumMap = &controlROMData[0x3200]; +bool Synth::initRhythmTimbres(Bit16u mapAddress, Bit16u count) { + const Bit8u *drumMap = &controlROMData[mapAddress]; int timbreNum = 192; - for (Bit16u i = 0; i < 60; i += 2) { + for (Bit16u i = 0; i < count * 2; i += 2) { Bit16u address = (drumMap[i + 1] << 8) | drumMap[i]; - initRhythmTimbre(timbreNum++, &controlROMData[address]); + /* + // This check is nonsensical when the control ROM is the full 64KB addressable by 16-bit absolute pointers (which it is) + if (address >= CONTROL_ROM_SIZE) { + printDebug("Control ROM error: Timbre map entry 0x%04x points to invalid timbre address 0x%04x", i, address); + return false; + } + */ + if (!initRhythmTimbre(timbreNum++, &controlROMData[address], CONTROL_ROM_SIZE - address)) { + printDebug("Control ROM error: Timbre map entry 0x%04x points to invalid timbre 0x%04x", i, address); + return false; + } } + return true; } -void Synth::initTimbres(Bit16u mapAddress, int startTimbre) { +bool Synth::initTimbres(Bit16u mapAddress, Bit16u offset, int startTimbre) { for (Bit16u i = mapAddress; i < mapAddress + 0x80; i += 2) { Bit16u address = (controlROMData[i + 1] << 8) | controlROMData[i]; - address = address + (mapAddress - 0x8000); + if (address + sizeof(TimbreParam) > CONTROL_ROM_SIZE) { + printDebug("Control ROM error: Timbre map entry 0x%04x points to invalid timbre address 0x%04x", i, address); + return false; + } + address = address + offset; TimbreParam *timbre = &mt32ram.timbres[startTimbre++].timbre; memcpy(timbre, &controlROMData[address], sizeof(TimbreParam)); } + return true; } bool Synth::open(SynthProperties &useProp) { @@ -351,41 +390,59 @@ bool Synth::open(SynthProperties &useProp) { // This is to help detect bugs memset(&mt32ram, '?', sizeof(mt32ram)); - for (int i = 128; i < 192; i++) { - // If something sets a patch to point to an uninitialised memory timbre, don't play anything - mt32ram.timbres[i].timbre.common.pmute = 0; - } printDebug("Loading Control ROM"); - if (!loadControlROM("MT32_CONTROL.ROM")) { - printDebug("Init Error - Missing or invalid MT32_CONTROL.ROM"); - report(ReportType_errorControlROM, &errno); - return false; + if (!loadControlROM("CM32L_CONTROL.ROM")) { + if (!loadControlROM("MT32_CONTROL.ROM")) { + printDebug("Init Error - Missing or invalid MT32_CONTROL.ROM"); + report(ReportType_errorControlROM, &errno); + return false; + } } + // 512KB PCM ROM for MT-32, etc. + // 1MB PCM ROM for CM-32L, LAPC-I, CM-64, CM-500 + // Note that the size below is given in samples (16-bit), not bytes + pcmROMSize = controlROMMap->pcmCount == 256 ? 512 * 1024 : 256 * 1024; + pcmROMData = new Bit16s[pcmROMSize]; + printDebug("Loading PCM ROM"); - if (!loadPCMROM("MT32_PCM.ROM")) { - printDebug("Init Error - Missing MT32_PCM.ROM"); - report(ReportType_errorPCMROM, &errno); - return false; + if (!loadPCMROM("CM32L_PCM.ROM")) { + if (!loadPCMROM("MT32_PCM.ROM")) { + printDebug("Init Error - Missing MT32_PCM.ROM"); + report(ReportType_errorPCMROM, &errno); + return false; + } } - partialManager = new PartialManager(this); - - printDebug("Initialising PCM List"); - initPCMList(); - printDebug("Initialising Timbre Bank A"); - initTimbres(0x8000, 0); + if (!initTimbres(controlROMMap->timbreAMap, controlROMMap->timbreAOffset, 0)) { + return false; + } printDebug("Initialising Timbre Bank B"); - initTimbres(0xC000, 64); + if (!initTimbres(controlROMMap->timbreBMap, controlROMMap->timbreBOffset, 64)) { + return false; + } printDebug("Initialising Timbre Bank R"); - initRhythmTimbres(); + if (!initRhythmTimbres(controlROMMap->timbreRMap, controlROMMap->timbreRCount)) { + return false; + } + + printDebug("Initialising Timbre Bank M"); + // CM-64 seems to initialise all bytes in this bank to 0. + memset(&mt32ram.timbres[128], 0, sizeof (mt32ram.timbres[128]) * 64); + + partialManager = new PartialManager(this); + + pcmWaves = new PCMWaveEntry[controlROMMap->pcmCount]; + + printDebug("Initialising PCM List"); + initPCMList(controlROMMap->pcmTable, controlROMMap->pcmCount); printDebug("Initialising Rhythm Temp"); - memcpy(mt32ram.rhythmSettings, &controlROMData[0x741C], 344); + memcpy(mt32ram.rhythmSettings, &controlROMData[controlROMMap->rhythmSettings], controlROMMap->rhythmSettingsCount * 4); printDebug("Initialising Patches"); for (Bit8u i = 0; i < 128; i++) { @@ -401,16 +458,12 @@ bool Synth::open(SynthProperties &useProp) { } printDebug("Initialising System"); - //FIXME: Confirm that these are all correct // The MT-32 manual claims that "Standard pitch" is 442Hz. - // 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.reverbLevel = 3; - memcpy(mt32ram.system.reserveSettings, &controlROMData[0x57E5], 9); + mt32ram.system.masterTune = 0x40; // Confirmed on CM-64 as 0x4A, but SCUMM games use 0x40 and we don't want to initialise twice + mt32ram.system.reverbMode = 0; // Confirmed + mt32ram.system.reverbTime = 5; // Confirmed + mt32ram.system.reverbLevel = 3; // Confirmed + memcpy(mt32ram.system.reserveSettings, &controlROMData[controlROMMap->reserveSettings], 9); // Confirmed for (Bit8u i = 0; i < 9; i++) { // This is the default: {1, 2, 3, 4, 5, 6, 7, 8, 9} // An alternative configuration can be selected by holding "Master Volume" @@ -418,16 +471,16 @@ bool Synth::open(SynthProperties &useProp) { // The channel assignment is then {0, 1, 2, 3, 4, 5, 6, 7, 9} mt32ram.system.chanAssign[i] = i + 1; } - mt32ram.system.masterVol = 100; + mt32ram.system.masterVol = 100; // Confirmed if (!refreshSystem()) return false; for (int i = 0; i < 8; i++) { mt32ram.patchSettings[i].outlevel = 80; - mt32ram.patchSettings[i].panpot = controlROMData[0x5800 + i]; + mt32ram.patchSettings[i].panpot = controlROMData[controlROMMap->panSettings + i]; memset(mt32ram.patchSettings[i].dummyv, 0, sizeof(mt32ram.patchSettings[i].dummyv)); parts[i] = new Part(this, i); - parts[i]->setProgram(controlROMData[0x57EE + i]); + parts[i]->setProgram(controlROMData[controlROMMap->programSettings + i]); } parts[8] = new RhythmPart(this, 8); @@ -467,7 +520,7 @@ void Synth::close(void) { if (!isOpen) return; - TableInitialiser::freeNotes(); + tables.freeNotes(); if (partialManager != NULL) { delete partialManager; partialManager = NULL; @@ -488,14 +541,18 @@ void Synth::close(void) { delete myProp.baseDir; myProp.baseDir = NULL; } + + delete[] pcmWaves; + delete[] pcmROMData; isOpen = false; } void Synth::playMsg(Bit32u msg) { - unsigned char code = (unsigned char)((msg & 0xf0) >> 4); - unsigned char chan = (unsigned char)(msg & 0xf); - unsigned char note = (unsigned char)((msg & 0xff00) >> 8); - unsigned char velocity = (unsigned char)((msg & 0xff0000) >> 16); + // FIXME: Implement active sensing + unsigned char code = (unsigned char)((msg & 0x0000F0) >> 4); + unsigned char chan = (unsigned char) (msg & 0x00000F); + unsigned char note = (unsigned char)((msg & 0x00FF00) >> 8); + unsigned char velocity = (unsigned char)((msg & 0xFF0000) >> 16); isEnabled = true; //printDebug("Playing chan %d, code 0x%01x note: 0x%02x", chan, code, note); @@ -534,7 +591,6 @@ void Synth::playMsgOnPart(unsigned char part, unsigned char code, unsigned char parts[part]->setModulation(velocity); break; case 0x07: // Set volume - //if (part!=3) return; //printDebug("Volume set: %d", velocity); parts[part]->setVolume(velocity); break; @@ -544,20 +600,26 @@ void Synth::playMsgOnPart(unsigned char part, unsigned char code, unsigned char break; case 0x0B: //printDebug("Expression set: %d", velocity); - parts[part]->setVolume(velocity); + parts[part]->setExpression(velocity); break; - case 0x40: // Hold pedal + case 0x40: // Hold (sustain) pedal //printDebug("Hold pedal set: %d", velocity); parts[part]->setHoldPedal(velocity>=64); break; case 0x79: // Reset all controllers - printDebug("Reset all controllers (NYI)"); + //printDebug("Reset all controllers"); + //FIXME: Check for accuracy against real thing + parts[part]->setVolume(100); + parts[part]->setExpression(127); + parts[part]->setPan(64); + parts[part]->setBend(0x2000); + parts[part]->setHoldPedal(false); break; case 0x7B: // All notes off //printDebug("All notes off"); - parts[part]->allStop(); + parts[part]->allNotesOff(); break; default: @@ -583,53 +645,49 @@ void Synth::playMsgOnPart(unsigned char part, unsigned char code, unsigned char //midiOutShortMsg(m_out, msg); } -void Synth::playSysex(const Bit8u * sysex,Bit32u len) { - if (len < 3) { +void Synth::playSysex(const Bit8u *sysex, Bit32u len) { + if (len < 2) { printDebug("playSysex: Message is too short for sysex (%d bytes)", len); } - if (sysex[0] != 0xf0) { - printDebug("playSysex: Message lacks start-of-sysex (0xf0)"); + if (sysex[0] != 0xF0) { + printDebug("playSysex: Message lacks start-of-sysex (0xF0)"); return; } - if (sysex[len - 1] != 0xf7) { + // Due to some programs (e.g. Java) sending buffers with junk at the end, we have to go through and find the end marker rather than relying on len. + Bit32u endPos; + for (endPos = 1; endPos < len; endPos++) + { + if (sysex[endPos] == 0xF7) + break; + } + if (endPos == len) { printDebug("playSysex: Message lacks end-of-sysex (0xf7)"); return; } - playSysexWithoutFraming(sysex + 1, len - 2); + playSysexWithoutFraming(sysex + 1, endPos - 1); } -void Synth::playSysexWithoutFraming(const 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; } - if (sysex[0] != 0x41) { + if (sysex[0] != SYSEX_MANUFACTURER_ROLAND) { printDebug("playSysexWithoutFraming: Header not intended for this device manufacturer: %02x %02x %02x %02x", (int)sysex[0], (int)sysex[1], (int)sysex[2], (int)sysex[3]); return; } - if (sysex[2] == 0x14) { - printDebug("playSysexWithoutFraming: Header is intended for Roland D-50 (not yet supported): %02x %02x %02x %02x", (int)sysex[0], (int)sysex[1], (int)sysex[2], (int)sysex[3]); + if (sysex[2] == SYSEX_MDL_D50) { + printDebug("playSysexWithoutFraming: Header is intended for model D-50 (not yet supported): %02x %02x %02x %02x", (int)sysex[0], (int)sysex[1], (int)sysex[2], (int)sysex[3]); return; } - else if (sysex[2] != 0x16) { - printDebug("playSysexWithoutFraming: Header not intended for MT-32: %02x %02x %02x %02x", (int)sysex[0], (int)sysex[1], (int)sysex[2], (int)sysex[3]); + else if (sysex[2] != SYSEX_MDL_MT32) { + printDebug("playSysexWithoutFraming: Header not intended for model MT-32: %02x %02x %02x %02x", (int)sysex[0], (int)sysex[1], (int)sysex[2], (int)sysex[3]); return; } - if (sysex[3] != 0x12) { - printDebug("playSysexWithoutFraming: Unsupported command %02x", sysex[3]); - return; - } - playSysexWithoutHeader(sysex[1], sysex + 4, len - 4); + playSysexWithoutHeader(sysex[1], sysex[3], sysex + 4, len - 4); } -// MEMADDR() converts from sysex-padded, SYSEXMEMADDR converts to it -// Roland provides documentation using the sysex-padded addresses, so we tend to use that int code and output -#define MEMADDR(x) ((((x) & 0x7f0000) >> 2) | (((x) & 0x7f00) >> 1) | ((x) & 0x7f)) -#define SYSEXMEMADDR(x) ((((x) & 0x1FC000) << 2) | (((x) & 0x3F80) << 1) | ((x) & 0x7f)) - -#define NUMTOUCHED(x,y) (((x) + sizeof(y) - 1) / sizeof(y)) - -void Synth::playSysexWithoutHeader(unsigned char device, const Bit8u *sysex, Bit32u len) { +void Synth::playSysexWithoutHeader(unsigned char device, unsigned char command, const Bit8u *sysex, Bit32u len) { if (device > 0x10) { // We have device ID 0x10 (default, but changeable, on real MT-32), < 0x10 is for channels printDebug("playSysexWithoutHeader: Message is not intended for this device ID (provided: %02x, expected: 0x10 or channel)", (int)device); @@ -645,15 +703,47 @@ void Synth::playSysexWithoutHeader(unsigned char device, const Bit8u *sysex, Bit return; } len -= 1; // Exclude checksum + switch (command) { + case SYSEX_CMD_DT1: + writeSysex(device, sysex, len); + break; + case SYSEX_CMD_RQ1: + readSysex(device, sysex, len); + break; + default: + printDebug("playSysexWithoutFraming: Unsupported command %02x", command); + return; + } +} + +void Synth::readSysex(unsigned char device, const Bit8u *sysex, Bit32u len) { +} + +const MemoryRegion memoryRegions[8] = { + {MR_PatchTemp, MT32EMU_MEMADDR(0x030000), sizeof(MemParams::PatchTemp), 9}, + {MR_RhythmTemp, MT32EMU_MEMADDR(0x030110), sizeof(MemParams::RhythmTemp), 85}, + {MR_TimbreTemp, MT32EMU_MEMADDR(0x040000), sizeof(TimbreParam), 8}, + {MR_Patches, MT32EMU_MEMADDR(0x050000), sizeof(PatchParam), 128}, + {MR_Timbres, MT32EMU_MEMADDR(0x080000), sizeof(MemParams::PaddedTimbre), 64 + 64 + 64 + 64}, + {MR_System, MT32EMU_MEMADDR(0x100000), sizeof(MemParams::SystemArea), 1}, + {MR_Display, MT32EMU_MEMADDR(0x200000), MAX_SYSEX_SIZE - 1, 1}, + {MR_Reset, MT32EMU_MEMADDR(0x7F0000), 0x3FFF, 1} +}; + +const int NUM_REGIONS = sizeof(memoryRegions) / sizeof(MemoryRegion); + +void Synth::writeSysex(unsigned char device, const Bit8u *sysex, Bit32u len) { Bit32u addr = (sysex[0] << 16) | (sysex[1] << 8) | (sysex[2]); - addr = MEMADDR(addr); + addr = MT32EMU_MEMADDR(addr); sysex += 3; len -= 3; - //printDebug("Sysex addr: 0x%06x", SYSEXMEMADDR(addr)); + //printDebug("Sysex addr: 0x%06x", MT32EMU_SYSEXMEMADDR(addr)); // NOTE: Please keep both lower and upper bounds in each check, for ease of reading + + // Process channel-specific sysex by converting it to device-global if (device < 0x10) { - printDebug("WRITE-CHANNEL: Channel %d temp area 0x%06x", device, SYSEXMEMADDR(addr)); - if (/*addr >= MEMADDR(0x000000) && */addr < MEMADDR(0x010000)) { + printDebug("WRITE-CHANNEL: Channel %d temp area 0x%06x", device, MT32EMU_SYSEXMEMADDR(addr)); + if (/*addr >= MT32EMU_MEMADDR(0x000000) && */addr < MT32EMU_MEMADDR(0x010000)) { int offset; if (chantable[device] == -1) { printDebug(" (Channel not mapped to a partial... 0 offset)"); @@ -665,10 +755,10 @@ void Synth::playSysexWithoutHeader(unsigned char device, const Bit8u *sysex, Bit offset = chantable[device] * sizeof(MemParams::PatchTemp); printDebug(" (Setting extra offset to %d)", offset); } - addr += MEMADDR(0x030000) + offset; - } else if (/*addr >= 0x010000 && */ addr < MEMADDR(0x020000)) { - addr += MEMADDR(0x030110) - MEMADDR(0x010000); - } else if (/*addr >= 0x020000 && */ addr < MEMADDR(0x030000)) { + addr += MT32EMU_MEMADDR(0x030000) + offset; + } else if (/*addr >= 0x010000 && */ addr < MT32EMU_MEMADDR(0x020000)) { + addr += MT32EMU_MEMADDR(0x030110) - MT32EMU_MEMADDR(0x010000); + } else if (/*addr >= 0x020000 && */ addr < MT32EMU_MEMADDR(0x030000)) { int offset; if (chantable[device] == -1) { printDebug(" (Channel not mapped to a partial... 0 offset)"); @@ -680,53 +770,132 @@ void Synth::playSysexWithoutHeader(unsigned char device, const Bit8u *sysex, Bit offset = chantable[device] * sizeof(TimbreParam); printDebug(" (Setting extra offset to %d)", offset); } - addr += MEMADDR(0x040000) - MEMADDR(0x020000) + offset; + addr += MT32EMU_MEMADDR(0x040000) - MT32EMU_MEMADDR(0x020000) + offset; } else { - printDebug("PlaySysexWithoutHeader: Invalid channel %d address 0x%06x", device, SYSEXMEMADDR(addr)); + printDebug("PlaySysexWithoutHeader: Invalid channel %d address 0x%06x", device, MT32EMU_SYSEXMEMADDR(addr)); return; } } - if (addr >= MEMADDR(0x030000) && addr < MEMADDR(0x030110)) { - int off = addr - MEMADDR(0x030000); - if (off + len > sizeof(mt32ram.patchSettings)) { - printDebug("playSysexWithoutHeader: Message goes beyond bounds of memory region (addr=0x%06x, len=%d)!", SYSEXMEMADDR(addr), len); - return; + + // Process device-global sysex (possibly converted from channel-specific sysex above) + for (;;) { + // Find the appropriate memory region + int regionNum; + const MemoryRegion *region = NULL; // Initialised to please compiler + for (regionNum = 0; regionNum < NUM_REGIONS; regionNum++) { + region = &memoryRegions[regionNum]; + if (region->contains(addr)) { + writeMemoryRegion(region, addr, region->getClampedLen(addr, len), sysex); + break; + } + } + if (regionNum == NUM_REGIONS) { + printDebug("Sysex write to unrecognised address %06x, len %d", MT32EMU_SYSEXMEMADDR(addr), len); + break; + } + Bit32u next = region->next(addr, len); + if (next == 0) { + break; + } + addr += next; + sysex += next; + len -= next; + } +} + +void Synth::readMemory(Bit32u addr, Bit32u len, Bit8u *data) { + int regionNum; + const MemoryRegion *region = NULL; + for (regionNum = 0; regionNum < NUM_REGIONS; regionNum++) { + region = &memoryRegions[regionNum]; + if (region->contains(addr)) { + readMemoryRegion(region, addr, len, data); + break; + } + } +} + +void Synth::readMemoryRegion(const MemoryRegion *region, Bit32u addr, Bit32u len, Bit8u *data) { + unsigned int first = region->firstTouched(addr); + //unsigned int last = region->lastTouched(addr, len); + unsigned int off = region->firstTouchedOffset(addr); + len = region->getClampedLen(addr, len); + + unsigned int m; + + switch(region->type) { + case MR_PatchTemp: + for (m = 0; m < len; m++) + data[m] = ((Bit8u *)&mt32ram.patchSettings[first])[off + m]; + break; + case MR_RhythmTemp: + for (m = 0; m < len; m++) + data[m] = ((Bit8u *)&mt32ram.rhythmSettings[first])[off + m]; + break; + case MR_TimbreTemp: + for (m = 0; m < len; m++) + data[m] = ((Bit8u *)&mt32ram.timbreSettings[first])[off + m]; + break; + case MR_Patches: + for (m = 0; m < len; m++) + data[m] = ((Bit8u *)&mt32ram.patches[first])[off + m]; + break; + case MR_Timbres: + for (m = 0; m < len; m++) + data[m] = ((Bit8u *)&mt32ram.timbres[first])[off + m]; + break; + case MR_System: + for (m = 0; m < len; m++) + data[m] = ((Bit8u *)&mt32ram.system)[m + off]; + break; + default: + for (m = 0; m < len; m += 2) { + data[m] = 0xff; + if (m + 1 < len) { + data[m+1] = (Bit8u)region->type; + } + } + // TODO: Don't care about the others ATM + break; + } + +} + +void Synth::writeMemoryRegion(const MemoryRegion *region, Bit32u addr, Bit32u len, const Bit8u *data) { + unsigned int first = region->firstTouched(addr); + unsigned int last = region->lastTouched(addr, len); + unsigned int off = region->firstTouchedOffset(addr); + switch (region->type) { + case MR_PatchTemp: + for (unsigned int m = 0; m < len; m++) { + ((Bit8u *)&mt32ram.patchSettings[first])[off + m] = data[m]; } - int firstPart = off / sizeof(MemParams::PatchTemp); - off %= sizeof(MemParams::PatchTemp); - for (unsigned int m = 0; m < len; 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++) { + for (unsigned int i = first; i <= last; i++) { int absTimbreNum = mt32ram.patchSettings[i].patch.timbreGroup * 64 + mt32ram.patchSettings[i].patch.timbreNum; char timbreName[11]; 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); + printDebug("WRITE-PARTPATCH (%d-%d@%d..%d): %d; timbre=%d (%s), outlevel=%d", first, last, off, off + len, i, absTimbreNum, timbreName, mt32ram.patchSettings[i].outlevel); if (parts[i] != NULL) { - if (i == firstPart && off > 2) { - 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.timbres[parts[i]->getAbsTimbreNum()].timbre); + if (i != 8) { + // Note: Confirmed on CM-64 that we definitely *should* update the timbre here, + // but only in the case that the sysex actually writes to those values + if (i == first && off > 2) { + printDebug(" (Not updating timbre, since those values weren't touched)"); + } else { + parts[i]->setTimbre(&mt32ram.timbres[parts[i]->getAbsTimbreNum()].timbre); + } } parts[i]->refresh(); } } - } else if (addr >= MEMADDR(0x030110) && addr < MEMADDR(0x040000)) { - int off = addr - MEMADDR(0x030110); - 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); + break; + case MR_RhythmTemp: for (unsigned int m = 0; m < len; 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++) { + ((Bit8u *)&mt32ram.rhythmSettings[first])[off + m] = data[m]; + for (unsigned int i = first; i <= last; i++) { int timbreNum = mt32ram.rhythmSettings[i].timbre; char timbreName[11]; if (timbreNum < 94) { @@ -735,51 +904,36 @@ void Synth::playSysexWithoutHeader(unsigned char device, const Bit8u *sysex, Bit } 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.rhythmSettings[i].outlevel, mt32ram.rhythmSettings[i].panpot, mt32ram.rhythmSettings[i].reverbSwitch, mt32ram.rhythmSettings[i].timbre, timbreName); + printDebug("WRITE-RHYTHM (%d-%d@%d..%d): %d; level=%02x, panpot=%02x, reverb=%02x, timbre=%d (%s)", first, last, 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]->refresh(); } - } else if (addr >= MEMADDR(0x040000) && addr < MEMADDR(0x050000)) { - int off = addr - MEMADDR(0x040000); - 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); + break; + case MR_TimbreTemp: for (unsigned int m = 0; m < len; m++) - ((Bit8u *)&mt32ram.timbreSettings[firstPart])[off + m] = sysex[m]; - int lastPart = firstPart + NUMTOUCHED(off + len, TimbreParam) - 1; - for (int i = firstPart; i <= lastPart; i++) { + ((Bit8u *)&mt32ram.timbreSettings[first])[off + m] = data[m]; + for (unsigned int i = first; i <= last; i++) { char instrumentName[11]; 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); + printDebug("WRITE-PARTTIMBRE (%d-%d@%d..%d): timbre=%d (%s)", first, last, off, off + len, i, instrumentName); if (parts[i] != NULL) { parts[i]->refresh(); } } - } - else if (addr >= MEMADDR(0x050000) && addr < MEMADDR(0x060000)) { - int off = addr - MEMADDR(0x050000); - 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); + break; + case MR_Patches: for (unsigned int m = 0; m < len; m++) - ((Bit8u *)&mt32ram.patches[firstPatch])[off + m] = sysex[m]; - int lastPatch = firstPatch + NUMTOUCHED(off + len, PatchParam) - 1; - for (int i = firstPatch; i <= lastPatch; i++) { + ((Bit8u *)&mt32ram.patches[first])[off + m] = data[m]; + for (unsigned int i = first; i <= last; i++) { PatchParam *patch = &mt32ram.patches[i]; int patchAbsTimbreNum = patch->timbreGroup * 64 + patch->timbreNum; char instrumentName[11]; memcpy(instrumentName, mt32ram.timbres[patchAbsTimbreNum].timbre.common.name, 10); instrumentName[10] = 0; Bit8u *n = (Bit8u *)patch; - printDebug("WRITE-PATCH (%d-%d@%d..%d): %d; timbre=%d (%s) %02X%02X%02X%02X%02X%02X%02X%02X", firstPatch, lastPatch, off, off + len, i, patchAbsTimbreNum, instrumentName, n[0], n[1], n[2], n[3], n[4], n[5], n[6], n[7]); + printDebug("WRITE-PATCH (%d-%d@%d..%d): %d; timbre=%d (%s) %02X%02X%02X%02X%02X%02X%02X%02X", first, last, off, off + len, i, patchAbsTimbreNum, instrumentName, n[0], n[1], n[2], n[3], n[4], n[5], n[6], n[7]); // 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. @@ -795,25 +949,18 @@ void Synth::playSysexWithoutHeader(unsigned char device, const Bit8u *sysex, Bit } */ } - } else if (addr >= MEMADDR(0x080000) && addr < MEMADDR(0x090000)) { + break; + case MR_Timbres: // Timbres - int off = addr - MEMADDR(0x080000); - if (off + len > sizeof(MemParams::PaddedTimbre) * 64) { - // You can only write to one group at a time - printDebug("playSysexWithoutHeader: Message goes beyond bounds of memory region (addr=0x%06x, len=%d)!", SYSEXMEMADDR(addr), len); - return; - } - unsigned int firstTimbre = off / sizeof (MemParams::PaddedTimbre); - off %= sizeof (MemParams::PaddedTimbre); - firstTimbre += 128; + first += 128; + last += 128; for (unsigned int m = 0; m < len; 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++) { + ((Bit8u *)&mt32ram.timbres[first])[off + m] = data[m]; + for (unsigned int i = first; i <= last; i++) { char instrumentName[11]; 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); + printDebug("WRITE-TIMBRE (%d-%d@%d..%d): %d; name=\"%s\"", first, last, 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? for (unsigned int part = 0; part < 9; part++) { @@ -822,31 +969,25 @@ void Synth::playSysexWithoutHeader(unsigned char device, const Bit8u *sysex, Bit } } } - } else if (addr >= MEMADDR(0x100000) && addr < MEMADDR(0x200000)) { - int off = addr - MEMADDR(0x100000); - if (off + len > sizeof(mt32ram.system)) { - printDebug("playSysexWithoutHeader: Message goes beyond bounds of memory region (addr=0x%06x, len=%d)!", SYSEXMEMADDR(addr), len); - return; - } + break; + case MR_System: for (unsigned int m = 0; m < len; m++) - ((Bit8u *)&mt32ram.system)[m + off] = sysex[m]; + ((Bit8u *)&mt32ram.system)[m + off] = data[m]; report(ReportType_devReconfig, NULL); printDebug("WRITE-SYSTEM:"); refreshSystem(); - } else if (addr == MEMADDR(0x200000)) { + break; + case MR_Display: char buf[MAX_SYSEX_SIZE]; - if (len > MAX_SYSEX_SIZE - 1) { - printDebug("WRITE-LCD sysex length (%d) exceeded MAX_SYSEX_SIZE (%d) - 1; truncating", len, MAX_SYSEX_SIZE); - len = MAX_SYSEX_SIZE - 1; - } - memcpy(&buf, &sysex[0], len); + memcpy(&buf, &data[0], len); buf[len] = 0; printDebug("WRITE-LCD: %s", buf); report(ReportType_lcdMessage, buf); - } else if (addr >= MEMADDR(0x7f0000)) { - printDebug("Reset"); + break; + case MR_Reset: + printDebug("RESET"); report(ReportType_devReset, NULL); partialManager->deactivateAll(); mt32ram = mt32default; @@ -854,18 +995,17 @@ void Synth::playSysexWithoutHeader(unsigned char device, const Bit8u *sysex, Bit parts[i]->refresh(); } isEnabled = false; - } else { - printDebug("Sysex write to unrecognised address %06x", SYSEXMEMADDR(addr)); + break; } } bool Synth::refreshSystem() { - memset(chantable,-1,sizeof(chantable)); + 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.system.chanAssign[i]); if (mt32ram.system.chanAssign[i] == 16 && parts[i] != NULL) { - parts[i]->allStop(); + parts[i]->allSoundOff(); } else { chantable[(int)mt32ram.system.chanAssign[i]] = (char)i; } @@ -894,8 +1034,8 @@ bool Synth::refreshSystem() { 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)) { + masterVolume = (Bit16u)(mt32ram.system.masterVol * 32767 / 100); + if (!tables.init(this, pcmWaves, (float)myProp.sampleRate, masterTune)) { report(ReportType_errorSampleRate, NULL); return false; } @@ -984,38 +1124,31 @@ void Synth::render(Bit16s *stream, Bit32u len) { } } -void Synth::doRender(Bit16s * stream,Bit32u len) { - Bit32u m; - +void Synth::doRender(Bit16s *stream, Bit32u len) { 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); - hasOutput = true; } } } - // No point in doing reverb on a mute buffer... - if (hasOutput) { - m=0; - for (unsigned int i = 0; i < len; i++) { - sndbufl[i] = (float)stream[m] / 32767.0f; - m++; - sndbufr[i] = (float)stream[m] / 32767.0f; - m++; - } - reverbModel->processreplace(sndbufl, sndbufr, outbufl, outbufr, len, 1); - m=0; - for (unsigned int i = 0; i < len; i++) { - stream[m] = (Bit16s)(outbufl[i] * 32767.0f); - m++; - stream[m] = (Bit16s)(outbufr[i] * 32767.0f); - m++; - } + Bit32u m = 0; + for (unsigned int i = 0; i < len; i++) { + sndbufl[i] = (float)stream[m] / 32767.0f; + m++; + sndbufr[i] = (float)stream[m] / 32767.0f; + m++; + } + reverbModel->processreplace(sndbufl, sndbufr, outbufl, outbufr, len, 1); + m=0; + for (unsigned int i = 0; i < len; i++) { + stream[m] = (Bit16s)(outbufl[i] * 32767.0f); + m++; + stream[m] = (Bit16s)(outbufr[i] * 32767.0f); + m++; } for (unsigned int i = 0; i < MT32EMU_MAX_PARTIALS; i++) { if (!partialManager->shouldReverb(i)) { @@ -1045,4 +1178,14 @@ void Synth::doRender(Bit16s * stream,Bit32u len) { #endif } +const Partial *Synth::getPartial(unsigned int partialNum) const { + return partialManager->getPartial(partialNum); +} + +const Part *Synth::getPart(unsigned int partNum) const { + if (partNum > 8) + return NULL; + return parts[partNum]; +} + } diff --git a/sound/softsynth/mt32/synth.h b/sound/softsynth/mt32/synth.h index 1501593cbd..9d57c8d3cd 100644 --- a/sound/softsynth/mt32/synth.h +++ b/sound/softsynth/mt32/synth.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2003-2004 Various contributors +/* Copyright (c) 2003-2005 Various contributors * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to @@ -28,10 +28,6 @@ class revmodel; namespace MT32Emu { -const int ROMSIZE = 512 * 1024; -const int PCMSIZE = ROMSIZE / 2; -const int GRAN = 512; - class File; class TableInitialiser; class Partial; @@ -104,28 +100,118 @@ typedef void (*recalcStatusCallback)(int percDone); // callback routine, no status is reported. bool RecalcWaveforms(char * baseDir, int sampRate, recalcStatusCallback callBack); -typedef float (*iir_filter_type)(float input,float *hist1_ptr, float *coef_ptr, int revLevel); +typedef float (*iir_filter_type)(float input,float *hist1_ptr, float *coef_ptr); + +const Bit8u SYSEX_MANUFACTURER_ROLAND = 0x41; + +const Bit8u SYSEX_MDL_MT32 = 0x16; +const Bit8u SYSEX_MDL_D50 = 0x14; + +const Bit8u SYSEX_CMD_RQ1 = 0x11; // Request data #1 +const Bit8u SYSEX_CMD_DT1 = 0x12; // Data set 1 +const Bit8u SYSEX_CMD_WSD = 0x40; // Want to send data +const Bit8u SYSEX_CMD_RQD = 0x41; // Request data +const Bit8u SYSEX_CMD_DAT = 0x42; // Data set +const Bit8u SYSEX_CMD_ACK = 0x43; // Acknowledge +const Bit8u SYSEX_CMD_EOD = 0x45; // End of data +const Bit8u SYSEX_CMD_ERR = 0x4E; // Communications error +const Bit8u SYSEX_CMD_RJC = 0x4F; // Rejection + +const unsigned int CONTROL_ROM_SIZE = 64 * 1024; + +struct ControlROMPCMStruct +{ + Bit8u pos; + Bit8u len; + Bit8u pitchLSB; + Bit8u pitchMSB; +}; + +struct ControlROMMap { + Bit16u idPos; + Bit16u idLen; + const char *idBytes; + Bit16u pcmTable; + Bit16u pcmCount; + Bit16u timbreAMap; + Bit16u timbreAOffset; + Bit16u timbreBMap; + Bit16u timbreBOffset; + Bit16u timbreRMap; + Bit16u timbreRCount; + Bit16u rhythmSettings; + Bit16u rhythmSettingsCount; + Bit16u reserveSettings; + Bit16u panSettings; + Bit16u programSettings; +}; + +enum MemoryRegionType { + MR_PatchTemp, MR_RhythmTemp, MR_TimbreTemp, MR_Patches, MR_Timbres, MR_System, MR_Display, MR_Reset +}; + +class MemoryRegion { +public: + MemoryRegionType type; + Bit32u startAddr, entrySize, entries; + + int lastTouched(Bit32u addr, Bit32u len) const { + return (offset(addr) + len - 1) / entrySize; + } + int firstTouchedOffset(Bit32u addr) const { + return offset(addr) % entrySize; + } + int firstTouched(Bit32u addr) const { + return offset(addr) / entrySize; + } + Bit32u regionEnd() const { + return startAddr + entrySize * entries; + } + bool contains(Bit32u addr) const { + return addr >= startAddr && addr < regionEnd(); + } + int offset(Bit32u addr) const { + return addr - startAddr; + } + Bit32u getClampedLen(Bit32u addr, Bit32u len) const { + if (addr + len > regionEnd()) + return regionEnd() - addr; + return len; + } + Bit32u next(Bit32u addr, Bit32u len) const { + if (addr + len > regionEnd()) { + return regionEnd() - addr; + } + return 0; + } +}; + class Synth { friend class Part; friend class RhythmPart; friend class Partial; -friend class TableInitialiser; +friend class Tables; private: bool isEnabled; iir_filter_type iirFilter; - PCMWaveEntry PCMList[128]; + PCMWaveEntry *pcmWaves; // Array + + const ControlROMMap *controlROMMap; + Bit8u controlROMData[CONTROL_ROM_SIZE]; + Bit16s *pcmROMData; + int pcmROMSize; // This is in 16-bit samples, therefore half the number of bytes in the ROM - Bit8u controlROMData[64 * 1024]; - Bit16s romfile[PCMSIZE + GRAN]; Bit8s chantable[32]; #if MT32EMU_MONITOR_PARTIALS == 1 static Bit32s samplepos = 0; #endif + Tables tables; + MemParams mt32ram, mt32default; revmodel *reverbModel; @@ -149,19 +235,23 @@ private: bool loadPreset(File *file); 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, const Bit8u *sysex, Bit32u len); + + void playAddressedSysex(unsigned char channel, const Bit8u *sysex, Bit32u len); + void readSysex(unsigned char channel, const Bit8u *sysex, Bit32u len); + void writeMemoryRegion(const MemoryRegion *region, Bit32u addr, Bit32u len, const Bit8u *data); + void readMemoryRegion(const MemoryRegion *region, Bit32u addr, Bit32u len, Bit8u *data); bool loadControlROM(const 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 initPCMList(Bit16u mapAddress, Bit16u count); + bool initRhythmTimbres(Bit16u mapAddress, Bit16u count); + bool initTimbres(Bit16u mapAddress, Bit16u offset, int startTimbre); + bool initRhythmTimbre(int drumNum, const Bit8u *mem, unsigned int memLen); bool refreshSystem(); + protected: int report(ReportType type, const void *reportData); File *openFile(const char *filename, File::OpenMode mode); @@ -183,16 +273,26 @@ public: // Sends a 4-byte MIDI message to the MT-32 for immediate playback void playMsg(Bit32u msg); + void playMsgOnPart(unsigned char part, unsigned char code, unsigned char note, unsigned char velocity); // Sends a string of Sysex commands to the MT-32 for immediate interpretation // The length is in bytes void playSysex(const Bit8u *sysex, Bit32u len); void playSysexWithoutFraming(const Bit8u *sysex, Bit32u len); + void playSysexWithoutHeader(unsigned char device, unsigned char command, const Bit8u *sysex, Bit32u len); + void writeSysex(unsigned char channel, 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, // one sample is 4 bytes) void render(Bit16s * stream, Bit32u len); + + const Partial *getPartial(unsigned int partialNum) const; + + void readMemory(Bit32u addr, Bit32u len, Bit8u *data); + + // partNum should be 0..7 for Part 1..8, or 8 for Rhythm + const Part *getPart(unsigned int partNum) const; }; } diff --git a/sound/softsynth/mt32/tables.cpp b/sound/softsynth/mt32/tables.cpp index 4a17165733..0acaa51df8 100644 --- a/sound/softsynth/mt32/tables.cpp +++ b/sound/softsynth/mt32/tables.cpp @@ -1,4 +1,4 @@ -/* Copyright (c) 2003-2004 Various contributors +/* Copyright (c) 2003-2005 Various contributors * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to @@ -30,8 +30,8 @@ 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}; +static const double tvcatconst[5] = {0.0, 0.002791309, 0.005942882, 0.012652792, 0.026938637}; +static const double tvcatmult[5] = {1.0, 1.072662811, 1.169129367, 1.288579123, 1.229630539}; // These are division constants for the TVF depth key follow static const Bit32u depexp[5] = {3000, 950, 485, 255, 138}; @@ -40,36 +40,6 @@ static const Bit32u depexp[5] = {3000, 950, 485, 255, 138}; static const double tkcatconst[5] = {0.0, 0.005853144, 0.011148054, 0.019086143, 0.043333215}; 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]; - -// Some optimization stuff -Bit32s keytable[217]; -Bit16s sintable[65536]; -Bit32u lfotable[101]; -Bit32s penvtable[16][101]; -Bit32s filveltable[128][101]; -Bit32s veltkeytable[5][128]; -Bit32s pulsetable[101]; -Bit32s pulseoffset[101]; -Bit32s ampbiastable[13][128]; -Bit32s fbiastable[15][128]; -float filtcoeff[FILTERGRAN][31][8]; -Bit32s finetable[201]; -Bit32u lfoptable[101][101]; -Bit32s ampveltable[128][64]; -Bit32s pwveltable[15][128]; -Bit32s envtimetable[101]; -Bit32s decaytimetable[101]; -Bit32s lasttimetable[101]; -Bit32s voltable[128]; -float ResonFactor[31]; -float ResonInv[31]; - -NoteLookup noteLookups[NUM_NOTES]; - // Begin filter stuff // Pre-warp the coefficients of a numerator or denominator. @@ -169,68 +139,66 @@ static void initFilter(float fs, float fc, float *icoeff, float Q) { icoeff[0] = (float)k; } -static void initFiltCoeff(float samplerate) { +void Tables::initFiltCoeff(float samplerate) { for (int j = 0; j < FILTERGRAN; j++) { for (int res = 0; res < 31; res++) { - float tres = ResonFactor[res]; - initFilter((float)samplerate, (((float)(j+1.0)/FILTERGRAN)) * ((float)samplerate/2), filtcoeff[j][res], tres); + float tres = resonanceFactor[res]; + initFilter((float)samplerate, (((float)(j+1.0)/FILTERGRAN)) * ((float)samplerate/2), filtCoeff[j][res], tres); } } } -static void initEnvelopes(float samplerate) { +void Tables::initEnvelopes(float samplerate) { for (int lf = 0; lf <= 100; lf++) { float elf = (float)lf; // General envelope - float logtime = elf * 0.088362939f; - envtimetable[lf] = (int)((exp(logtime)/312.12) * (float)samplerate); - - // Decay envelope -- shorter for some reason - // This is also the timing for the envelope right before the - // amp and filter envelope sustains - - lasttimetable[lf] = decaytimetable[lf] = (int)((exp(logtime)/(312.12*2)) * (float)samplerate); - //lasttimetable[lf] = (int)((exp(logtime)/(312.12*6)) * (float)samplerate); - - float mv = (float)lf / 100.0f; - float pt = mv - 0.5f; - if (pt < 0) - pt = 0; + // This formula fits observation of the CM-32L by +/- 0.03s or so for the second time value in the filter, + // when all other times were 0 and all levels were 100. Note that variations occur depending on the level + // delta of the section, which we're not fully emulating. + float seconds = powf(2.0f, (elf / 8.0f) + 7.0f) / 32768.0f; + int samples = (int)(seconds * samplerate); + envTime[lf] = samples; + + // Cap on envelope times depending on the level delta + if(elf == 0) { + envDeltaMaxTime[lf] = 63; + } else { + float cap = 11 * log(elf) + 64; + if(cap > 100.0f) { + cap = 100.0f; + } + envDeltaMaxTime[lf] = (int)cap; + } + - pulsetable[lf] = (int)(pt * 215.04f) + 128; + // This (approximately) represents the time durations when the target level is 0. + // Not sure why this is a special case, but it's seen to be from the real thing. + seconds = powf(2, (elf / 8.0f) + 6) / 32768.0f; + envDecayTime[lf] = (int)(seconds * samplerate); // I am certain of this: Verified by hand LFO log - lfotable[lf] = (Bit32u)(((float)samplerate) / (powf(1.088883372f,(float)lf) * 0.021236044f)); - - //LOG(LOG_ERROR|LOG_MISC,"lf %d = lfo %d pulsetable %d", lf, lfotable[lf], pulsetable[lf]); + lfoPeriod[lf] = (Bit32u)(((float)samplerate) / (powf(1.088883372f, (float)lf) * 0.021236044f)); } } -void TableInitialiser::initMT32ConstantTables(Synth *synth) { - if (initialisedSampleRate > 0.0f) { - return; - } +void Tables::initMT32ConstantTables(Synth *synth) { int lf; synth->printDebug("Initialising Pitch Tables"); for (lf = -108; lf <= 108; lf++) { - keytable[lf + 108] = (int)(256 * powf(2.0f, (float)(lf / 24.0f))); + tvfKeyfollowMult[lf + 108] = (int)(256 * powf(2.0f, (float)(lf / 24.0f))); //synth->printDebug("KT %d = %d", f, keytable[f+108]); } - int res; - float fres; - for (res = 0; res < 31; res++) { - fres = (float)res / 30.0f; - ResonFactor[res] = (powf(2.0f, logf(powf(fres, 16.0f))) * 2.5f) + 1.0f; - ResonInv[res] = 1 / ResonFactor[res]; + for (int res = 0; res < 31; res++) { + resonanceFactor[res] = powf((float)res / 30.0f, 5.0f) + 1.0f; } int period = 65536; for (int ang = 0; ang < period; ang++) { int halfang = (period / 2); - int angval = ang % halfang; + int angval = ang % halfang; float tval = (((float)angval / (float)halfang) - 0.5f) * 2; if (ang >= halfang) tval = -tval; @@ -242,14 +210,14 @@ void TableInitialiser::initMT32ConstantTables(Synth *synth) { for (velt = 0; velt < 128; velt++) { for (dep = 0; dep < 5; dep++) { if (dep > 0) { - float ff = (float)(exp(3.5f*tvcatconst[dep] * (59.0f-(float)velt)) * tvcatmult[dep]); + float ff = (float)(exp(3.5f * tvcatconst[dep] * (59.0f - (float)velt)) * tvcatmult[dep]); tempdep = 256.0f * ff; - veltkeytable[dep][velt] = (int)tempdep; + envTimeVelfollowMult[dep][velt] = (int)tempdep; //if ((velt % 16) == 0) { // synth->printDebug("Key %d, depth %d, factor %d", velt, dep, (int)tempdep); //} } else - veltkeytable[dep][velt] = 256; + envTimeVelfollowMult[dep][velt] = 256; } for (dep = -7; dep < 8; dep++) { @@ -257,7 +225,7 @@ void TableInitialiser::initMT32ConstantTables(Synth *synth) { fldep = powf(fldep,2.5f); if (dep < 0) fldep = fldep * -1.0f; - pwveltable[dep+7][velt] = Bit32s((fldep * (float)velt * 100) / 128.0); + pwVelfollowAdd[dep+7][velt] = Bit32s((fldep * (float)velt * 100) / 128.0); } } @@ -269,46 +237,54 @@ void TableInitialiser::initMT32ConstantTables(Synth *synth) { float fbase; if (velt > 64) - filveltable[velt][dep] = (int)(flogdep * 256.0); + synth->tables.tvfVelfollowMult[velt][dep] = (int)(flogdep * 256.0); else { //lff = 1 - (pow(((128.0 - (float)lf) / 64.0),.25) * ((float)velt / 96)); fbase = 1 - (powf(((float)dep / 100.0f),.25f) * ((float)(64-velt) / 96.0f)); - filveltable[velt][dep] = (int)(fbase * 256.0); + synth->tables.tvfVelfollowMult[velt][dep] = (int)(fbase * 256.0); } //synth->printDebug("Filvel dep %d velt %d = %x", dep, velt, filveltable[velt][dep]); } } - for (lf = 0; lf <= 200; lf++) { - //FIXME:KG: I'm fairly sure this is wrong... lf=100 should yield no fine-tuning (4096)? - finetable[lf] = (int)((powf(2.0f, (((float)lf / 200.0f) - 1.0f) / 12.0f)) * 4096.0f); - - // FIXME:KG: This now gives a range of -1 .. 1 semitone. Should be correct, but check - //finetable[lf] = (int)((powf(2.0f, (((float)lf / 100.0f) - 1.0f) / 12.0f)) * 4096.0f); - } - - float lff; for (lf = 0; lf < 128; lf++) { - for (velt = 0; velt < 64; velt++) { - lff = 1 - (powf(((128.0f - (float)lf) / 64.0f), 0.25f) * ((float)velt / 96)); - ampveltable[lf][velt] = (int)(lff * 256.0); - //synth->printDebug("Ampveltable: %d, %d = %d", lf, velt, ampveltable[lf][velt]); + float veloFract = lf / 127.0f; + for (int velsens = 0; velsens <= 100; velsens++) { + float sensFract = (velsens - 50) / 50.0f; + if (velsens < 50) { + tvaVelfollowMult[lf][velsens] = FIXEDPOINT_MAKE(1.0f / powf(2.0f, veloFract * -sensFract * 127.0f / 20.0f), 8); + } else { + tvaVelfollowMult[lf][velsens] = FIXEDPOINT_MAKE(1.0f / powf(2.0f, (1.0f - veloFract) * sensFract * 127.0f / 20.0f), 8); + } } } - for (lf = 0; lf < 128; lf++) { - // Converts MIDI velocity to volume. - voltable[lf] = FIXEDPOINT_MAKE(powf((float)lf / 127.0f, FLOAT_LN), 7); + for (lf = 0; lf <= 100; lf++) { + // Converts the 0-100 range used by the MT-32 to volume multiplier + volumeMult[lf] = FIXEDPOINT_MAKE(powf((float)lf / 100.0f, FLOAT_LN), 7); } + + for (lf = 0; lf <= 100; lf++) { + float mv = lf / 100.0f; + float pt = mv - 0.5f; + if (pt < 0) + pt = 0; + + // Original (CC version) + //pwFactor[lf] = (int)(pt * 210.04f) + 128; + + // Approximation from sample comparison + pwFactor[lf] = (int)(pt * 179.0f) + 128; + } + for (unsigned int i = 0; i < MAX_SAMPLE_OUTPUT; i++) { int myRand; myRand = rand(); - int origRand = myRand; - //myRand = ((myRand - 16383) * WGAMP) >> 16; + //myRand = ((myRand - 16383) * 7168) >> 16; // This one is slower but works with all values of RAND_MAX - myRand = (int)((origRand - RAND_MAX / 2) / (float)RAND_MAX * (WGAMP / 2)); + myRand = (int)((myRand - RAND_MAX / 2) / (float)RAND_MAX * (7168 / 2)); //FIXME:KG: Original ultimately set the lowest two bits to 0, for no obvious reason - smallnoise[i] = (Bit16s)myRand; + noiseBuf[i] = (Bit16s)myRand; } float tdist; @@ -360,10 +336,10 @@ void TableInitialiser::initMT32ConstantTables(Synth *synth) { finalval = 4096.0f * powf(2, lfp); pval = (int)finalval; - penvtable[lf][depat] = pval; + pitchEnvVal[lf][depat] = pval; //synth->printDebug("lf %d depat %d pval %d tlf %f lfp %f", lf,depat,pval, tlf, lfp); } else { - penvtable[lf][depat] = 4096; + pitchEnvVal[lf][depat] = 4096; //synth->printDebug("lf %d depat %d pval 4096", lf, depat); } } @@ -379,7 +355,7 @@ void TableInitialiser::initMT32ConstantTables(Synth *synth) { pval = (int)finalval; - lfoptable[lf][depat] = pval; + lfoShift[lf][depat] = pval; //synth->printDebug("lf %d depat %d pval %x", lf,depat,pval); } @@ -391,15 +367,27 @@ void TableInitialiser::initMT32ConstantTables(Synth *synth) { if (lf == 0) { amplog = 0; dval = 1; - ampbiastable[lf][distval] = 256; + tvaBiasMult[lf][distval] = 256; } else { + /* amplog = powf(1.431817011f, (float)lf) / FLOAT_PI; dval = ((128.0f - (float)distval) / 128.0f); amplog = expf(amplog); dval = powf(amplog, dval) / amplog; - ampbiastable[lf][distval] = (int)(dval * 256.0); + tvaBiasMult[lf][distval] = (int)(dval * 256.0); + */ + // Lets assume for a second it's linear + + // Distance of full volume reduction + amplog = (float)(12.0f / (float)lf) * 24.0f; + if(distval > amplog) { + tvaBiasMult[lf][distval] = 0; + } else { + dval = (amplog - (float)distval) / amplog; + tvaBiasMult[lf][distval] = (int)(dval * 256.0f); + } } - //synth->printDebug("Ampbias lf %d distval %d = %f (%x) %f", lf, distval, dval, ampbiastable[lf][distval],amplog); + //synth->printDebug("Ampbias lf %d distval %d = %f (%x) %f", lf, distval, dval, tvaBiasMult[lf][distval],amplog); } } @@ -410,7 +398,7 @@ void TableInitialiser::initMT32ConstantTables(Synth *synth) { if (lf == 7) { amplog = 0; dval = 1; - fbiastable[lf][distval] = 256; + tvfBiasMult[lf][distval] = 256; } else { //amplog = pow(1.431817011, filval) / FLOAT_PI; amplog = powf(1.531817011f, filval) / FLOAT_PI; @@ -418,30 +406,30 @@ void TableInitialiser::initMT32ConstantTables(Synth *synth) { amplog = expf(amplog); dval = powf(amplog,dval)/amplog; if (lf < 8) { - fbiastable[lf][distval] = (int)(dval * 256.0f); + tvfBiasMult[lf][distval] = (int)(dval * 256.0f); } else { dval = powf(dval, 0.3333333f); if (dval < 0.01f) dval = 0.01f; dval = 1 / dval; - fbiastable[lf][distval] = (int)(dval * 256.0f); + tvfBiasMult[lf][distval] = (int)(dval * 256.0f); } } - //synth->printDebug("Fbias lf %d distval %d = %f (%x) %f", lf, distval, dval, fbiastable[lf][distval],amplog); + //synth->printDebug("Fbias lf %d distval %d = %f (%x) %f", lf, distval, dval, tvfBiasMult[lf][distval],amplog); } } } // Per-note table initialisation follows -static void initSaw(NoteLookup *noteLookup, Bit32s div) { +static void initSaw(NoteLookup *noteLookup, Bit32s div2) { + int tmpdiv = div2 << 16; for (int rsaw = 0; rsaw <= 100; rsaw++) { float fsaw; if (rsaw < 50) fsaw = 50.0f; else fsaw = (float)rsaw; - int tmpdiv = div << 17; //(66 - (((A8 - 50) / 50) ^ 0.63) * 50) / 132 float sawfact = (66.0f - (powf((fsaw - 50.0f) / 50.0f, 0.63f) * 50.0f)) / 132.0f; @@ -450,11 +438,11 @@ static void initSaw(NoteLookup *noteLookup, Bit32s div) { } } -static void initDep(NoteLookup *noteLookup, float f) { +static void initDep(KeyLookup *keyLookup, float f) { for (int dep = 0; dep < 5; dep++) { if (dep == 0) { - noteLookup->fildepTable[dep] = 256; - noteLookup->timekeyTable[dep] = 256; + keyLookup->envDepthMult[dep] = 256; + keyLookup->envTimeMult[dep] = 256; } else { float depfac = 3000.0f; float ff, tempdep; @@ -462,20 +450,32 @@ static void initDep(NoteLookup *noteLookup, float f) { ff = (f - (float)MIDDLEC) / depfac; tempdep = powf(2, ff) * 256.0f; - noteLookup->fildepTable[dep] = (int)tempdep; + keyLookup->envDepthMult[dep] = (int)tempdep; ff = (float)(exp(tkcatconst[dep] * ((float)MIDDLEC - f)) * tkcatmult[dep]); - noteLookup->timekeyTable[dep] = (int)(ff * 256.0f); + keyLookup->envTimeMult[dep] = (int)(ff * 256.0f); } } //synth->printDebug("F %f d1 %x d2 %x d3 %x d4 %x d5 %x", f, noteLookup->fildepTable[0], noteLookup->fildepTable[1], noteLookup->fildepTable[2], noteLookup->fildepTable[3], noteLookup->fildepTable[4]); } -File *TableInitialiser::initWave(Synth *synth, NoteLookup *noteLookup, float ampsize, float div, File *file) { - int iDiv = (int)div; - noteLookup->waveformSize[0] = iDiv << 2; - noteLookup->waveformSize[1] = iDiv << 2; - noteLookup->waveformSize[2] = iDiv << 3; +Bit16s Tables::clampWF(Synth *synth, const char *n, float ampVal, double input) { + Bit32s x = (Bit32s)(input * ampVal); + if (x < -ampVal - 1) { + synth->printDebug("%s==%d<-WGAMP-1!", n, x); + x = (Bit32s)(-ampVal - 1); + } else if (x > ampVal) { + synth->printDebug("%s==%d>WGAMP!", n, x); + x = (Bit32s)ampVal; + } + return (Bit16s)x; +} + +File *Tables::initWave(Synth *synth, NoteLookup *noteLookup, float ampVal, float div2, File *file) { + int iDiv2 = (int)div2; + noteLookup->waveformSize[0] = iDiv2 << 1; + noteLookup->waveformSize[1] = iDiv2 << 1; + noteLookup->waveformSize[2] = iDiv2 << 2; for (int i = 0; i < 3; i++) { if (noteLookup->waveforms[i] == NULL) { noteLookup->waveforms[i] = new Bit16s[noteLookup->waveformSize[i]]; @@ -495,27 +495,27 @@ File *TableInitialiser::initWave(Synth *synth, NoteLookup *noteLookup, float amp } } if (file == NULL) { - double sd = DOUBLE_PI / (div * 2.0); + double sd = DOUBLE_PI / div2; - for (int fa = 0; fa < (iDiv << 2); fa++) { + for (int fa = 0; fa < (iDiv2 << 1); fa++) { + // sa ranges from 0 to 2PI double sa = fa * sd; -#if 0 - //FIXME:KG: Credit Timo Strunk (bastardo on #scummvm) for help with this! - double saw = 0.5 * DOUBLE_PI - sa / 2; -#else + // Calculate a sample for the bandlimited sawtooth wave double saw = 0.0; - for (int sinus = 1; sinus < div; sinus++) { - double fsinus = (double)sinus; - saw += sin(fsinus * sa) / fsinus; + int sincs = iDiv2 >> 1; + double sinus = 1.0; + for (int sincNum = 1; sincNum <= sincs; sincNum++) { + saw += sin(sinus * sa) / sinus; + sinus++; } -#endif // This works pretty well - noteLookup->waveforms[0][fa] = (Bit16s)(saw * -ampsize / 2); - noteLookup->waveforms[1][fa] = (Bit16s)(cos(sa / 2.0) * -ampsize); - noteLookup->waveforms[2][fa * 2] = (Bit16s)(cos(sa - DOUBLE_PI) * -ampsize); - noteLookup->waveforms[2][fa * 2 + 1] = (Bit16s)(cos((sa + (sd / 2)) - DOUBLE_PI) * -ampsize); + // Multiplied by 0.84 so that the spikes caused by bandlimiting don't overdrive the amplitude + noteLookup->waveforms[0][fa] = clampWF(synth, "saw", ampVal, -saw / (0.5 * DOUBLE_PI) * 0.84); + noteLookup->waveforms[1][fa] = clampWF(synth, "cos", ampVal, -cos(sa / 2.0)); + noteLookup->waveforms[2][fa * 2] = clampWF(synth, "cosoff_0", ampVal, -cos(sa - DOUBLE_PI)); + noteLookup->waveforms[2][fa * 2 + 1] = clampWF(synth, "cosoff_1", ampVal, -cos((sa + (sd / 2)) - DOUBLE_PI)); } } return file; @@ -547,11 +547,12 @@ static void initNFiltTable(NoteLookup *noteLookup, float freq, float rate) { float cfmult = (float)cf; for (int tf = 0;tf <= 100; tf++) { - float tfadd = (float)(tf - 0); - if (tfadd < 0) - tfadd = 0; + float tfadd = (float)tf; - float freqsum = expf((cfmult + tfadd) / 30.0f) / 4.0f; + //float freqsum = expf((cfmult + tfadd) / 30.0f) / 4.0f; + //float freqsum = 0.15f * expf(0.45f * ((cfmult + tfadd) / 10.0f)); + + float freqsum = powf(2.0f, ((cfmult + tfadd) - 40.0f) / 16.0f); noteLookup->nfiltTable[cf][tf] = (int)((freq * freqsum) / (rate / 2) * FILTERGRAN); if (noteLookup->nfiltTable[cf][tf] >= ((FILTERGRAN * 15) / 16)) @@ -560,26 +561,25 @@ static void initNFiltTable(NoteLookup *noteLookup, float freq, float rate) { } } -File *TableInitialiser::initNote(Synth *synth, NoteLookup *noteLookup, float note, float rate, float masterTune, PCMWaveEntry pcmWaves[128], File *file) { - float ampsize = WGAMP; +File *Tables::initNote(Synth *synth, NoteLookup *noteLookup, float note, float rate, float masterTune, PCMWaveEntry *pcmWaves, File *file) { float freq = (float)(masterTune * pow(2.0, ((double)note - MIDDLEA) / 12.0)); - float div = rate / freq; - noteLookup->div = (int)div; + float div2 = rate * 2.0f / freq; + noteLookup->div2 = (int)div2; - if (noteLookup->div == 0) - noteLookup->div = 1; + if (noteLookup->div2 == 0) + noteLookup->div2 = 1; - initSaw(noteLookup, noteLookup->div); - initDep(noteLookup, note); + initSaw(noteLookup, noteLookup->div2); //synth->printDebug("Note %f; freq=%f, div=%f", note, freq, rate / freq); - file = initWave(synth, noteLookup, ampsize, div, file); + file = initWave(synth, noteLookup, (const float)WGAMP, div2, file); // Create the pitch tables - + if (noteLookup->wavTable == NULL) + noteLookup->wavTable = new Bit32u[synth->controlROMMap->pcmCount]; double rateMult = 32000.0 / rate; double tuner = freq * 65536.0f; - for (int pc = 0; pc < 128; pc++) { + for (int pc = 0; pc < synth->controlROMMap->pcmCount; pc++) { noteLookup->wavTable[pc] = (int)(tuner / pcmWaves[pc].tune * rateMult); } @@ -588,13 +588,13 @@ File *TableInitialiser::initNote(Synth *synth, NoteLookup *noteLookup, float not return file; } -bool TableInitialiser::initNotes(Synth *synth, PCMWaveEntry pcmWaves[128], float rate, float masterTune) { +bool Tables::initNotes(Synth *synth, PCMWaveEntry *pcmWaves, 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, 3}; + char version[4] = {0, 0, 0, 5}; sprintf(filename, "waveformcache-%d-%.2f.raw", intRate, masterTune); File *file = NULL; @@ -649,7 +649,7 @@ bool TableInitialiser::initNotes(Synth *synth, PCMWaveEntry pcmWaves[128], float 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); + synth->printDebug("Initialising note %s%d", NoteNames[f % 12], (f / 12) - 2); NoteLookup *noteLookup = ¬eLookups[f - LOWEST_NOTE]; file = initNote(synth, noteLookup, (float)f, rate, masterTune, pcmWaves, file); progress = (f - LOWEST_NOTE + 1) / (float)NUM_NOTES; @@ -690,7 +690,7 @@ bool TableInitialiser::initNotes(Synth *synth, PCMWaveEntry pcmWaves[128], float return !abort; } -void TableInitialiser::freeNotes() { +void Tables::freeNotes() { for (int t = 0; t < 3; t++) { for (int m = 0; m < NUM_NOTES; m++) { if (noteLookups[m].waveforms[t] != NULL) { @@ -698,12 +698,22 @@ void TableInitialiser::freeNotes() { noteLookups[m].waveforms[t] = NULL; noteLookups[m].waveformSize[t] = 0; } + if (noteLookups[m].wavTable != NULL) { + delete[] noteLookups[m].wavTable; + noteLookups[m].wavTable = NULL; + } } } initialisedMasterTune = 0.0f; } -bool TableInitialiser::initMT32Tables(Synth *synth, PCMWaveEntry pcmWaves[128], float sampleRate, float masterTune) { +Tables::Tables() { + initialisedSampleRate = 0.0f; + initialisedMasterTune = 0.0f; + memset(¬eLookups, 0, sizeof(noteLookups)); +} + +bool Tables::init(Synth *synth, PCMWaveEntry *pcmWaves, float sampleRate, float masterTune) { if (sampleRate <= 0.0f) { synth->printDebug("Bad sampleRate (%d <= 0.0f)", sampleRate); return false; @@ -714,6 +724,9 @@ bool TableInitialiser::initMT32Tables(Synth *synth, PCMWaveEntry pcmWaves[128], if (initialisedSampleRate != sampleRate) { initFiltCoeff(sampleRate); initEnvelopes(sampleRate); + for (int key = 12; key <= 108; key++) { + initDep(&keyLookups[key - 12], (float)key); + } } if (initialisedSampleRate != sampleRate || initialisedMasterTune != masterTune) { freeNotes(); diff --git a/sound/softsynth/mt32/tables.h b/sound/softsynth/mt32/tables.h index 0c85796f4f..2858716587 100644 --- a/sound/softsynth/mt32/tables.h +++ b/sound/softsynth/mt32/tables.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2003-2004 Various contributors +/* Copyright (c) 2003-2005 Various contributors * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to @@ -33,67 +33,82 @@ const float FLOAT_LN = 2.3025850929940456840179914546844f; // Filter settings const int FILTERGRAN = 512; +// Amplitude of waveform generator +// FIXME: This value is the amplitude possible whilst avoiding +// overdriven values immediately after filtering when playing +// back SQ3MT.MID. Needs to be checked. +const int WGAMP = 12382; + const int MIDDLEC = 60; const int MIDDLEA = 69; // By this I mean "A above middle C" -//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? +// 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. +// Should we 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 Bit16s smallnoise[MAX_SAMPLE_OUTPUT]; - -// Some optimization stuff -extern Bit32s keytable[217]; -extern Bit16s sintable[65536]; -extern Bit32u lfotable[101]; -extern Bit32s penvtable[16][101]; -extern Bit32s filveltable[128][101]; -extern Bit32s veltkeytable[5][128]; -extern Bit32s pulsetable[101]; -extern Bit32s ampbiastable[13][128]; -extern Bit32s fbiastable[15][128]; -extern float filtcoeff[FILTERGRAN][31][8]; -extern Bit32s finetable[201]; -extern Bit32u lfoptable[101][101]; -extern Bit32s ampveltable[128][64]; -extern Bit32s pwveltable[15][128]; -extern Bit32s envtimetable[101]; -extern Bit32s decaytimetable[101]; -extern Bit32s lasttimetable[101]; -extern Bit32s voltable[128]; -extern float ResonInv[31]; - struct NoteLookup { - Bit32u div; - Bit32u wavTable[128]; + Bit32u div2; + Bit32u *wavTable; Bit32s sawTable[101]; - Bit32s fildepTable[5]; - Bit32s timekeyTable[5]; int filtTable[2][201]; int nfiltTable[101][101]; Bit16s *waveforms[3]; Bit32u waveformSize[3]; }; -extern NoteLookup noteLookups[NUM_NOTES]; +struct KeyLookup { + Bit32s envTimeMult[5]; // For envelope time adjustment for key pressed + Bit32s envDepthMult[5]; +}; -class TableInitialiser { - static void initMT32ConstantTables(Synth *synth); - static File *initWave(Synth *synth, NoteLookup *noteLookup, float ampsize, float div, File *file); - static bool initNotes(Synth *synth, PCMWaveEntry pcmWaves[128], float rate, float tuning); +class Tables { + float initialisedSampleRate; + float initialisedMasterTune; + void initMT32ConstantTables(Synth *synth); + static Bit16s clampWF(Synth *synth, const char *n, float ampVal, double input); + static File *initWave(Synth *synth, NoteLookup *noteLookup, float ampsize, float div2, File *file); + bool initNotes(Synth *synth, PCMWaveEntry pcmWaves[128], float rate, float tuning); + void initEnvelopes(float sampleRate); + void initFiltCoeff(float samplerate); public: - 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(); + // Constant LUTs + Bit32s tvfKeyfollowMult[217]; + Bit32s tvfVelfollowMult[128][101]; + Bit32s tvfBiasMult[15][128]; + Bit32u tvaVelfollowMult[128][101]; + Bit32s tvaBiasMult[13][128]; + Bit16s noiseBuf[MAX_SAMPLE_OUTPUT]; + Bit16s sintable[65536]; + Bit32s pitchEnvVal[16][101]; + Bit32s envTimeVelfollowMult[5][128]; + Bit32s pwVelfollowAdd[15][128]; + float resonanceFactor[31]; + Bit32u lfoShift[101][101]; + Bit32s pwFactor[101]; + Bit32s volumeMult[101]; + + // LUTs varying with sample rate + Bit32u envTime[101]; + Bit32u envDeltaMaxTime[101]; + Bit32u envDecayTime[101]; + Bit32u lfoPeriod[101]; + float filtCoeff[FILTERGRAN][31][8]; + + // Various LUTs for each note and key + NoteLookup noteLookups[NUM_NOTES]; + KeyLookup keyLookups[97]; + + Tables(); + bool init(Synth *synth, PCMWaveEntry pcmWaves[128], float sampleRate, float masterTune); + File *initNote(Synth *synth, NoteLookup *noteLookup, float note, float rate, float tuning, PCMWaveEntry pcmWaves[128], File *file); + void freeNotes(); }; } -- cgit v1.2.3