Skip to content

Trigger a sound on Memotech MTX

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
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
    ret

SN76489 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

no-sound.asm
; 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.

Memotech MTX native caller after accepted input.
Captured from the running caller in the configuration below.

Where setup, trigger and service belong

demo.asm
; 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.