ref: 89cb1fd4c15081a81c760389e6ff1f2f7cd5dd19
parent: 0909b673f37e1eb88c4948e3b95e9d6091d0a8e4
author: Olav Sørensen <olav.sorensen@live.no>
date: Fri Dec 1 12:12:55 EST 2023
float instr. envelopes + double->float on voice vols 64-bit double-precision was not really needed for these calculations.
--- a/src/ft2_audio.c
+++ b/src/ft2_audio.c
@@ -26,7 +26,7 @@
static int32_t smpShiftValue;
static uint32_t oldAudioFreq, tickTimeLenInt;
static uint64_t tickTimeLenFrac;
-static double dAudioNormalizeMul, dSqrtPanningTable[256+1];
+static float fAudioNormalizeMul, fSqrtPanningTable[256+1];
static voice_t voice[MAX_CHANNELS * 2];
// globalized
@@ -115,7 +115,7 @@
if (!bitDepth32Flag)
dAmp *= 32768.0;
- dAudioNormalizeMul = dAmp;
+ fAudioNormalizeMul = (float)dAmp;
}
void decreaseMasterVol(void)
@@ -196,7 +196,7 @@
lockMixerCallback();
audio.interpolationType = interpolationType;
- // set sinc polyphase LUT pointers
+ // set sinc LUT pointers
if (config.interpolation == INTERPOLATION_SINC8)
{
fKaiserSinc = fKaiserSinc_8;
@@ -217,7 +217,7 @@
{
// same formula as FT2's panning table (with 0.0 .. 1.0 scale)
for (int32_t i = 0; i <= 256; i++)
- dSqrtPanningTable[i] = sqrt(i / 256.0);
+ fSqrtPanningTable[i] = (float)sqrt(i / 256.0);
}
static void voiceUpdateVolumes(int32_t i, uint8_t status)
@@ -224,8 +224,8 @@
{
voice_t *v = &voice[i];
- v->fTargetVolumeL = (float)(v->dVolume * dSqrtPanningTable[256-v->panning]);
- v->fTargetVolumeR = (float)(v->dVolume * dSqrtPanningTable[ v->panning]);
+ v->fTargetVolumeL = v->fVolume * fSqrtPanningTable[256-v->panning];
+ v->fTargetVolumeR = v->fVolume * fSqrtPanningTable[ v->panning];
if (!audio.volumeRampingFlag)
{
@@ -250,7 +250,7 @@
voice_t *f = &voice[MAX_CHANNELS+i];
- *f = *v; // store current voice in respective fadeout ramp voice
+ *f = *v; // copy current voice to new fadeout-ramp voice
const float fVolumeLDiff = 0.0f - f->fCurrVolumeL;
const float fVolumeRDiff = 0.0f - f->fCurrVolumeR;
@@ -365,10 +365,10 @@
if (status & IS_Vol)
{
- v->dVolume = ch->dFinalVol;
+ v->fVolume = ch->fFinalVol;
// set scope volume
- const int32_t scopeVolume = (int32_t)((SCOPE_HEIGHT * ch->dFinalVol) + 0.5); // rounded
+ const int32_t scopeVolume = (int32_t)((SCOPE_HEIGHT * ch->fFinalVol) + 0.5f); // rounded
v->scopeVolume = (uint8_t)scopeVolume;
}
@@ -387,9 +387,9 @@
if (ch->finalPeriod == 0) // in FT2, period 0 -> delta 0
{
- v->scopeDelta = 0;
v->oldDelta = 0;
v->fSincLUT = fKaiserSinc;
+ v->scopeDelta = 0;
}
else
{
@@ -396,7 +396,7 @@
const double dHz = dPeriod2Hz(ch->finalPeriod);
const uintCPUWord_t delta = v->oldDelta = (intCPUWord_t)((dHz * audio.dHz2MixDeltaMul) + 0.5); // Hz -> fixed-point delta (rounded)
- // decide which polyphase sinc LUT to use according to resampling ratio
+ // decide which sinc LUT to use according to the resampling ratio
if (delta <= (uintCPUWord_t)(1.1875 * MIXER_FRAC_SCALE))
v->fSincLUT = fKaiserSinc;
else if (delta <= (uintCPUWord_t)(1.5 * MIXER_FRAC_SCALE))
@@ -418,19 +418,22 @@
}
}
-static void sendSamples16BitStereo(uint8_t *stream, uint32_t sampleBlockLength)
+static void sendSamples16BitStereo(void *stream, uint32_t sampleBlockLength)
{
- int16_t *streamPointer16 = (int16_t *)stream;
+ int16_t *streamPtr16 = (int16_t *)stream;
for (uint32_t i = 0; i < sampleBlockLength; i++)
{
- int32_t L = (int32_t)((double)audio.fMixBufferL[i] * dAudioNormalizeMul);
- CLAMP16(L);
- *streamPointer16++ = (int16_t)L;
+ // TODO: This could use dithering (a proper implementation, that is...)
- int32_t R = (int32_t)((double)audio.fMixBufferR[i] * dAudioNormalizeMul);
+ int32_t L = (int32_t)(audio.fMixBufferL[i] * fAudioNormalizeMul);
+ int32_t R = (int32_t)(audio.fMixBufferR[i] * fAudioNormalizeMul);
+
+ CLAMP16(L);
CLAMP16(R);
- *streamPointer16++ = (int16_t)R;
+ *streamPtr16++ = (int16_t)L;
+ *streamPtr16++ = (int16_t)R;
+
// clear what we read from the mixing buffer
audio.fMixBufferL[i] = 0.0f;
audio.fMixBufferR[i] = 0.0f;
@@ -437,18 +440,16 @@
}
}
-static void sendSamples32BitFloatStereo(uint8_t *stream, uint32_t sampleBlockLength)
+static void sendSamples32BitFloatStereo(void *stream, uint32_t sampleBlockLength)
{
- float *fStreamPointer32 = (float *)stream;
+ float *fStreamPtr32 = (float *)stream;
for (uint32_t i = 0; i < sampleBlockLength; i++)
{
- double dL = (double)audio.fMixBufferL[i] * dAudioNormalizeMul;
- dL = CLAMP(dL, -1.0, 1.0);
- *fStreamPointer32++ = (float)dL;
+ const float fL = audio.fMixBufferL[i] * fAudioNormalizeMul;
+ const float fR = audio.fMixBufferR[i] * fAudioNormalizeMul;
- double dR = (double)audio.fMixBufferR[i] * dAudioNormalizeMul;
- dR = CLAMP(dR, -1.0, 1.0);
- *fStreamPointer32++ = (float)dR;
+ *fStreamPtr32++ = CLAMP(fL, -1.0f, 1.0f);
+ *fStreamPtr32++ = CLAMP(fR, -1.0f, 1.0f);
// clear what we read from the mixing buffer
audio.fMixBufferL[i] = 0.0f;
@@ -495,7 +496,7 @@
}
// used for song-to-WAV renderer
-void mixReplayerTickToBuffer(uint32_t samplesToMix, uint8_t *stream, uint8_t bitDepth)
+void mixReplayerTickToBuffer(uint32_t samplesToMix, void *stream, uint8_t bitDepth)
{
doChannelMixing(0, samplesToMix);
--- a/src/ft2_audio.h
+++ b/src/ft2_audio.h
@@ -78,8 +78,7 @@
const int16_t *leftEdgeTaps16;
const float *fSincLUT;
- double dVolume;
- float fCurrVolumeL, fCurrVolumeR, fVolumeLDelta, fVolumeRDelta, fTargetVolumeL, fTargetVolumeR;
+ float fVolume, fCurrVolumeL, fCurrVolumeR, fVolumeLDelta, fVolumeRDelta, fTargetVolumeL, fTargetVolumeR;
} voice_t;
#ifdef _MSC_VER
@@ -155,7 +154,7 @@
void unlockMixerCallback(void);
void resetRampVolumes(void);
void updateVoices(void);
-void mixReplayerTickToBuffer(uint32_t samplesToMix, uint8_t *stream, uint8_t bitDepth);
+void mixReplayerTickToBuffer(uint32_t samplesToMix, void *stream, uint8_t bitDepth);
// in ft2_audio.c
extern audio_t audio;
--- a/src/ft2_header.h
+++ b/src/ft2_header.h
@@ -12,7 +12,7 @@
#endif
#include "ft2_replayer.h"
-#define PROG_VER_STR "1.73"
+#define PROG_VER_STR "1.74"
// do NOT change these! It will only mess things up...
--- a/src/ft2_replayer.c
+++ b/src/ft2_replayer.c
@@ -758,12 +758,12 @@
{
bool envUpdate;
int8_t point;
- int16_t tick;
+ int32_t tick;
instr_t *ins = ch->instrPtr;
assert(ins != NULL);
- // (envelope precision has been updated from x.8fp to x.16fp)
+ // envelope precision has been upgraded from .8fp to single-precision float
// *** VOLUME ENVELOPE ***
if (ins->volEnvFlags & ENV_ENABLED)
@@ -784,23 +784,25 @@
point--;
tick -= ins->volEnvPoints[point][0];
- if (tick == 0)
+ if (tick == 0) // FT2 doesn't test for <= 0 here
{
envUpdate = false;
break;
}
- if (ins->volEnvPoints[point+1][0] <= ins->volEnvPoints[point][0])
+ const int32_t xDiff = ins->volEnvPoints[point+1][0] - ins->volEnvPoints[point+0][0];
+ if (xDiff <= 0)
{
envUpdate = true;
break;
}
- int32_t delta = (int8_t)((ins->volEnvPoints[point+1][1] - ins->volEnvPoints[point][1]) & 0xFF) << 16;
- delta /= (ins->volEnvPoints[point+1][0]-ins->volEnvPoints[point][0]);
+ const int32_t y0 = ins->volEnvPoints[point+0][1] & 0xFF;
+ const int32_t y1 = ins->volEnvPoints[point+1][1] & 0xFF;
+ const int32_t yDiff = y1 - y0;
- ch->volEnvDelta = delta;
- ch->volEnvValue = (ch->volEnvDelta * (tick-1)) + ((int8_t)(ins->volEnvPoints[point][1] & 0xFF) << 16);
+ ch->fVolEnvDelta = (float)yDiff / (float)xDiff;
+ ch->fVolEnvValue = (float)y0 + (ch->fVolEnvDelta * (tick-1));
point++;
@@ -817,8 +819,8 @@
if (envUpdate)
{
- ch->volEnvDelta = 0;
- ch->volEnvValue = (int8_t)(ins->volEnvPoints[point][1] & 0xFF) << 16;
+ ch->fVolEnvDelta = 0.0f;
+ ch->fVolEnvValue = (float)(int32_t)(ins->volEnvPoints[point][1] & 0xFF);
}
if (point >= ins->volEnvLength)
@@ -832,7 +834,7 @@
}
// *** PANNING ENVELOPE ***
- if (ins->volEnvFlags & ENV_SUSTAIN) // FT2 PLAYER BUG: Should've been ins->panEnvFlags
+ if (ins->volEnvFlags & ENV_SUSTAIN) // FT2 REPLAYER BUG: Should've been ins->panEnvFlags
{
ch->panEnvTick = param-1;
@@ -850,23 +852,25 @@
point--;
tick -= ins->panEnvPoints[point][0];
- if (tick == 0)
+ if (tick == 0) // FT2 doesn't test for <= 0 here
{
envUpdate = false;
break;
}
- if (ins->panEnvPoints[point+1][0] <= ins->panEnvPoints[point][0])
+ const int32_t xDiff = ins->panEnvPoints[point+1][0] - ins->panEnvPoints[point+0][0];
+ if (xDiff <= 0)
{
envUpdate = true;
break;
}
- int32_t delta = (int8_t)((ins->panEnvPoints[point+1][1] - ins->panEnvPoints[point][1]) & 0xFF) << 16;
- delta /= (ins->panEnvPoints[point+1][0]-ins->panEnvPoints[point][0]);
+ const int32_t y0 = ins->panEnvPoints[point+0][1] & 0xFF;
+ const int32_t y1 = ins->panEnvPoints[point+1][1] & 0xFF;
+ const int32_t yDiff = y1 - y0;
- ch->panEnvDelta = delta;
- ch->panEnvValue = (ch->panEnvDelta * (tick-1)) + ((int8_t)(ins->panEnvPoints[point][1] & 0xFF) << 16);
+ ch->fPanEnvDelta = (float)yDiff / (float)xDiff;
+ ch->fPanEnvValue = (float)y0 + (ch->fPanEnvDelta * (tick-1));
point++;
@@ -883,8 +887,8 @@
if (envUpdate)
{
- ch->panEnvDelta = 0;
- ch->panEnvValue = (int8_t)(ins->panEnvPoints[point][1] & 0xFF) << 16;
+ ch->fPanEnvDelta = 0.0f;
+ ch->fPanEnvValue = (float)(int32_t)(ins->panEnvPoints[point][1] & 0xFF);
}
if (point >= ins->panEnvLength)
@@ -1375,8 +1379,7 @@
{
bool envInterpolateFlag, envDidInterpolate;
uint8_t envPos;
- int32_t envVal;
- double dVol;
+ float fEnvVal, fVol;
instr_t *ins = ch->instrPtr;
assert(ins != NULL);
@@ -1399,10 +1402,10 @@
if (!ch->mute)
{
- // (envelope precision has been updated from x.8fp to x.16fp)
+ // envelope precision has been upgraded from .8fp to single-precision float
// *** VOLUME ENVELOPE ***
- envVal = 0;
+ fEnvVal = 0.0f;
if (ins->volEnvFlags & ENV_ENABLED)
{
envDidInterpolate = false;
@@ -1410,7 +1413,7 @@
if (++ch->volEnvTick == ins->volEnvPoints[envPos][0])
{
- ch->volEnvValue = (int8_t)(ins->volEnvPoints[envPos][1] & 0xFF) << 16;
+ ch->fVolEnvValue = (float)(int32_t)(ins->volEnvPoints[envPos][1] & 0xFF);
envPos++;
if (ins->volEnvFlags & ENV_LOOP)
@@ -1423,7 +1426,7 @@
{
envPos = ins->volEnvLoopStart;
ch->volEnvTick = ins->volEnvPoints[envPos][0];
- ch->volEnvValue = (int8_t)(ins->volEnvPoints[envPos][1] & 0xFF) << 16;
+ ch->fVolEnvValue = (float)(int32_t)(ins->volEnvPoints[envPos][1] & 0xFF);
}
}
@@ -1438,7 +1441,7 @@
if (envPos-1 == ins->volEnvSustain)
{
envPos--;
- ch->volEnvDelta = 0;
+ ch->fVolEnvDelta = 0.0f;
envInterpolateFlag = false;
}
}
@@ -1447,44 +1450,49 @@
{
ch->volEnvPos = envPos;
- ch->volEnvDelta = 0;
- if (ins->volEnvPoints[envPos][0] > ins->volEnvPoints[envPos-1][0])
+ const int32_t x0 = ins->volEnvPoints[envPos-1][0];
+ const int32_t x1 = ins->volEnvPoints[envPos-0][0];
+
+ const int32_t xDiff = x1 - x0;
+ if (xDiff > 0)
{
- int32_t delta = (int8_t)((ins->volEnvPoints[envPos][1] - ins->volEnvPoints[envPos-1][1]) & 0xFF) << 16;
- delta /= (ins->volEnvPoints[envPos][0]-ins->volEnvPoints[envPos-1][0]);
- ch->volEnvDelta = delta;
+ const int32_t y0 = ins->volEnvPoints[envPos-1][1] & 0xFF;
+ const int32_t y1 = ins->volEnvPoints[envPos-0][1] & 0xFF;
- envVal = ch->volEnvValue;
+ const int32_t yDiff = y1 - y0;
+ ch->fVolEnvDelta = (float)yDiff / (float)xDiff;
+
+ fEnvVal = ch->fVolEnvValue;
envDidInterpolate = true;
}
+ else
+ {
+ ch->fVolEnvDelta = 0.0f;
+ }
}
}
else
{
- ch->volEnvDelta = 0;
+ ch->fVolEnvDelta = 0.0f;
}
}
if (!envDidInterpolate)
{
- ch->volEnvValue += ch->volEnvDelta;
+ ch->fVolEnvValue += ch->fVolEnvDelta;
- envVal = ch->volEnvValue;
- if (envVal > 64<<16)
+ fEnvVal = ch->fVolEnvValue;
+ if (fEnvVal < 0.0f || fEnvVal > 64.0f)
{
- if (envVal > 128<<16)
- envVal = 64<<16;
- else
- envVal = 0;
-
- ch->volEnvDelta = 0;
+ fEnvVal = CLAMP(fEnvVal, 0.0f, 64.0f);
+ ch->fVolEnvDelta = 0.0f;
}
}
const int32_t vol = song.globalVolume * ch->outVol * ch->fadeoutVol;
- dVol = vol * (1.0 / (64.0 * 64.0 * 32768.0));
- dVol *= envVal * (1.0 / (64.0 * (1 << 16))); // volume envelope value
+ fVol = vol * (1.0f / (64.0f * 64.0f * 32768.0f));
+ fVol *= fEnvVal * (1.0f / 64.0f); // volume envelope value
ch->status |= IS_Vol; // update mixer vol every tick when vol envelope is enabled
}
@@ -1492,22 +1500,20 @@
{
const int32_t vol = song.globalVolume * ch->outVol * ch->fadeoutVol;
- dVol = vol * (1.0 / (64.0 * 64.0 * 32768.0));
+ fVol = vol * (1.0f / (64.0f * 64.0f * 32768.0f));
}
- /* FT2 doesn't clamp here, but it's actually important if you
- ** move envelope points with the mouse while playing the instrument.
- */
- ch->dFinalVol = CLAMP(dVol, 0.0, 1.0);
+ // FT2 doesn't clamp the volume, but let's do it anyway
+ ch->fFinalVol = CLAMP(fVol, 0.0f, 1.0f);
}
else
{
- ch->dFinalVol = 0.0;
+ ch->fFinalVol = 0.0f;
}
// *** PANNING ENVELOPE ***
- envVal = 0;
+ fEnvVal = 0.0f;
if (ins->panEnvFlags & ENV_ENABLED)
{
envDidInterpolate = false;
@@ -1515,7 +1521,7 @@
if (++ch->panEnvTick == ins->panEnvPoints[envPos][0])
{
- ch->panEnvValue = (int8_t)(ins->panEnvPoints[envPos][1] & 0xFF) << 16;
+ ch->fPanEnvValue = (float)(int32_t)(ins->panEnvPoints[envPos][1] & 0xFF);
envPos++;
if (ins->panEnvFlags & ENV_LOOP)
@@ -1529,7 +1535,7 @@
envPos = ins->panEnvLoopStart;
ch->panEnvTick = ins->panEnvPoints[envPos][0];
- ch->panEnvValue = (int8_t)(ins->panEnvPoints[envPos][1] & 0xFF) << 16;
+ ch->fPanEnvValue = (float)(int32_t)(ins->panEnvPoints[envPos][1] & 0xFF);
}
}
@@ -1544,7 +1550,7 @@
if (envPos-1 == ins->panEnvSustain)
{
envPos--;
- ch->panEnvDelta = 0;
+ ch->fPanEnvDelta = 0.0f;
envInterpolateFlag = false;
}
}
@@ -1553,46 +1559,53 @@
{
ch->panEnvPos = envPos;
- ch->panEnvDelta = 0;
- if (ins->panEnvPoints[envPos][0] > ins->panEnvPoints[envPos-1][0])
+ const int32_t x0 = ins->panEnvPoints[envPos-1][0];
+ const int32_t x1 = ins->panEnvPoints[envPos-0][0];
+
+ const int32_t xDiff = x1 - x0;
+ if (xDiff > 0)
{
- int32_t delta = (int8_t)((ins->panEnvPoints[envPos][1] - ins->panEnvPoints[envPos-1][1]) & 0xFF) << 16;
- delta /= (ins->panEnvPoints[envPos][0]-ins->panEnvPoints[envPos-1][0]);
- ch->panEnvDelta = delta;
+ const int32_t y0 = ins->panEnvPoints[envPos-1][1] & 0xFF;
+ const int32_t y1 = ins->panEnvPoints[envPos-0][1] & 0xFF;
- envVal = ch->panEnvValue;
+ const int32_t yDiff = y1 - y0;
+ ch->fPanEnvDelta = (float)yDiff / (float)xDiff;
+
+ fEnvVal = ch->fPanEnvValue;
envDidInterpolate = true;
}
+ else
+ {
+ ch->fPanEnvDelta = 0.0f;
+ }
}
}
else
{
- ch->panEnvDelta = 0;
+ ch->fPanEnvDelta = 0.0f;
}
}
if (!envDidInterpolate)
{
- ch->panEnvValue += ch->panEnvDelta;
+ ch->fPanEnvValue += ch->fPanEnvDelta;
- envVal = ch->panEnvValue;
- if (envVal > 64<<16)
+ fEnvVal = ch->fPanEnvValue;
+ if (fEnvVal < 0.0f || fEnvVal > 64.0f)
{
- if (envVal > 128<<16)
- envVal = 64<<16;
- else
- envVal = 0;
-
- ch->panEnvDelta = 0;
+ fEnvVal = CLAMP(fEnvVal, 0.0f, 64.0f);
+ ch->fPanEnvDelta = 0.0f;
}
}
- envVal -= 32<<16; // center panning envelope value
+ fEnvVal -= 32.0f; // center panning envelope value (0..64 -> -32..32)
- const int32_t pan = 128 - ABS(ch->outPan-128);
- const int32_t panAdd = (pan * envVal) >> (16+5);
- ch->finalPan = (uint8_t)(ch->outPan + panAdd);
+ const int32_t pan = 128 - ABS(ch->outPan - 128);
+ const float fPanAdd = (pan * fEnvVal) * (1.0f / 32.0f);
+ const int32_t newPan = (int32_t)(ch->outPan + fPanAdd); // truncate here, do not round
+ ch->finalPan = (uint8_t)CLAMP(newPan, 0, 255);
+
ch->status |= IS_Pan; // update pan every tick because pan envelope is enabled
}
else
@@ -3096,7 +3109,7 @@
ch->realVol = 0;
ch->outVol = 0;
ch->oldVol = 0;
- ch->dFinalVol = 0.0;
+ ch->fFinalVol = 0.0f;
ch->oldPan = 128;
ch->outPan = 128;
ch->finalPan = 128;
--- a/src/ft2_replayer.h
+++ b/src/ft2_replayer.h
@@ -256,10 +256,9 @@
uint16_t volEnvTick, panEnvTick, autoVibAmp, autoVibSweep;
uint16_t midiVibDepth;
int32_t fadeoutVol, fadeoutSpeed;
- int32_t volEnvDelta, panEnvDelta, volEnvValue, panEnvValue;
int32_t oldFinalPeriod, smpStartPos;
- double dFinalVol; // 0.0 .. 1.0
+ float fFinalVol, fVolEnvDelta, fPanEnvDelta, fVolEnvValue, fPanEnvValue;
sample_t *smpPtr;
instr_t *instrPtr;
--- a/src/mixer/ft2_mix_macros.h
+++ b/src/mixer/ft2_mix_macros.h
@@ -383,8 +383,8 @@
#define LIMIT_MIX_NUM_RAMP \
if (v->volumeRampLength == 0) \
{ \
- fVolumeLDelta = 0.0; \
- fVolumeRDelta = 0.0; \
+ fVolumeLDelta = 0.0f; \
+ fVolumeRDelta = 0.0f; \
\
if (v->isFadeOutVoice) \
{ \
@@ -403,7 +403,7 @@
#define LIMIT_MIX_NUM_MONO_RAMP \
if (v->volumeRampLength == 0) \
{ \
- fVolumeLDelta = 0.0; \
+ fVolumeLDelta = 0.0f; \
if (v->isFadeOutVoice) \
{ \
v->active = false; /* volume ramp fadeout-voice is done, shut it down */ \
--- a/src/scopes/ft2_scopes.c
+++ b/src/scopes/ft2_scopes.c
@@ -78,7 +78,7 @@
ch->realVol = 0;
ch->outVol = 0;
ch->oldVol = 0;
- ch->dFinalVol = 0.0;
+ ch->fFinalVol = 0.0f;
ch->outPan = 128;
ch->oldPan = 128;
ch->finalPan = 128;