diff options
author | Filippos Karapetis | 2013-03-02 14:09:39 +0200 |
---|---|---|
committer | Filippos Karapetis | 2013-03-02 14:09:39 +0200 |
commit | f4cc45d367442f850baecd814003ec09790a43b0 (patch) | |
tree | bfc904c0e8ced825749fa910696fae1d24fe10ca /audio/softsynth/mt32/Partial.cpp | |
parent | 8884c240fc4683975cd76802b600c019ebcb260c (diff) | |
download | scummvm-rg350-f4cc45d367442f850baecd814003ec09790a43b0.tar.gz scummvm-rg350-f4cc45d367442f850baecd814003ec09790a43b0.tar.bz2 scummvm-rg350-f4cc45d367442f850baecd814003ec09790a43b0.zip |
MT32: Sync with the latest changes in munt
The major change is the addition of a refined wave generator based on
logarithmic fixed-point computations and LUTs
Diffstat (limited to 'audio/softsynth/mt32/Partial.cpp')
-rw-r--r-- | audio/softsynth/mt32/Partial.cpp | 438 |
1 files changed, 79 insertions, 359 deletions
diff --git a/audio/softsynth/mt32/Partial.cpp b/audio/softsynth/mt32/Partial.cpp index 0ef2f6242f..a0aec90ec4 100644 --- a/audio/softsynth/mt32/Partial.cpp +++ b/audio/softsynth/mt32/Partial.cpp @@ -35,7 +35,12 @@ static const float PAN_NUMERATOR_MASTER[] = {0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, static const float PAN_NUMERATOR_SLAVE[] = {0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 7.0f, 7.0f, 7.0f, 7.0f, 7.0f, 7.0f, 7.0f}; Partial::Partial(Synth *useSynth, int useDebugPartialNum) : - synth(useSynth), debugPartialNum(useDebugPartialNum), sampleNum(0), tva(new TVA(this, &Ramp)), tvp(new TVP(this)), tvf(new TVF(this, &cutoffModifierRamp)) { + synth(useSynth), debugPartialNum(useDebugPartialNum), sampleNum(0) { + // Initialisation of tva, tvp and tvf uses 'this' pointer + // and thus should not be in the initializer list to avoid a compiler warning + tva = new TVA(this, &Ramp); + tvp = new TVP(this); + tvf = new TVF(this, &cutoffModifierRamp); ownerPart = -1; poly = NULL; pair = NULL; @@ -81,27 +86,23 @@ void Partial::deactivate() { ownerPart = -1; if (poly != NULL) { poly->partialDeactivated(this); - if (pair != NULL) { - pair->pair = NULL; - } } synth->partialStateChanged(this, tva->getPhase(), TVA_PHASE_DEAD); #if MT32EMU_MONITOR_PARTIALS > 2 synth->printDebug("[+%lu] [Partial %d] Deactivated", sampleNum, debugPartialNum); synth->printPartialUsage(sampleNum); #endif -} - -// DEPRECATED: This should probably go away eventually, it's currently only used as a kludge to protect our old assumptions that -// rhythm part notes were always played as key MIDDLEC. -int Partial::getKey() const { - if (poly == NULL) { - return -1; - } else if (ownerPart == 8) { - // FIXME: Hack, should go away after new pitch stuff is committed (and possibly some TVF changes) - return MIDDLEC; + if (isRingModulatingSlave()) { + pair->la32Pair.deactivate(LA32PartialPair::SLAVE); } else { - return poly->getKey(); + la32Pair.deactivate(LA32PartialPair::MASTER); + if (hasRingModulatingSlave()) { + pair->deactivate(); + pair = NULL; + } + } + if (pair != NULL) { + pair->pair = NULL; } } @@ -164,8 +165,6 @@ void Partial::startPartial(const Part *part, Poly *usePoly, const PatchCache *us pcmWave = &synth->pcmWaves[pcmNum]; } else { pcmWave = NULL; - wavePos = 0.0f; - lastFreq = 0.0; } // CONFIRMED: pulseWidthVal calculation is based on information from Mok @@ -176,26 +175,65 @@ void Partial::startPartial(const Part *part, Poly *usePoly, const PatchCache *us pulseWidthVal = 255; } - pcmPosition = 0.0f; pair = pairPartial; alreadyOutputed = false; tva->reset(part, patchCache->partialParam, rhythmTemp); tvp->reset(part, patchCache->partialParam); tvf->reset(patchCache->partialParam, tvp->getBasePitch()); + + LA32PartialPair::PairType pairType; + LA32PartialPair *useLA32Pair; + if (isRingModulatingSlave()) { + pairType = LA32PartialPair::SLAVE; + useLA32Pair = &pair->la32Pair; + } else { + pairType = LA32PartialPair::MASTER; + la32Pair.init(hasRingModulatingSlave(), mixType == 1); + useLA32Pair = &la32Pair; + } + if (isPCM()) { + useLA32Pair->initPCM(pairType, &synth->pcmROMData[pcmWave->addr], pcmWave->len, pcmWave->loop); + } else { + useLA32Pair->initSynth(pairType, (patchCache->waveform & 1) != 0, pulseWidthVal, patchCache->srcPartial.tvf.resonance + 1); + } + if (!hasRingModulatingSlave()) { + la32Pair.deactivate(LA32PartialPair::SLAVE); + } + // Temporary integration hack + stereoVolume.leftVol /= 8192.0f; + stereoVolume.rightVol /= 8192.0f; } -float Partial::getPCMSample(unsigned int position) { - if (position >= pcmWave->len) { - if (!pcmWave->loop) { - return 0; - } - position = position % pcmWave->len; +Bit32u Partial::getAmpValue() { + // SEMI-CONFIRMED: From sample analysis: + // (1) Tested with a single partial playing PCM wave 77 with pitchCoarse 36 and no keyfollow, velocity follow, etc. + // This gives results within +/- 2 at the output (before any DAC bitshifting) + // when sustaining at levels 156 - 255 with no modifiers. + // (2) Tested with a special square wave partial (internal capture ID tva5) at TVA envelope levels 155-255. + // This gives deltas between -1 and 0 compared to the real output. Note that this special partial only produces + // positive amps, so negative still needs to be explored, as well as lower levels. + // + // Also still partially unconfirmed is the behaviour when ramping between levels, as well as the timing. + // TODO: The tests above were performed using the float model, to be refined + Bit32u ampRampVal = 67117056 - ampRamp.nextValue(); + if (ampRamp.checkInterrupt()) { + tva->handleInterrupt(); } - return synth->pcmROMData[pcmWave->addr + position]; + return ampRampVal; } -unsigned long Partial::generateSamples(float *partialBuf, unsigned long length) { - const Tables &tables = Tables::getInstance(); +Bit32u Partial::getCutoffValue() { + if (isPCM()) { + return 0; + } + Bit32u cutoffModifierRampVal = cutoffModifierRamp.nextValue(); + if (cutoffModifierRamp.checkInterrupt()) { + tvf->handleInterrupt(); + } + return (tvf->getBaseCutoff() << 18) + cutoffModifierRampVal; +} + +unsigned long Partial::generateSamples(Bit16s *partialBuf, unsigned long length) { if (!isActive() || alreadyOutputed) { return 0; } @@ -203,310 +241,31 @@ unsigned long Partial::generateSamples(float *partialBuf, unsigned long length) synth->printDebug("[Partial %d] *** ERROR: poly is NULL at Partial::generateSamples()!", debugPartialNum); return 0; } - alreadyOutputed = true; - // Generate samples - for (sampleNum = 0; sampleNum < length; sampleNum++) { - float sample = 0; - Bit32u ampRampVal = ampRamp.nextValue(); - if (ampRamp.checkInterrupt()) { - tva->handleInterrupt(); - } - if (!tva->isPlaying()) { + if (!tva->isPlaying() || !la32Pair.isActive(LA32PartialPair::MASTER)) { deactivate(); break; } - - Bit16u pitch = tvp->nextPitch(); - - // SEMI-CONFIRMED: From sample analysis: - // (1) Tested with a single partial playing PCM wave 77 with pitchCoarse 36 and no keyfollow, velocity follow, etc. - // This gives results within +/- 2 at the output (before any DAC bitshifting) - // when sustaining at levels 156 - 255 with no modifiers. - // (2) Tested with a special square wave partial (internal capture ID tva5) at TVA envelope levels 155-255. - // This gives deltas between -1 and 0 compared to the real output. Note that this special partial only produces - // positive amps, so negative still needs to be explored, as well as lower levels. - // - // Also still partially unconfirmed is the behaviour when ramping between levels, as well as the timing. - -#if MT32EMU_ACCURATE_WG == 1 - float amp = EXP2F((32772 - ampRampVal / 2048) / -2048.0f); - float freq = EXP2F(pitch / 4096.0f - 16.0f) * SAMPLE_RATE; -#else - static const float ampFactor = EXP2F(32772 / -2048.0f); - float amp = EXP2I(ampRampVal >> 10) * ampFactor; - - static const float freqFactor = EXP2F(-16.0f) * SAMPLE_RATE; - float freq = EXP2I(pitch) * freqFactor; -#endif - - if (patchCache->PCMPartial) { - // Render PCM waveform - int len = pcmWave->len; - int intPCMPosition = (int)pcmPosition; - if (intPCMPosition >= len && !pcmWave->loop) { - // We're now past the end of a non-looping PCM waveform so it's time to die. - deactivate(); - break; - } - Bit32u pcmAddr = pcmWave->addr; - float positionDelta = freq * 2048.0f / SAMPLE_RATE; - - // Linear interpolation - float firstSample = synth->pcmROMData[pcmAddr + intPCMPosition]; - // We observe that for partial structures with ring modulation the interpolation is not applied to the slave PCM partial. - // It's assumed that the multiplication circuitry intended to perform the interpolation on the slave PCM partial - // is borrowed by the ring modulation circuit (or the LA32 chip has a similar lack of resources assigned to each partial pair). - if (pair == NULL || mixType == 0 || structurePosition == 0) { - sample = firstSample + (getPCMSample(intPCMPosition + 1) - firstSample) * (pcmPosition - intPCMPosition); - } else { - sample = firstSample; - } - - float newPCMPosition = pcmPosition + positionDelta; - if (pcmWave->loop) { - newPCMPosition = fmod(newPCMPosition, (float)pcmWave->len); - } - pcmPosition = newPCMPosition; - } else { - // Render synthesised waveform - wavePos *= lastFreq / freq; - lastFreq = freq; - - Bit32u cutoffModifierRampVal = cutoffModifierRamp.nextValue(); - if (cutoffModifierRamp.checkInterrupt()) { - tvf->handleInterrupt(); - } - float cutoffModifier = cutoffModifierRampVal / 262144.0f; - - // res corresponds to a value set in an LA32 register - Bit8u res = patchCache->srcPartial.tvf.resonance + 1; - - float resAmp; - { - // resAmp = EXP2F(1.0f - (32 - res) / 4.0f); - static const float resAmpFactor = EXP2F(-7); - resAmp = EXP2I(res << 10) * resAmpFactor; - } - - // The cutoffModifier may not be supposed to be directly added to the cutoff - - // it may for example need to be multiplied in some way. - // The 240 cutoffVal limit was determined via sample analysis (internal Munt capture IDs: glop3, glop4). - // More research is needed to be sure that this is correct, however. - float cutoffVal = tvf->getBaseCutoff() + cutoffModifier; - if (cutoffVal > 240.0f) { - cutoffVal = 240.0f; - } - - // Wave length in samples - float waveLen = SAMPLE_RATE / freq; - - // Init cosineLen - float cosineLen = 0.5f * waveLen; - if (cutoffVal > 128.0f) { -#if MT32EMU_ACCURATE_WG == 1 - cosineLen *= EXP2F((cutoffVal - 128.0f) / -16.0f); // found from sample analysis -#else - static const float cosineLenFactor = EXP2F(128.0f / -16.0f); - cosineLen *= EXP2I(Bit32u((256.0f - cutoffVal) * 256.0f)) * cosineLenFactor; -#endif - } - - // Start playing in center of first cosine segment - // relWavePos is shifted by a half of cosineLen - float relWavePos = wavePos + 0.5f * cosineLen; - if (relWavePos > waveLen) { - relWavePos -= waveLen; - } - - // Ratio of positive segment to wave length - float pulseLen = 0.5f; - if (pulseWidthVal > 128) { - // pulseLen = EXP2F((64 - pulseWidthVal) / 64); - static const float pulseLenFactor = EXP2F(-192 / 64); - pulseLen = EXP2I((256 - pulseWidthVal) << 6) * pulseLenFactor; - } - pulseLen *= waveLen; - - float hLen = pulseLen - cosineLen; - - // Ignore pulsewidths too high for given freq - if (hLen < 0.0f) { - hLen = 0.0f; - } - - // Ignore pulsewidths too high for given freq and cutoff - float lLen = waveLen - hLen - 2 * cosineLen; - if (lLen < 0.0f) { - lLen = 0.0f; - } - - // Correct resAmp for cutoff in range 50..66 - if ((cutoffVal >= 128.0f) && (cutoffVal < 144.0f)) { -#if MT32EMU_ACCURATE_WG == 1 - resAmp *= sinf(FLOAT_PI * (cutoffVal - 128.0f) / 32.0f); -#else - resAmp *= tables.sinf10[Bit32u(64 * (cutoffVal - 128.0f))]; -#endif - } - - // Produce filtered square wave with 2 cosine waves on slopes - - // 1st cosine segment - if (relWavePos < cosineLen) { -#if MT32EMU_ACCURATE_WG == 1 - sample = -cosf(FLOAT_PI * relWavePos / cosineLen); -#else - sample = -tables.sinf10[Bit32u(2048.0f * relWavePos / cosineLen) + 1024]; -#endif - } else - - // high linear segment - if (relWavePos < (cosineLen + hLen)) { - sample = 1.f; - } else - - // 2nd cosine segment - if (relWavePos < (2 * cosineLen + hLen)) { -#if MT32EMU_ACCURATE_WG == 1 - sample = cosf(FLOAT_PI * (relWavePos - (cosineLen + hLen)) / cosineLen); -#else - sample = tables.sinf10[Bit32u(2048.0f * (relWavePos - (cosineLen + hLen)) / cosineLen) + 1024]; -#endif - } else { - - // low linear segment - sample = -1.f; - } - - if (cutoffVal < 128.0f) { - - // Attenuate samples below cutoff 50 - // Found by sample analysis -#if MT32EMU_ACCURATE_WG == 1 - sample *= EXP2F(-0.125f * (128.0f - cutoffVal)); -#else - static const float cutoffAttenuationFactor = EXP2F(-0.125f * 128.0f); - sample *= EXP2I(Bit32u(512.0f * cutoffVal)) * cutoffAttenuationFactor; -#endif - } else { - - // Add resonance sine. Effective for cutoff > 50 only - float resSample = 1.0f; - - // Now relWavePos counts from the middle of first cosine - relWavePos = wavePos; - - // negative segments - if (!(relWavePos < (cosineLen + hLen))) { - resSample = -resSample; - relWavePos -= cosineLen + hLen; + la32Pair.generateNextSample(LA32PartialPair::MASTER, getAmpValue(), tvp->nextPitch(), getCutoffValue()); + if (hasRingModulatingSlave()) { + la32Pair.generateNextSample(LA32PartialPair::SLAVE, pair->getAmpValue(), pair->tvp->nextPitch(), pair->getCutoffValue()); + if (!pair->tva->isPlaying() || !la32Pair.isActive(LA32PartialPair::SLAVE)) { + pair->deactivate(); + if (mixType == 2) { + deactivate(); + break; } - - // Resonance sine WG -#if MT32EMU_ACCURATE_WG == 1 - resSample *= sinf(FLOAT_PI * relWavePos / cosineLen); -#else - resSample *= tables.sinf10[Bit32u(2048.0f * relWavePos / cosineLen) & 4095]; -#endif - - // Resonance sine amp - float resAmpFadeLog2 = -tables.resAmpFadeFactor[res >> 2] * (relWavePos / cosineLen); // seems to be exact -#if MT32EMU_ACCURATE_WG == 1 - float resAmpFade = EXP2F(resAmpFadeLog2); -#else - static const float resAmpFadeFactor = EXP2F(-30.0f); - float resAmpFade = (resAmpFadeLog2 < -30.0f) ? 0.0f : EXP2I(Bit32u((30.0f + resAmpFadeLog2) * 4096.0f)) * resAmpFadeFactor; -#endif - - // Now relWavePos set negative to the left from center of any cosine - relWavePos = wavePos; - - // negative segment - if (!(wavePos < (waveLen - 0.5f * cosineLen))) { - relWavePos -= waveLen; - } else - - // positive segment - if (!(wavePos < (hLen + 0.5f * cosineLen))) { - relWavePos -= cosineLen + hLen; - } - - // Fading to zero while within cosine segments to avoid jumps in the wave - // Sample analysis suggests that this window is very close to cosine - if (relWavePos < 0.5f * cosineLen) { -#if MT32EMU_ACCURATE_WG == 1 - resAmpFade *= 0.5f * (1.0f - cosf(FLOAT_PI * relWavePos / (0.5f * cosineLen))); -#else - resAmpFade *= 0.5f * (1.0f + tables.sinf10[Bit32s(2048.0f * relWavePos / (0.5f * cosineLen)) + 3072]); -#endif - } - - sample += resSample * resAmp * resAmpFade; - } - - // sawtooth waves - if ((patchCache->waveform & 1) != 0) { -#if MT32EMU_ACCURATE_WG == 1 - sample *= cosf(FLOAT_2PI * wavePos / waveLen); -#else - sample *= tables.sinf10[(Bit32u(4096.0f * wavePos / waveLen) & 4095) + 1024]; -#endif - } - - wavePos++; - - // wavePos isn't supposed to be > waveLen - if (wavePos > waveLen) { - wavePos -= waveLen; } } - - // Multiply sample with current TVA value - sample *= amp; - *partialBuf++ = sample; + *partialBuf++ = la32Pair.nextOutSample(); } unsigned long renderedSamples = sampleNum; sampleNum = 0; return renderedSamples; } -float *Partial::mixBuffersRingMix(float *buf1, float *buf2, unsigned long len) { - if (buf1 == NULL) { - return NULL; - } - if (buf2 == NULL) { - return buf1; - } - - while (len--) { - // FIXME: At this point we have no idea whether this is remotely correct... - *buf1 = *buf1 * *buf2 + *buf1; - buf1++; - buf2++; - } - return buf1; -} - -float *Partial::mixBuffersRing(float *buf1, float *buf2, unsigned long len) { - if (buf1 == NULL) { - return NULL; - } - if (buf2 == NULL) { - return NULL; - } - - while (len--) { - // FIXME: At this point we have no idea whether this is remotely correct... - *buf1 = *buf1 * *buf2; - buf1++; - buf2++; - } - return buf1; -} - bool Partial::hasRingModulatingSlave() const { return pair != NULL && structurePosition == 0 && (mixType == 1 || mixType == 2); } @@ -538,53 +297,14 @@ bool Partial::produceOutput(float *leftBuf, float *rightBuf, unsigned long lengt synth->printDebug("[Partial %d] *** ERROR: poly is NULL at Partial::produceOutput()!", debugPartialNum); return false; } - - float *partialBuf = &myBuffer[0]; - unsigned long numGenerated = generateSamples(partialBuf, length); - if (mixType == 1 || mixType == 2) { - float *pairBuf; - unsigned long pairNumGenerated; - if (pair == NULL) { - pairBuf = NULL; - pairNumGenerated = 0; - } else { - pairBuf = &pair->myBuffer[0]; - pairNumGenerated = pair->generateSamples(pairBuf, numGenerated); - // pair will have been set to NULL if it deactivated within generateSamples() - if (pair != NULL) { - if (!isActive()) { - pair->deactivate(); - pair = NULL; - } else if (!pair->isActive()) { - pair = NULL; - } - } - } - if (pairNumGenerated > 0) { - if (mixType == 1) { - mixBuffersRingMix(partialBuf, pairBuf, pairNumGenerated); - } else { - mixBuffersRing(partialBuf, pairBuf, pairNumGenerated); - } - } - if (numGenerated > pairNumGenerated) { - if (mixType == 2) { - numGenerated = pairNumGenerated; - deactivate(); - } - } - } - - for (unsigned int i = 0; i < numGenerated; i++) { - *leftBuf++ = partialBuf[i] * stereoVolume.leftVol; - } + unsigned long numGenerated = generateSamples(myBuffer, length); for (unsigned int i = 0; i < numGenerated; i++) { - *rightBuf++ = partialBuf[i] * stereoVolume.rightVol; + *leftBuf++ = myBuffer[i] * stereoVolume.leftVol; + *rightBuf++ = myBuffer[i] * stereoVolume.rightVol; } - while (numGenerated < length) { + for (; numGenerated < length; numGenerated++) { *leftBuf++ = 0.0f; *rightBuf++ = 0.0f; - numGenerated++; } return true; } |