Skip to content

Trigger a sound on Atari 2600

Reuse a small real effect, understand who stops it, and compare its native output with an omitted-trigger control.

Give an accepted action a sound. The same caller changes its picture and triggers this effect; a second version performs the action with the trigger removed. Listen to both.

Working effect

Trigger omitted

Take the effect

The reusable effect

sound.inc
; TIA channel0; sound duration is serviced in overscan, not a CPU delay.
trigger_sound:
    lda #4
    sta $15
    lda #15
    sta $17
    sta $19
    lda #8
    sta $85
    rts
service_sound:
    lda $85
    beq sound_done
    dec $85
    bne sound_done
    lda #0
    sta $19
sound_done:
    rts

TIA channel 0 ($15/$17/$19), duration byte $85; trigger changes A/flags and service changes A/flags. The caller owns TIA/RIOT and the software scanline schedule.

Include sound.inc once with the caller’s constants and duration byte. Initialise the duration byte to zero before the first service call. Call trigger_sound for an accepted action, then call service_sound exactly once per accepted display update. The eighth service call mutes the effect. Because the supplied caller services in the triggering iteration too, this is a count of service calls, not eight complete elapsed frame intervals. Triggering again restarts the duration; there is no queue.

The complete initialiser also mutes other channels. To reuse just this effect beside music, agree channel ownership and remove unrelated global setup only after providing a replacement. Device latches, power, routing and master volume may affect more than one voice.

Change it and recognise a mistake

Change AUDC0/AUDF0/AUDV0 for another effect. Keep all work within the overscan budget; moving a blocking effect here can break the picture.

The omitted-trigger control still accepts the action but fails the same audio guard as the working caller. Keyboard clicks or other machine output are not enough: the BASIC checks compare against a separate rejected-command recording.

Deliberately omitted sound trigger

no-sound.asm
; Capability caller: original input/frame service, explicit drawing and sound.
; NTSC 4 KiB cartridge. Display servicing runs every field, but only a new
; fire press advances the turn. Owns all TIA state and RIOT RAM $80-$84.
* = $f000
start:
    sei
    cld
    ldx #$ff
    txs
    lda #0
    ldx #$7f
clear_ram:
    sta $80,x
    dex
    bpl clear_ram
    lda #2
    sta $01                   ; VBLANK: blank, button latching disabled
    jsr init_picture
frame:
    lda #2
    sta $00                   ; VSYNC
    sta $02
    sta $02
    sta $02                   ; three sync lines
    lda #0
    sta $00
    ldx #37
blank_lines:
    sta $02                   ; WSYNC
    dex
    bne blank_lines
    lda #0
    sta $01
    ldx #192
visible_lines:
    sta $02
    dex
    bne visible_lines
    lda #2
    sta $01
    sta $02                   ; overscan line 1; next line contains short work
    !source "../sample.inc"
    lda $81
    eor #$ff
    and $84
    beq no_action
    inc $80                   ; byte turn counter, wraps naturally
    nop
    nop
    nop
no_action:
    lda $84
    sta $81
    inc $82                   ; field counter, low
    bne show
    inc $83
show:
    jsr draw_picture
    jsr service_sound
    sta $02                   ; finish overscan line 3 (bounded drawing/audio work)
    ldx #27
overscan:
    sta $02
    dex
    bne overscan              ; 30 overscan lines including the two-line work window
    jmp frame
!source "../picture.inc"
!source "../sound.inc"
    !fill $fffa-*,0
    !word start,start,start

The working implementation above repairs that omission. If an effect instead drones on, check the mute/service path and that its state is initialised. Servicing twice makes a counted effect shorter; servicing only after input can leave it running while the player waits. Those are lifecycle decisions to test when adapting it.

Run the caller

Build the complete caller with make -C atari-2600/patterns/assembly/input/read-action. Press fire once, keep it held, then release and press again. The piece moves on each new press and returns to its starting position after two; holding does not repeat the action. Load the cartridge produced by the Makefile in the named configuration; retain its reset/header setup.

Atari 2600 native caller after accepted input.
Captured from the running caller in the configuration below.

Where setup, trigger and service belong

demo.asm
; Capability caller: original input/frame service, explicit drawing and sound.
; NTSC 4 KiB cartridge. Display servicing runs every field, but only a new
; fire press advances the turn. Owns all TIA state and RIOT RAM $80-$84.
* = $f000
start:
    sei
    cld
    ldx #$ff
    txs
    lda #0
    ldx #$7f
clear_ram:
    sta $80,x
    dex
    bpl clear_ram
    lda #2
    sta $01                   ; VBLANK: blank, button latching disabled
    jsr init_picture
frame:
    lda #2
    sta $00                   ; VSYNC
    sta $02
    sta $02
    sta $02                   ; three sync lines
    lda #0
    sta $00
    ldx #37
blank_lines:
    sta $02                   ; WSYNC
    dex
    bne blank_lines
    lda #0
    sta $01
    ldx #192
visible_lines:
    sta $02
    dex
    bne visible_lines
    lda #2
    sta $01
    sta $02                   ; overscan line 1; next line contains short work
    !source "sample.inc"
    lda $81
    eor #$ff
    and $84
    beq no_action
    inc $80                   ; byte turn counter, wraps naturally
    jsr trigger_sound
no_action:
    lda $84
    sta $81
    inc $82                   ; field counter, low
    bne show
    inc $83
show:
    jsr draw_picture
    jsr service_sound
    sta $02                   ; finish overscan line 3 (bounded drawing/audio work)
    ldx #27
overscan:
    sta $02
    dex
    bne overscan              ; 30 overscan lines including the two-line work window
    jmp frame
!source "picture.inc"
!source "sound.inc"
    !fill $fffa-*,0
    !word start,start,start

Compare blocking, serviced and queued sound when choosing the next step.

Verification and sources

Target: NTSC 4 KiB cartridge. These callers were executed with released Emu198x 0.44.1; assembly uses Asm198x 0.0.71. The sample folder contains reproduction instructions and retained observations. The independent checker binds source, native captures and actual output; its rejection tests exercise wrong state, missing files and false audio claims.

This qualifies the named emulator/firmware path. Pitch accuracy, original hardware and other peripherals require their own observations.

Atari, Stella Programmer’s Guide: INPT4, VBLANK/WSYNC and audio registers; MOS 6507/RIOT execution.