ref: fc55f5c1b127721c26809a0732ba1b6893e9c6cc
parent: 71666cfc9fdb10b223032a30a4c0862af49c1e33
author: Olav Sørensen <olav.sorensen@live.no>
date: Sun Jun 7 14:22:22 EDT 2020
Pushed v1.18 code - Bugfix: Pasting copied sample data to an empty sample didn't work! Also fixed an issue with the sample length not changing when pasting data. - Bugfix: Scopes would never stop showing looped samples after channel muting - Bugfix: The sampling position line in the sampler screen would not behave correctly during "sample swapping". - Bugfix: Left/right/up/down cursor keys should not be repeated in keyrepeat mode (toggled with Caps Lock). - Windows bugfix: The Windows key could get stuck if you held down ALT while pressing it. - Windows bugfix: Num Lock now works ike it should when the program is in focus (yes, this ruins drumpad mode, but it never worked right to begin with). - The "real VU-meter" bars are now sinking a bit faster - Code cleanup
--- a/.gitignore
+++ b/.gitignore
@@ -19,3 +19,5 @@
*.opendb
*.cod
vs2019_project/pt2-clone/Debug/pt2-clone.vcxproj.FileListAbsolute.txt
+*.db-wal
+*.db-shm
--- a/src/pt2_audio.c
+++ b/src/pt2_audio.c
@@ -337,12 +337,6 @@
paulaSetData(i, ch->n_start + s->loopStart);
paulaSetLength(i, s->loopLength >> 1);
-
- if (!editor.songPlaying)
- {
- scopeSetData(i, ch->n_start + s->loopStart);
- scopeSetLength(i, s->loopLength >> 1);
- }
}
}
}
@@ -388,15 +382,16 @@
const double dOldPanR = paula[ch].dPanR;
memset(&paula[ch], 0, sizeof (paulaVoice_t));
- stopScope(ch);
+ memset(&blep[ch], 0, sizeof (blep_t));
+ memset(&blepVol[ch], 0, sizeof (blep_t));
- // store old pans
+ stopScope(ch); // it should be safe to clear the scope now
+ memset(&scope[ch], 0, sizeof (scope_t));
+
+ // restore old pans
paula[ch].dPanL = dOldPanL;
paula[ch].dPanR = dOldPanR;
- memset(&blep[ch], 0, sizeof (blep_t));
- memset(&blepVol[ch], 0, sizeof (blep_t));
-
if (audioWasntLocked)
unlockAudio();
}
@@ -432,21 +427,24 @@
void paulaSetPeriod(int32_t ch, uint16_t period)
{
- int32_t realPeriod;
double dPeriodToDeltaDiv;
- paulaVoice_t *v;
+ paulaVoice_t *v = &paula[ch];
- v = &paula[ch];
-
- v->syncPeriod = period; // used for pt2_sync.c
- v->syncFlags |= UPDATE_PERIOD; // used for pt2_sync.c
-
- if (period == 0)
+ int32_t realPeriod = period;
+ if (realPeriod == 0)
realPeriod = 1+65535; // confirmed behavior on real Amiga
- else if (period < 113)
+ else if (realPeriod < 113)
realPeriod = 113; // close to what happens on real Amiga (and needed for BLEP synthesis)
+
+ if (editor.songPlaying)
+ {
+ v->syncPeriod = realPeriod;
+ v->syncFlags |= SET_SCOPE_PERIOD;
+ }
else
- realPeriod = period;
+ {
+ scopeSetPeriod(ch, realPeriod);
+ }
// if the new period was the same as the previous period, use cached deltas
if (realPeriod != oldPeriod)
@@ -478,48 +476,72 @@
void paulaSetVolume(int32_t ch, uint16_t vol)
{
- paulaVoice_t *v;
+ paulaVoice_t *v = &paula[ch];
- v = &paula[ch];
+ int32_t realVol = vol;
- vol &= 127; // confirmed behavior on real Amiga
+ // confirmed behavior on real Amiga
+ realVol &= 127;
+ if (realVol > 64)
+ realVol = 64;
- if (vol > 64)
- vol = 64; // confirmed behavior on real Amiga
+ v->dVolume = realVol * (1.0 / 64.0);
- v->dVolume = vol * (1.0 / 64.0);
-
- v->syncVolume = (int8_t)vol; // used for pt2_sync.c
- v->syncFlags |= UPDATE_VOLUME; // used for pt2_sync.c
+ if (editor.songPlaying)
+ {
+ v->syncVolume = (uint8_t)realVol;
+ v->syncFlags |= SET_SCOPE_VOLUME;
+ }
+ else
+ {
+ scope[ch].volume = (uint8_t)realVol;
+ }
}
void paulaSetLength(int32_t ch, uint16_t len)
{
- if (len == 0)
+ int32_t realLength = len;
+ if (realLength == 0)
{
- len = 65535;
- /* Confirmed behavior on real Amiga (also needed for safety).
- ** And yes, we have room for this, it will never overflow!
+ realLength = 1+65535;
+ /* Confirmed behavior on real Amiga. We have room for this
+ ** even at the last sample slot, so it will never overflow!
+ **
+ ** PS: I don't really know if it's possible for ProTracker to
+ ** set a Paula length of 0, but I fully support this Paula
+ ** behavior just in case.
*/
}
- paula[ch].newLength = len << 1; // our mixer works with bytes, not words
- paula[ch].syncFlags |= UPDATE_LENGTH; // for pt2_sync.c
+ realLength <<= 1; // we work with bytes, not words
+
+ paula[ch].newLength = realLength;
+ if (editor.songPlaying)
+ paula[ch].syncFlags |= SET_SCOPE_LENGTH;
+ else
+ scope[ch].newLength = realLength;
}
void paulaSetData(int32_t ch, const int8_t *src)
{
- // set voice data
if (src == NULL)
- src = &song->sampleData[RESERVED_SAMPLE_OFFSET]; // dummy sample
+ src = &song->sampleData[RESERVED_SAMPLE_OFFSET]; // 128K reserved sample
paula[ch].newData = src;
- paula[ch].syncFlags |= UPDATE_DATA; // for pt2_sync.c
+ if (editor.songPlaying)
+ paula[ch].syncFlags |= SET_SCOPE_DATA;
+ else
+ scope[ch].newData = src;
}
void paulaStopDMA(int32_t ch)
{
paula[ch].active = false;
+
+ if (editor.songPlaying)
+ paula[ch].syncFlags |= STOP_SCOPE;
+ else
+ scope[ch].active = false;
}
void paulaStartDMA(int32_t ch)
@@ -534,9 +556,9 @@
dat = v->newData;
if (dat == NULL)
- dat = &song->sampleData[RESERVED_SAMPLE_OFFSET]; // dummy sample
+ dat = &song->sampleData[RESERVED_SAMPLE_OFFSET]; // 128K reserved sample
- length = v->newLength;
+ length = v->newLength; // in bytes, not words
if (length < 2)
length = 2; // for safety
@@ -546,10 +568,18 @@
v->length = length;
v->active = true;
- // for pt2_sync.c
- v->syncTriggerData = dat;
- v->syncTriggerLength = (uint16_t)(length >> 1);
- v->syncFlags |= TRIGGER_SAMPLE;
+ if (editor.songPlaying)
+ {
+ v->syncTriggerData = dat;
+ v->syncTriggerLength = length;
+ v->syncFlags |= TRIGGER_SCOPE;
+ }
+ else
+ {
+ scope[ch].newData = dat;
+ scope[ch].newLength = length;
+ scopeTrigger(ch);
+ }
}
void toggleA500Filters(void)
@@ -1015,7 +1045,7 @@
s->triggerData = v->syncTriggerData;
s->triggerLength = v->syncTriggerLength;
s->newData = v->newData;
- s->newLength = (uint16_t)(v->newLength >> 1);
+ s->newLength = v->newLength;
s->vuVolume = c->syncVuVolume;
s->analyzerVolume = c->syncAnalyzerVolume;
s->analyzerPeriod = c->syncAnalyzerPeriod;
@@ -1139,7 +1169,7 @@
if (audio.outputRate >= 96000) // cutoff is too high for 44.1kHz/48kHz
{
- // A1200 one-pole 6db/oct static RC low-pass filter:
+ // A1200 1-pole (6db/oct) static RC low-pass filter:
R = 680.0; // R321 (680 ohm resistor)
C = 6.8e-9; // C321 (6800pf capacitor)
fc = 1.0 / (2.0 * M_PI * R * C);
@@ -1146,7 +1176,7 @@
calcRCFilterCoeffs(audio.outputRate, fc, &filterLoA1200);
}
- // A500 one-pole 6db/oct static RC low-pass filter:
+ // A500 1-pole (6db/oct) static RC low-pass filter:
R = 360.0; // R321 (360 ohm resistor)
C = 1e-7; // C321 (0.1uF capacitor)
fc = 1.0 / (2.0 * M_PI * R * C);
@@ -1161,7 +1191,7 @@
fb = 0.125; // Fb = 0.125 : Q ~= 1/sqrt(2)
calcLEDFilterCoeffs(audio.outputRate, fc, fb, &filterLED);
- // A1200 one-pole 6db/oct static RC high-pass filter:
+ // A1200 1-pole (6db/oct) static RC high-pass filter:
R = 1390.0; // R324 (1K ohm resistor) + R325 (390 ohm resistor)
C = 2.2e-5; // C334 (22uF capacitor)
fc = 1.0 / (2.0 * M_PI * R * C);
--- a/src/pt2_audio.h
+++ b/src/pt2_audio.h
@@ -40,9 +40,9 @@
// used for pt2_sync.c
uint8_t syncFlags;
- int8_t syncVolume;
- uint16_t syncPeriod;
- uint16_t syncTriggerLength;
+ uint8_t syncVolume;
+ int32_t syncPeriod;
+ int32_t syncTriggerLength;
const int8_t *syncTriggerData;
} paulaVoice_t;
--- a/src/pt2_diskop.c
+++ b/src/pt2_diskop.c
@@ -900,7 +900,7 @@
modFree();
song = newSong;
- setupNewMod();
+ setupLoadedMod();
song->loaded = true;
statusAllRight();
--- a/src/pt2_edit.c
+++ b/src/pt2_edit.c
@@ -929,34 +929,14 @@
paulaSetData(ch, n_start);
paulaSetLength(ch, n_length);
- if (!editor.songPlaying)
- {
- scopeSetVolume(ch, vol);
- scopeSetPeriod(ch, period);
- scopeSetData(ch, n_start);
- scopeSetLength(ch, n_length);
- }
-
if (!editor.muted[ch])
- {
paulaStartDMA(ch);
- if (!editor.songPlaying)
- scopeTrigger(ch);
- }
else
- {
paulaStopDMA(ch);
- }
// these take effect after the current DMA cycle is done
paulaSetData(ch, NULL);
paulaSetLength(ch, 1);
-
- if (!editor.songPlaying)
- {
- scopeSetData(ch, NULL);
- scopeSetLength(ch, 1);
- }
}
void jamAndPlaceSample(SDL_Scancode scancode, bool normalMode)
@@ -1006,34 +986,14 @@
paulaSetData(ch, chn->n_start);
paulaSetLength(ch, chn->n_length);
- if (!editor.songPlaying)
- {
- scopeSetVolume(ch, chn->n_volume);
- scopeSetPeriod(ch, chn->n_period);
- scopeSetData(ch, chn->n_start);
- scopeSetLength(ch, chn->n_length);
- }
-
if (!editor.muted[ch])
- {
paulaStartDMA(ch);
- if (!editor.songPlaying)
- scopeTrigger(ch);
- }
else
- {
paulaStopDMA(ch);
- }
// these take effect after the current DMA cycle is done
paulaSetData(ch, chn->n_loopstart);
paulaSetLength(ch, chn->n_replen);
-
- if (!editor.songPlaying)
- {
- scopeSetData(ch, chn->n_loopstart);
- scopeSetLength(ch, chn->n_replen);
- }
}
// normalMode = normal keys, or else keypad keys (in jam mode)
--- 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.17"
+#define PROG_VER_STR "1.18"
#ifdef _WIN32
#define DIR_DELIMITER '\\'
@@ -36,13 +36,13 @@
*/
#define VBLANK_HZ 60
-/* Scopes are clocked at 64Hz instead of 60Hz to prevent +/- interference
-** from monitors not being exactly 60Hz (and unstable non-vsync mode).
-** Sadly the scopes might midly flicker from this.
+/* Scopes are clocked at 64Hz instead of 60Hz to prevent the small +/- Hz
+** interference from monitors not being exactly 60Hz (and unstable non-vsync mode).
+** Sadly, the scopes might midly flicker from this in some cases.
*/
#define SCOPE_HZ 64
-#define AMIGA_PAL_VBLANK_HZ 50
+#define AMIGA_PAL_VBLANK_HZ 49.9204092835
#define FONT_BMP_WIDTH
#define FONT_CHAR_W 8 // actual data length is 7, includes right spacing (1px column)
@@ -54,8 +54,10 @@
#define MAX_PATTERNS 100
#define MAX_SAMPLE_LEN 65534
-#define RESERVED_SAMPLE_OFFSET (31 * MAX_SAMPLE_LEN)
+// for NULL pointers
+#define RESERVED_SAMPLE_OFFSET ((31+1) * MAX_SAMPLE_LEN)
+
#define AMIGA_VOICES 4
#define SCOPE_WIDTH 40
#define SCOPE_HEIGHT 33
@@ -65,6 +67,7 @@
#define POSED_LIST_SIZE 12
+
// main crystal oscillator
#define AMIGA_PAL_XTAL_HZ 28375160
@@ -71,6 +74,9 @@
#define PAULA_PAL_CLK (AMIGA_PAL_XTAL_HZ / 8)
#define CIA_PAL_CLK (AMIGA_PAL_XTAL_HZ / 40)
+#define PAL_PAULA_MIN_SAFE_PERIOD 124
+#define PAL_PAULA_MAX_SAFE_HZ (PAULA_PAL_CLK / (double)PAL_PAULA_MIN_SAFE_PERIOD)
+
#define FILTERS_BASE_FREQ (PAULA_PAL_CLK / 214.0)
#define KEYB_REPEAT_DELAY 17
@@ -206,6 +212,8 @@
TEXT_EDIT_DECIMAL = 1,
TEXT_EDIT_HEX = 2
};
+
+int8_t *allocMemForAllSamples(void); // pt2_replayer.c
void restartSong(void);
void resetSong(void);
--- a/src/pt2_keyboard.c
+++ b/src/pt2_keyboard.c
@@ -73,67 +73,42 @@
}
#if defined _WIN32 && !defined _DEBUG
-/* For taking control over windows key if the program has focus.
+/* For taking control over the windows key if the program has focus.
** Warning: Don't do this in debug mode, it will completely ruin the keyboard input
** latency (in the OS in general) when the debugger is breaking.
*/
LRESULT CALLBACK lowLevelKeyboardProc(int32_t nCode, WPARAM wParam, LPARAM lParam)
{
- SDL_Event inputEvent;
SDL_Window *window = video.window;
- 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)
+ if (nCode == HC_ACTION && window != NULL)
{
- case WM_KEYUP:
- case WM_KEYDOWN:
+ switch (wParam)
{
- const bool windowHasFocus = SDL_GetWindowFlags(window) & SDL_WINDOW_INPUT_FOCUS;
-
- bEatKeystroke = windowHasFocus && (p->vkCode == VK_LWIN || p->vkCode == VK_NUMLOCK);
- if (!bEatKeystroke)
- break;
-
- memset(&inputEvent, 0, sizeof (SDL_Event));
-
- const bool keyDown = (wParam == WM_KEYDOWN);
- if (keyDown)
+ case WM_KEYUP:
+ case WM_KEYDOWN:
+ case WM_SYSKEYUP: // needed to prevent stuck Windows key if used with ALT
{
- if (windowsKeyIsDown)
- break; // Windows-key is already down (XXX: Do we need this check?)
+ const bool windowHasFocus = SDL_GetWindowFlags(window) & SDL_WINDOW_INPUT_FOCUS;
+ if (!windowHasFocus)
+ {
+ windowsKeyIsDown = false;
+ break;
+ }
- inputEvent.type = SDL_KEYDOWN;
- inputEvent.key.type = SDL_KEYDOWN;
- inputEvent.key.state = SDL_PRESSED;
- windowsKeyIsDown = true;
+ if (((KBDLLHOOKSTRUCT *)lParam)->vkCode != VK_LWIN)
+ break;
+
+ windowsKeyIsDown = (wParam == WM_KEYDOWN);
+ return 1; // eat keystroke
}
- else
- {
- inputEvent.type = SDL_KEYUP;
- inputEvent.key.type = SDL_KEYUP;
- inputEvent.key.state = SDL_RELEASED;
- windowsKeyIsDown = false;
- }
+ break;
- inputEvent.key.keysym.sym = SDLK_LGUI;
- inputEvent.key.keysym.scancode = SDL_SCANCODE_LGUI;
- inputEvent.key.keysym.mod = SDL_GetModState();
- inputEvent.key.timestamp = SDL_GetTicks();
- inputEvent.key.windowID = SDL_GetWindowID(window);
-
- SDL_PushEvent(&inputEvent);
+ default: break;
}
- break;
-
- default: break;
}
- return bEatKeystroke ? 1 : CallNextHookEx(g_hKeyboardHook, nCode, wParam, lParam);
+ return CallNextHookEx(g_hKeyboardHook, nCode, wParam, lParam);
}
#endif
@@ -366,12 +341,12 @@
return;
}
- // kludge to allow certain repeat-keys to use custom repeat/delay values
- if (editor.repeatKeyFlag && keyb.repeatKey && scancode == keyb.lastRepKey &&
- (keyb.leftAltPressed || keyb.leftAmigaPressed || keyb.leftCtrlPressed))
- {
+ // these keys should not allow to be repeated in keyrepeat mode (caps lock)
+ const bool illegalKeys = keyb.leftAltPressed || keyb.leftAmigaPressed || keyb.leftCtrlPressed
+ || scancode == SDL_SCANCODE_LEFT || scancode == SDL_SCANCODE_RIGHT
+ || scancode == SDL_SCANCODE_UP || scancode == SDL_SCANCODE_DOWN;
+ if (editor.repeatKeyFlag && keyb.repeatKey && scancode == keyb.lastRepKey && illegalKeys)
return;
- }
if (scancode == SDL_SCANCODE_KP_PLUS)
keyb.keypadEnterPressed = true;
@@ -428,6 +403,8 @@
}
}
+ // XXX: This really needs some refactoring, it's messy and not logical
+
if (!handleGeneralModes(keycode, scancode)) return;
if (!handleTextEditMode(scancode)) return;
if (ui.samplerVolBoxShown || ui.samplingBoxShown) return;
@@ -3453,9 +3430,8 @@
break;
}
- // repeat keys at 50Hz rate
-
- const uint64_t keyRepeatDelta = ((uint64_t)AMIGA_PAL_VBLANK_HZ << 32) / VBLANK_HZ;
+ // repeat keys at 49.92Hz (Amiga PAL) rate
+ const uint64_t keyRepeatDelta = (uint64_t)(((UINT32_MAX + 1.0) * (AMIGA_PAL_VBLANK_HZ / (double)VBLANK_HZ)) + 0.5);
keyb.repeatFrac += keyRepeatDelta; // 32.32 fixed-point counter
if (keyb.repeatFrac > 0xFFFFFFFF)
--- a/src/pt2_main.c
+++ b/src/pt2_main.c
@@ -267,7 +267,7 @@
setupSprites();
- song = createNewMod();
+ song = createEmptyMod();
if (song == NULL)
{
cleanUp();
@@ -327,6 +327,7 @@
setupWaitVBL();
while (editor.programRunning)
{
+ sinkVisualizerBars();
updateChannelSyncBuffer();
readMouseXY();
readKeyModifiers(); // set/clear CTRL/ALT/SHIFT/AMIGA key states
@@ -342,7 +343,6 @@
renderFrame();
flipFrame();
- sinkVisualizerBars();
}
cleanUp();
--- a/src/pt2_module_loader.c
+++ b/src/pt2_module_loader.c
@@ -585,8 +585,7 @@
}
}
- // allocate sample data (+2 sample slots for overflow safety (Paula and scopes))
- newMod->sampleData = (int8_t *)calloc(MOD_SAMPLES + 2, MAX_SAMPLE_LEN);
+ newMod->sampleData = allocMemForAllSamples();
if (newMod->sampleData == NULL)
{
statusOutOfMemory();
@@ -905,7 +904,7 @@
return true;
}
-void setupNewMod(void)
+void setupLoadedMod(void)
{
int8_t i;
@@ -988,7 +987,7 @@
song->loaded = false;
modFree();
song = newSong;
- setupNewMod();
+ setupLoadedMod();
song->loaded = true;
}
else
@@ -1120,7 +1119,7 @@
modFree();
song = newSong;
- setupNewMod();
+ setupLoadedMod();
song->loaded = true;
statusAllRight();
@@ -1183,7 +1182,7 @@
loadDroppedFile(oldFullPath, oldFullPathLen, oldAutoPlay, false);
}
-module_t *createNewMod(void)
+module_t *createEmptyMod(void)
{
uint8_t i;
module_t *newMod;
@@ -1199,23 +1198,23 @@
goto oom;
}
- // +2 sample slots for overflow safety (Paula and scopes)
- newMod->sampleData = (int8_t *)calloc(MOD_SAMPLES + 2, MAX_SAMPLE_LEN);
+ newMod->sampleData = allocMemForAllSamples();
if (newMod->sampleData == NULL)
goto oom;
newMod->header.numOrders = 1;
- for (i = 0; i < MOD_SAMPLES; i++)
+ moduleSample_t *s = newMod->samples;
+ for (i = 0; i < MOD_SAMPLES; i++, s++)
{
- newMod->samples[i].offset = MAX_SAMPLE_LEN * i;
- newMod->samples[i].loopLength = 2;
+ s->offset = MAX_SAMPLE_LEN * i;
+ s->loopLength = 2;
// setup GUI text pointers
- newMod->samples[i].volumeDisp = &newMod->samples[i].volume;
- newMod->samples[i].lengthDisp = &newMod->samples[i].length;
- newMod->samples[i].loopStartDisp = &newMod->samples[i].loopStart;
- newMod->samples[i].loopLengthDisp = &newMod->samples[i].loopLength;
+ s->volumeDisp = &s->volume;
+ s->lengthDisp = &s->length;
+ s->loopStartDisp = &s->loopStart;
+ s->loopLengthDisp = &s->loopLength;
}
for (i = 0; i < AMIGA_VOICES; i++)
--- a/src/pt2_module_loader.h
+++ b/src/pt2_module_loader.h
@@ -10,6 +10,6 @@
void loadModFromArg(char *arg);
void loadDroppedFile(char *fullPath, uint32_t fullPathLen, bool autoPlay, bool songModifiedCheck);
void loadDroppedFile2(void);
-module_t *createNewMod(void);
+module_t *createEmptyMod(void);
module_t *modLoad(UNICHAR *fileName);
-void setupNewMod(void);
+void setupLoadedMod(void);
--- a/src/pt2_pat2smp.h
+++ b/src/pt2_pat2smp.h
@@ -2,7 +2,7 @@
#include "pt2_header.h"
-#define PAT2SMP_HI_PERIOD 124 /* A-3 finetune +4, 28604.99Hz */
+#define PAT2SMP_HI_PERIOD 124 /* A-3 finetune +4, 28603.99Hz */
#define PAT2SMP_LO_PERIOD 160 /* F-3 finetune +1, 22168.09Hz */
#define PAT2SMP_HI_FREQ (PAULA_PAL_CLK / (double)PAT2SMP_HI_PERIOD)
--- a/src/pt2_replayer.c
+++ b/src/pt2_replayer.c
@@ -35,6 +35,25 @@
0x10, 0x13, 0x16, 0x1A, 0x20, 0x2B, 0x40, 0x80
};
+int8_t *allocMemForAllSamples(void)
+{
+ /* Allocate memoru for all sample data blocks.
+ **
+ ** We need three extra sample slots:
+ ** The 1st is extra safety padding since setting a Paula length of 0
+ ** results in reading (1+65535)*2 bytes. The 2nd and 3rd (64K*2 = 1x 128K)
+ ** are reserved for NULL pointers. This is needed for emulating a PT quirk.
+ **
+ ** We have a padding of 4 bytes at the end for length=0 quirk safety.
+ **
+ ** PS: I don't really know if it's possible for ProTracker to set a Paula
+ ** length of 0, but I fully support this Paula behavior just in case.
+ */
+ const size_t allocLen = ((MOD_SAMPLES + 3) * MAX_SAMPLE_LEN) + 4;
+
+ return (int8_t *)calloc(1, allocLen);
+}
+
void modSetSpeed(uint8_t speed)
{
song->speed = speed;
@@ -105,11 +124,15 @@
if (vol > 64)
vol = 64;
- ch->syncVuVolume = vol;
- ch->syncFlags |= UPDATE_VUMETER;
-
if (!editor.songPlaying)
+ {
editor.vuMeterVolumes[ch->n_chanindex] = vuMeterHeights[vol];
+ }
+ else
+ {
+ ch->syncVuVolume = vol;
+ ch->syncFlags |= UPDATE_VUMETER;
+ }
}
static void updateFunk(moduleChannel_t *ch)
@@ -853,7 +876,7 @@
// non-PT2 quirk
if (ch->n_length == 0)
- ch->n_loopstart = ch->n_wavestart = &song->sampleData[RESERVED_SAMPLE_OFFSET]; // dummy sample
+ ch->n_loopstart = ch->n_wavestart = &song->sampleData[RESERVED_SAMPLE_OFFSET]; // 128K reserved sample
}
if ((ch->n_note & 0xFFF) > 0)
--- a/src/pt2_sampler.c
+++ b/src/pt2_sampler.c
@@ -1501,12 +1501,6 @@
paulaSetData(ch, tuneToneData);
paulaSetLength(ch, sizeof (tuneToneData) / 2);
paulaStartDMA(ch);
-
- scopeSetPeriod(ch, periodTable[editor.tuningNote]);
- scopeSetVolume(ch, 64);
- scopeSetData(ch, tuneToneData);
- scopeSetLength(ch, sizeof (tuneToneData) / 2);
- scopeTrigger(ch);
}
else
{
@@ -1909,7 +1903,7 @@
readPos += markStart;
}
- // copy buffer
+ // copy actual buffer
memcpy(&tmpBuf[readPos], sampler.copyBuf, sampler.copyBufSize);
// copy end part
@@ -1925,7 +1919,7 @@
if (newLength > MAX_SAMPLE_LEN)
newLength = MAX_SAMPLE_LEN;
- sampler.samLength = (uint16_t)newLength;
+ sampler.samLength = s->length = (uint16_t)newLength;
if (s->loopLength > 2) // loop enabled?
{
@@ -1974,6 +1968,8 @@
}
memcpy(&song->sampleData[s->offset], tmpBuf, s->length);
+
+ // clear data after sample's length (if present)
if (s->length < MAX_SAMPLE_LEN)
memset(&song->sampleData[s->offset+s->length], 0, MAX_SAMPLE_LEN - s->length);
@@ -2036,24 +2032,10 @@
paulaSetData(chn, ch->n_start);
paulaSetLength(chn, ch->n_length);
- if (!editor.songPlaying)
- {
- scopeSetVolume(chn, ch->n_volume);
- scopeSetPeriod(chn, ch->n_period);
- scopeSetData(chn, ch->n_start);
- scopeSetLength(chn, ch->n_length);
- }
-
if (!editor.muted[chn])
- {
paulaStartDMA(chn);
- if (!editor.songPlaying)
- scopeTrigger(chn);
- }
else
- {
paulaStopDMA(chn);
- }
// these take effect after the current DMA cycle is done
if (playWaveformFlag)
@@ -2060,23 +2042,11 @@
{
paulaSetData(chn, ch->n_loopstart);
paulaSetLength(chn, ch->n_replen);
-
- if (!editor.songPlaying)
- {
- scopeSetData(chn, ch->n_loopstart);
- scopeSetLength(chn, ch->n_replen);
- }
}
else
{
paulaSetData(chn, NULL);
paulaSetLength(chn, 1);
-
- if (!editor.songPlaying)
- {
- scopeSetData(chn, NULL);
- scopeSetLength(chn, 1);
- }
}
updateSpectrumAnalyzer(ch->n_volume, ch->n_period);
@@ -2854,24 +2824,18 @@
void drawSamplerLine(void)
{
- uint8_t i;
- int32_t pos;
-
hideSprite(SPRITE_SAMPLING_POS_LINE);
if (!ui.samplerScreenShown || ui.samplerVolBoxShown || ui.samplerFiltersBoxShown)
return;
- for (i = 0; i < AMIGA_VOICES; i++)
+ for (int32_t ch = 0; ch < AMIGA_VOICES; ch++)
{
- if (song->channels[i].n_samplenum == editor.currSample && !editor.muted[i])
+ int32_t pos = getSampleReadPos(ch);
+ if (pos >= 0)
{
- pos = getSampleReadPos(i, editor.currSample);
- if (pos >= 0)
- {
- pos = 3 + smpPos2Scr(pos);
- if (pos >= 3 && pos <= 316)
- setSpritePos(SPRITE_SAMPLING_POS_LINE, pos, 138);
- }
+ pos = 3 + smpPos2Scr(pos);
+ if (pos >= 3 && pos <= 316)
+ setSpritePos(SPRITE_SAMPLING_POS_LINE, pos, 138);
}
}
}
--- a/src/pt2_sampling.c
+++ b/src/pt2_sampling.c
@@ -312,7 +312,9 @@
{
char str[16];
sprintf(str, "%05dHZ", roundedOutputFrequency);
- textOutBg(262, 208, str, roundedOutputFrequency <= 28604 ? video.palette[PAL_GENTXT] : 0x8C0F0F, video.palette[PAL_GENBKG]);
+
+ const int32_t maxSafeFrequency = (int32_t)(PAL_PAULA_MAX_SAFE_HZ + 0.5); // rounded
+ textOutBg(262, 208, str, roundedOutputFrequency <= maxSafeFrequency ? video.palette[PAL_GENTXT] : 0x8C0F0F, video.palette[PAL_GENBKG]);
}
static void drawSamplingModeCross(void)
--- a/src/pt2_scopes.c
+++ b/src/pt2_scopes.c
@@ -35,89 +35,81 @@
oldPeriod = -1;
}
-int32_t getSampleReadPos(int32_t ch, uint8_t smpNum)
+// this is quite hackish, but fixes sample swapping issues
+static int32_t getSampleSlotFromReadAddress(const int8_t *sampleReadAddress)
{
- const int8_t *data;
- volatile bool active;
- volatile int32_t pos;
- volatile scope_t *sc;
+ assert(song != NULL);
+ const int8_t *sampleData = song->sampleData;
+ const int32_t sampleSlotSize = MAX_SAMPLE_LEN;
- moduleSample_t *s;
-
- sc = &scope[ch];
-
- // cache some stuff
- active = sc->active;
- data = sc->data;
- pos = sc->pos;
-
- if (!active || data == NULL || pos <= 2) // pos 0..2 = sample loop area for non-looping samples
+ if (sampleData == NULL) // shouldn't really happen, but just in case
return -1;
- s = &song->samples[smpNum];
+ int32_t sampleSlot = 30; // start at last slot
- // 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;
+ const int8_t *sampleBaseAddress = &sampleData[sampleSlot * sampleSlotSize];
+ if (sampleReadAddress == NULL || sampleReadAddress >= sampleBaseAddress+sampleSlotSize)
+ return -1; // out of range
- return pos;
-}
+ for (; sampleSlot >= 0; sampleSlot--)
+ {
+ if (sampleReadAddress >= sampleBaseAddress)
+ break;
-void scopeSetVolume(int32_t ch, uint16_t vol)
-{
- vol &= 127; // confirmed behavior on real Amiga
+ sampleBaseAddress -= sampleSlotSize;
+ }
- if (vol > 64)
- vol = 64; // confirmed behavior on real Amiga
-
- scope[ch].volume = (uint8_t)vol;
+ return sampleSlot; // 0..30, or -1 if out of range
}
-void scopeSetPeriod(int32_t ch, uint16_t period)
+int32_t getSampleReadPos(int32_t ch) // used for the sampler screen
{
- int32_t realPeriod;
+ // cache some stuff
+ const scope_t *sc = &scope[ch];
+ const bool active = sc->active;
+ const int8_t *data = sc->data;
+ const int32_t pos = sc->pos;
+ const int32_t len = sc->length;
- if (period == 0)
- realPeriod = 1+65535; // confirmed behavior on real Amiga
- else if (period < 113)
- realPeriod = 113; // close to what happens on real Amiga (and needed for BLEP synthesis)
- else
- realPeriod = period;
+ if (song == NULL || !active || data == NULL)
+ return -1;
- // if the new period was the same as the previous period, use cached deltas
- if (realPeriod != oldPeriod)
- {
- oldPeriod = realPeriod;
+ /* Because the scopes work like the Paula emulation, we have a DATA
+ ** and LENGTH variable, which are not static. This means that we have
+ ** to get creative to get the absolute sampling position.
+ */
- // this period is not cached, calculate scope delta
+ int32_t sample = getSampleSlotFromReadAddress(data);
+ if (sample != editor.currSample)
+ return -1; // sample is not the one we're seeing in the sampler screen
- const float fPeriodToScopeDeltaDiv = PAULA_PAL_CLK / (float)SCOPE_HZ;
- fOldScopeDelta = fPeriodToScopeDeltaDiv / realPeriod;
- }
+ const moduleSample_t *s = &song->samples[sample];
+ const int8_t *sampleReadAddress = &data[pos];
+ const int8_t *sampleBaseAddress = &song->sampleData[s->offset];
+ const int32_t realPos = (int32_t)(sampleReadAddress - sampleBaseAddress);
- scope[ch].fDelta = fOldScopeDelta;
-}
+ // return -1 if sample has no loop and read length is 2 (playing sample "loop" area)
+ const bool loopEnabled = (s->loopStart + s->loopLength) > 2;
+ if (!loopEnabled && len == 2)
+ return -1;
-void scopeSetData(int32_t ch, const int8_t *src)
-{
- // set voice data
- if (src == NULL)
- src = &song->sampleData[RESERVED_SAMPLE_OFFSET]; // dummy sample
+ if (realPos < 0 || realPos >= s->length)
+ return -1;
- scope[ch].newData = src;
+ return realPos;
}
-void scopeSetLength(int32_t ch, uint16_t len)
+void scopeSetPeriod(int32_t ch, int32_t period)
{
- if (len == 0)
+ // if the new period was the same as the previous period, use cached deltas
+ if (period != oldPeriod)
{
- len = 65535;
- /* Confirmed behavior on real Amiga (also needed for safety).
- ** And yes, we have room for this, it will never overflow!
- */
+ oldPeriod = period;
+ const float fPeriodToScopeDeltaDiv = PAULA_PAL_CLK / (float)SCOPE_HZ;
+ fOldScopeDelta = fPeriodToScopeDeltaDiv / period;
}
- scope[ch].newLength = len << 1;
+
+ scope[ch].fDelta = fOldScopeDelta;
}
void scopeTrigger(int32_t ch)
@@ -127,11 +119,11 @@
const int8_t *newData = tempState.newData;
if (newData == NULL)
- newData = &song->sampleData[RESERVED_SAMPLE_OFFSET]; // dummy sample
+ newData = &song->sampleData[RESERVED_SAMPLE_OFFSET]; // 128K reserved sample
- int32_t newLength = tempState.newLength;
+ int32_t newLength = tempState.newLength; // in bytes, not words
if (newLength < 2)
- newLength = 2;
+ newLength = 2; // for safety
tempState.fPhase = 0.0f;
tempState.pos = 0;
@@ -180,7 +172,7 @@
tempState.pos -= tempState.length;
tempState.length = tempState.newLength;
- if (tempState.length > 0)
+ if (tempState.pos >= tempState.length && tempState.length > 0)
tempState.pos %= tempState.length;
tempState.data = tempState.newData;
@@ -202,7 +194,7 @@
// sink VU-meters first
for (int32_t i = 0; i < AMIGA_VOICES; i++)
{
- editor.realVuMeterVolumes[i] -= 3;
+ editor.realVuMeterVolumes[i] -= 4;
if (editor.realVuMeterVolumes[i] < 0)
editor.realVuMeterVolumes[i] = 0;
}
@@ -260,41 +252,34 @@
void drawScopes(void)
{
- int16_t scopeData;
- int32_t i, x;
- uint32_t *scopeDrawPtr;
- volatile scope_t *sc;
- scope_t tmpScope;
+ volatile scope_t *sc = scope; // cache it
+ int32_t scopeX = 128;
- 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++)
+ for (int32_t i = 0; i < AMIGA_VOICES; i++, sc++)
{
- tmpScope = *sc; // cache it
+ scope_t tmpScope = *sc; // cache it
// render scope
if (tmpScope.active && tmpScope.data != NULL && tmpScope.volume != 0 && tmpScope.length > 0)
{
- // scope is active
-
sc->emptyScopeDrawn = false;
// fill scope background
- fillRect(128 + (i * (SCOPE_WIDTH + 8)), 55, SCOPE_WIDTH, SCOPE_HEIGHT, video.palette[PAL_BACKGRD]);
+ fillRect(scopeX, 55, SCOPE_WIDTH, SCOPE_HEIGHT, bgColor);
// render scope data
-
+ int16_t scopeData;
int32_t pos = tmpScope.pos;
int32_t length = tmpScope.length;
const int16_t volume = -(tmpScope.volume << 7);
const int8_t *data = tmpScope.data;
+ uint32_t *scopeDrawPtr = &video.frameBuffer[(71 * SCREEN_W) + scopeX];
- for (x = 0; x < SCOPE_WIDTH; x++)
+ for (int32_t x = 0; x < SCOPE_WIDTH; x++)
{
scopeData = 0;
if (data != NULL)
@@ -302,37 +287,30 @@
scopeDrawPtr[(scopeData * SCREEN_W) + x] = fgColor;
- pos++;
- if (pos >= length)
+ if (++pos >= length)
{
pos = 0;
- /* Read cycle done, temporarily update the display data/length variables
- ** before the scope thread does it.
- */
+ // read cycle done, update the drawing data/length variables
length = tmpScope.newLength;
data = tmpScope.newData;
}
}
}
- else
+ else if (!sc->emptyScopeDrawn)
{
- // scope is inactive, draw empty scope once until it gets active again
+ // scope is inactive (or vol=0), draw empty scope once until it gets active again
- if (!sc->emptyScopeDrawn)
- {
- // fill scope background
- fillRect(128 + (i * (SCOPE_WIDTH + 8)), 55, SCOPE_WIDTH, SCOPE_HEIGHT, video.palette[PAL_BACKGRD]);
+ // fill scope background
+ fillRect(scopeX, 55, SCOPE_WIDTH, SCOPE_HEIGHT, bgColor);
- // draw scope line
- for (x = 0; x < SCOPE_WIDTH; x++)
- scopeDrawPtr[x] = fgColor;
+ // draw scope line
+ hLine(scopeX, 71, SCOPE_WIDTH, fgColor);
- sc->emptyScopeDrawn = true;
- }
+ sc->emptyScopeDrawn = true;
}
- scopeDrawPtr += SCOPE_WIDTH+8;
+ scopeX += SCOPE_WIDTH+8;
}
scopesDisplayingFlag = false;
}
--- a/src/pt2_scopes.h
+++ b/src/pt2_scopes.h
@@ -23,13 +23,10 @@
void resetCachedScopePeriod(void);
-void scopeSetVolume(int32_t ch, uint16_t vol);
-void scopeSetPeriod(int32_t ch, uint16_t period);
-void scopeSetData(int32_t ch, const int8_t *src);
-void scopeSetLength(int32_t ch, uint16_t len);
+void scopeSetPeriod(int32_t ch, int32_t period);
void scopeTrigger(int32_t ch);
-int32_t getSampleReadPos(int32_t ch, uint8_t smpNum);
+int32_t getSampleReadPos(int32_t ch);
void updateScopes(void);
void drawScopes(void);
bool initScopes(void);
--- a/src/pt2_sync.c
+++ b/src/pt2_sync.c
@@ -147,31 +147,34 @@
{
scope_t *s = scope;
syncedChannel_t *c = chSyncEntry->channels;
- for (int32_t i = 0; i < AMIGA_VOICES; i++, s++, c++)
+ for (int32_t ch = 0; ch < AMIGA_VOICES; ch++, s++, c++)
{
- const uint8_t flags = updateFlags[i];
+ const uint8_t flags = updateFlags[ch];
if (flags == 0)
continue;
- if (flags & UPDATE_VOLUME)
- scopeSetVolume(i, c->volume);
+ if (flags & SET_SCOPE_VOLUME)
+ scope[ch].volume = c->volume;
- if (flags & UPDATE_PERIOD)
- scopeSetPeriod(i, c->period);
+ if (flags & SET_SCOPE_PERIOD)
+ scopeSetPeriod(ch, c->period);
- if (flags & TRIGGER_SAMPLE)
+ if (flags & TRIGGER_SCOPE)
{
s->newData = c->triggerData;
- s->newLength = c->triggerLength << 1;
- scopeTrigger(i);
+ s->newLength = c->triggerLength;
+ scopeTrigger(ch);
}
- if (flags & UPDATE_DATA)
- scopeSetData(i, c->newData);
+ if (flags & SET_SCOPE_DATA)
+ scope[ch].newData = c->newData;
- if (flags & UPDATE_LENGTH)
- scopeSetLength(i, c->newLength);
+ if (flags & SET_SCOPE_LENGTH)
+ scope[ch].newLength = c->newLength;
+ if (flags & STOP_SCOPE)
+ scope[ch].active = false;
+
if (flags & UPDATE_ANALYZER)
updateSpectrumAnalyzer(c->analyzerVolume, c ->analyzerPeriod);
@@ -178,7 +181,7 @@
if (flags & UPDATE_VUMETER) // for fake VU-meters only
{
if (c->vuVolume <= 64)
- editor.vuMeterVolumes[i] = vuMeterHeights[c->vuVolume];
+ editor.vuMeterVolumes[ch] = vuMeterHeights[c->vuVolume];
}
}
}
--- a/src/pt2_sync.h
+++ b/src/pt2_sync.h
@@ -6,13 +6,15 @@
enum // flags
{
- UPDATE_VOLUME = 1,
- UPDATE_PERIOD = 2,
- TRIGGER_SAMPLE = 4,
- UPDATE_DATA = 8,
- UPDATE_LENGTH = 16,
- UPDATE_VUMETER = 32,
- UPDATE_ANALYZER = 64
+ SET_SCOPE_VOLUME = 1,
+ SET_SCOPE_PERIOD = 2,
+ SET_SCOPE_DATA = 4,
+ SET_SCOPE_LENGTH = 8,
+ TRIGGER_SCOPE = 16,
+ STOP_SCOPE = 32,
+
+ UPDATE_VUMETER = 64,
+ UPDATE_ANALYZER = 128
};
// 2^n-1 - don't change this! Queue buffer is already ~1MB in size
@@ -22,9 +24,10 @@
{
uint8_t flags;
const int8_t *triggerData, *newData;
- uint16_t triggerLength, newLength;
+ int32_t triggerLength, newLength;
uint8_t volume, vuVolume, analyzerVolume;
- uint16_t period, analyzerPeriod;
+ uint16_t analyzerPeriod;
+ int32_t period;
} syncedChannel_t;
typedef struct chSyncData_t
--- a/src/pt2_visuals.c
+++ b/src/pt2_visuals.c
@@ -2186,10 +2186,10 @@
{
int32_t i;
- // sink stuff @ 50Hz rate
+ // sink stuff @ 49.92Hz (Amiga PAL) rate
static uint64_t counter50Hz;
- const uint64_t counter50HzDelta = ((uint64_t)AMIGA_PAL_VBLANK_HZ << 32) / VBLANK_HZ;
+ const uint64_t counter50HzDelta = (uint64_t)(((UINT32_MAX + 1.0) * (AMIGA_PAL_VBLANK_HZ / (double)VBLANK_HZ)) + 0.5);
counter50Hz += counter50HzDelta; // 32.32 fixed-point counter
if (counter50Hz > 0xFFFFFFFF)