ref: 527d14082914c733a5f60d7361db69897cc761d3
parent: 4205fc20cb6541818c9ffc3ea62e69bce0f7cee6
author: Olav Sørensen <olav.sorensen@live.no>
date: Thu May 7 16:26:52 EDT 2020
Pushed v1.15 code - Bugfix: The EFx (Invert Loop) effect didn't work on the whole sample loop, but only on the half of it. - Bugfix: The quadrascope didn't show the volume changes from the 7xy (Tremolo) effect. - Windows bugfix: Certain key modifiers didn't work (bug appeared in v1.13) - The "real VU-meters" mode now acts more like real VU-meters by showing average amplitudes instead of peak amplitudes. This mode can be toggled on/off by pressing ALT+F11 or changing a setting in protracker.ini. - The quadrascope code has been refactored a little bit to be slightly simpler - Some other cosmetic changes to the code (to better match the PT source code, which makes it easier to verify that it's correct).
--- a/src/pt2_audio.c
+++ b/src/pt2_audio.c
@@ -314,15 +314,13 @@
void mixerUpdateLoops(void) // updates Paula loop (+ scopes)
{
- moduleChannel_t *ch;
- moduleSample_t *s;
-
for (int32_t i = 0; i < AMIGA_VOICES; i++)
{
- ch = &song->channels[i];
+ const moduleChannel_t *ch = &song->channels[i];
if (ch->n_samplenum == editor.currSample)
{
- s = &song->samples[editor.currSample];
+ const moduleSample_t *s = &song->samples[editor.currSample];
+
paulaSetData(i, ch->n_start + s->loopStart);
paulaSetLength(i, s->loopLength >> 1);
}
@@ -406,7 +404,7 @@
if (period == 0)
realPeriod = 1+65535; // confirmed behavior on real Amiga
else if (period < 113)
- realPeriod = 113; // confirmed behavior on real Amiga
+ realPeriod = 113; // close to what happens on real Amiga (and needed for BLEP synthesis)
else
realPeriod = period;
@@ -417,9 +415,6 @@
// this period is not cached, calculate mixer/scope deltas
-#if SCOPE_HZ != 64
-#error Scope Hz is not 64 (2^n), change rate calc. to use doubles+round in pt2_scope.c
-#endif
// during PAT2SMP or doing MOD2WAV, use different audio output rates
if (editor.isSMPRendering)
dPeriodToDeltaDiv = editor.pat2SmpHQ ? (PAULA_PAL_CLK / 28836.0) : (PAULA_PAL_CLK / 22168.0);
@@ -428,17 +423,19 @@
else
dPeriodToDeltaDiv = audio.dPeriodToDeltaDiv;
+ const double dPeriodToScopeDeltaDiv = ((double)PAULA_PAL_CLK * SCOPE_FRAC_SCALE) / SCOPE_HZ;
+
// cache these
dOldVoiceDelta = dPeriodToDeltaDiv / realPeriod;
+ oldScopeDelta = (int32_t)((dPeriodToScopeDeltaDiv / realPeriod) + 0.5);
dOldVoiceDeltaMul = 1.0 / dOldVoiceDelta; // for BLEP synthesis
- oldScopeDelta = (PAULA_PAL_CLK * (65536UL / SCOPE_HZ)) / realPeriod;
}
v->dDelta = dOldVoiceDelta;
- v->dDeltaMul = dOldVoiceDeltaMul; // for BLEP synthesis
- setScopeDelta(ch, oldScopeDelta);
+ scope[ch].delta = oldScopeDelta;
// for BLEP synthesis
+ v->dDeltaMul = dOldVoiceDeltaMul;
if (v->dLastDelta == 0.0) v->dLastDelta = v->dDelta;
if (v->dLastDeltaMul == 0.0) v->dLastDeltaMul = v->dDeltaMul;
}
@@ -451,6 +448,7 @@
vol = 64; // confirmed behavior on real Amiga
paula[ch].dVolume = vol * (1.0 / 64.0);
+ scope[ch].volume = (uint8_t)vol;
}
void paulaSetLength(int32_t ch, uint16_t len)
@@ -463,40 +461,21 @@
*/
}
- // our mixer works with bytes, not words. Multiply by two
- scopeExt[ch].newLength = paula[ch].newLength = len << 1;
+ scope[ch].newLength = paula[ch].newLength = len << 1; // our mixer works with bytes, not words
}
void paulaSetData(int32_t ch, const int8_t *src)
{
- uint8_t smp;
- moduleSample_t *s;
- scopeChannelExt_t *se, tmp;
-
- smp = song->channels[ch].n_samplenum;
- assert(smp <= 30);
- s = &song->samples[smp];
-
// set voice data
if (src == NULL)
src = &song->sampleData[RESERVED_SAMPLE_OFFSET]; // dummy sample
- paula[ch].newData = src;
-
- // set external scope data
- se = &scopeExt[ch];
- tmp = *se; // cache it
-
- tmp.newData = src;
- tmp.newLoopFlag = (s->loopStart + s->loopLength) > 2;
- tmp.newLoopStart = s->loopStart;
-
- *se = tmp; // update it
+ scope[ch].newData = paula[ch].newData = src;
}
void paulaStopDMA(int32_t ch)
{
- scopeExt[ch].active = paula[ch].active = false;
+ scope[ch].active = paula[ch].active = false;
}
void paulaStartDMA(int32_t ch)
@@ -504,8 +483,6 @@
const int8_t *dat;
int32_t length;
paulaVoice_t *v;
- scopeChannel_t s, *sc;
- scopeChannelExt_t *se;
// trigger voice
@@ -525,30 +502,7 @@
v->length = length;
v->active = true;
- // trigger scope
-
- sc = &scope[ch];
- se = &scopeExt[ch];
- s = *sc; // cache it
-
- dat = se->newData;
- if (dat == NULL)
- dat = &song->sampleData[RESERVED_SAMPLE_OFFSET]; // dummy sample
-
- s.length = length;
- s.data = dat;
-
- s.pos = 0;
- s.posFrac = 0;
-
- // data/length is already set from replayer thread (important)
- s.loopFlag = se->newLoopFlag;
- s.loopStart = se->newLoopStart;
-
- se->didSwapData = false;
- se->active = true;
-
- *sc = s; // update it
+ scopeTrigger(ch, length);
}
void toggleA500Filters(void)
@@ -1074,9 +1028,9 @@
calcLEDFilterCoeffs(audio.outputRate, fc, fb, &filterLED);
// A500/A1200 one-pole 6db/oct static RC high-pass filter:
- R = 1000.0 + 390.0; // R324 (1K ohm resistor) + R325 (390 ohm resistor)
- C = 2.2e-5; // C334 (22uF capacitor) (+ C324 (0.33uF capacitor) if A500)
- fc = 1.0 / (2.0 * M_PI * R * C); // ~5.20Hz
+ R = 1000.0 + 390.0; // R324 (1K ohm resistor) + R325 (390 ohm resistor)
+ C = 2.2e-5; // C334 (22uF capacitor) (+ C324 (0.33uF capacitor) if A500)
+ fc = 1.0 / (2.0 * M_PI * R * C); // ~5.2Hz
calcRCFilterCoeffs(audio.outputRate, fc, &filterHi);
}
--- a/src/pt2_header.h
+++ b/src/pt2_header.h
@@ -14,7 +14,7 @@
#include "pt2_unicode.h"
#include "pt2_palette.h"
-#define PROG_VER_STR "1.14"
+#define PROG_VER_STR "1.15"
#ifdef _WIN32
#define DIR_DELIMITER '\\'
--- a/src/pt2_keyboard.c
+++ b/src/pt2_keyboard.c
@@ -60,6 +60,7 @@
keyb.leftAltPressed = (modState & KMOD_LALT) ? true : false;
keyb.shiftPressed = (modState & (KMOD_LSHIFT + KMOD_RSHIFT)) ? true : false;
+
#ifdef __APPLE__
keyb.leftCommandPressed = (modState & KMOD_LGUI) ? true : false;
#endif
@@ -84,6 +85,8 @@
if (window == NULL || nCode < 0 || nCode != HC_ACTION) // do not process message
return CallNextHookEx(g_hKeyboardHook, nCode, wParam, lParam);
+ bool bEatKeystroke = false;
+
KBDLLHOOKSTRUCT *p = (KBDLLHOOKSTRUCT *)lParam;
switch (wParam)
{
@@ -92,9 +95,9 @@
{
const bool windowHasFocus = SDL_GetWindowFlags(window) & SDL_WINDOW_INPUT_FOCUS;
- const bool bEatKeystroke = windowHasFocus && p->vkCode == VK_LWIN;
+ bEatKeystroke = windowHasFocus && (p->vkCode == VK_LWIN || p->vkCode == VK_NUMLOCK);
if (!bEatKeystroke)
- return CallNextHookEx(g_hKeyboardHook, nCode, wParam, lParam);
+ break;
memset(&inputEvent, 0, sizeof (SDL_Event));
@@ -107,7 +110,6 @@
inputEvent.type = SDL_KEYDOWN;
inputEvent.key.type = SDL_KEYDOWN;
inputEvent.key.state = SDL_PRESSED;
-
windowsKeyIsDown = true;
}
else
@@ -115,7 +117,6 @@
inputEvent.type = SDL_KEYUP;
inputEvent.key.type = SDL_KEYUP;
inputEvent.key.state = SDL_RELEASED;
-
windowsKeyIsDown = false;
}
@@ -132,7 +133,7 @@
default: break;
}
- return true;
+ return bEatKeystroke ? 1 : CallNextHookEx(g_hKeyboardHook, nCode, wParam, lParam);
}
#endif
--- a/src/pt2_replayer.c
+++ b/src/pt2_replayer.c
@@ -71,14 +71,17 @@
pointerSetMode(POINTER_MODE_IDLE, DO_CARRY);
}
- for (int32_t i = 0; i < AMIGA_VOICES; i++)
+ if (song != NULL)
{
- moduleChannel_t *c = &song->channels[i];
+ for (int32_t i = 0; i < AMIGA_VOICES; i++)
+ {
+ moduleChannel_t *c = &song->channels[i];
- c->n_wavecontrol = 0;
- c->n_glissfunk = 0;
- c->n_finetune = 0;
- c->n_loopcount = 0;
+ c->n_wavecontrol = 0;
+ c->n_glissfunk = 0;
+ c->n_finetune = 0;
+ c->n_loopcount = 0;
+ }
}
}
@@ -129,7 +132,7 @@
if (ch->n_loopstart != NULL && ch->n_wavestart != NULL) // non-PT2 bug fix
{
- if (++ch->n_wavestart >= ch->n_loopstart+ch->n_replen)
+ if (++ch->n_wavestart >= ch->n_loopstart + (ch->n_replen << 1))
ch->n_wavestart = ch->n_loopstart;
*ch->n_wavestart = -1 - *ch->n_wavestart;
@@ -166,13 +169,9 @@
else
{
if (ch->n_loopcount == 0)
- {
ch->n_loopcount = ch->n_cmd & 0xF;
- }
else if (--ch->n_loopcount == 0)
- {
return;
- }
pBreakPosition = ch->n_pattpos;
pBreakFlag = true;
@@ -520,26 +519,25 @@
tonePortNoChange(ch);
}
-static void vibratoNoChange(moduleChannel_t *ch)
+static void vibrato2(moduleChannel_t *ch)
{
- uint8_t vibratoTemp;
- int16_t vibratoData;
+ uint16_t vibratoData;
- vibratoTemp = (ch->n_vibratopos / 4) & 31;
- vibratoData = ch->n_wavecontrol & 3;
+ const uint8_t vibratoPos = (ch->n_vibratopos >> 2) & 0x1F;
+ const uint8_t vibratoType = ch->n_wavecontrol & 3;
- if (vibratoData == 0)
+ if (vibratoType == 0)
{
- vibratoData = vibratoTable[vibratoTemp];
+ vibratoData = vibratoTable[vibratoPos];
}
else
{
- if (vibratoData == 1)
+ if (vibratoType == 1)
{
- if (ch->n_vibratopos < 0)
- vibratoData = 255 - (vibratoTemp * 8);
+ if (ch->n_vibratopos < 128)
+ vibratoData = vibratoPos << 3;
else
- vibratoData = vibratoTemp * 8;
+ vibratoData = 255 - (vibratoPos << 3);
}
else
{
@@ -547,30 +545,27 @@
}
}
- vibratoData = (vibratoData * (ch->n_vibratocmd & 0xF)) / 128;
+ vibratoData = (vibratoData * (ch->n_vibratocmd & 0xF)) >> 7;
- if (ch->n_vibratopos < 0)
- vibratoData = ch->n_period - vibratoData;
- else
+ if (ch->n_vibratopos < 128)
vibratoData = ch->n_period + vibratoData;
+ else
+ vibratoData = ch->n_period - vibratoData;
paulaSetPeriod(ch->n_chanindex, vibratoData);
- ch->n_vibratopos += ((ch->n_vibratocmd >> 4) * 4);
+ ch->n_vibratopos += (ch->n_vibratocmd >> 2) & 0x3C;
}
static void vibrato(moduleChannel_t *ch)
{
- if ((ch->n_cmd & 0xFF) > 0)
- {
- if ((ch->n_cmd & 0x0F) > 0)
- ch->n_vibratocmd = (ch->n_vibratocmd & 0xF0) | (ch->n_cmd & 0x0F);
+ if ((ch->n_cmd & 0x0F) > 0)
+ ch->n_vibratocmd = (ch->n_vibratocmd & 0xF0) | (ch->n_cmd & 0x0F);
- if ((ch->n_cmd & 0xF0) > 0)
- ch->n_vibratocmd = (ch->n_cmd & 0xF0) | (ch->n_vibratocmd & 0x0F);
- }
+ if ((ch->n_cmd & 0xF0) > 0)
+ ch->n_vibratocmd = (ch->n_cmd & 0xF0) | (ch->n_vibratocmd & 0x0F);
- vibratoNoChange(ch);
+ vibrato2(ch);
}
static void tonePlusVolSlide(moduleChannel_t *ch)
@@ -581,39 +576,35 @@
static void vibratoPlusVolSlide(moduleChannel_t *ch)
{
- vibratoNoChange(ch);
+ vibrato2(ch);
volumeSlide(ch);
}
static void tremolo(moduleChannel_t *ch)
{
- int8_t tremoloTemp;
int16_t tremoloData;
- if ((ch->n_cmd & 0xFF) > 0)
- {
- if ((ch->n_cmd & 0x0F) > 0)
- ch->n_tremolocmd = (ch->n_tremolocmd & 0xF0) | (ch->n_cmd & 0x0F);
+ if ((ch->n_cmd & 0x0F) > 0)
+ ch->n_tremolocmd = (ch->n_tremolocmd & 0xF0) | (ch->n_cmd & 0x0F);
- if ((ch->n_cmd & 0xF0) > 0)
- ch->n_tremolocmd = (ch->n_cmd & 0xF0) | (ch->n_tremolocmd & 0x0F);
- }
+ if ((ch->n_cmd & 0xF0) > 0)
+ ch->n_tremolocmd = (ch->n_cmd & 0xF0) | (ch->n_tremolocmd & 0x0F);
- tremoloTemp = (ch->n_tremolopos / 4) & 31;
- tremoloData = (ch->n_wavecontrol >> 4) & 3;
+ const uint8_t tremoloPos = (ch->n_tremolopos >> 2) & 0x1F;
+ const uint8_t tremoloType = (ch->n_wavecontrol >> 4) & 3;
- if (!tremoloData)
+ if (tremoloType == 0)
{
- tremoloData = vibratoTable[tremoloTemp];
+ tremoloData = vibratoTable[tremoloPos];
}
else
{
- if (tremoloData == 1)
+ if (tremoloType == 1)
{
- if (ch->n_vibratopos < 0) // PT bug, should've been n_tremolopos
- tremoloData = 255 - (tremoloTemp * 8);
+ if (ch->n_vibratopos < 128) // PT bug, should've been ch->n_tremolopos
+ tremoloData = tremoloPos << 3;
else
- tremoloData = tremoloTemp * 8;
+ tremoloData = 255 - (tremoloPos << 3);
}
else
{
@@ -621,39 +612,37 @@
}
}
- tremoloData = (tremoloData * (ch->n_tremolocmd & 0xF)) / 64;
+ tremoloData = ((uint16_t)tremoloData * (ch->n_tremolocmd & 0xF)) >> 6;
- if (ch->n_tremolopos < 0)
+ if (ch->n_tremolopos < 128)
{
- tremoloData = ch->n_volume - tremoloData;
- if (tremoloData < 0)
- tremoloData = 0;
- }
- else
- {
tremoloData = ch->n_volume + tremoloData;
if (tremoloData > 64)
tremoloData = 64;
}
+ else
+ {
+ tremoloData = ch->n_volume - tremoloData;
+ if (tremoloData < 0)
+ tremoloData = 0;
+ }
paulaSetVolume(ch->n_chanindex, tremoloData);
- ch->n_tremolopos += (ch->n_tremolocmd >> 4) * 4;
+ ch->n_tremolopos += (ch->n_tremolocmd >> 2) & 0x3C;
}
static void sampleOffset(moduleChannel_t *ch)
{
- uint16_t newOffset;
-
if ((ch->n_cmd & 0xFF) > 0)
ch->n_sampleoffset = ch->n_cmd & 0xFF;
- newOffset = ch->n_sampleoffset << 7;
+ uint16_t newOffset = ch->n_sampleoffset << 7;
- if ((int16_t)newOffset < (int16_t)ch->n_length)
+ if ((int16_t)newOffset < ch->n_length)
{
ch->n_length -= newOffset;
- ch->n_start += newOffset*2;
+ ch->n_start += newOffset << 1;
}
else
{
@@ -663,9 +652,7 @@
static void E_Commands(moduleChannel_t *ch)
{
- uint8_t cmd;
-
- cmd = (ch->n_cmd & 0xF0) >> 4;
+ const uint8_t cmd = (ch->n_cmd & 0xF0) >> 4;
switch (cmd)
{
case 0x0: filterOnOff(ch); break;
@@ -725,15 +712,12 @@
static void checkEffects(moduleChannel_t *ch)
{
- uint8_t effect;
-
if (editor.muted[ch->n_chanindex])
return;
updateFunk(ch);
- effect = (ch->n_cmd & 0xF00) >> 8;
-
+ const uint8_t effect = (ch->n_cmd & 0xF00) >> 8;
if ((ch->n_cmd & 0xFFF) > 0)
{
switch (effect)
@@ -771,10 +755,9 @@
static void setPeriod(moduleChannel_t *ch)
{
- uint8_t i;
- uint16_t note;
+ int32_t i;
- note = ch->n_note & 0xFFF;
+ uint16_t note = ch->n_note & 0xFFF;
for (i = 0; i < 37; i++)
{
// periodTable[36] = 0, so i=36 is safe
@@ -782,7 +765,7 @@
break;
}
- // BUG: yes it's 'safe' if i=37 because of padding at the end of period table
+ // yes it's safe if i=37 because of zero-padding
ch->n_period = periodTable[(ch->n_finetune * 37) + i];
if ((ch->n_cmd & 0xFF0) != 0xED0) // no note delay
@@ -850,16 +833,17 @@
s = &song->samples[ch->n_samplenum];
ch->n_start = &song->sampleData[s->offset];
- ch->n_finetune = s->fineTune;
+ ch->n_finetune = s->fineTune & 0xF;
ch->n_volume = s->volume;
- ch->n_length = s->length / 2;
- ch->n_replen = s->loopLength / 2;
+ ch->n_length = s->length >> 1;
+ ch->n_replen = s->loopLength >> 1;
- if (s->loopStart > 0)
+ const uint16_t repeat = s->loopStart >> 1;
+ if (repeat > 0)
{
- ch->n_loopstart = ch->n_start + s->loopStart;
+ ch->n_loopstart = ch->n_start + (repeat << 1);
ch->n_wavestart = ch->n_loopstart;
- ch->n_length = (s->loopStart / 2) + ch->n_replen;
+ ch->n_length = repeat + ch->n_replen;
}
else
{
--- a/src/pt2_sampler.c
+++ b/src/pt2_sampler.c
@@ -1539,9 +1539,6 @@
paulaSetData(editor.tuningChan, tuneToneData);
paulaSetLength(editor.tuningChan, sizeof (tuneToneData) / 2);
paulaStartDMA(editor.tuningChan);
-
- // force loop flag on for scopes
- scopeExt[editor.tuningChan].newLoopFlag = scope[editor.tuningChan].loopFlag = true;
}
else
{
--- a/src/pt2_scopes.c
+++ b/src/pt2_scopes.c
@@ -21,75 +21,94 @@
// this uses code that is not entirely thread safe, but I have never had any issues so far...
-static volatile bool scopesReading;
+static volatile bool scopesUpdatingFlag, scopesDisplayingFlag;
static uint32_t scopeTimeLen, scopeTimeLenFrac;
static uint64_t timeNext64, timeNext64Frac;
static SDL_Thread *scopeThread;
-scopeChannel_t scope[AMIGA_VOICES]; // global
-scopeChannelExt_t scopeExt[AMIGA_VOICES]; // global
+scope_t scope[AMIGA_VOICES]; // global
int32_t getSampleReadPos(int32_t ch, uint8_t smpNum)
{
const int8_t *data;
- int32_t pos;
- scopeChannel_t *sc;
+ volatile bool active;
+ volatile int32_t pos;
+ volatile scope_t *sc;
+
moduleSample_t *s;
sc = &scope[ch];
// cache some stuff
+ active = sc->active;
data = sc->data;
pos = sc->pos;
- if (scopeExt[ch].active && pos >= 2)
- {
- s = &song->samples[smpNum];
+ if (!active || data == NULL || pos <= 2) // pos 0..2 = sample loop area for non-looping samples
+ return -1;
- /* Get real sampling position regardless of where the scope data points to
- ** sc->data changes during loop, offset and so on, so this has to be done
- ** (sadly, because it's really hackish).
- */
- pos = (int32_t)(&data[pos] - &song->sampleData[s->offset]);
- if (pos >= s->length)
- return -1;
+ s = &song->samples[smpNum];
- return pos;
- }
+ // hackish way of getting real scope/sampling position
+ pos = (int32_t)(&data[pos] - &song->sampleData[s->offset]);
+ if (pos < 0 || pos >= s->length)
+ return -1;
- return -1;
+ return pos;
}
-void setScopeDelta(int32_t ch, uint32_t delta)
+void scopeTrigger(int32_t ch, int32_t length)
{
- scope[ch].delta = delta;
+ volatile scope_t *sc = &scope[ch];
+ scope_t tempState = *sc; // cache it
+
+ const int8_t *newData = tempState.newData;
+ if (newData == NULL)
+ newData = &song->sampleData[RESERVED_SAMPLE_OFFSET]; // dummy sample
+
+ if (length < 2)
+ {
+ sc->active = false;
+ return;
+ }
+
+ tempState.posFrac = 0;
+ tempState.pos = 0;
+ tempState.data = newData;
+ tempState.length = length;
+ tempState.active = true;
+
+ /* Update live scope now.
+ ** In theory it -can- be written to in the middle of a cached read,
+ ** then the read thread writes its own non-updated cached copy back and
+ ** the trigger never happens. So far I have never seen it happen,
+ ** so it's probably very rare. Yes, this is not good coding...
+ */
+ *sc = tempState;
}
void updateScopes(void)
{
- scopeChannel_t *sc, tmp;
- scopeChannelExt_t *se, tmpExt;
+ scope_t tempState;
if (editor.isWAVRendering)
return;
- for (int32_t i = 0; i < AMIGA_VOICES; i++)
+ volatile scope_t *sc = scope;
+
+ scopesUpdatingFlag = true;
+ for (int32_t i = 0; i < AMIGA_VOICES; i++, sc++)
{
- sc = &scope[i];
- se = &scopeExt[i];
+ tempState = *sc; // cache it
- // cache these
- tmp = *sc;
- tmpExt = *se;
-
- if (!tmpExt.active)
+ if (!tempState.active)
continue; // scope is not active
- tmp.posFrac += tmp.delta;
- tmp.pos += tmp.posFrac >> 16;
- tmp.posFrac &= 0xFFFF;
+ tempState.posFrac += tempState.delta;
+ tempState.pos += tempState.posFrac >> SCOPE_FRAC_BITS;
+ tempState.posFrac &= SCOPE_FRAC_MASK;
- if (tmp.pos >= tmp.length)
+ if (tempState.pos >= tempState.length)
{
// sample reached end, simulate Paula register update (sample swapping)
@@ -96,37 +115,30 @@
/* Wrap pos around one time with current length, then set new length
** and wrap around it (handles one-shot loops and sample swapping).
*/
- tmp.pos -= tmp.length;
- tmp.length = tmpExt.newLength;
+ tempState.pos -= tempState.length;
- if (tmp.length > 0)
- tmp.pos %= tmp.length;
+ tempState.length = tempState.newLength;
+ if (tempState.length > 0)
+ tempState.pos %= tempState.length;
- tmp.data = tmpExt.newData;
- tmp.loopFlag = tmpExt.newLoopFlag;
- tmp.loopStart = tmpExt.newLoopStart;
-
- se->didSwapData = true;
+ tempState.data = tempState.newData;
}
- *sc = tmp; // update it
+ *sc = tempState; // update scope state
}
+ scopesUpdatingFlag = false;
}
-/* This routine gets the average sample peak through the running scope voices.
-** This gives a much more smooth and stable result than getting the peak from
-** the mixer, and we don't care about including filters/BLEP in the peak calculation.
+/* This routine gets the average sample amplitude through the running scope voices.
+** This gives a somewhat more stable result than getting the peak from the mixer,
+** and we don't care about including filters/BLEP in the peak calculation.
*/
static void updateRealVuMeters(void)
{
- bool didSwapData;
- int16_t volume;
- int32_t i, x, readPos, samplesToScan, smpDat, smpPeak;
- scopeChannel_t tmpScope, *sc;
- scopeChannelExt_t *se;
+ scope_t tmpScope, *sc;
// sink VU-meters first
- for (i = 0; i < AMIGA_VOICES; i++)
+ for (int32_t i = 0; i < AMIGA_VOICES; i++)
{
editor.realVuMeterVolumes[i] -= 3;
if (editor.realVuMeterVolumes[i] < 0)
@@ -134,206 +146,157 @@
}
// get peak sample data from running scope voices
- for (i = 0; i < AMIGA_VOICES; i++)
+ sc = scope;
+ for (int32_t i = 0; i < AMIGA_VOICES; i++, sc++)
{
- sc = &scope[i];
- se = &scopeExt[i];
+ tmpScope = *sc; // cache it
- // cache these two
- tmpScope = *sc;
- didSwapData = se->didSwapData;
+ if (!tmpScope.active || tmpScope.data == NULL || tmpScope.volume == 0 || tmpScope.length == 0)
+ continue;
- samplesToScan = tmpScope.delta >> 16;
+ int32_t samplesToScan = tmpScope.delta >> SCOPE_FRAC_BITS; // amount of integer samples getting skipped every frame
if (samplesToScan <= 0)
continue;
- if (samplesToScan > 512) // don't waste cycles on reading a ton of samples
+ // shouldn't happen (low period 113 -> samplesToScan=490), but let's not waste cycles if it does
+ if (samplesToScan > 512)
samplesToScan = 512;
- volume = song->channels[i].n_volume;
+ int32_t pos = tmpScope.pos;
+ int32_t length = tmpScope.length;
+ const int8_t *data = tmpScope.data;
- if (se->active && tmpScope.data != NULL && volume != 0 && tmpScope.length > 0)
+ int32_t runningAmplitude = 0;
+ for (int32_t x = 0; x < samplesToScan; x++)
{
- smpPeak = 0;
- readPos = tmpScope.pos;
+ int16_t amplitude = 0;
+ if (data != NULL)
+ amplitude = data[pos] * tmpScope.volume;
- if (tmpScope.loopFlag)
+ runningAmplitude += ABS(amplitude);
+
+ pos++;
+ if (pos >= length)
{
- for (x = 0; x < samplesToScan; x += 2) // loop enabled
- {
- if (didSwapData)
- {
- if (readPos >= tmpScope.length)
- readPos %= tmpScope.length; // s.data = loopStartPtr, wrap readPos to 0
- }
- else if (readPos >= tmpScope.length)
- {
- readPos = tmpScope.loopStart; // s.data = sampleStartPtr, wrap readPos to loop start
- }
+ pos = 0;
- smpDat = tmpScope.data[readPos] * volume;
-
- smpDat = ABS(smpDat);
- if (smpDat > smpPeak)
- smpPeak = smpDat;
-
- readPos += 2;
- }
+ /* Read cycle done, temporarily update the display data/length variables
+ ** before the scope thread does it.
+ */
+ data = tmpScope.newData;
+ length = tmpScope.newLength;
}
- else
- {
- for (x = 0; x < samplesToScan; x += 2) // no loop
- {
- if (readPos >= tmpScope.length)
- break;
+ }
- smpDat = tmpScope.data[readPos] * volume;
+ double dAvgAmplitude = runningAmplitude / (double)samplesToScan;
- smpDat = ABS(smpDat);
- if (smpDat > smpPeak)
- smpPeak = smpDat;
+ dAvgAmplitude *= (96.0 / (128.0 * 64.0)); // normalize
- readPos += 2;
- }
- }
+ int32_t vuHeight = (int32_t)dAvgAmplitude;
+ if (vuHeight > 48) // max VU-meter height
+ vuHeight = 48;
- smpPeak = ((smpPeak * 48) + (1 << 12)) >> 13; // rounded
- if (smpPeak > editor.realVuMeterVolumes[i])
- editor.realVuMeterVolumes[i] = (int8_t)smpPeak;
- }
+ if ((int8_t)vuHeight > editor.realVuMeterVolumes[i])
+ editor.realVuMeterVolumes[i] = (int8_t)vuHeight;
}
}
void drawScopes(void)
{
- bool didSwapData;
- int16_t scopeData, volume;
- int32_t i, x, y, readPos;
- uint32_t *dstPtr, *scopePtr, scopePixel;
- scopeChannel_t tmpScope, *sc;
- scopeChannelExt_t *se;
+ int16_t scopeData;
+ int32_t i, x, y;
+ uint32_t *dstPtr, *scopeDrawPtr;
+ volatile scope_t *sc;
+ scope_t tmpScope;
- scopesReading = true;
- if (ui.visualizerMode == VISUAL_QUADRASCOPE)
+ scopeDrawPtr = &video.frameBuffer[(71 * SCREEN_W) + 128];
+
+ const uint32_t bgColor = video.palette[PAL_BACKGRD];
+ const uint32_t fgColor = video.palette[PAL_QADSCP];
+
+ sc = scope;
+
+ scopesDisplayingFlag = true;
+ for (i = 0; i < AMIGA_VOICES; i++, sc++)
{
- // --- QUADRASCOPE ---
+ tmpScope = *sc; // cache it
- scopePtr = &video.frameBuffer[(71 * SCREEN_W) + 128];
- for (i = 0; i < AMIGA_VOICES; i++)
+ // render scope
+ if (tmpScope.active && tmpScope.data != NULL && tmpScope.volume != 0 && tmpScope.length > 0)
{
- sc = &scope[i];
- se = &scopeExt[i];
+ // scope is active
- // cache these two
- tmpScope = *sc;
- didSwapData = se->didSwapData;
+ sc->emptyScopeDrawn = false;
- volume = -song->channels[i].n_volume; // invert volume
-
- // render scope
- if (se->active && tmpScope.data != NULL && volume != 0 && tmpScope.length > 0)
+ // fill scope background
+ dstPtr = &video.frameBuffer[(55 * SCREEN_W) + (128 + (i * (SCOPE_WIDTH + 8)))];
+ for (y = 0; y < SCOPE_HEIGHT; y++)
{
- // scope is active
+ for (x = 0; x < SCOPE_WIDTH; x++)
+ dstPtr[x] = bgColor;
- se->emptyScopeDrawn = false;
+ dstPtr += SCREEN_W;
+ }
- // draw scope background
+ // render scope data
- dstPtr = &video.frameBuffer[(55 * SCREEN_W) + (128 + (i * (SCOPE_WIDTH + 8)))];
- scopePixel = video.palette[PAL_BACKGRD]; // this palette can change
+ int32_t pos = tmpScope.pos;
+ int32_t length = tmpScope.length;
+ const int16_t volume = -(tmpScope.volume << 7);
+ const int8_t *data = tmpScope.data;
- for (y = 0; y < SCOPE_HEIGHT; y++)
- {
- for (x = 0; x < SCOPE_WIDTH; x++)
- dstPtr[x] = scopePixel;
+ for (x = 0; x < SCOPE_WIDTH; x++)
+ {
+ scopeData = 0;
+ if (data != NULL)
+ scopeData = (data[pos] * volume) >> 16;
- dstPtr += SCREEN_W;
- }
+ scopeDrawPtr[(scopeData * SCREEN_W) + x] = fgColor;
- // render scope data
-
- scopePixel = video.palette[PAL_QADSCP];
-
- readPos = tmpScope.pos;
- if (tmpScope.loopFlag)
+ pos++;
+ if (pos >= length)
{
- // loop enabled
+ pos = 0;
- for (x = 0; x < SCOPE_WIDTH; x++)
- {
- if (didSwapData)
- {
- if (readPos >= tmpScope.length)
- readPos %= tmpScope.length; // s.data = loopStartPtr, wrap readPos to 0
- }
- else if (readPos >= tmpScope.length)
- {
- readPos = tmpScope.loopStart; // s.data = sampleStartPtr, wrap readPos to loop start
- }
-
- scopeData = (tmpScope.data[readPos++] * volume) >> 9; // (-128..127)*(-64..0) / 2^9 = -15..16
- scopePtr[(scopeData * SCREEN_W) + x] = scopePixel;
- }
+ /* Read cycle done, temporarily update the display data/length variables
+ ** before the scope thread does it.
+ */
+ length = tmpScope.newLength;
+ data = tmpScope.newData;
}
- else
- {
- // no loop
-
- for (x = 0; x < SCOPE_WIDTH; x++)
- {
- if (readPos >= tmpScope.length)
- {
- scopePtr[x] = scopePixel; // end of data, draw center pixel
- }
- else
- {
- scopeData = (tmpScope.data[readPos++] * volume) >> 9; // (-128..127)*(-64..0) / 2^9 = -15..16
- scopePtr[(scopeData * SCREEN_W) + x] = scopePixel;
- }
- }
- }
}
- else
- {
- // scope is inactive, draw empty scope once until it gets active again
+ }
+ else
+ {
+ // scope is inactive, draw empty scope once until it gets active again
- if (!se->emptyScopeDrawn)
+ if (!sc->emptyScopeDrawn)
+ {
+ // fill scope background
+ dstPtr = &video.frameBuffer[(55 * SCREEN_W) + (128 + (i * (SCOPE_WIDTH + 8)))];
+ for (y = 0; y < SCOPE_HEIGHT; y++)
{
- // draw scope background
-
- dstPtr = &video.frameBuffer[(55 * SCREEN_W) + (128 + (i * (SCOPE_WIDTH + 8)))];
- scopePixel = video.palette[PAL_BACKGRD];
-
- for (y = 0; y < SCOPE_HEIGHT; y++)
- {
- for (x = 0; x < SCOPE_WIDTH; x++)
- dstPtr[x] = scopePixel;
-
- dstPtr += SCREEN_W;
- }
-
- // draw line
-
- scopePixel = video.palette[PAL_QADSCP];
for (x = 0; x < SCOPE_WIDTH; x++)
- scopePtr[x] = scopePixel;
+ dstPtr[x] = bgColor;
- se->emptyScopeDrawn = true;
+ dstPtr += SCREEN_W;
}
- }
- scopePtr += SCOPE_WIDTH+8;
+ // draw scope line
+ for (x = 0; x < SCOPE_WIDTH; x++)
+ scopeDrawPtr[x] = fgColor;
+
+ sc->emptyScopeDrawn = true;
+ }
}
+
+ scopeDrawPtr += SCOPE_WIDTH+8;
}
- scopesReading = false;
+ scopesDisplayingFlag = false;
}
static int32_t SDLCALL scopeThreadFunc(void *ptr)
{
- int32_t time32;
- uint32_t diff32;
- uint64_t time64;
-
// this is needed for scope stability (confirmed)
SDL_SetThreadPriority(SDL_THREAD_PRIORITY_HIGH);
@@ -348,16 +311,19 @@
updateScopes();
- time64 = SDL_GetPerformanceCounter();
+ uint64_t time64 = SDL_GetPerformanceCounter();
if (time64 < timeNext64)
{
- assert(timeNext64-time64 <= 0xFFFFFFFFULL);
- diff32 = (uint32_t)(timeNext64 - time64);
+ time64 = timeNext64 - time64;
+ if (time64 > UINT32_MAX)
+ time64 = UINT32_MAX;
+ const uint32_t diff32 = (uint32_t)time64;
+
// convert to microseconds and round to integer
- time32 = (int32_t)((diff32 * editor.dPerfFreqMulMicro) + 0.5);
+ const int32_t time32 = (int32_t)((diff32 * editor.dPerfFreqMulMicro) + 0.5);
- // delay until we have reached next tick
+ // delay until we have reached the next frame
if (time32 > 0)
usleep(time32);
}
@@ -406,18 +372,23 @@
void stopScope(int32_t ch)
{
- while (scopesReading);
- memset(&scopeExt[ch], 0, sizeof (scopeChannelExt_t));
+ // wait for scopes to finish updating
+ while (scopesUpdatingFlag);
- while (scopesReading);
- memset(&scope[ch], 0, sizeof (scopeChannel_t));
+ scope[ch].active = false;
- scope[ch].length = scopeExt[ch].newLength = 2;
- while (scopesReading); // final wait to make sure scopes are all inactive
+ // wait for scope displaying to be done (safety)
+ while (scopesDisplayingFlag);
}
void stopAllScopes(void)
{
+ // wait for scopes to finish updating
+ while (scopesUpdatingFlag);
+
for (int32_t i = 0; i < AMIGA_VOICES; i++)
- stopScope(i);
+ scope[i].active = false;
+
+ // wait for scope displaying to be done (safety)
+ while (scopesDisplayingFlag);
}
--- a/src/pt2_scopes.h
+++ b/src/pt2_scopes.h
@@ -3,24 +3,25 @@
#include <stdint.h>
#include <stdbool.h>
#include "pt2_header.h"
+#include "pt2_structs.h"
-typedef struct scopeChannel_t // internal scope state
+#define SCOPE_FRAC_BITS 16
+#define SCOPE_FRAC_SCALE (1UL << 16)
+#define SCOPE_FRAC_MASK (SCOPE_FRAC_SCALE-1)
+
+typedef struct scope_t
{
const int8_t *data;
- bool loopFlag;
- int32_t length, pos, loopStart;
+ bool active, emptyScopeDrawn;
+ uint8_t volume;
+ int32_t length, pos;
uint32_t delta, posFrac;
-} scopeChannel_t;
-typedef struct scopeChannelExt // external scope state
-{
const int8_t *newData;
- volatile bool active, didSwapData;
- bool emptyScopeDrawn, newLoopFlag;
- int32_t newLength, newLoopStart;
-} scopeChannelExt_t;
+ int32_t newLength;
+} scope_t;
-void setScopeDelta(int32_t ch, uint32_t delta);
+void scopeTrigger(int32_t ch, int32_t length);
int32_t getSampleReadPos(int32_t ch, uint8_t smpNum);
void updateScopes(void);
void drawScopes(void);
@@ -28,5 +29,4 @@
void stopScope(int32_t ch);
void stopAllScopes(void);
-extern scopeChannel_t scope[AMIGA_VOICES];
-extern scopeChannelExt_t scopeExt[AMIGA_VOICES];
+extern scope_t scope[AMIGA_VOICES];
--- a/src/pt2_structs.h
+++ b/src/pt2_structs.h
@@ -68,9 +68,10 @@
typedef struct moduleChannel_t
{
int8_t *n_start, *n_wavestart, *n_loopstart, n_chanindex, n_volume;
- int8_t n_toneportdirec, n_vibratopos, n_tremolopos, n_pattpos, n_loopcount;
+ int8_t n_toneportdirec, n_pattpos, n_loopcount;
uint8_t n_wavecontrol, n_glissfunk, n_sampleoffset, n_toneportspeed;
uint8_t n_vibratocmd, n_tremolocmd, n_finetune, n_funkoffset, n_samplenum;
+ uint8_t n_vibratopos, n_tremolopos;
int16_t n_period, n_note, n_wantedperiod;
uint16_t n_cmd, n_length, n_replen;
uint32_t n_scopedelta;
--- a/src/pt2_visuals.c
+++ b/src/pt2_visuals.c
@@ -128,20 +128,19 @@
{
// this routine almost never delays if we have 60Hz vsync, but it's still needed in some occasions
- int32_t time32;
- uint32_t diff32;
- uint64_t time64;
-
- time64 = SDL_GetPerformanceCounter();
+ uint64_t time64 = SDL_GetPerformanceCounter();
if (time64 < timeNext64)
{
- assert(timeNext64-time64 <= 0xFFFFFFFFULL);
- diff32 = (uint32_t)(timeNext64 - time64);
+ time64 = timeNext64 - time64;
+ if (time64 > UINT32_MAX)
+ time64 = UINT32_MAX;
+ const uint32_t diff32 = (uint32_t)time64;
+
// convert to microseconds and round to integer
- time32 = (int32_t)((diff32 * editor.dPerfFreqMulMicro) + 0.5);
+ const int32_t time32 = (int32_t)((diff32 * editor.dPerfFreqMulMicro) + 0.5);
- // delay until we have reached next tick
+ // delay until we have reached the next frame
if (time32 > 0)
usleep(time32);
}
@@ -1262,8 +1261,8 @@
dstPtr += SCREEN_W;
}
- for (uint32_t i = 0; i < AMIGA_VOICES; i++)
- scopeExt[i].emptyScopeDrawn = false;
+ for (int32_t i = 0; i < AMIGA_VOICES; i++)
+ scope[i].emptyScopeDrawn = false;
}
void renderSpectrumAnalyzerBg(void)