Skip to content

Trigger a sound on BBC Micro

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
; OSWORD7 copies this eight-byte SOUND block into MOS's sound system.
; Flush channel 1, amplitude -15, pitch 100, duration 2 twentieths of a second.
; MOS services audio asynchronously. Call only from main, not inside IRQ.
; Changes A/X/Y/flags. Caller owns channel 1; no music arbitration here.
trigger_sound:
    lda #7
    ldx #<sound_block
    ldy #>sound_block
    jmp OSWORD
sound_block: !word 17,-15,100,2

The SOUND block is eight bytes: channel 17 flushes and uses channel 1, amplitude -15, pitch 100, duration 2. OSWORD 7 copies it into MOS audio state. Playback is asynchronous; call from main with MOS IRQ services intact. A/X/Y/flags change.

Define OSWORD=$FFF1, include the routine and its eight-byte block, then JSR trigger_sound from main. The MOS copies the block; its IRQ-driven sound system handles playback after the call. Channel 17 requests a flush of channel 1’s queue. That policy deliberately replaces a previous effect on that channel.

Change it and recognise a mistake

Change the SOUND block. Queuing versus flushing is a deliberate channel policy; copying the block gives the OS its own queued parameters.

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.

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 bbc-micro/patterns/assembly/input/mos-controls, type the generated build/loader.bas in the recorded BASIC, then RUN. Move with Q/A/O/P; Space restarts. Holding a direction keeps moving; holding Space must allow movement after the first restart.

BBC Micro native caller after accepted input.
Captured from the running caller in the configuration below.

Where setup, trigger and service belong

demo.asm
; BBC Model B MOS 1.20/BASIC II. Typed loader, then CALL &3000.
; Q/A/O/P move, Space restarts. MOS owns IRQ and SOUND queue; reset to leave.
*=$3000
OSWRCH=$ffee
OSWORD=$fff1
start:
    cli
    jsr reset_game
main_loop:
    jsr wait_frame
    jsr read_controls
    jsr detect_edges
    jsr update_player
    jsr draw_screen
    inc updates
frame_done:
    jmp main_loop
updates: !byte 0
!source "state.inc"
!source "rules.inc"
!source "input.inc"
!source "draw.inc"
!source "sound.inc"
!source "../../framework/game-loop/frame.inc"

!source "collision.inc"

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

Verification and sources

Target: BBC Model B MOS 1.20/BASIC II, MODE 6. 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. The muted MOS run is an explicit diagnostic RAM patch to RTS at trigger_sound, restored after the check.

Acorn, BBC Microcomputer Advanced User Guide / Electron technical documentation: OSBYTE &81 negative INKEY and OSWORD &07 SOUND block; MOS IRQ ownership.