ft2-clone

Fasttracker 2 clone
Log | Files | Refs | README | LICENSE

commit 1e5706a2419f55dc66900ecf6783a9a17172ee50
parent 065a79216e2697245dc175423fe4a85f27106ede
Author: Olav Sørensen <olav.sorensen@live.no>
Date:   Mon, 15 Jun 2020 21:39:05 +0200

Small scope/mixer delta calculation rework

Eliminated the need for a 512kB table, and also did some small code cleanup.

Diffstat:
M.gitignore | 2++
Msrc/ft2_audio.c | 41++++++++++++++++++++++-------------------
Msrc/ft2_audio.h | 25++++++++++---------------
Msrc/ft2_header.h | 2+-
Msrc/ft2_main.c | 2+-
Msrc/ft2_replayer.c | 153++++++++++++++++++++++++-------------------------------------------------------
Msrc/ft2_replayer.h | 8++++----
Msrc/ft2_scopedraw.c | 8++++----
Msrc/ft2_scopedraw.h | 2+-
Msrc/ft2_scopes.c | 72++++++++++++++++++++++++++++++++++++++++++------------------------------
Msrc/ft2_scopes.h | 6++++++
Mvs2019_project/ft2-clone/ft2-clone.vcxproj.filters | 2+-
12 files changed, 140 insertions(+), 183 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -18,3 +18,5 @@ vs2019_project/ft2-clone/x64/Debug/ft2-clone.vcxproj.FileListAbsolute.txt *.cod vs2019_project/ft2-clone/Debug/ft2-clone.vcxproj.FileListAbsolute.txt *.opendb +*.db-shm +*.db-wal diff --git a/src/ft2_audio.c b/src/ft2_audio.c @@ -18,14 +18,6 @@ #define INITIAL_DITHER_SEED 0x12345000 -// globalized -audio_t audio; -pattSyncData_t *pattSyncEntry; -chSyncData_t *chSyncEntry; -chSync_t chSync; -pattSync_t pattSync; -volatile bool pattQueueClearing, chQueueClearing; - static int8_t pmpCountDiv, pmpChannels = 2; static uint16_t smpBuffSize; static int32_t masterVol, oldAudioFreq, pmpLeft, randSeed = INITIAL_DITHER_SEED; @@ -35,19 +27,29 @@ static float fAudioAmpMul; static voice_t voice[MAX_VOICES * 2]; static void (*sendAudSamplesFunc)(uint8_t *, uint32_t, uint8_t); // "send mixed samples" routines -#if !defined __amd64__ && !defined _WIN64 static int32_t oldPeriod; +#if !defined __amd64__ && !defined _WIN64 static uint32_t oldSFrq, oldSFrqRev; +#else +static uint64_t oldSFrq; #endif -#if !defined __amd64__ && !defined _WIN64 +// globalized +audio_t audio; +pattSyncData_t *pattSyncEntry; +chSyncData_t *chSyncEntry; +chSync_t chSync; +pattSync_t pattSync; +volatile bool pattQueueClearing, chQueueClearing; + void resetCachedMixerVars(void) { oldPeriod = -1; oldSFrq = 0; +#if !defined __amd64__ && !defined _WIN64 oldSFrqRev = 0xFFFFFFFF; -} #endif +} void stopVoice(int32_t i) { @@ -368,22 +370,23 @@ void mix_UpdateChannelVolPanFrq(void) if (status & IS_Period) { -#if defined __amd64__ || defined _WIN64 - v->SFrq = getFrequenceValue(ch->finalPeriod); -#else - // use cached values to prevent a 32-bit divsion all the time - if (ch->finalPeriod != oldPeriod) - { - oldPeriod = ch->finalPeriod; + // use cached values if possible - oldSFrq = getFrequenceValue(ch->finalPeriod); + const uint16_t period = ch->finalPeriod; + if (period != oldPeriod) + { + oldPeriod = period; + oldSFrq = getMixerDelta(period); +#if !defined __amd64__ && !defined _WIN64 oldSFrqRev = 0xFFFFFFFF; if (oldSFrq != 0) oldSFrqRev /= oldSFrq; +#endif } v->SFrq = oldSFrq; +#if !defined __amd64__ && !defined _WIN64 v->SFrqRev = oldSFrqRev; #endif } diff --git a/src/ft2_audio.h b/src/ft2_audio.h @@ -54,7 +54,7 @@ typedef struct audio_t uint32_t freq; uint32_t audLatencyPerfValInt, audLatencyPerfValFrac, speedVal, musicTimeSpeedVal; uint64_t tickTime64, tickTime64Frac, tickTimeLengthTab[MAX_BPM+1]; - double dAudioLatencyMs, dSpeedValMul, dPianoDeltaMul; + double dAudioLatencyMs; SDL_AudioDeviceID dev; uint32_t wantFreq, haveFreq, wantSamples, haveSamples, wantChannels, haveChannels; } audio_t; @@ -104,21 +104,7 @@ typedef struct chSync_t chSyncData_t data[SYNC_QUEUE_LEN + 1]; } chSync_t; -// in ft2_audio.c -extern audio_t audio; -extern pattSyncData_t *pattSyncEntry; -extern chSyncData_t *chSyncEntry; -extern chSync_t chSync; -extern pattSync_t pattSync; - -extern volatile bool pattQueueClearing, chQueueClearing; - -#if !defined __amd64__ && !defined _WIN64 void resetCachedMixerVars(void); -#endif - -void calcAudioTables(void); - int32_t pattQueueReadSize(void); int32_t pattQueueWriteSize(void); bool pattQueuePush(pattSyncData_t t); @@ -153,3 +139,12 @@ void updateSendAudSamplesRoutine(bool lockMixer); void mix_SaveIPVolumes(void); void mix_UpdateChannelVolPanFrq(void); uint32_t mixReplayerTickToBuffer(uint8_t *stream, uint8_t bitDepth); + +// in ft2_audio.c +extern audio_t audio; +extern pattSyncData_t *pattSyncEntry; +extern chSyncData_t *chSyncEntry; +extern chSync_t chSync; +extern pattSync_t pattSync; + +extern volatile bool pattQueueClearing, chQueueClearing; diff --git a/src/ft2_header.h b/src/ft2_header.h @@ -12,7 +12,7 @@ #endif #include "ft2_replayer.h" -#define PROG_VER_STR "1.25" +#define PROG_VER_STR "1.26" // do NOT change these! It will only mess things up... diff --git a/src/ft2_main.c b/src/ft2_main.c @@ -312,7 +312,7 @@ static void initializeVars(void) audio.linearFreqTable = true; - calcAudioTables(); + calcReplayerLogTab(); } static void cleanUpAndExit(void) // never call this inside the main loop! diff --git a/src/ft2_replayer.c b/src/ft2_replayer.c @@ -26,19 +26,9 @@ ** If something looks to be off, it probably isn't! */ -/* Tables for pre-calculated stuff on run time and when changing freq. and/or linear/amiga mode. -** FT2 obviously didn't have such big tables. -*/ - +// non-FT2 precalced stuff static uint32_t musicTimeTab[MAX_BPM+1]; -static uint64_t period2ScopeDeltaTab[65536]; -static double dLogTab[768], dLogTabMul[32], dAudioRateFactor; - -#if defined _WIN64 || defined __amd64__ -static uint64_t period2DeltaTab[65536]; -#else -static uint32_t period2DeltaTab[65536]; -#endif +static double dPeriod2HzTab[65536], dLogTab[768], dAudioRateFactor; static bool bxxOverflow; static tonTyp nilPatternLine; @@ -141,8 +131,8 @@ void tuneSample(sampleTyp *s, int32_t midCFreq) return; } - double dFreq = log2(midCFreq * (1.0 / 8363.0)) * (12.0 * 128.0); - int32_t linearFreq = (int32_t)(dFreq + 0.5); + double dFreq = log2(midCFreq / 8363.0) * (12.0 * 128.0); + int32_t linearFreq = (int32_t)(dFreq + 0.5); // rounded s->fine = ((linearFreq + 128) & 255) - 128; int32_t relTon = (linearFreq - s->fine) >> 7; @@ -220,61 +210,29 @@ int16_t getRealUsedSamples(int16_t nr) return i+1; } -// called every time you change linear/amiga mode and mixing frequency -static void calcPeriod2DeltaTables(void) +static void calcPeriod2HzTab(void) // called every time you change linear/amiga period mode { - int32_t baseDelta; - uint32_t i; - - period2DeltaTab[0] = 0; // in FT2, a period of 0 is converted to a delta of 0 - - const double dScopeRateFactor = SCOPE_FRAC_SCALE / (double)SCOPE_HZ; + dPeriod2HzTab[0] = 0.0; // in FT2, a period of 0 yields 0Hz if (audio.linearFreqTable) { // linear periods - for (i = 1; i < 65536; i++) + for (int32_t i = 1; i < 65536; i++) { const uint16_t invPeriod = (12 * 192 * 4) - (uint16_t)i; // this intentionally overflows uint16_t to be accurate to FT2 const int32_t octave = invPeriod / 768; const int32_t period = invPeriod % 768; - const int32_t shift = (14 - octave) & 0x1F; // 100% accurate to FT2! - - const double dHz = dLogTab[period] * dLogTabMul[shift]; + const int32_t bitshift = (14 - octave) & 0x1F; // 100% accurate to FT2 -#if defined _WIN64 || defined __amd64__ - period2DeltaTab[i] = (uint64_t)((dHz * dAudioRateFactor) + 0.5); -#else - period2DeltaTab[i] = (uint32_t)((dHz * dAudioRateFactor) + 0.5); -#endif - period2ScopeDeltaTab[i] = (uint64_t)((dHz * dScopeRateFactor) + 0.5); + dPeriod2HzTab[i] = dLogTab[period] / (1UL << bitshift); } } else { // Amiga periods - for (i = 1; i < 65536; i++) - { - double dHz = (8363.0 * 1712.0) / i; - -#if defined _WIN64 || defined __amd64__ - period2DeltaTab[i] = (uint64_t)((dHz * dAudioRateFactor) + 0.5); -#else - period2DeltaTab[i] = (uint32_t)((dHz * dAudioRateFactor) + 0.5); -#endif - period2ScopeDeltaTab[i] = (uint64_t)((dHz * dScopeRateFactor) + 0.5); - } + for (int32_t i = 1; i < 65536; i++) + dPeriod2HzTab[i] = (8363.0 * 1712.0) / i; } - - // for piano in Instr. Ed. - - // (this delta is small enough to fit in int32_t even with 32.32 deltas) - if (audio.linearFreqTable) - baseDelta = (int32_t)period2DeltaTab[7680]; - else - baseDelta = (int32_t)period2DeltaTab[1712*16]; - - audio.dPianoDeltaMul = 1.0 / baseDelta; } void setFrqTab(bool linear) @@ -288,7 +246,7 @@ void setFrqTab(bool linear) else note2Period = amigaPeriods; - calcPeriod2DeltaTables(); + calcPeriod2HzTab(); resumeAudio(); @@ -302,7 +260,7 @@ static void retrigVolume(stmTyp *ch) ch->realVol = ch->oldVol; ch->outVol = ch->oldVol; ch->outPan = ch->oldPan; - ch->status |= (IS_Vol + IS_Pan + IS_QuickVol); + ch->status |= IS_Vol + IS_Pan + IS_QuickVol; } static void retrigEnvelopeVibrato(stmTyp *ch) @@ -376,26 +334,18 @@ void keyOff(stmTyp *ch) { ch->realVol = 0; ch->outVol = 0; - ch->status |= (IS_Vol + IS_QuickVol); + ch->status |= IS_Vol + IS_QuickVol; } } -void calcAudioTables(void) +void calcReplayerLogTab(void) { - int32_t i; - - for (i = 0; i < 768; i++) + for (int32_t i = 0; i < 768; i++) dLogTab[i] = exp2(i / 768.0) * (8363.0 * 256.0); - - dLogTabMul[0] = 1.0; - for (i = 1; i < 32; i++) - dLogTabMul[i] = exp2(-i); } void calcReplayRate(int32_t audioFreq) { - int32_t i; - if (audioFreq == 0) return; @@ -403,77 +353,66 @@ void calcReplayRate(int32_t audioFreq) audio.quickVolSizeVal = audioFreq / 200; // FT2 truncates here audio.rampQuickVolMul = (int32_t)(((UINT32_MAX + 1.0) / audio.quickVolSizeVal) + 0.5); - audio.dSpeedValMul = editor.dPerfFreq / audioFreq; // for audio/video sync /* Calculate tables to prevent floating point operations on systems that ** might have a slow FPU. This is quite hackish and not really needed, - ** but it doesn't take up THAT much RAM anyway. + ** but it doesn't take up a lot of RAM, so why not. */ - // calculate table used to count replayer time (displayed as hours/minutes/seconds) - const double dMul = (UINT32_MAX + 1.0) / audioFreq; - audio.speedValTab[0] = 0; musicTimeTab[0] = UINT32_MAX; - audio.rampSpeedValMulTab[0] = UINT32_MAX; - audio.tickTimeLengthTab[0] = (uint64_t)UINT32_MAX << 32; + audio.tickTimeLengthTab[0] = UINT64_MAX; + audio.rampSpeedValMulTab[0] = INT32_MAX; - const double dTickTimeLenMul = audio.dSpeedValMul * (UINT32_MAX + 1.0); - for (i = 1; i <= MAX_BPM; i++) - { - int32_t samplesPerTick = (int32_t)(((audioFreq * 2.5) / i) + 0.5); // rounded + const double dMul1 = (UINT32_MAX + 1.0) / audioFreq; + const double dMul2 = (editor.dPerfFreq / audioFreq) * (UINT32_MAX + 1.0); + for (int32_t i = 1; i <= MAX_BPM; i++) + { + const int32_t samplesPerTick = (int32_t)(((audioFreq * 2.5) / i) + 0.5); // rounded audio.speedValTab[i] = samplesPerTick; // used for song playback counter (hh:mm:ss) - musicTimeTab[i] = (uint32_t)((samplesPerTick * dMul) + 0.5); + musicTimeTab[i] = (uint32_t)((samplesPerTick * dMul1) + 0.5); // number of samples per tick -> tick length for performance counter (syncing visuals to audio) - audio.tickTimeLengthTab[i] = (uint64_t)(samplesPerTick * dTickTimeLenMul); + audio.tickTimeLengthTab[i] = (uint64_t)(samplesPerTick * dMul2); // for calculating volume ramp length for "tick" ramps audio.rampSpeedValMulTab[i] = (int32_t)(((UINT32_MAX + 1.0) / samplesPerTick) + 0.5); } +} - calcPeriod2DeltaTables(); +double period2Hz(uint16_t period) +{ + return dPeriod2HzTab[period]; } #if defined _WIN64 || defined __amd64__ -uint64_t getFrequenceValue(uint16_t period) +int64_t getMixerDelta(uint16_t period) { - return period2DeltaTab[period]; + return (int64_t)((dPeriod2HzTab[period] * dAudioRateFactor) + 0.5); // Hz -> rounded fixed-point mixer delta } #else -uint32_t getFrequenceValue(uint16_t period) +int32_t getMixerDelta(uint16_t period) { - return period2DeltaTab[period]; + return (int32_t)((dPeriod2HzTab[period] * dAudioRateFactor) + 0.5); // Hz -> rounded fixed-point mixer delta } #endif int32_t getPianoKey(uint16_t period, int32_t finetune, int32_t relativeNote) // for piano in Instr. Ed. { -#if defined _WIN64 || defined __amd64__ - uint64_t delta = period2DeltaTab[period]; -#else - uint32_t delta = period2DeltaTab[period]; -#endif - finetune >>= 3; // FT2 does this in the replayer internally, so the actual range is -16..15 - const double dNote = (log2(delta * audio.dPianoDeltaMul) * 12.0) - (finetune * (1.0 / 16.0)); - int32_t note = (int32_t)(dNote + 0.5); + const double dRelativeHz = dPeriod2HzTab[period] * (1.0 / (8363.0 / 16.0)); + const double dNote = (log2(dRelativeHz) * 12.0) - (finetune * (1.0 / 16.0)); - note -= relativeNote; + const int32_t note = (int32_t)(dNote + 0.5) - relativeNote; // rounded - // "note" is now the raw piano key number, unaffected by finetune/relativeNote + // "note" is now the raw piano key number, unaffected by finetune and relativeNote return note; } -uint64_t getScopeFrequenceValue(uint16_t period) -{ - return period2ScopeDeltaTab[period]; -} - static void startTone(uint8_t ton, uint8_t effTyp, uint8_t eff, stmTyp *ch) { uint8_t smp; @@ -538,7 +477,7 @@ static void startTone(uint8_t ton, uint8_t effTyp, uint8_t eff, stmTyp *ch) } } - ch->status |= (IS_Period + IS_Vol + IS_Pan + IS_NyTon + IS_QuickVol); + ch->status |= IS_Period + IS_Vol + IS_Pan + IS_NyTon + IS_QuickVol; if (effTyp == 9) { @@ -787,7 +726,7 @@ static void checkMoreEffects(stmTyp *ch) // called even if channel is muted { ch->realVol = 0; ch->outVol = 0; - ch->status |= (IS_Vol + IS_QuickVol); + ch->status |= IS_Vol + IS_QuickVol; } } @@ -978,7 +917,7 @@ static void checkEffects(stmTyp *ch) ch->outVol = volKol; ch->realVol = volKol; - ch->status |= (IS_Vol + IS_QuickVol); + ch->status |= IS_Vol + IS_QuickVol; } // fine volume slide down @@ -1035,7 +974,7 @@ static void checkEffects(stmTyp *ch) ch->realVol = 64; ch->outVol = ch->realVol; - ch->status |= (IS_Vol + IS_QuickVol); + ch->status |= IS_Vol + IS_QuickVol; return; } @@ -1939,7 +1878,7 @@ static void doEffects(stmTyp *ch) { ch->outVol = 0; ch->realVol = 0; - ch->status |= (IS_Vol + IS_QuickVol); + ch->status |= IS_Vol + IS_QuickVol; } } @@ -2064,7 +2003,7 @@ static void doEffects(stmTyp *ch) ch->tremorPos = tremorSign | tremorData; ch->outVol = (tremorSign == 0x80) ? ch->realVol : 0; - ch->status |= (IS_Vol + IS_QuickVol); + ch->status |= IS_Vol + IS_QuickVol; } } @@ -2779,6 +2718,7 @@ bool setupReplayer(void) audio.linearFreqTable = true; note2Period = linearPeriods; + calcPeriod2HzTab(); setPos(0, 0, true); @@ -3114,9 +3054,8 @@ void stopVoices(void) stopAllScopes(); resetAudioDither(); -#if !defined __amd64__ && !defined _WIN64 resetCachedMixerVars(); -#endif + resetCachedScopeVars(); // wait for scope thread to finish, so that we know pointers aren't deprecated while (editor.scopeThreadMutex); diff --git a/src/ft2_replayer.h b/src/ft2_replayer.h @@ -237,18 +237,18 @@ typedef struct syncedChannel_t // used for audio/video sync queue void fixSongName(void); // removes spaces from right side of song name void fixSampleName(int16_t nr); // removes spaces from right side of ins/smp names - void calcReplayRate(int32_t rate); void tuneSample(sampleTyp *s, int32_t midCFreq); +void calcReplayerLogTab(void); +double period2Hz(uint16_t period); #if defined _WIN64 || defined __amd64__ -uint64_t getFrequenceValue(uint16_t period); +int64_t getMixerDelta(uint16_t period); #else -uint32_t getFrequenceValue(uint16_t period); +int32_t getMixerDelta(uint16_t period); #endif -uint64_t getScopeFrequenceValue(uint16_t period); int32_t getPianoKey(uint16_t period, int32_t finetune, int32_t relativeNote); // for piano in Instr. Ed. bool allocateInstr(int16_t nr); diff --git a/src/ft2_scopedraw.c b/src/ft2_scopedraw.c @@ -101,13 +101,13 @@ #define SCOPE_UPDATE_DRAWPOS \ scopeDrawFrac += scopeDrawDelta; \ - scopeDrawPos += scopeDrawFrac >> 16; \ - scopeDrawFrac &= 0xFFFF; \ + scopeDrawPos += scopeDrawFrac >> SCOPE_DRAW_FRAC_BITS; \ + scopeDrawFrac &= SCOPE_DRAW_FRAC_MASK; \ #define SCOPE_UPDATE_DRAWPOS_PINGPONG \ scopeDrawFrac += scopeDrawDelta; \ - scopeDrawPos += (scopeDrawFrac >> 16) * drawPosDir; \ - scopeDrawFrac &= 0xFFFF; \ + scopeDrawPos += (scopeDrawFrac >> SCOPE_DRAW_FRAC_BITS) * drawPosDir; \ + scopeDrawFrac &= SCOPE_DRAW_FRAC_MASK; \ #define SCOPE_DRAW_SMP \ video.frameBuffer[((lineY - sample) * SCREEN_W) + x] = scopePixelColor; diff --git a/src/ft2_scopedraw.h b/src/ft2_scopedraw.h @@ -3,6 +3,6 @@ #include <stdint.h> #include "ft2_scopes.h" -typedef void (*scopeDrawRoutine)(scope_t *, uint32_t, uint32_t, uint32_t); +typedef void (*scopeDrawRoutine)(const scope_t *, uint32_t, uint32_t, uint32_t); extern const scopeDrawRoutine scopeDrawRoutineTable[12]; // ft2_scopedraw.c diff --git a/src/ft2_scopes.c b/src/ft2_scopes.c @@ -41,14 +41,22 @@ typedef struct scopeState_t } scopeState_t; static volatile bool scopesUpdatingFlag, scopesDisplayingFlag; -static uint32_t scopeTimeLen, scopeTimeLenFrac; -static uint64_t timeNext64, timeNext64Frac; +static int32_t oldPeriod; +static uint32_t oldDFrq, scopeTimeLen, scopeTimeLenFrac; +static uint64_t oldSFrq, timeNext64, timeNext64Frac; static volatile scope_t scope[MAX_VOICES]; static SDL_Thread *scopeThread; static uint8_t *scopeMuteBMP_Ptrs[16]; lastChInstr_t lastChInstr[MAX_VOICES]; // global +void resetCachedScopeVars(void) +{ + oldPeriod = -1; + oldSFrq = 0; + oldDFrq = 0; +} + int32_t getSamplePosition(uint8_t ch) { volatile bool active, sample16Bit; @@ -202,7 +210,7 @@ static void redrawScope(int32_t ch) void refreshScopes(void) { - for (int16_t i = 0; i < MAX_VOICES; i++) + for (int32_t i = 0; i < MAX_VOICES; i++) scope[i].wasCleared = false; } @@ -292,7 +300,7 @@ bool testScopesMouseDown(void) if (mouse.x >= x && mouse.x < x+scopeLens[i]) break; - x += scopeLens[i] + 3; + x += scopeLens[i]+3; } if (i == chansPerRow) @@ -309,7 +317,7 @@ bool testScopesMouseDown(void) return false; } -static void scopeTrigger(int32_t ch, sampleTyp *s, int32_t playOffset) +static void scopeTrigger(int32_t ch, const sampleTyp *s, int32_t playOffset) { bool sampleIs16Bit; uint8_t loopType; @@ -387,32 +395,28 @@ static void scopeTrigger(int32_t ch, sampleTyp *s, int32_t playOffset) static void updateScopes(void) { int32_t loopOverflowVal; - volatile scope_t *sc; - scope_t tempState; scopesUpdatingFlag = true; - sc = scope; + volatile scope_t *sc = scope; for (int32_t i = 0; i < song.antChn; i++, sc++) { - tempState = *sc; // cache it + scope_t tempState = *sc; // cache it if (!tempState.active) continue; // scope is not active, no need // scope position update tempState.SPosDec += tempState.SFrq; - const uint32_t posAdd = tempState.SPosDec >> SCOPE_FRAC_BITS; + tempState.SPosDec &= SCOPE_FRAC_MASK; + if (tempState.backwards) tempState.SPos -= posAdd; else tempState.SPos += posAdd; - tempState.SPosDec &= SCOPE_FRAC_MASK; - // handle loop wrapping or sample end - if (tempState.backwards && tempState.SPos < tempState.SRepS) // sampling backwards (definitely pingpong loop) { tempState.backwards = false; // change direction to forwards @@ -460,9 +464,7 @@ void drawScopes(void) const uint16_t *scopeLens; uint16_t scopeXOffs, scopeYOffs, scopeDrawLen; int32_t chansPerRow; - volatile scope_t *sc; - scope_t s; - + scopesDisplayingFlag = true; chansPerRow = (uint32_t)song.antChn >> 1; @@ -482,18 +484,16 @@ void drawScopes(void) } scopeDrawLen = scopeLens[i]; - if (!editor.chnMode[i]) // scope muted (mute graphics blit()'ed elsewhere) { - scopeXOffs += scopeDrawLen + 3; // align x to next scope + scopeXOffs += scopeDrawLen+3; // align x to next scope continue; } - s = scope[i]; // cache scope to lower thread race condition issues + const scope_t s = scope[i]; // cache scope to lower thread race condition issues if (s.active && s.SVol > 0 && !audio.locked) { // scope is active - scope[i].wasCleared = false; // clear scope background @@ -506,8 +506,7 @@ void drawScopes(void) else { // scope is inactive - - sc = &scope[i]; + volatile scope_t *sc = &scope[i]; if (!sc->wasCleared) { // clear scope background @@ -528,7 +527,7 @@ void drawScopes(void) if (config.multiRecChn[i]) blit(scopeXOffs + 1, scopeYOffs + 31, bmp.scopeRec, 13, 4); - scopeXOffs += scopeDrawLen + 3; // align x to next scope + scopeXOffs += scopeDrawLen+3; // align x to next scope } scopesDisplayingFlag = false; @@ -537,18 +536,16 @@ void drawScopes(void) void drawScopeFramework(void) { drawFramework(0, 92, 291, 81, FRAMEWORK_TYPE1); - for (uint8_t i = 0; i < song.antChn; i++) + for (int32_t i = 0; i < song.antChn; i++) redrawScope(i); } void handleScopesFromChQueue(chSyncData_t *chSyncData, uint8_t *scopeUpdateStatus) { uint8_t status; - syncedChannel_t *ch; - volatile scope_t *sc; - sc = scope; - ch = chSyncData->channels; + volatile scope_t *sc = scope; + syncedChannel_t *ch = chSyncData->channels; for (int32_t i = 0; i < song.antChn; i++, sc++, ch++) { status = scopeUpdateStatus[i]; @@ -558,8 +555,23 @@ void handleScopesFromChQueue(chSyncData_t *chSyncData, uint8_t *scopeUpdateStatu if (status & IS_Period) { - sc->SFrq = getScopeFrequenceValue(ch->finalPeriod); - sc->DFrq = (uint32_t)(sc->SFrq >> (SCOPE_FRAC_BITS - 10)); // amount of samples to skip after drawing a pixel + // use cached values if possible + + const uint16_t period = ch->finalPeriod; + if (period != oldPeriod) + { + oldPeriod = period; + const double dHz = period2Hz(period); + + const double dScopeRateFactor = SCOPE_FRAC_SCALE / (double)SCOPE_HZ; + oldSFrq = (int64_t)((dHz * dScopeRateFactor) + 0.5); // Hz -> rounded fixed-point delta + + const double dRelativeHz = dHz * (1.0 / (8363.0 / 2.0)); + oldDFrq = (int32_t)((dRelativeHz * SCOPE_DRAW_FRAC_SCALE) + 0.5); // Hz -> rounded fixed-point draw delta + } + + sc->SFrq = oldSFrq; + sc->DFrq = oldDFrq; } if (status & IS_NyTon) diff --git a/src/ft2_scopes.h b/src/ft2_scopes.h @@ -9,6 +9,12 @@ #define SCOPE_FRAC_SCALE (1ULL << SCOPE_FRAC_BITS) #define SCOPE_FRAC_MASK (SCOPE_FRAC_SCALE-1) +// just about max safe bits, don't mess with it! +#define SCOPE_DRAW_FRAC_BITS 20 +#define SCOPE_DRAW_FRAC_SCALE (1UL << SCOPE_DRAW_FRAC_BITS) +#define SCOPE_DRAW_FRAC_MASK (SCOPE_DRAW_FRAC_SCALE-1) + +void resetCachedScopeVars(void); int32_t getSamplePosition(uint8_t ch); void stopAllScopes(void); void refreshScopes(void); diff --git a/vs2019_project/ft2-clone/ft2-clone.vcxproj.filters b/vs2019_project/ft2-clone/ft2-clone.vcxproj.filters @@ -22,7 +22,6 @@ <ClCompile Include="..\..\src\ft2_unicode.c" /> <ClCompile Include="..\..\src\ft2_midi.c" /> <ClCompile Include="..\..\src\ft2_wav_renderer.c" /> - <ClCompile Include="..\..\src\ft2_about.c" /> <ClCompile Include="..\..\src\ft2_trim.c" /> <ClCompile Include="..\..\src\ft2_checkboxes.c" /> <ClCompile Include="..\..\src\ft2_pushbuttons.c" /> @@ -77,6 +76,7 @@ <Filter>graphics</Filter> </ClCompile> <ClCompile Include="..\..\src\ft2_structs.c" /> + <ClCompile Include="..\..\src\ft2_about.c" /> </ItemGroup> <ItemGroup> <ClInclude Include="..\..\src\ft2_audio.h">