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
PSG equ $06
; Pulse on SN76489 tone0, eight serviced frames. Other channels are muted here.
trigger_sound:
push af
ld a,$80
out (PSG),a
ld a,$10
out (PSG),a
ld a,$90
out (PSG),a
ld a,8
ld (SOUND_TICKS),a
pop af
ret
service_sound:
ld a,(SOUND_TICKS)
or a
ret z
dec a
ld (SOUND_TICKS),a
ret nz
ld a,$9f
out (PSG),a
retSN76489 tone 0 writes port $06, duration $DF05. Reads at $06 belong to keyboard input while writes belong to sound. Trigger preserves AF/B; service changes AF.
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 the tone divisor or duration counter. The USR entry takes over until reset; it does not return to a live BASIC session.
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
; Original capability caller: owns VDP, one audio channel and declared RAM.
VDP_DATA equ $01
VDP_CTRL equ $02
TURNS equ $df00
SOUND_TICKS equ $df05
; Entered from stock MTX BASIC using POKE and USR. Takes over until reset.
org $e000
start:
di
ld sp,$f7f0
xor a
ld ($df00),a
ld ($df01),a
ld ($df02),a
ld ($df03),a
in a,($02)
xor a
out ($02),a
ld a,$80
out ($02),a ; R0 legacy graphics
xor a
out ($02),a
ld a,$81
out ($02),a ; R1 display and VDP IRQ disabled
call init_picture
main:
call wait_frame
ld hl,($df02)
inc hl
ld ($df02),hl
include "../sample.inc"
ld a,($df01)
cpl
and b
jr z,no_action
ld a,($df00)
inc a
ld ($df00),a
nop
nop
nop
no_action:
ld a,b
ld ($df01),a
call draw_picture
call service_sound
jp main
; AF clobbered; caller exclusively owns status/control-port latch.
wait_frame:
in a,($02)
wait_new:
in a,($02)
and $80
jr z,wait_new
ret
include "../picture.inc"
include "../sound.inc"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 memotech-mtx/patterns/assembly/input/read-action. Press Space 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. Enter the generated DATA/POKE loader through stock BASIC, then RUN; USR takes over until reset.

Where setup, trigger and service belong
; Original capability caller: owns VDP, one audio channel and declared RAM.
VDP_DATA equ $01
VDP_CTRL equ $02
TURNS equ $df00
SOUND_TICKS equ $df05
; Entered from stock MTX BASIC using POKE and USR. Takes over until reset.
org $e000
start:
di
ld sp,$f7f0
xor a
ld ($df00),a
ld ($df01),a
ld ($df02),a
ld ($df03),a
in a,($02)
xor a
out ($02),a
ld a,$80
out ($02),a ; R0 legacy graphics
xor a
out ($02),a
ld a,$81
out ($02),a ; R1 display and VDP IRQ disabled
call init_picture
main:
call wait_frame
ld hl,($df02)
inc hl
ld ($df02),hl
include "sample.inc"
ld a,($df01)
cpl
and b
jr z,no_action
ld a,($df00)
inc a
ld ($df00),a
call trigger_sound
no_action:
ld a,b
ld ($df01),a
call draw_picture
call service_sound
jp main
; AF clobbered; caller exclusively owns status/control-port latch.
wait_frame:
in a,($02)
wait_new:
in a,($02)
and $80
jr z,wait_new
ret
include "picture.inc"
include "sound.inc"Compare blocking, serviced and queued sound when choosing the next step.
Verification and sources
Target: MTX500 PAL, stock BASIC DATA/POKE/USR entry. 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.
Memotech, MTX Series Operator’s Manual/technical description: keyboard matrix, sound I/O and USR; TI SN76489A data manual.