Skip to content

Fix envelope generator freezing when envelope period is 0#13

Open
lxpollitt wants to merge 1 commit into
lanceewing:masterfrom
lxpollitt:fix/web-envelope-period-0
Open

Fix envelope generator freezing when envelope period is 0#13
lxpollitt wants to merge 1 commit into
lanceewing:masterfrom
lxpollitt:fix/web-envelope-period-0

Conversation

@lxpollitt

@lxpollitt lxpollitt commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

Context

Second in the series of AY-3-8912 sound emulation improvements, web version first as before. The WIP deployment with the whole series applied is available here if you want to hear the overall impact: https://joric-wip.pages.dev

Problem

The current emulation effectively treats an envelope period of 0 as a freeze (doesn't advance the envelope) whereas on the real chip an envelope period of 0 does not freeze the envelope: it runs at twice the speed of period 1. (Note, this is unlike the SOUND tone and noise periods, where 0 behaves the same as 1.)

Example symptoms / repros scenarios

  • PLAY 0,0,0,0 (the usual BASIC idiom for silencing sound) produces a loud click/pop on the current web version. An easy way to hear it: disable keyboard-click (CTRL-F), type PING, then PLAY 0,0,0,0. The PLAY makes a loud click/pop, and repeated PLAY 0,0,0,0s after it then do not generate any audible output. In contrast, on a real Oric-1 the first PLAY generates a very quiet click, and the subsequent PLAYs do the same. (Verified on real hardware.)
  • PLAY 1,0,0,0:SOUND1,100,4:WAIT300:SOUND1,100,0 should play a quiet tone and then silence, but the current emulator code plays a short chirp of whatever tone was previously on channel 1, then plays the quiet tone, then plays max volume tone instead of silence.

The cause is the special case handling of an envelope period of 0 by the emulation code. When a program writes 0 to the envelope period registers (R11/R12), the emulated envelope generator stops advancing entirely - a period != 0 guard skips the envelope tick. A subsequent envelope shape write (R13) presents the envelope's starting level (15 for the attack-low shapes), so any channel in envelope mode freezes at maximum output, producing a large persistent DC offset. The DC blocker then takes a small amount of time to drain that offset away, which is why the click/pop is particularly loud.

On a real chip an envelope period of 0 running at twice the speed of period 1 executes the full 16-step envelope cycle in 128us at 1 MHz. This results in a much briefer signal peak and quieter click.

Fix

The envelope period handler now maps a written value of 0 to half the period-1 value (the correct 2x rate), the envelope period is initialised to a non-zero value at reset, and the freezing guard is removed. The envelope also now starts in its "holding" state at reset, matching the real chip's steady state shortly after power-on (the reset-default shape decays to 0 and holds within 128us); previously the never-ticking guard happened to mask this.

With the fix, PLAY 0,0,0,0 after a sound produces a very brief quiet click as the envelope completes its fast decay to 0, rather than freezing at full level. Subsequent PLAY 0,0,0,0 commands produce the same quiet click.

Scope

Web platform only - one file (GwtAYPSG). No changes to core or the other platforms. Only the envelope-period-0 case is affected; envelope periods of 1 and above are unaffected.

Testing

Tested on macOS in Chrome. Confirmed that PING followed by PLAY 0,0,0,0 now produces a brief quiet click instead of a louder click/pop, and that repeated PLAY 0,0,0,0 commands each behave consistently rather than the first popping and the rest doing nothing.

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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant