Skip to content
RetrogamingDeep Dive Published Updated 8 min readViews unavailable

Commodore 64 SID Envelopes and Filters: Register Semantics Across Revisions

Model SID voice timing, ADSR state, filter routing, and chip-revision uncertainty with register-level traces and repeatable Commodore 64 tests.

The SID is a programmable sound device whose audible output depends on a sequence of register writes, internal oscillator and envelope state, filter configuration, and analog behavior. Reconstructing one familiar tune is not a sufficient implementation test: many register errors can be hidden by a tolerant instrument patch or by a particular chip sample. A useful emulator model treats each voice and the shared filter as independently observable state, then compares digital register behavior separately from analog output.

Commodore’s Programmer’s Reference Guide documents three voices, each with frequency, pulse-width, control, attack/decay, and sustain/release registers. The chip’s shared filter has cutoff, resonance, routing, mode, and master-volume controls. The MOS 6581 data sheet describes the hardware register map and the envelope generator. Software should therefore be represented as writes to this device interface, not as a list of abstract notes. The hardware does not receive concepts such as “instrument,” “beat,” or “sample”; those are conventions established by a music driver.

Voice state is more than frequency and waveform

Each voice has a 16-bit frequency value, a 12-bit pulse-width control, a control register, and two nibbles of envelope parameters. Frequency is written as low and high bytes, and a change in either byte affects the oscillator’s phase increment. An implementation must preserve the ordering of those bus writes and model the hardware’s update boundary. A game or tracker may temporarily write an intermediate frequency while changing the two halves, so an emulator that only records the final combined value can hide useful evidence about the machine-visible sequence.

The control register combines waveform selection with gate and synchronization/modulation controls. Gate is an edge-driven state transition: setting it starts an envelope attack, and clearing it requests release. Repeatedly writing the same gate state is not necessarily equivalent to a fresh edge. Preserve the previous and new values so that the edge behavior is explicit. The waveform bits can also be changed while a voice is active; tests should not assume the waveform is chosen only at note-on.

The ADSR bytes pack attack and decay rates into one register, and sustain and release values into another. Attack, decay, sustain, and release are not four independent timers. They form a state machine whose current phase, envelope counter, rate comparison state, and gate history determine the next amplitude step. A correct implementation cannot simply interpolate linearly from zero to a target over a guessed number of audio samples. It must follow the documented rate table and hardware counter semantics at the SID clock rate, then integrate or sample that state for the host audio renderer.

Sustain is a held level, not a duration. Clearing gate starts release from the current envelope level. If software changes ADSR parameters while a voice is running, future envelope transitions use the live register values according to chip behavior; the model should not freeze a copied “note settings” object at gate-on. Include writes during every phase in conformance tests, especially a new attack value before release and a sustain change while decay is still in progress.

Rate counters and the envelope edge cases

An envelope generator is a clocked digital circuit. Its visible output changes at rate periods derived from an internal counter, rather than once per host callback. The 6581 data sheet’s table gives the nominal rate periods. Implement that table as a table of SID-clock counts, not as a table of milliseconds. The SID clock source varies with the C64 video standard and system configuration, and audio output rates such as 44.1 kHz are unrelated host scheduling choices.

The envelope counter also has behavior that makes simple attack/decay/release models diverge over long runs. A test should include maximum and minimum rate settings, transitions between phases, rapid gate toggling, and sustained operation through counter wrap boundaries. Do not “fix” a long delay by resetting all envelope timing whenever any ADSR register changes; that can turn a hardware quirk into a systematic musical error. If an emulator intentionally implements a documented silicon revision difference, keep it as an explicit model choice and cite that evidence rather than silently making the default depend on platform floating-point behavior.

The MOS data sheet and Commodore programming guide are appropriate sources for register-level expectations, but they do not justify attributing every observed sound difference to a specific SID revision. Real chips can differ in analog response and board-level filtering; software-based comparisons must record chip model, board and revision when known, clock, output chain, and capture method. When those facts are unknown, describe the data as a capture from an unspecified SID configuration.

Shared filter routing and output modes

The filter is shared by the voices. Each voice can be routed into the filter through bits in the filter-routing register; another bit controls whether the external input path is included. The three voice outputs and the external input are therefore not equivalent to three independent per-voice filters. Routing mistakes can make the oscillator appear silent when it is actually being sent to a disabled or differently configured path.

The filter cutoff is programmed across low and high register fields. The high cutoff field is not a linear frequency value. Treating its integer value as hertz, or assuming the same frequency mapping for every SID revision, confuses a digital register with an analog transfer curve. The mode/volume register selects low-pass, band-pass, and high-pass contributions, along with the master output level. Test each filter mode independently with a stable tone, then vary cutoff, resonance, routing, and volume one dimension at a time. Keep a non-filtered voice as a control signal so a silent filter path is distinguishable from a stopped oscillator.

The filter’s audible response can depend on the physical chip and surrounding circuit. For reproducibility, a useful test artifact includes raw register writes, the oscillator/envelope values used to generate the signal, a direct digital emulator capture, and if available a line-level hardware recording. A screenshot of a tracker patch or an MP3 alone cannot identify whether the difference is in the filter model, volume scaling, analog output, or resampler.

A register trace as a conformance fixture

The following Python snippet is a small trace validator for tests. It checks address range and monotonically increasing SID-cycle timestamps; it intentionally does not claim to emulate the chip or decode every write.

from dataclasses import dataclass


@dataclass(frozen=True)
class SidWrite:
    cycle: int
    register: int
    value: int


def validate_trace(writes):
    previous_cycle = -1
    for write in writes:
        if write.cycle < previous_cycle:
            raise ValueError("SID writes must be time ordered")
        if not 0 <= write.register <= 0x18:
            raise ValueError("register is outside the documented SID map")
        if not 0 <= write.value <= 0xFF:
            raise ValueError("SID register writes are bytes")
        previous_cycle = write.cycle


trace = [SidWrite(10, 0x04, 0x21), SidWrite(11, 0x05, 0x24)]
validate_trace(trace)

For a production test, extend each record with voice identity, bus initiator, pre-write and post-write values, emulated chip revision, and a capture-frame index. The test harness should keep a golden register trace separate from its expected audio so failures can be localized: a register mismatch is different from a correct register stream that produces a different analog response.

Revision-aware measurements without folklore

“6581 sound” and “8580 sound” are labels, not complete experimental descriptions. A comparison should identify the chip marking and board if inspected, but also the C64 clock, SID clock relationship, load, power state, cable, digitizer gain, and any software volume control. Run a short calibration sequence that disables all voices, establishes a known volume state, exercises one waveform and envelope, then enables one filter route. Store the exact commands and raw capture. Repeating the test after warm-up is useful because thermal and supply conditions can affect an analog device.

An emulator should expose model selection as a stateful configuration and serialize that choice with save states. If it supports measured filter curves, record their provenance and sampling procedure. Do not claim that a set of curves represents every chip in a model family unless the sample count and uncertainty support that claim. If measurements are unavailable, a deterministic approximation with known limitations is more honest and more testable than presenting a hand-tuned response as physically exact.

Verification plan

First validate register addressing, reset defaults, byte masking, and readback behavior against the data sheet. Then test each voice in isolation: gate attack, decay to sustain, gate-off release, retrigger, waveform change while active, and frequency updates. Use cycle-based assertions for envelope transitions and a fixed SID clock so the test does not vary with host frame rate.

Next test the shared filter with one routed source at a time. Include zero and maximum cutoff fields, each mode combination, resonance changes, and routing changes during an active note. Check that voices excluded from the filter still reach the output path expected by the chip specification. Exercise the external input only when a model of that path exists; do not inject a fabricated signal as a substitute for an undocumented electrical condition.

Finally, run real music-driver traces and compare in layers: CPU writes, SID register state, oscillator/envelope samples, filter output, and final PCM. Re-run with multiple chip profiles or measured hardware captures only when each profile has provenance. A discrepancy at the final output should not be patched by changing the envelope rate table until the upstream register trace and clock alignment have been checked.

Acceptance criteria

A reliable SID implementation has a deterministic clocked envelope model, edge-aware gate transitions, explicit shared filter routing, documented register-width behavior, and separately testable analog output assumptions. It can explain a sound difference in terms of a register event, internal phase, filter route, chip profile, or audio pipeline stage. That is a stronger preservation result than matching one song by ear, and it keeps 6581/8580 distinctions evidence-led rather than folklore-driven.

Related:

Sources:

Comments