From 05e2a83f990ad8895fac193a30bd19bccee3a620 Mon Sep 17 00:00:00 2001 From: lxpollitt <630494+lxpollitt@users.noreply.github.com> Date: Wed, 10 Jun 2026 21:47:35 +0100 Subject: [PATCH] Fix envelope generator freezing when envelope period is 0 When a program set the envelope period registers (R11/R12) to 0, the emulated envelope stopped advancing entirely: the period value of 0 tripped a guard in the per-sample envelope logic that skipped the envelope counter. A subsequent envelope shape write (R13) unconditionally presents the envelope's starting volume (15 for the attack-low shapes), so any channel in envelope mode froze at maximum DAC level, producing a large persistent DC offset. This was the root cause of the loud click on PLAY 0,0,0,0 (the Oric BASIC idiom for silencing sound), as the DC blocker took seconds to drain the stuck level away. On the real chip an envelope period of 0 does not freeze: it runs at twice the speed of period 1, completing a full 16-step envelope cycle in 128us at 1 MHz. (This is unlike the tone and noise periods, where 0 behaves the same as 1.) The fix maps an envelope period value of 0 to half of the period 1 value in the R11/R12 handler, initialises the envelope period consistently at reset, and removes the freezing guard. The envelope now also starts in the holding state at reset, matching the real chip's steady state shortly after power-on (the reset-default shape 0 decays to 0 and holds within 128us); previously the never-ticking guard masked this. --- html/src/main/java/emu/joric/gwt/GwtAYPSG.java | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/html/src/main/java/emu/joric/gwt/GwtAYPSG.java b/html/src/main/java/emu/joric/gwt/GwtAYPSG.java index a7c98b5..16f92bc 100644 --- a/html/src/main/java/emu/joric/gwt/GwtAYPSG.java +++ b/html/src/main/java/emu/joric/gwt/GwtAYPSG.java @@ -151,12 +151,18 @@ public void init(Via via, Keyboard keyboard, Snapshot snapshot) { updateStep = (int) (((long) step * 8L * (long) SAMPLE_RATE) / (long) CLOCK_1MHZ); output = new int[] { 0, 0, 0, 0xFF }; count = new int[] { updateStep, updateStep, updateStep, 0x7fff, updateStep }; - period = new int[] { updateStep, updateStep, updateStep, updateStep, 0 }; + // Every period must be non-zero: writeSample's counter catch-up loops + // never terminate on a zero period. + period = new int[] { updateStep, updateStep, updateStep, updateStep, updateStep }; registers = new int[16]; volumeA = volumeB = volumeC = volumeEnvelope = 0; disableToneA = disableToneB = disableToneC = disableAllNoise = false; - countEnv = hold = alternate = attack = holding = 0; + countEnv = hold = alternate = attack = 0; + // The envelope starts out holding, as if the reset-default shape 0 + // (decay then hold) had already completed its decay to 0. It starts + // moving when a program first writes the envelope shape register. + holding = 1; enable = 0; outNoise = 0; random = 1; @@ -384,6 +390,12 @@ public void writeRegister(int address, int value) { case 0x0B: case 0x0C: { int val = (((registers[0x0C] << 8) | registers[0x0B]) * updateStep) << 1; + // On the real chip an envelope period register value of 0 runs at + // twice the speed of period 1, i.e. a full 16-step envelope cycle + // in 128us at 1 MHz. (This is unlike the tone and noise periods, + // where 0 behaves the same as 1.) Period 1 is (updateStep << 1) + // here, so period 0 maps to half of that, which is updateStep. + val = (val == 0 ? updateStep : val); int last = period[ENVELOPE]; period[ENVELOPE] = val; int newCount = count[ENVELOPE] - (val - last); @@ -514,7 +526,7 @@ public void writeSample() { left -= add; } while (left > 0); - if (holding == 0 && period[ENVELOPE] != 0) { + if (holding == 0) { if ((count[ENVELOPE] -= step) <= 0) { int ce = countEnv; int envelopePeriod = period[ENVELOPE];