aboutsummaryrefslogtreecommitdiff
path: root/audio/softsynth/mt32/Partial.cpp
diff options
context:
space:
mode:
authorFilippos Karapetis2013-03-02 14:09:39 +0200
committerFilippos Karapetis2013-03-02 14:09:39 +0200
commitf4cc45d367442f850baecd814003ec09790a43b0 (patch)
treebfc904c0e8ced825749fa910696fae1d24fe10ca /audio/softsynth/mt32/Partial.cpp
parent8884c240fc4683975cd76802b600c019ebcb260c (diff)
downloadscummvm-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.cpp438
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, &ampRamp)), 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, &ampRamp);
+ 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;
}