Tachibana
Language: English

Group 8 — Sound Official

8,1 Reset Sound · 8,2 Reset Channel · 8,3 Beep · 8,4 Queue Sound · 8,5 Play Sound · 8,6 Sound Status · 8,7 Queue Sound Extended · 8,8 Get Channel Count · 8,9 Set Channel Volume · 8,10 Get Channel Volume · 8,11 Stream Start · 8,12 Stream Stop · 8,13 Stream Status · 8,14 Stream Filled · 8,15 Get Master Volume · 8,16 Set Master Volume

Sound has 4 channels (0 to 3). Each channel has a queue of at most 32 notes: a queued note (8,4 or 8,7) plays as soon as the previous one ends, and the program goes on without waiting. 8,6 tells how many notes are left, including the one playing. A note beyond 32 is ignored, without an error.

A note has a frequency in Hz, a duration in hundredths of a second (counted in steps of 2 cs, as the firmware steps at 50 Hz: 25 cs last 24), a slide in Hz per hundredth, a waveform (0 square, 1 noise) and a volume from 0 to 100. While sliding, the frequency stays between 100 and 1200 Hz: past that, it wraps 1100 Hz lower or higher. A frequency of 0 makes a silence of the given duration.

Trinity adds the volume of a channel while it plays (8,9, 8,10), a stream of PCM samples mixed with the 4 channels (8,11 to 8,14) and a master volume (8,15, 8,16). Volumes 0-100 are stored as 0 to 127: a value read back may lose one unit (25 comes back as 24).

On the bench, the emulator's sound is recorded and checked: it must be heard for the examples that play, and stay silent when the sound is muted (8,16). The examples do not just play: they print what the firmware reports (queued notes, volumes, stream state).

8,1 Reset Sound Official

Silences all sound: empties the queues of the 4 channels and stops the sample stream.

Inputs: none.

Outputs: none.

The example queues 3 notes on channel 0 and 2 on channel 1, then clears them all at once. The master volume (8,16) is left alone. In NeoBASIC, sound clear makes this call.

NeoBASIC keywords that make this call: sound

Firmware documentation text

Reset the sound system. This empties all channel queues and silences all channels immediately.

C (llvm-mos)
/* Vider d'un coup les files de tous les canaux (groupe 8, fonction 1). */
#include <stdio.h>
#include <neo/api.h>

int main(void) {
    neo_sound_queue(0, 262, 50, 0, 0);          /* canal 0 : trois notes de 50 cs */
    neo_sound_queue(0, 330, 50, 0, 0);
    neo_sound_queue(0, 392, 50, 0, 0);
    neo_sound_queue(1, 131, 50, 0, 0);          /* canal 1 : deux notes */
    neo_sound_queue(1, 196, 50, 0, 0);
    uint8_t a = neo_sound_status(0), b = neo_sound_status(1);
    neo_sound_reset();                          /* Reset Sound : silence et files vides partout */
    printf("Avant : %u et %u, apres : %u et %u\n", a, b, neo_sound_status(0), neo_sound_status(1));
    return 0;
}
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Avant : 3 et 2, apres : 0 et 0
Mad Pascal
// Vider d'un coup les files de tous les canaux (groupe 8, fonction 1).
program resetsound;
uses crt, neo6502;
var a, b: byte;
begin
  NeoQueueNote(0, 262, 50, 0, 0);               // canal 0 : trois notes de 50 cs
  NeoQueueNote(0, 330, 50, 0, 0);
  NeoQueueNote(0, 392, 50, 0, 0);
  NeoQueueNote(1, 131, 50, 0, 0);               // canal 1 : deux notes
  NeoQueueNote(1, 196, 50, 0, 0);
  a := NeoGetQueueLen(0);
  b := NeoGetQueueLen(1);
  NeoMute;                                      // Reset Sound : silence et files vides partout
  writeln('Avant : ', a, ' et ', b, ', apres : ', NeoGetQueueLen(0), ' et ', NeoGetQueueLen(1));
end.
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Avant : 3 et 2, apres : 0 et 0
NeoBASIC
10 ' Vider d'un coup les files de tous les canaux (groupe 8, fonction 1)
20 sound 0,262,50 : sound 0,330,50 : sound 0,392,50 : ' canal 0 : trois notes de 50 cs
30 sound 1,131,50 : sound 1,196,50 : ' canal 1 : deux notes
40 a = notes(0) : b = notes(1)
50 sound clear : ' Reset Sound : silence et files vides partout
60 print "Avant : ";a;" et ";b;", apres : ";notes(0);" et ";notes(1)
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Avant : 3 et 2, apres : 0 et 0
65C02 assembly (64tass)
; Vider d'un coup les files de tous les canaux (groupe 8, fonction 1).
        .cpu "65c02"
        * = $0800
ControlPort    = $FF00             ; +0 groupe, +1 fonction, +2 erreur, +4.. paramètres
WaitMessage    = $FFF4             ; attend que la boîte aux lettres soit libre
WriteCharacter = $FFF1             ; affiche le caractère de A

start: lda #<n1
        ldx #>n1
        jsr envoi
        lda #<n2
        ldx #>n2
        jsr envoi
        lda #<n3
        ldx #>n3
        jsr envoi
        lda #<n4
        ldx #>n4
        jsr envoi
        lda #<n5
        ldx #>n5
        jsr envoi
        lda #0
        jsr statut
        sta a
        lda #1
        jsr statut
        sta b
        lda #<raz
        ldx #>raz
        jsr envoi
                                   ; Reset Sound : silence et files vides partout
        lda #<t0
        ldx #>t0
        jsr ecrire
        lda a
        jsr decimal
        lda #<t1
        ldx #>t1
        jsr ecrire
        lda b
        jsr decimal
        lda #<t2
        ldx #>t2
        jsr ecrire
        lda #0
        jsr statut
        jsr decimal
        lda #<t1
        ldx #>t1
        jsr ecrire
        lda #1
        jsr statut
        jsr decimal
        lda #13
        jsr WriteCharacter
        rts

n1: .byte 8, 4, 8, 0     ; 8,4 : canal 0 : trois notes de 50 cs
        .word 262, 50, 0
        .byte 0
n2: .byte 8, 4, 8, 0     ; 8,4 : canal 0
        .word 330, 50, 0
        .byte 0
n3: .byte 8, 4, 8, 0     ; 8,4 : canal 0
        .word 392, 50, 0
        .byte 0
n4: .byte 8, 4, 8, 1     ; 8,4 : canal 1 : deux notes
        .word 131, 50, 0
        .byte 0
n5: .byte 8, 4, 8, 1     ; 8,4 : canal 1
        .word 196, 50, 0
        .byte 0
raz:    .byte 8, 1, 0              ; 8,1 : Reset Sound
t0:     .text "Avant : ", 0
t1:     .text " et ", 0
t2:     .text ", apres : ", 0
a:      .byte 0
b:      .byte 0

; Appel décrit par un bloc dont l'adresse est dans A (poids faible) et X (poids fort) :
; groupe, fonction, nombre de paramètres, puis les paramètres (P0, P1...).
envoi:  sta blk
        stx blk+1
        jsr WaitMessage
        ldy #2
        lda (blk),y                ; nombre de paramètres
        clc
        adc #3
        sta fin
        ldy #3
_copie: cpy fin
        beq _lance
        lda (blk),y
        sta ControlPort+1,y        ; octet 3 du bloc -> P0 ($FF04)...
        iny
        bra _copie
_lance: ldy #1
        lda (blk),y
        tax
        lda (blk)
        jmp appel

; Nombre de notes en file sur le canal A (8,6), rendu dans A.
statut: pha
        jsr WaitMessage
        pla
        sta ControlPort+4
        lda #8
        ldx #6                     ; 8,6 : Sound Status
        jsr appel
        lda ControlPort+4
        rts

; Attend que le canal 0 se taise.
attendre:
        lda #0
        jsr statut
        bne attendre
        rts

; Affiche la chaîne terminée par 0 dont l'adresse est dans A (poids faible) et X (poids fort).
ecrire: sta ptr
        stx ptr+1
        ldy #0
_e:     lda (ptr),y
        beq _f
        jsr WriteCharacter
        iny
        bne _e
_f:     rts

; Affiche A (0-255) en décimal, sans zéros de tête.
decimal:
        phx
        phy
        ldx #0
        ldy #100
        jsr _chiffre
        ldy #10
        jsr _chiffre
        ora #'0'
        jsr WriteCharacter
        ply
        plx
        rts
_chiffre:
        sty diviseur
        ldy #0
_div:   cmp diviseur
        bcc _fini
        sbc diviseur
        iny
        bne _div
_fini:  pha
        tya
        bne _ecrit
        cpx #0
        beq _saute
_ecrit: ora #'0'
        jsr WriteCharacter
        ldx #1
_saute: pla
        rts

; Appel : A = groupe, X = fonction (les paramètres sont déjà en place).
appel:  stx ControlPort+1
        sta ControlPort            ; écrit en dernier : lance l'appel
        jmp WaitMessage

ptr      = $80
blk      = $82
diviseur: .byte 0
fin:     .byte 0
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Avant : 3 et 2, apres : 0 et 0

8,2 Reset Channel Official

Silences one channel: empties its queue and stops it, leaving the others alone.

Inputs
BytesTypeMeaning
P0uint8channel (0 to 3)
Outputs
BytesTypeMeaning
erreuruint81 if the channel does not exist

The two notes of channel 1 stay queued. In NeoBASIC, sound n clear makes this call; sfx makes it too, before the effect.

NeoBASIC keywords that make this call: sfx, sound

Firmware documentation text

Reset the sound channel specified in Parameter:0.

C (llvm-mos)
/* Vider la file d'un seul canal (groupe 8, fonction 2). */
#include <stdio.h>
#include <neo/api.h>

int main(void) {
    neo_sound_queue(0, 262, 50, 0, 0);          /* canal 0 : trois notes de 50 cs */
    neo_sound_queue(0, 330, 50, 0, 0);
    neo_sound_queue(0, 392, 50, 0, 0);
    neo_sound_queue(1, 131, 50, 0, 0);          /* canal 1 : deux notes */
    neo_sound_queue(1, 196, 50, 0, 0);
    neo_sound_reset_channel(0);                 /* Reset Channel : le canal 0 seulement */
    printf("Canal 0 : %u, canal 1 : %u\n", neo_sound_status(0), neo_sound_status(1));
    neo_sound_reset();                          /* 8,1 : faire taire le canal 1 aussi */
    return 0;
}
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Canal 0 : 0, canal 1 : 2
Mad Pascal
// Vider la file d'un seul canal (groupe 8, fonction 2).
program resetchannel;
uses crt, neo6502;
begin
  NeoQueueNote(0, 262, 50, 0, 0);               // canal 0 : trois notes de 50 cs
  NeoQueueNote(0, 330, 50, 0, 0);
  NeoQueueNote(0, 392, 50, 0, 0);
  NeoQueueNote(1, 131, 50, 0, 0);               // canal 1 : deux notes
  NeoQueueNote(1, 196, 50, 0, 0);
  NeoMute(0);                                   // Reset Channel : le canal 0 seulement
  writeln('Canal 0 : ', NeoGetQueueLen(0), ', canal 1 : ', NeoGetQueueLen(1));
  NeoMute;                                      // 8,1 : faire taire le canal 1 aussi
end.
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Canal 0 : 0, canal 1 : 2
NeoBASIC
10 ' Vider la file d'un seul canal (groupe 8, fonction 2)
20 sound 0,262,50 : sound 0,330,50 : sound 0,392,50 : ' canal 0 : trois notes de 50 cs
30 sound 1,131,50 : sound 1,196,50 : ' canal 1 : deux notes
40 sound 0 clear : ' Reset Channel : le canal 0 seulement
50 print "Canal 0 : ";notes(0);", canal 1 : ";notes(1)
60 sound clear : ' 8,1 : faire taire le canal 1 aussi
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Canal 0 : 0, canal 1 : 2
65C02 assembly (64tass)
; Vider la file d'un seul canal (groupe 8, fonction 2).
        .cpu "65c02"
        * = $0800
ControlPort    = $FF00             ; +0 groupe, +1 fonction, +2 erreur, +4.. paramètres
WaitMessage    = $FFF4             ; attend que la boîte aux lettres soit libre
WriteCharacter = $FFF1             ; affiche le caractère de A

start: lda #<n1
        ldx #>n1
        jsr envoi
        lda #<n2
        ldx #>n2
        jsr envoi
        lda #<n3
        ldx #>n3
        jsr envoi
        lda #<n4
        ldx #>n4
        jsr envoi
        lda #<n5
        ldx #>n5
        jsr envoi
        lda #<raz0
        ldx #>raz0
        jsr envoi
                                   ; Reset Channel : le canal 0 seulement
        lda #<t0
        ldx #>t0
        jsr ecrire
        lda #0
        jsr statut
        jsr decimal
        lda #<t1
        ldx #>t1
        jsr ecrire
        lda #1
        jsr statut
        jsr decimal
        lda #13
        jsr WriteCharacter
        lda #<raz
        ldx #>raz
        jsr envoi
                                   ; 8,1 : faire taire le canal 1 aussi
        rts

n1: .byte 8, 4, 8, 0     ; 8,4 : canal 0 : trois notes de 50 cs
        .word 262, 50, 0
        .byte 0
n2: .byte 8, 4, 8, 0     ; 8,4 : canal 0
        .word 330, 50, 0
        .byte 0
n3: .byte 8, 4, 8, 0     ; 8,4 : canal 0
        .word 392, 50, 0
        .byte 0
n4: .byte 8, 4, 8, 1     ; 8,4 : canal 1 : deux notes
        .word 131, 50, 0
        .byte 0
n5: .byte 8, 4, 8, 1     ; 8,4 : canal 1
        .word 196, 50, 0
        .byte 0
raz0:   .byte 8, 2, 1, 0           ; 8,2 : Reset Channel, P0 = canal 0
raz:    .byte 8, 1, 0              ; 8,1 : Reset Sound
t0:     .text "Canal 0 : ", 0
t1:     .text ", canal 1 : ", 0

; Appel décrit par un bloc dont l'adresse est dans A (poids faible) et X (poids fort) :
; groupe, fonction, nombre de paramètres, puis les paramètres (P0, P1...).
envoi:  sta blk
        stx blk+1
        jsr WaitMessage
        ldy #2
        lda (blk),y                ; nombre de paramètres
        clc
        adc #3
        sta fin
        ldy #3
_copie: cpy fin
        beq _lance
        lda (blk),y
        sta ControlPort+1,y        ; octet 3 du bloc -> P0 ($FF04)...
        iny
        bra _copie
_lance: ldy #1
        lda (blk),y
        tax
        lda (blk)
        jmp appel

; Nombre de notes en file sur le canal A (8,6), rendu dans A.
statut: pha
        jsr WaitMessage
        pla
        sta ControlPort+4
        lda #8
        ldx #6                     ; 8,6 : Sound Status
        jsr appel
        lda ControlPort+4
        rts

; Attend que le canal 0 se taise.
attendre:
        lda #0
        jsr statut
        bne attendre
        rts

; Affiche la chaîne terminée par 0 dont l'adresse est dans A (poids faible) et X (poids fort).
ecrire: sta ptr
        stx ptr+1
        ldy #0
_e:     lda (ptr),y
        beq _f
        jsr WriteCharacter
        iny
        bne _e
_f:     rts

; Affiche A (0-255) en décimal, sans zéros de tête.
decimal:
        phx
        phy
        ldx #0
        ldy #100
        jsr _chiffre
        ldy #10
        jsr _chiffre
        ora #'0'
        jsr WriteCharacter
        ply
        plx
        rts
_chiffre:
        sty diviseur
        ldy #0
_div:   cmp diviseur
        bcc _fini
        sbc diviseur
        iny
        bne _div
_fini:  pha
        tya
        bne _ecrit
        cpx #0
        beq _saute
_ecrit: ora #'0'
        jsr WriteCharacter
        ldx #1
_saute: pla
        rts

; Appel : A = groupe, X = fonction (les paramètres sont déjà en place).
appel:  stx ControlPort+1
        sta ControlPort            ; écrit en dernier : lance l'appel
        jmp WaitMessage

ptr      = $80
blk      = $82
diviseur: .byte 0
fin:     .byte 0
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Canal 0 : 0, canal 1 : 2

8,3 Beep Official

Plays the startup beep: A 220 Hz for 0.5 s then A 440 Hz for 0.25 s, on channel 0.

Inputs: none.

Outputs: none.

The two notes are queued on channel 0, behind those already there. The firmware plays this beep on the board only (code reserved for the Pico, in the official firmware as in Trinity): in the emulator, the call does nothing and the queue stays empty, hence the immediate observed output. NeoBASIC has no keyword for this call.

Firmware documentation text

Play the startup beep immediately.

C (llvm-mos)
/* Jouer le bip du démarrage (groupe 8, fonction 3). */
#include <stdio.h>
#include <neo/api.h>

int main(void) {
    neo_sound_beep();                           /* Beep : deux notes sur le canal 0 */
    while (neo_sound_status(0) != 0)            /* 8,6 : attendre la fin du bip */
        ;
    printf("Bip termine\n");
    return 0;
}
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Bip termine
Mad Pascal
// Jouer le bip du démarrage (groupe 8, fonction 3).
program beep;
uses crt, neo6502;
begin
  NeoBeep;                                      // Beep : deux notes sur le canal 0
  repeat until NeoGetQueueLen(0) = 0;           // 8,6 : attendre la fin du bip
  writeln('Bip termine');
end.
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Bip termine
NeoBASIC
10 ' Jouer le bip du demarrage (groupe 8, fonction 3)
20 while peek($ff00) : wend : poke $ff01, 3 : poke $ff00, 8 : ' Beep : deux notes sur le canal 0
30 repeat : until notes(0) = 0 : ' 8,6 : attendre la fin du bip
40 print "Bip termine"
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Bip termine
65C02 assembly (64tass)
; Jouer le bip du démarrage (groupe 8, fonction 3).
        .cpu "65c02"
        * = $0800
ControlPort    = $FF00             ; +0 groupe, +1 fonction, +2 erreur, +4.. paramètres
WaitMessage    = $FFF4             ; attend que la boîte aux lettres soit libre
WriteCharacter = $FFF1             ; affiche le caractère de A

start: lda #<bip
        ldx #>bip
        jsr envoi
        jsr attendre               ; 8,6 : attendre la fin du bip
        lda #<t0
        ldx #>t0
        jsr ecrire
        rts

bip:    .byte 8, 3, 0              ; 8,3 : Beep, deux notes sur le canal 0
t0:     .text "Bip termine", 13, 0

; Appel décrit par un bloc dont l'adresse est dans A (poids faible) et X (poids fort) :
; groupe, fonction, nombre de paramètres, puis les paramètres (P0, P1...).
envoi:  sta blk
        stx blk+1
        jsr WaitMessage
        ldy #2
        lda (blk),y                ; nombre de paramètres
        clc
        adc #3
        sta fin
        ldy #3
_copie: cpy fin
        beq _lance
        lda (blk),y
        sta ControlPort+1,y        ; octet 3 du bloc -> P0 ($FF04)...
        iny
        bra _copie
_lance: ldy #1
        lda (blk),y
        tax
        lda (blk)
        jmp appel

; Nombre de notes en file sur le canal A (8,6), rendu dans A.
statut: pha
        jsr WaitMessage
        pla
        sta ControlPort+4
        lda #8
        ldx #6                     ; 8,6 : Sound Status
        jsr appel
        lda ControlPort+4
        rts

; Attend que le canal 0 se taise.
attendre:
        lda #0
        jsr statut
        bne attendre
        rts

; Affiche la chaîne terminée par 0 dont l'adresse est dans A (poids faible) et X (poids fort).
ecrire: sta ptr
        stx ptr+1
        ldy #0
_e:     lda (ptr),y
        beq _f
        jsr WriteCharacter
        iny
        bne _e
_f:     rts

; Affiche A (0-255) en décimal, sans zéros de tête.
decimal:
        phx
        phy
        ldx #0
        ldy #100
        jsr _chiffre
        ldy #10
        jsr _chiffre
        ora #'0'
        jsr WriteCharacter
        ply
        plx
        rts
_chiffre:
        sty diviseur
        ldy #0
_div:   cmp diviseur
        bcc _fini
        sbc diviseur
        iny
        bne _div
_fini:  pha
        tya
        bne _ecrit
        cpx #0
        beq _saute
_ecrit: ora #'0'
        jsr WriteCharacter
        ldx #1
_saute: pla
        rts

; Appel : A = groupe, X = fonction (les paramètres sont déjà en place).
appel:  stx ControlPort+1
        sta ControlPort            ; écrit en dernier : lance l'appel
        jmp WaitMessage

ptr      = $80
blk      = $82
diviseur: .byte 0
fin:     .byte 0
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Bip termine

8,4 Queue Sound Official

Queues a note on a channel: square wave, volume 100.

Inputs
BytesTypeMeaning
P0uint8channel (0 to 3)
P1-P2uint16frequency in Hz (0: silence)
P3-P4uint16duration in hundredths of a second
P5-P6int16slide in Hz per hundredth
P7uint8"source" in the documentation: the firmware does not read it
Outputs
BytesTypeMeaning
erreuruint81 if the channel does not exist

The example plays C, E, G, then a high C rising by 20 Hz per hundredth; past 1200 Hz, the slide wraps 1100 Hz lower. NeoBASIC has no keyword for 8,4: sound and noise go through 8,7.

Firmware documentation text

Queue a sound. Refer to Section \#\ref{sound} "Sound" for details.

The parameters are : Channel, Frequency Low, Frequency High, Duration Low, Duration High, Slide Low, Slide High and Source.

C (llvm-mos)
/* Mettre en file un petit arpège et un glissando (groupe 8, fonction 4). */
#include <stdio.h>
#include <neo/api.h>

int main(void) {
    neo_sound_queue(0, 262, 25, 0, 0);          /* Queue Sound : do 262 Hz, 25 cs */
    neo_sound_queue(0, 330, 25, 0, 0);          /* mi */
    neo_sound_queue(0, 392, 25, 0, 0);          /* sol */
    neo_sound_queue(0, 523, 50, 20, 0);         /* do aigu qui monte de 20 Hz par cs */
    printf("En file : %u notes\n", neo_sound_status(0));
    while (neo_sound_status(0) != 0)            /* 8,6 : attendre la fin */
        ;
    printf("Fini\n");
    return 0;
}
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
En file : 4 notes
Fini
Mad Pascal
// Mettre en file un petit arpège et un glissando (groupe 8, fonction 4).
program queuesound;
uses crt, neo6502;
begin
  NeoQueueNote(0, 262, 25, 0, 0);               // Queue Sound : do 262 Hz, 25 cs
  NeoQueueNote(0, 330, 25, 0, 0);               // mi
  NeoQueueNote(0, 392, 25, 0, 0);               // sol
  NeoQueueNote(0, 523, 50, 20, 0);              // do aigu qui monte de 20 Hz par cs
  writeln('En file : ', NeoGetQueueLen(0), ' notes');
  repeat until NeoGetQueueLen(0) = 0;           // 8,6 : attendre la fin
  writeln('Fini');
end.
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
En file : 4 notes
Fini
NeoBASIC
10 ' Mettre en file un petit arpege et un glissando (groupe 8, fonction 4)
20 f = 262 : t = 25 : g = 0 : gosub 1000 : ' do 262 Hz, 25 cs
30 f = 330 : gosub 1000 : ' mi
40 f = 392 : gosub 1000 : ' sol
50 f = 523 : t = 50 : g = 20 : gosub 1000 : ' do aigu qui monte de 20 Hz par cs
60 print "En file : ";notes(0);" notes"
70 repeat : until notes(0) = 0 : ' 8,6 : attendre la fin
80 print "Fini"
90 end
1000 ' Queue Sound sur le canal 0 : frequence f, duree t (cs), glissement g
1010 while peek($ff00) : wend
1020 poke $ff04, 0 : doke $ff05, f : doke $ff07, t : doke $ff09, g : poke $ff0b, 0
1030 poke $ff01, 4 : poke $ff00, 8
1040 while peek($ff00) : wend
1050 return
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
En file : 4 notes
Fini
65C02 assembly (64tass)
; Mettre en file un petit arpège et un glissando (groupe 8, fonction 4).
        .cpu "65c02"
        * = $0800
ControlPort    = $FF00             ; +0 groupe, +1 fonction, +2 erreur, +4.. paramètres
WaitMessage    = $FFF4             ; attend que la boîte aux lettres soit libre
WriteCharacter = $FFF1             ; affiche le caractère de A

start: lda #<n1
        ldx #>n1
        jsr envoi
        lda #<n2
        ldx #>n2
        jsr envoi
        lda #<n3
        ldx #>n3
        jsr envoi
        lda #<n4
        ldx #>n4
        jsr envoi
        lda #<t0
        ldx #>t0
        jsr ecrire
        lda #0
        jsr statut
        jsr decimal
        lda #<t1
        ldx #>t1
        jsr ecrire
        jsr attendre               ; 8,6 : attendre la fin
        lda #<t2
        ldx #>t2
        jsr ecrire
        rts

n1: .byte 8, 4, 8, 0     ; 8,4 : canal 0 : do 262 Hz, 25 cs
        .word 262, 25, 0
        .byte 0
n2: .byte 8, 4, 8, 0     ; 8,4 : canal 0 : mi
        .word 330, 25, 0
        .byte 0
n3: .byte 8, 4, 8, 0     ; 8,4 : canal 0 : sol
        .word 392, 25, 0
        .byte 0
n4: .byte 8, 4, 8, 0     ; 8,4 : canal 0 : do aigu qui monte de 20 Hz par cs
        .word 523, 50, 20
        .byte 0
t0:     .text "En file : ", 0
t1:     .text " notes", 13, 0
t2:     .text "Fini", 13, 0

; Appel décrit par un bloc dont l'adresse est dans A (poids faible) et X (poids fort) :
; groupe, fonction, nombre de paramètres, puis les paramètres (P0, P1...).
envoi:  sta blk
        stx blk+1
        jsr WaitMessage
        ldy #2
        lda (blk),y                ; nombre de paramètres
        clc
        adc #3
        sta fin
        ldy #3
_copie: cpy fin
        beq _lance
        lda (blk),y
        sta ControlPort+1,y        ; octet 3 du bloc -> P0 ($FF04)...
        iny
        bra _copie
_lance: ldy #1
        lda (blk),y
        tax
        lda (blk)
        jmp appel

; Nombre de notes en file sur le canal A (8,6), rendu dans A.
statut: pha
        jsr WaitMessage
        pla
        sta ControlPort+4
        lda #8
        ldx #6                     ; 8,6 : Sound Status
        jsr appel
        lda ControlPort+4
        rts

; Attend que le canal 0 se taise.
attendre:
        lda #0
        jsr statut
        bne attendre
        rts

; Affiche la chaîne terminée par 0 dont l'adresse est dans A (poids faible) et X (poids fort).
ecrire: sta ptr
        stx ptr+1
        ldy #0
_e:     lda (ptr),y
        beq _f
        jsr WriteCharacter
        iny
        bne _e
_f:     rts

; Affiche A (0-255) en décimal, sans zéros de tête.
decimal:
        phx
        phy
        ldx #0
        ldy #100
        jsr _chiffre
        ldy #10
        jsr _chiffre
        ora #'0'
        jsr WriteCharacter
        ply
        plx
        rts
_chiffre:
        sty diviseur
        ldy #0
_div:   cmp diviseur
        bcc _fini
        sbc diviseur
        iny
        bne _div
_fini:  pha
        tya
        bne _ecrit
        cpx #0
        beq _saute
_ecrit: ora #'0'
        jsr WriteCharacter
        ldx #1
_saute: pla
        rts

; Appel : A = groupe, X = fonction (les paramètres sont déjà en place).
appel:  stx ControlPort+1
        sta ControlPort            ; écrit en dernier : lance l'appel
        jmp WaitMessage

ptr      = $80
blk      = $82
diviseur: .byte 0
fin:     .byte 0
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
En file : 4 notes
Fini

8,5 Play Sound Official

Queues one of the 24 sound effects of the firmware (0 to 23) on a channel.

Inputs
BytesTypeMeaning
P0uint8channel (0 to 3)
P1uint8effect: 0 positive, 1 negative, 2 error, 3 confirm, 4 reject, 5 sweep, 6 coin, 7 las70, 8 powerup, 9 victory, 10 defeat, 11 fanfare, 12-14 alarm, 15-17 ringtone, 18 danger, 19-21 explosions, 22-23 lasers
Outputs
BytesTypeMeaning
erreuruint81 missing channel, 2 missing effect

The firmware documentation, llvm-mos and Mad Pascal say the effect plays "immediately, clearing the queue". The code, official as well as Trinity, does not clear the queue: it adds the notes of the effect behind those waiting. Here, a one-second note then effect 6 (three notes) make 4 queued notes; for an immediate effect, call 8,2 first. That is what sfx does in NeoBASIC (8,2 then 8,5), hence only 3 notes.

NeoBASIC keywords that make this call: sfx

Firmware documentation text

Play the sound effect specified in Parameter:1 on the channel specified in Parameter:0 immediately, clearing the channel queue.

C (llvm-mos)
/* Jouer un effet sonore derrière une note déjà en file (groupe 8, fonction 5). */
#include <stdio.h>
#include <neo/api.h>

int main(void) {
    neo_sound_queue(0, 440, 100, 0, 0);         /* 8,4 : une note d'une seconde */
    neo_sound_play_effect(0, 6);                /* Play Sound : effet 6, « coin », trois notes */
    printf("Notes en file : %u\n", neo_sound_status(0));
    while (neo_sound_status(0) != 0)            /* 8,6 : attendre la fin */
        ;
    return 0;
}
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Notes en file : 4
Mad Pascal
// Jouer un effet sonore derrière une note déjà en file (groupe 8, fonction 5).
program playsound;
uses crt, neo6502;
begin
  NeoQueueNote(0, 440, 100, 0, 0);              // 8,4 : une note d'une seconde
  NeoSoundFx(0, 6);                             // Play Sound : effet 6, « coin », trois notes
  writeln('Notes en file : ', NeoGetQueueLen(0));
  repeat until NeoGetQueueLen(0) = 0;           // 8,6 : attendre la fin
end.
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Notes en file : 4
NeoBASIC
10 ' Jouer un effet sonore derriere une note deja en file (groupe 8, fonction 5)
20 sound 0,440,100 : ' une note d'une seconde
30 sfx 0,6 : ' Play Sound : effet 6, coin, trois notes (sfx vide d'abord le canal)
40 print "Notes en file : ";notes(0)
50 repeat : until notes(0) = 0 : ' 8,6 : attendre la fin
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Notes en file : 3
65C02 assembly (64tass)
; Jouer un effet sonore derrière une note déjà en file (groupe 8, fonction 5).
        .cpu "65c02"
        * = $0800
ControlPort    = $FF00             ; +0 groupe, +1 fonction, +2 erreur, +4.. paramètres
WaitMessage    = $FFF4             ; attend que la boîte aux lettres soit libre
WriteCharacter = $FFF1             ; affiche le caractère de A

start: lda #<n1
        ldx #>n1
        jsr envoi
        lda #<fx
        ldx #>fx
        jsr envoi
        lda #<t0
        ldx #>t0
        jsr ecrire
        lda #0
        jsr statut
        jsr decimal
        lda #13
        jsr WriteCharacter
        jmp attendre               ; 8,6 : attendre la fin

n1: .byte 8, 4, 8, 0     ; 8,4 : canal 0 : une note d'une seconde
        .word 440, 100, 0
        .byte 0
fx:     .byte 8, 5, 2, 0, 6        ; 8,5 : Play Sound, canal 0, effet 6 (« coin », trois notes)
t0:     .text "Notes en file : ", 0

; Appel décrit par un bloc dont l'adresse est dans A (poids faible) et X (poids fort) :
; groupe, fonction, nombre de paramètres, puis les paramètres (P0, P1...).
envoi:  sta blk
        stx blk+1
        jsr WaitMessage
        ldy #2
        lda (blk),y                ; nombre de paramètres
        clc
        adc #3
        sta fin
        ldy #3
_copie: cpy fin
        beq _lance
        lda (blk),y
        sta ControlPort+1,y        ; octet 3 du bloc -> P0 ($FF04)...
        iny
        bra _copie
_lance: ldy #1
        lda (blk),y
        tax
        lda (blk)
        jmp appel

; Nombre de notes en file sur le canal A (8,6), rendu dans A.
statut: pha
        jsr WaitMessage
        pla
        sta ControlPort+4
        lda #8
        ldx #6                     ; 8,6 : Sound Status
        jsr appel
        lda ControlPort+4
        rts

; Attend que le canal 0 se taise.
attendre:
        lda #0
        jsr statut
        bne attendre
        rts

; Affiche la chaîne terminée par 0 dont l'adresse est dans A (poids faible) et X (poids fort).
ecrire: sta ptr
        stx ptr+1
        ldy #0
_e:     lda (ptr),y
        beq _f
        jsr WriteCharacter
        iny
        bne _e
_f:     rts

; Affiche A (0-255) en décimal, sans zéros de tête.
decimal:
        phx
        phy
        ldx #0
        ldy #100
        jsr _chiffre
        ldy #10
        jsr _chiffre
        ora #'0'
        jsr WriteCharacter
        ply
        plx
        rts
_chiffre:
        sty diviseur
        ldy #0
_div:   cmp diviseur
        bcc _fini
        sbc diviseur
        iny
        bne _div
_fini:  pha
        tya
        bne _ecrit
        cpx #0
        beq _saute
_ecrit: ora #'0'
        jsr WriteCharacter
        ldx #1
_saute: pla
        rts

; Appel : A = groupe, X = fonction (les paramètres sont déjà en place).
appel:  stx ControlPort+1
        sta ControlPort            ; écrit en dernier : lance l'appel
        jmp WaitMessage

ptr      = $80
blk      = $82
diviseur: .byte 0
fin:     .byte 0
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Notes en file : 4

8,6 Sound Status Official

Gives the number of notes left on a channel, including the one playing; 0 once it is silent.

Inputs
BytesTypeMeaning
P0uint8channel (0 to 3)
Outputs
BytesTypeMeaning
P0uint8notes left
erreuruint81 if the channel does not exist

The example prints the count at each change, until silence. Waiting for 0 is the way to know a sound has ended. In NeoBASIC, notes(n) makes this call.

NeoBASIC keywords that make this call: notes()

Firmware documentation text

Deposit in Parameter:0 the number of notes outstanding before silence in the queue of the channel specified in Parameter:0, including the current playing sound, if any.

C (llvm-mos)
/* Suivre la file d'un canal pendant qu'elle se vide (groupe 8, fonction 6). */
#include <stdio.h>
#include <neo/api.h>

int main(void) {
    neo_sound_reset();                          /* 8,1 : files vides */
    neo_sound_queue(0, 440, 20, 0, 0);          /* canal 0 : la 440 Hz, 20 cs */
    neo_sound_queue(0, 660, 20, 0, 0);          /* puis mi 660 Hz, 20 cs */
    uint8_t avant = 255, n;
    printf("Notes :");
    do {
        n = neo_sound_status(0);                /* Sound Status : notes restantes, celle qui joue comprise */
        if (n != avant)
            printf(" %u", n);
        avant = n;
    } while (n != 0);
    printf("\n");
    return 0;
}
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Notes : 2 1 0
Mad Pascal
// Suivre la file d'un canal pendant qu'elle se vide (groupe 8, fonction 6).
program soundstatus;
uses crt, neo6502;
var avant, n: byte;
begin
  NeoMute;                                      // 8,1 : files vides
  NeoQueueNote(0, 440, 20, 0, 0);               // canal 0 : la 440 Hz, 20 cs
  NeoQueueNote(0, 660, 20, 0, 0);               // puis mi 660 Hz, 20 cs
  avant := 255;
  write('Notes :');
  repeat
    n := NeoGetQueueLen(0);                     // Sound Status : notes restantes, celle qui joue comprise
    if n <> avant then write(' ', n);
    avant := n;
  until n = 0;
  writeln;
end.
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Notes : 2 1 0
NeoBASIC
10 ' Suivre la file d'un canal pendant qu'elle se vide (groupe 8, fonction 6)
20 sound clear : ' 8,1 : files vides
30 sound 0,440,20 : sound 0,660,20 : ' la 440 Hz puis mi 660 Hz, 20 cs chacun
40 avant = 255 : print "Notes :";
50 repeat
60 n = notes(0) : ' Sound Status : notes restantes, celle qui joue comprise
70 if n <> avant then print " ";n;
80 avant = n
90 until n = 0
100 print
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Notes : 2 1 0
65C02 assembly (64tass)
; Suivre la file d'un canal pendant qu'elle se vide (groupe 8, fonction 6).
        .cpu "65c02"
        * = $0800
ControlPort    = $FF00             ; +0 groupe, +1 fonction, +2 erreur, +4.. paramètres
WaitMessage    = $FFF4             ; attend que la boîte aux lettres soit libre
WriteCharacter = $FFF1             ; affiche le caractère de A

start: lda #<raz
        ldx #>raz
        jsr envoi
        lda #<n1
        ldx #>n1
        jsr envoi
        lda #<n2
        ldx #>n2
        jsr envoi
        lda #<t0
        ldx #>t0
        jsr ecrire
        lda #255
        sta avant
_boucle:
        lda #0
        jsr statut                 ; Sound Status : notes restantes, celle qui joue comprise
        cmp avant
        beq _meme
        sta avant
        lda #' '
        jsr WriteCharacter
        lda avant
        jsr decimal
_meme:  lda avant
        bne _boucle
        lda #13
        jsr WriteCharacter
        rts

raz:    .byte 8, 1, 0              ; 8,1 : Reset Sound
n1: .byte 8, 4, 8, 0     ; 8,4 : canal 0 : la 440 Hz, 20 cs
        .word 440, 20, 0
        .byte 0
n2: .byte 8, 4, 8, 0     ; 8,4 : canal 0 : puis mi 660 Hz
        .word 660, 20, 0
        .byte 0
t0:     .text "Notes :", 0
avant:  .byte 0

; Appel décrit par un bloc dont l'adresse est dans A (poids faible) et X (poids fort) :
; groupe, fonction, nombre de paramètres, puis les paramètres (P0, P1...).
envoi:  sta blk
        stx blk+1
        jsr WaitMessage
        ldy #2
        lda (blk),y                ; nombre de paramètres
        clc
        adc #3
        sta fin
        ldy #3
_copie: cpy fin
        beq _lance
        lda (blk),y
        sta ControlPort+1,y        ; octet 3 du bloc -> P0 ($FF04)...
        iny
        bra _copie
_lance: ldy #1
        lda (blk),y
        tax
        lda (blk)
        jmp appel

; Nombre de notes en file sur le canal A (8,6), rendu dans A.
statut: pha
        jsr WaitMessage
        pla
        sta ControlPort+4
        lda #8
        ldx #6                     ; 8,6 : Sound Status
        jsr appel
        lda ControlPort+4
        rts

; Attend que le canal 0 se taise.
attendre:
        lda #0
        jsr statut
        bne attendre
        rts

; Affiche la chaîne terminée par 0 dont l'adresse est dans A (poids faible) et X (poids fort).
ecrire: sta ptr
        stx ptr+1
        ldy #0
_e:     lda (ptr),y
        beq _f
        jsr WriteCharacter
        iny
        bne _e
_f:     rts

; Affiche A (0-255) en décimal, sans zéros de tête.
decimal:
        phx
        phy
        ldx #0
        ldy #100
        jsr _chiffre
        ldy #10
        jsr _chiffre
        ora #'0'
        jsr WriteCharacter
        ply
        plx
        rts
_chiffre:
        sty diviseur
        ldy #0
_div:   cmp diviseur
        bcc _fini
        sbc diviseur
        iny
        bne _div
_fini:  pha
        tya
        bne _ecrit
        cpx #0
        beq _saute
_ecrit: ora #'0'
        jsr WriteCharacter
        ldx #1
_saute: pla
        rts

; Appel : A = groupe, X = fonction (les paramètres sont déjà en place).
appel:  stx ControlPort+1
        sta ControlPort            ; écrit en dernier : lance l'appel
        jmp WaitMessage

ptr      = $80
blk      = $82
diviseur: .byte 0
fin:     .byte 0
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Notes : 2 1 0

8,7 Queue Sound Extended Official

Queues a note as 8,4 does, choosing the waveform and the volume.

Inputs
BytesTypeMeaning
P0uint8channel (0 to 3)
P1-P2uint16frequency in Hz (0: silence)
P3-P4uint16duration in hundredths of a second
P5-P6int16slide in Hz per hundredth
P7uint8waveform: 0 square, 1 noise
P8uint8volume, 0 to 100
Outputs
BytesTypeMeaning
erreuruint81 if the channel does not exist

The example queues a noise at half volume and reads its volume back with 8,10. In NeoBASIC, sound c,f,d[,s] (square wave) and noise c,f,d[,s] (noise) make this call, always at volume 100; the NeoBASIC example therefore writes the mailbox itself.

NeoBASIC keywords that make this call: sound

Firmware documentation text

Queue a sound. Refer to Section \#\ref{sound} "Sound" for details. This is an extension of call 4 to support different waveform types and volumes. The source parameter is no longer used.

The parameters are : Channel, Frequency Low, Frequency High, Duration Low, Duration High, Slide Low, Slide High, Sound Type and Sound Volume. All these are 16 bit parameters except the sound type and volume, and the channel number.

C (llvm-mos)
/* Mettre en file un bruit à mi-volume (groupe 8, fonction 7). */
#include <stdio.h>
#include <neo6502.h>
#include <neo/api.h>

/* Appel brut de la boîte aux lettres : fonction puis groupe (écrit en dernier, il lance l'appel). */
static void api(uint8_t groupe, uint8_t fonction) {
    ControlPort.function = fonction;
    ControlPort.command = groupe;
    while (ControlPort.command)        /* attendre la fin */
        ;
}
static void libre(void) {              /* attendre que la boîte aux lettres soit libre */
    while (ControlPort.command)
        ;
}

/* 8,7 : note en file sur un canal, forme d'onde (0 carré, 1 bruit) et volume (0-100). */
static void note(uint8_t canal, uint16_t freq, uint16_t duree, uint8_t onde, uint8_t volume) {
    libre();
    ControlPort.params[0] = canal;
    ControlPort.params[1] = freq & 0xFF;        /* P1-P2 : fréquence en Hz */
    ControlPort.params[2] = freq >> 8;
    ControlPort.params[3] = duree & 0xFF;       /* P3-P4 : durée en centièmes de seconde */
    ControlPort.params[4] = duree >> 8;
    ControlPort.params[5] = 0;                  /* P5-P6 : pas de glissement */
    ControlPort.params[6] = 0;
    ControlPort.params[7] = onde;
    ControlPort.params[8] = volume;
    api(8, 7);                                  /* Queue Sound Extended */
}

static uint8_t volume(uint8_t canal) {          /* 8,10 : volume courant ; 255 si erreur */
    libre();
    ControlPort.params[0] = canal;
    api(8, 10);
    return ControlPort.error ? 255 : ControlPort.params[1];
}

int main(void) {
    note(0, 2000, 50, 1, 50);                   /* bruit, 50 cs, volume 50 */
    printf("Bruit en file, volume lu : %u\n", volume(0));
    while (neo_sound_status(0) != 0)            /* 8,6 : attendre la fin */
        ;
    return 0;
}
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Bruit en file, volume lu : 50
Mad Pascal
// Mettre en file un bruit à mi-volume (groupe 8, fonction 7).
program queueextended;
uses crt, neo6502;

function volume(canal: byte): byte;             // 8,10 : volume courant ; 255 si erreur
begin
  NeoMessage.params[0] := canal;
  NeoSendMessage(8, 10);
  NeoWaitMessage;
  if NeoMessage.error <> 0 then result := 255
  else result := NeoMessage.params[1];
end;

begin
  NeoQueueNoteX(0, 2000, 50, 0, 1, 50);         // Queue Sound Extended : bruit, 50 cs, volume 50
  writeln('Bruit en file, volume lu : ', volume(0));
  repeat until NeoGetQueueLen(0) = 0;           // 8,6 : attendre la fin
end.
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Bruit en file, volume lu : 50
NeoBASIC
10 ' Mettre en file un bruit a mi-volume (groupe 8, fonction 7)
20 c = 0 : f = 2000 : t = 50 : o = 1 : v = 50 : gosub 1000 : ' Queue Sound Extended : bruit, 50 cs, volume 50
30 gosub 1100 : print "Bruit en file, volume lu : ";v
40 repeat : until notes(0) = 0 : ' 8,6 : attendre la fin
50 end
1000 ' 8,7 : note sur le canal c, frequence f, duree t (cs), onde o (0 carre, 1 bruit), volume v
1010 while peek($ff00) : wend
1020 poke $ff04, c : doke $ff05, f : doke $ff07, t : doke $ff09, 0 : poke $ff0b, o : poke $ff0c, v
1030 poke $ff01, 7 : poke $ff00, 8
1040 while peek($ff00) : wend
1050 return
1100 ' 8,10 : volume du canal c dans v, erreur dans e
1110 while peek($ff00) : wend
1120 poke $ff04, c : poke $ff01, 10 : poke $ff00, 8
1130 while peek($ff00) : wend
1140 e = peek($ff02) : v = peek($ff05)
1150 return
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Bruit en file, volume lu : 50
65C02 assembly (64tass)
; Mettre en file un bruit à mi-volume (groupe 8, fonction 7).
        .cpu "65c02"
        * = $0800
ControlPort    = $FF00             ; +0 groupe, +1 fonction, +2 erreur, +4.. paramètres
WaitMessage    = $FFF4             ; attend que la boîte aux lettres soit libre
WriteCharacter = $FFF1             ; affiche le caractère de A

start: lda #<n1
        ldx #>n1
        jsr envoi
        lda #<t0
        ldx #>t0
        jsr ecrire
        lda #0
        jsr volume
        jsr decimal
        lda #13
        jsr WriteCharacter
        jmp attendre               ; 8,6 : attendre la fin

; Volume courant du canal A (8,10), rendu dans A ; C = 1 si erreur.
volume: pha
        jsr WaitMessage
        pla
        sta ControlPort+4
        lda #8
        ldx #10                    ; 8,10 : Get Channel Volume
        jsr appel
        lda ControlPort+2
        cmp #1                     ; C = 1 si erreur
        lda ControlPort+5
        rts

n1: .byte 8, 7, 9, 0     ; 8,7 : canal 0 : bruit (onde 1), 50 cs, volume 50
        .word 2000, 50, 0
        .byte 1, 50
t0:     .text "Bruit en file, volume lu : ", 0

; Appel décrit par un bloc dont l'adresse est dans A (poids faible) et X (poids fort) :
; groupe, fonction, nombre de paramètres, puis les paramètres (P0, P1...).
envoi:  sta blk
        stx blk+1
        jsr WaitMessage
        ldy #2
        lda (blk),y                ; nombre de paramètres
        clc
        adc #3
        sta fin
        ldy #3
_copie: cpy fin
        beq _lance
        lda (blk),y
        sta ControlPort+1,y        ; octet 3 du bloc -> P0 ($FF04)...
        iny
        bra _copie
_lance: ldy #1
        lda (blk),y
        tax
        lda (blk)
        jmp appel

; Nombre de notes en file sur le canal A (8,6), rendu dans A.
statut: pha
        jsr WaitMessage
        pla
        sta ControlPort+4
        lda #8
        ldx #6                     ; 8,6 : Sound Status
        jsr appel
        lda ControlPort+4
        rts

; Attend que le canal 0 se taise.
attendre:
        lda #0
        jsr statut
        bne attendre
        rts

; Affiche la chaîne terminée par 0 dont l'adresse est dans A (poids faible) et X (poids fort).
ecrire: sta ptr
        stx ptr+1
        ldy #0
_e:     lda (ptr),y
        beq _f
        jsr WriteCharacter
        iny
        bne _e
_f:     rts

; Affiche A (0-255) en décimal, sans zéros de tête.
decimal:
        phx
        phy
        ldx #0
        ldy #100
        jsr _chiffre
        ldy #10
        jsr _chiffre
        ora #'0'
        jsr WriteCharacter
        ply
        plx
        rts
_chiffre:
        sty diviseur
        ldy #0
_div:   cmp diviseur
        bcc _fini
        sbc diviseur
        iny
        bne _div
_fini:  pha
        tya
        bne _ecrit
        cpx #0
        beq _saute
_ecrit: ora #'0'
        jsr WriteCharacter
        ldx #1
_saute: pla
        rts

; Appel : A = groupe, X = fonction (les paramètres sont déjà en place).
appel:  stx ControlPort+1
        sta ControlPort            ; écrit en dernier : lance l'appel
        jmp WaitMessage

ptr      = $80
blk      = $82
diviseur: .byte 0
fin:     .byte 0
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Bruit en file, volume lu : 50

8,8 Get Channel Count Official

Gives the number of sound channels: 4.

Inputs: none.

Outputs
BytesTypeMeaning
P0uint8number of channels

The sample stream (8,11) comes on top without being counted. NeoBASIC has no keyword for this call.

Firmware documentation text

This returns the number of channels in Parameter #0

C (llvm-mos)
/* Demander le nombre de canaux sonores (groupe 8, fonction 8). */
#include <stdio.h>
#include <neo/api.h>

int main(void) {
    printf("Canaux : %u\n", neo_sound_channel_count());   /* Get Channel Count */
    return 0;
}
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Canaux : 4
Mad Pascal
// Demander le nombre de canaux sonores (groupe 8, fonction 8).
program channelcount;
uses crt, neo6502;
begin
  writeln('Canaux : ', NeoGetChannels);         // Get Channel Count
end.
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Canaux : 4
NeoBASIC
10 ' Demander le nombre de canaux sonores (groupe 8, fonction 8)
20 while peek($ff00) : wend : poke $ff01, 8 : poke $ff00, 8 : ' Get Channel Count
30 while peek($ff00) : wend : n = peek($ff04)
40 print "Canaux : ";n
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Canaux : 4
65C02 assembly (64tass)
; Demander le nombre de canaux sonores (groupe 8, fonction 8).
        .cpu "65c02"
        * = $0800
ControlPort    = $FF00             ; +0 groupe, +1 fonction, +2 erreur, +4.. paramètres
WaitMessage    = $FFF4             ; attend que la boîte aux lettres soit libre
WriteCharacter = $FFF1             ; affiche le caractère de A

start: lda #<nb
        ldx #>nb
        jsr envoi
        lda ControlPort+4          ; P0 : nombre de canaux
        sta n
        lda #<t0
        ldx #>t0
        jsr ecrire
        lda n
        jsr decimal
        lda #13
        jsr WriteCharacter
        rts

nb:     .byte 8, 8, 0              ; 8,8 : Get Channel Count
t0:     .text "Canaux : ", 0
n:      .byte 0

; Appel décrit par un bloc dont l'adresse est dans A (poids faible) et X (poids fort) :
; groupe, fonction, nombre de paramètres, puis les paramètres (P0, P1...).
envoi:  sta blk
        stx blk+1
        jsr WaitMessage
        ldy #2
        lda (blk),y                ; nombre de paramètres
        clc
        adc #3
        sta fin
        ldy #3
_copie: cpy fin
        beq _lance
        lda (blk),y
        sta ControlPort+1,y        ; octet 3 du bloc -> P0 ($FF04)...
        iny
        bra _copie
_lance: ldy #1
        lda (blk),y
        tax
        lda (blk)
        jmp appel

; Nombre de notes en file sur le canal A (8,6), rendu dans A.
statut: pha
        jsr WaitMessage
        pla
        sta ControlPort+4
        lda #8
        ldx #6                     ; 8,6 : Sound Status
        jsr appel
        lda ControlPort+4
        rts

; Attend que le canal 0 se taise.
attendre:
        lda #0
        jsr statut
        bne attendre
        rts

; Affiche la chaîne terminée par 0 dont l'adresse est dans A (poids faible) et X (poids fort).
ecrire: sta ptr
        stx ptr+1
        ldy #0
_e:     lda (ptr),y
        beq _f
        jsr WriteCharacter
        iny
        bne _e
_f:     rts

; Affiche A (0-255) en décimal, sans zéros de tête.
decimal:
        phx
        phy
        ldx #0
        ldy #100
        jsr _chiffre
        ldy #10
        jsr _chiffre
        ora #'0'
        jsr WriteCharacter
        ply
        plx
        rts
_chiffre:
        sty diviseur
        ldy #0
_div:   cmp diviseur
        bcc _fini
        sbc diviseur
        iny
        bne _div
_fini:  pha
        tya
        bne _ecrit
        cpx #0
        beq _saute
_ecrit: ora #'0'
        jsr WriteCharacter
        ldx #1
_saute: pla
        rts

; Appel : A = groupe, X = fonction (les paramètres sont déjà en place).
appel:  stx ControlPort+1
        sta ControlPort            ; écrit en dernier : lance l'appel
        jmp WaitMessage

ptr      = $80
blk      = $82
diviseur: .byte 0
fin:     .byte 0
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Canaux : 4

8,9 Set Channel Volume Trinity

Changes the volume of the note playing on a channel, at once or as a fade, without restarting the wave.

Inputs
BytesTypeMeaning
P0uint8channel (0 to 3)
P1uint8target volume, 0 to 100
P2-P3uint16fade time in hundredths (0: at once)
Outputs
BytesTypeMeaning
erreuruint81 if the channel does not exist or plays nothing

Only the current note changes: the notes still queued keep their volume, and the next note cancels an unfinished fade. The fade moves by a whole step every 50th of a second and may end a little later than asked (63 to 0 in 50 cs: 2 per step, 32 steps). The example causes an error on channel 1, which is silent.

Firmware documentation text

Sets the volume of the note currently playing on the channel in Parameter:0 to Parameter:1 (0-100), reached in Parameters:2,3 hundredths of a second (0 = at once) by a linear ramp (Trinity T-22, F-12 of the fork) : fade in, fade out or release without restarting the wave. The notes still queued keep the volume given when they were queued, and a new note cancels the ramp. Sets the error flag if the channel does not exist or is silent.

C (llvm-mos)
/* Baisser le volume d'une note qui joue, d'un coup puis en fondu (groupe 8, fonction 9). */
#include <stdio.h>
#include <neo6502.h>
#include <neo/api.h>

/* Appel brut de la boîte aux lettres : fonction puis groupe (écrit en dernier, il lance l'appel). */
static void api(uint8_t groupe, uint8_t fonction) {
    ControlPort.function = fonction;
    ControlPort.command = groupe;
    while (ControlPort.command)        /* attendre la fin */
        ;
}
static void libre(void) {              /* attendre que la boîte aux lettres soit libre */
    while (ControlPort.command)
        ;
}

/* 8,7 : note en file sur un canal, forme d'onde (0 carré, 1 bruit) et volume (0-100). */
static void note(uint8_t canal, uint16_t freq, uint16_t duree, uint8_t onde, uint8_t volume) {
    libre();
    ControlPort.params[0] = canal;
    ControlPort.params[1] = freq & 0xFF;        /* P1-P2 : fréquence en Hz */
    ControlPort.params[2] = freq >> 8;
    ControlPort.params[3] = duree & 0xFF;       /* P3-P4 : durée en centièmes de seconde */
    ControlPort.params[4] = duree >> 8;
    ControlPort.params[5] = 0;                  /* P5-P6 : pas de glissement */
    ControlPort.params[6] = 0;
    ControlPort.params[7] = onde;
    ControlPort.params[8] = volume;
    api(8, 7);                                  /* Queue Sound Extended */
}

static uint8_t volume(uint8_t canal) {          /* 8,10 : volume courant ; 255 si erreur */
    libre();
    ControlPort.params[0] = canal;
    api(8, 10);
    return ControlPort.error ? 255 : ControlPort.params[1];
}

static uint8_t regler(uint8_t canal, uint8_t vol, uint16_t cs) {   /* 8,9 : rend l'indicateur d'erreur */
    libre();
    ControlPort.params[0] = canal;
    ControlPort.params[1] = vol;                /* P1 : volume visé, 0-100 */
    ControlPort.params[2] = cs & 0xFF;          /* P2-P3 : durée du fondu en cs (0 : tout de suite) */
    ControlPort.params[3] = cs >> 8;
    api(8, 9);                                  /* Set Channel Volume */
    return ControlPort.error;
}

int main(void) {
    note(0, 440, 200, 0, 100);                  /* la 440 Hz, 2 s, volume 100 */
    uint8_t v0 = volume(0);
    regler(0, 50, 0);                           /* 50, tout de suite */
    uint8_t v1 = volume(0);
    regler(0, 0, 50);                           /* 0 en 50 cs */
    while (volume(0) != 0)                      /* le fondu avance à 50 Hz */
        ;
    printf("Volume : %u, puis %u, puis 0 en fondu\n", v0, v1);
    printf("Canal 1 muet : erreur %u\n", regler(1, 50, 0));
    neo_sound_reset();                          /* 8,1 : fin de la note */
    return 0;
}
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Volume : 100, puis 50, puis 0 en fondu
Canal 1 muet : erreur 1
Mad Pascal
// Baisser le volume d'une note qui joue, d'un coup puis en fondu (groupe 8, fonction 9).
program setvolume;
uses crt, neo6502;
var v0, v1, e: byte;

function volume(canal: byte): byte;             // 8,10 : volume courant ; 255 si erreur
begin
  NeoMessage.params[0] := canal;
  NeoSendMessage(8, 10);
  NeoWaitMessage;
  if NeoMessage.error <> 0 then result := 255
  else result := NeoMessage.params[1];
end;

function regler(canal, vol: byte; cs: word): byte;   // 8,9 : rend l'indicateur d'erreur
begin
  NeoMessage.params[0] := canal;
  NeoMessage.params[1] := vol;                  // P1 : volume visé, 0-100
  NeoMessage.params[2] := lo(cs);               // P2-P3 : durée du fondu en cs (0 : tout de suite)
  NeoMessage.params[3] := hi(cs);
  NeoSendMessage(8, 9);                         // Set Channel Volume
  NeoWaitMessage;
  result := NeoMessage.error;
end;

begin
  NeoQueueNoteX(0, 440, 200, 0, 0, 100);        // la 440 Hz, 2 s, volume 100
  v0 := volume(0);
  regler(0, 50, 0);                             // 50, tout de suite
  v1 := volume(0);
  regler(0, 0, 50);                             // 0 en 50 cs
  repeat until volume(0) = 0;                   // le fondu avance à 50 Hz
  writeln('Volume : ', v0, ', puis ', v1, ', puis 0 en fondu');
  e := regler(1, 50, 0);
  writeln('Canal 1 muet : erreur ', e);
  NeoMute;                                      // 8,1 : fin de la note
end.
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Volume : 100, puis 50, puis 0 en fondu
Canal 1 muet : erreur 1
NeoBASIC
10 ' Baisser le volume d'une note qui joue, d'un coup puis en fondu (groupe 8, fonction 9)
20 c = 0 : f = 440 : t = 200 : o = 0 : v = 100 : gosub 1000 : ' la 440 Hz, 2 s, volume 100
30 gosub 1100 : v0 = v
40 v = 50 : d = 0 : gosub 1200 : ' 50, tout de suite
50 gosub 1100 : v1 = v
60 v = 0 : d = 50 : gosub 1200 : ' 0 en 50 cs
70 repeat : gosub 1100 : until v = 0 : ' le fondu avance a 50 Hz
80 print "Volume : ";v0;", puis ";v1;", puis 0 en fondu"
90 c = 1 : v = 50 : d = 0 : gosub 1200 : print "Canal 1 muet : erreur ";e
100 sound clear : ' 8,1 : fin de la note
110 end
1000 ' 8,7 : note sur le canal c, frequence f, duree t (cs), onde o (0 carre, 1 bruit), volume v
1010 while peek($ff00) : wend
1020 poke $ff04, c : doke $ff05, f : doke $ff07, t : doke $ff09, 0 : poke $ff0b, o : poke $ff0c, v
1030 poke $ff01, 7 : poke $ff00, 8
1040 while peek($ff00) : wend
1050 return
1100 ' 8,10 : volume du canal c dans v, erreur dans e
1110 while peek($ff00) : wend
1120 poke $ff04, c : poke $ff01, 10 : poke $ff00, 8
1130 while peek($ff00) : wend
1140 e = peek($ff02) : v = peek($ff05)
1150 return
1200 ' 8,9 : volume v (0-100) du canal c, atteint en d cs (0 : tout de suite) ; erreur dans e
1210 while peek($ff00) : wend
1220 poke $ff04, c : poke $ff05, v : doke $ff06, d
1230 poke $ff01, 9 : poke $ff00, 8
1240 while peek($ff00) : wend
1250 e = peek($ff02)
1260 return
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Volume : 100, puis 50, puis 0 en fondu
Canal 1 muet : erreur 1
65C02 assembly (64tass)
; Baisser le volume d'une note qui joue, d'un coup puis en fondu (groupe 8, fonction 9).
        .cpu "65c02"
        * = $0800
ControlPort    = $FF00             ; +0 groupe, +1 fonction, +2 erreur, +4.. paramètres
WaitMessage    = $FFF4             ; attend que la boîte aux lettres soit libre
WriteCharacter = $FFF1             ; affiche le caractère de A

start: lda #<n1
        ldx #>n1
        jsr envoi
        lda #0
        jsr volume
        sta v0
        lda #<v50
        ldx #>v50
        jsr envoi
        lda #0
        jsr volume
        sta v1
        lda #<fondu
        ldx #>fondu
        jsr envoi
_fondu: lda #0                     ; le fondu avance à 50 Hz
        jsr volume
        bne _fondu
        lda #<t0
        ldx #>t0
        jsr ecrire
        lda v0
        jsr decimal
        lda #<t1
        ldx #>t1
        jsr ecrire
        lda v1
        jsr decimal
        lda #<t2
        ldx #>t2
        jsr ecrire
        lda #<muet
        ldx #>muet
        jsr envoi
        lda ControlPort+2          ; erreur : le canal 1 ne joue rien
        sta e
        lda #<t3
        ldx #>t3
        jsr ecrire
        lda e
        jsr decimal
        lda #13
        jsr WriteCharacter
        lda #<raz
        ldx #>raz
        jsr envoi
        rts

; Volume courant du canal A (8,10), rendu dans A ; C = 1 si erreur.
volume: pha
        jsr WaitMessage
        pla
        sta ControlPort+4
        lda #8
        ldx #10                    ; 8,10 : Get Channel Volume
        jsr appel
        lda ControlPort+2
        cmp #1                     ; C = 1 si erreur
        lda ControlPort+5
        rts

n1: .byte 8, 7, 9, 0     ; 8,7 : canal 0 : la 440 Hz, 2 s, volume 100
        .word 440, 200, 0
        .byte 0, 100
v50:    .byte 8, 9, 4, 0, 50       ; 8,9 : canal 0, volume 50...
        .word 0                    ; ... tout de suite
fondu:  .byte 8, 9, 4, 0, 0        ; 8,9 : canal 0, volume 0...
        .word 50                   ; ... en 50 cs
muet:   .byte 8, 9, 4, 1, 50       ; 8,9 : canal 1, qui ne joue rien
        .word 0
raz:    .byte 8, 1, 0              ; 8,1 : Reset Sound
t0:     .text "Volume : ", 0
t1:     .text ", puis ", 0
t2:     .text ", puis 0 en fondu", 13, 0
t3:     .text "Canal 1 muet : erreur ", 0
v0:     .byte 0
v1:     .byte 0
e:      .byte 0

; Appel décrit par un bloc dont l'adresse est dans A (poids faible) et X (poids fort) :
; groupe, fonction, nombre de paramètres, puis les paramètres (P0, P1...).
envoi:  sta blk
        stx blk+1
        jsr WaitMessage
        ldy #2
        lda (blk),y                ; nombre de paramètres
        clc
        adc #3
        sta fin
        ldy #3
_copie: cpy fin
        beq _lance
        lda (blk),y
        sta ControlPort+1,y        ; octet 3 du bloc -> P0 ($FF04)...
        iny
        bra _copie
_lance: ldy #1
        lda (blk),y
        tax
        lda (blk)
        jmp appel

; Nombre de notes en file sur le canal A (8,6), rendu dans A.
statut: pha
        jsr WaitMessage
        pla
        sta ControlPort+4
        lda #8
        ldx #6                     ; 8,6 : Sound Status
        jsr appel
        lda ControlPort+4
        rts

; Attend que le canal 0 se taise.
attendre:
        lda #0
        jsr statut
        bne attendre
        rts

; Affiche la chaîne terminée par 0 dont l'adresse est dans A (poids faible) et X (poids fort).
ecrire: sta ptr
        stx ptr+1
        ldy #0
_e:     lda (ptr),y
        beq _f
        jsr WriteCharacter
        iny
        bne _e
_f:     rts

; Affiche A (0-255) en décimal, sans zéros de tête.
decimal:
        phx
        phy
        ldx #0
        ldy #100
        jsr _chiffre
        ldy #10
        jsr _chiffre
        ora #'0'
        jsr WriteCharacter
        ply
        plx
        rts
_chiffre:
        sty diviseur
        ldy #0
_div:   cmp diviseur
        bcc _fini
        sbc diviseur
        iny
        bne _div
_fini:  pha
        tya
        bne _ecrit
        cpx #0
        beq _saute
_ecrit: ora #'0'
        jsr WriteCharacter
        ldx #1
_saute: pla
        rts

; Appel : A = groupe, X = fonction (les paramètres sont déjà en place).
appel:  stx ControlPort+1
        sta ControlPort            ; écrit en dernier : lance l'appel
        jmp WaitMessage

ptr      = $80
blk      = $82
diviseur: .byte 0
fin:     .byte 0
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Volume : 100, puis 50, puis 0 en fondu
Canal 1 muet : erreur 1

8,10 Get Channel Volume Trinity

Gives the current volume of a channel, 0 when it is silent.

Inputs
BytesTypeMeaning
P0uint8channel (0 to 3)
Outputs
BytesTypeMeaning
P1uint8volume, 0 to 100
erreuruint81 if the channel does not exist

The result is in P1, not P0. The volume, stored as 0 to 127, is converted back to 0-100: the note asked at 25 reads back as 24. During a fade (8,9), the value changes every 50th of a second.

Firmware documentation text

Returns in Parameter:1 the current volume (0-100) of the channel in Parameter:0, 0 when silent (Trinity T-22, F-12 of the fork). Sets the error flag if the channel does not exist.

C (llvm-mos)
/* Lire le volume de trois canaux (groupe 8, fonction 10). */
#include <stdio.h>
#include <neo6502.h>
#include <neo/api.h>

/* Appel brut de la boîte aux lettres : fonction puis groupe (écrit en dernier, il lance l'appel). */
static void api(uint8_t groupe, uint8_t fonction) {
    ControlPort.function = fonction;
    ControlPort.command = groupe;
    while (ControlPort.command)        /* attendre la fin */
        ;
}
static void libre(void) {              /* attendre que la boîte aux lettres soit libre */
    while (ControlPort.command)
        ;
}

/* 8,7 : note en file sur un canal, forme d'onde (0 carré, 1 bruit) et volume (0-100). */
static void note(uint8_t canal, uint16_t freq, uint16_t duree, uint8_t onde, uint8_t volume) {
    libre();
    ControlPort.params[0] = canal;
    ControlPort.params[1] = freq & 0xFF;        /* P1-P2 : fréquence en Hz */
    ControlPort.params[2] = freq >> 8;
    ControlPort.params[3] = duree & 0xFF;       /* P3-P4 : durée en centièmes de seconde */
    ControlPort.params[4] = duree >> 8;
    ControlPort.params[5] = 0;                  /* P5-P6 : pas de glissement */
    ControlPort.params[6] = 0;
    ControlPort.params[7] = onde;
    ControlPort.params[8] = volume;
    api(8, 7);                                  /* Queue Sound Extended */
}

static uint8_t volume(uint8_t canal) {          /* 8,10 : volume courant ; 255 si erreur */
    libre();
    ControlPort.params[0] = canal;
    api(8, 10);
    return ControlPort.error ? 255 : ControlPort.params[1];
}

int main(void) {
    note(0, 440, 100, 0, 25);                   /* canal 0 : une note au volume 25 */
    printf("Canal 0 : %u\n", volume(0));        /* Get Channel Volume */
    printf("Canal 1 : %u\n", volume(1));        /* rien ne joue : 0 */
    printf("Canal 4 : %s\n", volume(4) == 255 ? "erreur" : "?");   /* 4 canaux, 0 à 3 */
    neo_sound_reset();                          /* 8,1 : fin de la note */
    return 0;
}
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Canal 0 : 24
Canal 1 : 0
Canal 4 : erreur
Mad Pascal
// Lire le volume de trois canaux (groupe 8, fonction 10).
program getvolume;
uses crt, neo6502;

function volume(canal: byte): byte;             // 8,10 : volume courant ; 255 si erreur
begin
  NeoMessage.params[0] := canal;
  NeoSendMessage(8, 10);
  NeoWaitMessage;
  if NeoMessage.error <> 0 then result := 255
  else result := NeoMessage.params[1];
end;

begin
  NeoQueueNoteX(0, 440, 100, 0, 0, 25);         // canal 0 : une note au volume 25
  writeln('Canal 0 : ', volume(0));             // Get Channel Volume
  writeln('Canal 1 : ', volume(1));             // rien ne joue : 0
  if volume(4) = 255 then writeln('Canal 4 : erreur');   // 4 canaux, 0 à 3
  NeoMute;                                      // 8,1 : fin de la note
end.
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Canal 0 : 24
Canal 1 : 0
Canal 4 : erreur
NeoBASIC
10 ' Lire le volume de trois canaux (groupe 8, fonction 10)
20 c = 0 : f = 440 : t = 100 : o = 0 : v = 25 : gosub 1000 : ' canal 0 : une note au volume 25
30 c = 0 : gosub 1100 : print "Canal 0 : ";v : ' Get Channel Volume
40 c = 1 : gosub 1100 : print "Canal 1 : ";v : ' rien ne joue : 0
50 c = 4 : gosub 1100 : if e then print "Canal 4 : erreur" : ' 4 canaux, 0 a 3
60 sound clear : ' 8,1 : fin de la note
70 end
1000 ' 8,7 : note sur le canal c, frequence f, duree t (cs), onde o (0 carre, 1 bruit), volume v
1010 while peek($ff00) : wend
1020 poke $ff04, c : doke $ff05, f : doke $ff07, t : doke $ff09, 0 : poke $ff0b, o : poke $ff0c, v
1030 poke $ff01, 7 : poke $ff00, 8
1040 while peek($ff00) : wend
1050 return
1100 ' 8,10 : volume du canal c dans v, erreur dans e
1110 while peek($ff00) : wend
1120 poke $ff04, c : poke $ff01, 10 : poke $ff00, 8
1130 while peek($ff00) : wend
1140 e = peek($ff02) : v = peek($ff05)
1150 return
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Canal 0 : 24
Canal 1 : 0
Canal 4 : erreur
65C02 assembly (64tass)
; Lire le volume de trois canaux (groupe 8, fonction 10).
        .cpu "65c02"
        * = $0800
ControlPort    = $FF00             ; +0 groupe, +1 fonction, +2 erreur, +4.. paramètres
WaitMessage    = $FFF4             ; attend que la boîte aux lettres soit libre
WriteCharacter = $FFF1             ; affiche le caractère de A

start: lda #<n1
        ldx #>n1
        jsr envoi
        lda #<t0
        ldx #>t0
        jsr ecrire
        lda #0
        jsr volume                 ; Get Channel Volume
        jsr decimal
        lda #13
        jsr WriteCharacter
        lda #<t1
        ldx #>t1
        jsr ecrire
        lda #1
        jsr volume                 ; rien ne joue : 0
        jsr decimal
        lda #13
        jsr WriteCharacter
        lda #4                     ; 4 canaux, 0 à 3
        jsr volume
        bcc _fin
        lda #<t2
        ldx #>t2
        jsr ecrire
_fin:   lda #<raz
        ldx #>raz
        jsr envoi
        rts

; Volume courant du canal A (8,10), rendu dans A ; C = 1 si erreur.
volume: pha
        jsr WaitMessage
        pla
        sta ControlPort+4
        lda #8
        ldx #10                    ; 8,10 : Get Channel Volume
        jsr appel
        lda ControlPort+2
        cmp #1                     ; C = 1 si erreur
        lda ControlPort+5
        rts

n1: .byte 8, 7, 9, 0     ; 8,7 : canal 0 : une note au volume 25
        .word 440, 100, 0
        .byte 0, 25
raz:    .byte 8, 1, 0              ; 8,1 : Reset Sound
t0:     .text "Canal 0 : ", 0
t1:     .text "Canal 1 : ", 0
t2:     .text "Canal 4 : erreur", 13, 0

; Appel décrit par un bloc dont l'adresse est dans A (poids faible) et X (poids fort) :
; groupe, fonction, nombre de paramètres, puis les paramètres (P0, P1...).
envoi:  sta blk
        stx blk+1
        jsr WaitMessage
        ldy #2
        lda (blk),y                ; nombre de paramètres
        clc
        adc #3
        sta fin
        ldy #3
_copie: cpy fin
        beq _lance
        lda (blk),y
        sta ControlPort+1,y        ; octet 3 du bloc -> P0 ($FF04)...
        iny
        bra _copie
_lance: ldy #1
        lda (blk),y
        tax
        lda (blk)
        jmp appel

; Nombre de notes en file sur le canal A (8,6), rendu dans A.
statut: pha
        jsr WaitMessage
        pla
        sta ControlPort+4
        lda #8
        ldx #6                     ; 8,6 : Sound Status
        jsr appel
        lda ControlPort+4
        rts

; Attend que le canal 0 se taise.
attendre:
        lda #0
        jsr statut
        bne attendre
        rts

; Affiche la chaîne terminée par 0 dont l'adresse est dans A (poids faible) et X (poids fort).
ecrire: sta ptr
        stx ptr+1
        ldy #0
_e:     lda (ptr),y
        beq _f
        jsr WriteCharacter
        iny
        bne _e
_f:     rts

; Affiche A (0-255) en décimal, sans zéros de tête.
decimal:
        phx
        phy
        ldx #0
        ldy #100
        jsr _chiffre
        ldy #10
        jsr _chiffre
        ora #'0'
        jsr WriteCharacter
        ply
        plx
        rts
_chiffre:
        sty diviseur
        ldy #0
_div:   cmp diviseur
        bcc _fini
        sbc diviseur
        iny
        bne _div
_fini:  pha
        tya
        bne _ecrit
        cpx #0
        beq _saute
_ecrit: ora #'0'
        jsr WriteCharacter
        ldx #1
_saute: pla
        rts

; Appel : A = groupe, X = fonction (les paramètres sont déjà en place).
appel:  stx ControlPort+1
        sta ControlPort            ; écrit en dernier : lance l'appel
        jmp WaitMessage

ptr      = $80
blk      = $82
diviseur: .byte 0
fin:     .byte 0
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Canal 0 : 24
Canal 1 : 0
Canal 4 : erreur

8,11 Stream Start Trinity

Starts a stream of 8 bit PCM samples read from a double buffer in 6502 memory.

Inputs
BytesTypeMeaning
P0-P1uint16buffer address (two halves one after the other)
P2-P3uint16size of one half in bytes
P4-P5uint16samples per second (11025, 22050...)
P6uint8volume, 0 to 100
Outputs
BytesTypeMeaning
erreuruint81 if a size or the rate is 0, the volume is above 100, or the buffer does not end below $FF00

The samples are unsigned, $80 being silence. Both halves must be filled before the call: they count as full. The firmware plays half 0, then 1, then 0..., and hands each one back once played (8,13); the program refills it and marks it full (8,14). The example plays a square wave of 256 samples per half at 11025 Hz, waits until half 0 has been played (state 129 = $81), then shows the error of a buffer that would overlap $FF00. NeoBASIC has no keyword for the stream.

Firmware documentation text

Starts playing a stream of 8 bit unsigned PCM samples ($80 = silence) mixed with the four channels (Trinity T-79). The buffer is in 6502 RAM at Parameters:0,1 and made of two halves of Parameters:2,3 bytes each, one after the other ; Parameters:4,5 is the rate of the samples in Hz (11025 or 22050 for instance, the firmware follows it whatever the output rate of the board) and Parameter:6 the volume (0-100, as Function 8,9). Fill both halves before the call : they are taken as full. The firmware plays half 0 then half 1 then half 0 again..., handing each half back when it has played it (Function 8,13) ; the program refills it and marks it full (Function 8,14). A half not refilled in time is not replayed : the stream is silent until it is marked full, and the underrun is counted. The stream counts as one more channel for the volume scaling. Sets the error flag if a size or the rate is 0, the volume above 100, or the buffer does not end below $FF00. Function 8,1 stops the stream as well.

C (llvm-mos)
/* Lancer un flux d'échantillons (groupe 8, fonction 11). */
#include <stdio.h>
#include <neo6502.h>
#include <neo/api.h>

/* Appel brut de la boîte aux lettres : fonction puis groupe (écrit en dernier, il lance l'appel). */
static void api(uint8_t groupe, uint8_t fonction) {
    ControlPort.function = fonction;
    ControlPort.command = groupe;
    while (ControlPort.command)        /* attendre la fin */
        ;
}
static void libre(void) {              /* attendre que la boîte aux lettres soit libre */
    while (ControlPort.command)
        ;
}

static uint8_t tampon[2 * 256];                 /* deux moitiés de 256 échantillons */
static uint16_t manques;                        /* sous-alimentations, lues par etat() */

static void creneau(uint8_t moitie) {           /* écrit un créneau : 16 échantillons hauts, 16 bas */
    uint8_t *p = tampon + moitie * 256;
    for (uint16_t i = 0; i < 256; i++)
        p[i] = (i & 16) ? 0xC0 : 0x40;          /* $80 = silence */
}

static uint8_t lancer(uint16_t adresse) {       /* 8,11 : 256 octets par moitié, 11025 Hz, volume 100 */
    libre();
    ControlPort.params[0] = adresse & 0xFF;     /* P0-P1 : adresse du tampon */
    ControlPort.params[1] = adresse >> 8;
    ControlPort.params[2] = 0;                  /* P2-P3 : taille d'une moitié */
    ControlPort.params[3] = 1;
    ControlPort.params[4] = 11025 & 0xFF;       /* P4-P5 : échantillons par seconde */
    ControlPort.params[5] = 11025 >> 8;
    ControlPort.params[6] = 100;                /* P6 : volume */
    api(8, 11);                                 /* Stream Start */
    return ControlPort.error;
}

static uint8_t etat(void) {                     /* 8,13 : bit 7 joue, bits 0 et 1 moitiés à remplir */
    libre();
    api(8, 13);
    manques = ControlPort.params[1] | ControlPort.params[2] << 8;
    return ControlPort.params[0];
}

static void arreter(void) {                     /* 8,12 */
    libre();
    api(8, 12);
}

int main(void) {
    creneau(0);                                 /* remplir les deux moitiés avant de lancer */
    creneau(1);
    lancer((uint16_t)tampon);                   /* Stream Start */
    printf("Lance : %u\n", etat());
    while (etat() != 129)                       /* 128 + 1 : la moitié 0 a été jouée */
        ;
    printf("Moitie 0 jouee : %u\n", etat());
    arreter();
    printf("Tampon trop haut : erreur %u\n", lancer(0xFE80));   /* $FE80 + 512 dépasse $FF00 */
    return 0;
}
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Lance : 128
Moitie 0 jouee : 129
Tampon trop haut : erreur 1
Mad Pascal
// Lancer un flux d'échantillons (groupe 8, fonction 11).
program streamstart;
uses crt, neo6502;
var tampon: array[0..511] of byte;              // deux moitiés de 256 échantillons
    manques: word;                              // sous-alimentations, lues par etat

procedure creneau(moitie: byte);                // écrit un créneau : 16 échantillons hauts, 16 bas
var i: word;
begin
  for i := 0 to 255 do
    if i and 16 <> 0 then tampon[moitie * 256 + i] := $C0
    else tampon[moitie * 256 + i] := $40;       // $80 = silence
end;

function lancer(adresse: word): byte;           // 8,11 : 256 octets par moitié, 11025 Hz, volume 100
begin
  NeoMessage.params[0] := lo(adresse);          // P0-P1 : adresse du tampon
  NeoMessage.params[1] := hi(adresse);
  NeoMessage.params[2] := 0;                    // P2-P3 : taille d'une moitié
  NeoMessage.params[3] := 1;
  NeoMessage.params[4] := lo(11025);            // P4-P5 : échantillons par seconde
  NeoMessage.params[5] := hi(11025);
  NeoMessage.params[6] := 100;                  // P6 : volume
  NeoSendMessage(8, 11);                        // Stream Start
  NeoWaitMessage;
  result := NeoMessage.error;
end;

function etat: byte;                            // 8,13 : bit 7 joue, bits 0 et 1 moitiés à remplir
begin
  NeoSendMessage(8, 13);
  NeoWaitMessage;
  result := NeoMessage.params[0];
  manques := NeoMessage.params[1] + NeoMessage.params[2] shl 8;
end;

procedure arreter;                              // 8,12
begin
  NeoSendMessage(8, 12);
  NeoWaitMessage;
end;
var e: byte;

begin
  creneau(0);                                   // remplir les deux moitiés avant de lancer
  creneau(1);
  lancer(word(@tampon));                        // Stream Start
  writeln('Lance : ', etat);
  repeat until etat = 129;                      // 128 + 1 : la moitié 0 a été jouée
  writeln('Moitie 0 jouee : ', etat);
  arreter;
  e := lancer($FE80);                           // $FE80 + 512 dépasse $FF00
  writeln('Tampon trop haut : erreur ', e);
end.
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Lance : 128
Moitie 0 jouee : 129
Tampon trop haut : erreur 1
NeoBASIC
10 ' Lancer un flux d'echantillons (groupe 8, fonction 11)
20 a = alloc(512) : b = a : gosub 1000 : b = a + 256 : gosub 1000 : ' remplir les deux moities avant
30 gosub 1100 : ' Stream Start
40 gosub 1200 : print "Lance : ";s
50 repeat : gosub 1200 : until s = 129 : ' 128 + 1 : la moitie 0 a ete jouee
60 print "Moitie 0 jouee : ";s
70 gosub 1300
80 a = $fe80 : gosub 1100 : print "Tampon trop haut : erreur ";e : ' $fe80 + 512 depasse $ff00
90 end
1000 ' creneau dans la moitie qui commence en b : 16 echantillons hauts, 16 bas ($80 = silence)
1010 for i = 0 to 255 : x = $40 : if i & 16 then x = $c0
1020 poke b + i, x : next i
1030 return
1100 ' 8,11 : tampon en a, 256 octets par moitie, 11025 Hz, volume 100 ; erreur dans e
1110 while peek($ff00) : wend
1120 doke $ff04, a : doke $ff06, 256 : doke $ff08, 11025 : poke $ff0a, 100
1130 poke $ff01, 11 : poke $ff00, 8
1140 while peek($ff00) : wend
1150 e = peek($ff02)
1160 return
1200 ' 8,13 : etat dans s (bit 7 joue, bits 0 et 1 moities a remplir), sous-alimentations dans u
1210 while peek($ff00) : wend
1220 poke $ff01, 13 : poke $ff00, 8
1230 while peek($ff00) : wend
1240 s = peek($ff04) : u = deek($ff05)
1250 return
1300 ' 8,12 : arret du flux
1310 while peek($ff00) : wend
1320 poke $ff01, 12 : poke $ff00, 8
1330 while peek($ff00) : wend
1340 return
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Lance : 128
Moitie 0 jouee : 129
Tampon trop haut : erreur 1
65C02 assembly (64tass)
; Lancer un flux d'échantillons (groupe 8, fonction 11).
        .cpu "65c02"
        * = $0800
ControlPort    = $FF00             ; +0 groupe, +1 fonction, +2 erreur, +4.. paramètres
WaitMessage    = $FFF4             ; attend que la boîte aux lettres soit libre
WriteCharacter = $FFF1             ; affiche le caractère de A

start:   lda #0                     ; remplir les deux moitiés avant de lancer
        jsr creneau
        lda #1
        jsr creneau
        lda #<lance
        ldx #>lance
        jsr envoi
        jsr etat
        sta e0
        lda #<t0
        ldx #>t0
        jsr ecrire
        lda e0
        jsr decimal
        lda #13
        jsr WriteCharacter
_joue:  jsr etat                   ; 128 + 1 : la moitié 0 a été jouée
        cmp #129
        bne _joue
        lda #<t1
        ldx #>t1
        jsr ecrire
        lda etat_
        jsr decimal
        lda #13
        jsr WriteCharacter
        lda #<arret
        ldx #>arret
        jsr envoi
        lda #<haut
        ldx #>haut
        jsr envoi
        lda ControlPort+2
        sta e
        lda #<t2
        ldx #>t2
        jsr ecrire
        lda e
        jsr decimal
        lda #13
        jsr WriteCharacter
        rts

; Écrit un créneau dans la moitié A (0 ou 1) : 16 échantillons hauts, 16 bas ($80 = silence).
creneau:
        clc
        adc #>tampon
        sta ptr+1
        lda #<tampon
        sta ptr
        ldy #0
_r:     tya
        and #16
        beq _bas
        lda #$C0
        bra _ecr
_bas:   lda #$40
_ecr:   sta (ptr),y
        iny
        bne _r
        rts

; État du flux (8,13) dans A et etat_ ; sous-alimentations (octet bas) dans manques.
etat:   jsr WaitMessage
        lda #8
        ldx #13                    ; 8,13 : Stream Status
        jsr appel
        lda ControlPort+5          ; P1-P2 : sous-alimentations
        sta manques
        lda ControlPort+4          ; P0 : bit 7 joue, bits 0 et 1 moitiés à remplir
        sta etat_
        rts
etat_:  .byte 0
manques: .byte 0

lance:  .byte 8, 11, 7              ; 8,11 : Stream Start
        .word tampon, 256, 11025   ; adresse, taille d'une moitié, échantillons par seconde
        .byte 100                  ; volume
haut:   .byte 8, 11, 7
        .word $FE80, 256, 11025    ; $FE80 + 512 dépasse $FF00
        .byte 100
arret:  .byte 8, 12, 0             ; 8,12 : Stream Stop
t0:     .text "Lance : ", 0
t1:     .text "Moitie 0 jouee : ", 0
t2:     .text "Tampon trop haut : erreur ", 0
e0:     .byte 0
e:      .byte 0
        .align 256
tampon: .fill 512                  ; deux moitiés de 256 échantillons

; Appel décrit par un bloc dont l'adresse est dans A (poids faible) et X (poids fort) :
; groupe, fonction, nombre de paramètres, puis les paramètres (P0, P1...).
envoi:  sta blk
        stx blk+1
        jsr WaitMessage
        ldy #2
        lda (blk),y                ; nombre de paramètres
        clc
        adc #3
        sta fin
        ldy #3
_copie: cpy fin
        beq _lance
        lda (blk),y
        sta ControlPort+1,y        ; octet 3 du bloc -> P0 ($FF04)...
        iny
        bra _copie
_lance: ldy #1
        lda (blk),y
        tax
        lda (blk)
        jmp appel

; Nombre de notes en file sur le canal A (8,6), rendu dans A.
statut: pha
        jsr WaitMessage
        pla
        sta ControlPort+4
        lda #8
        ldx #6                     ; 8,6 : Sound Status
        jsr appel
        lda ControlPort+4
        rts

; Attend que le canal 0 se taise.
attendre:
        lda #0
        jsr statut
        bne attendre
        rts

; Affiche la chaîne terminée par 0 dont l'adresse est dans A (poids faible) et X (poids fort).
ecrire: sta ptr
        stx ptr+1
        ldy #0
_e:     lda (ptr),y
        beq _f
        jsr WriteCharacter
        iny
        bne _e
_f:     rts

; Affiche A (0-255) en décimal, sans zéros de tête.
decimal:
        phx
        phy
        ldx #0
        ldy #100
        jsr _chiffre
        ldy #10
        jsr _chiffre
        ora #'0'
        jsr WriteCharacter
        ply
        plx
        rts
_chiffre:
        sty diviseur
        ldy #0
_div:   cmp diviseur
        bcc _fini
        sbc diviseur
        iny
        bne _div
_fini:  pha
        tya
        bne _ecrit
        cpx #0
        beq _saute
_ecrit: ora #'0'
        jsr WriteCharacter
        ldx #1
_saute: pla
        rts

; Appel : A = groupe, X = fonction (les paramètres sont déjà en place).
appel:  stx ControlPort+1
        sta ControlPort            ; écrit en dernier : lance l'appel
        jmp WaitMessage

ptr      = $80
blk      = $82
diviseur: .byte 0
fin:     .byte 0
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Lance : 128
Moitie 0 jouee : 129
Tampon trop haut : erreur 1

8,12 Stream Stop Trinity

Stops the sample stream at once; the buffer may be reused.

Inputs: none.

Outputs: none.

The state (8,13) goes from 128 ($80, the stream plays) to 0. 8,1 stops the stream too.

Firmware documentation text

Stops the stream at once (Trinity T-79). The buffer is no longer read and may be reused.

C (llvm-mos)
/* Arrêter un flux d'échantillons (groupe 8, fonction 12). */
#include <stdio.h>
#include <neo6502.h>
#include <neo/api.h>

/* Appel brut de la boîte aux lettres : fonction puis groupe (écrit en dernier, il lance l'appel). */
static void api(uint8_t groupe, uint8_t fonction) {
    ControlPort.function = fonction;
    ControlPort.command = groupe;
    while (ControlPort.command)        /* attendre la fin */
        ;
}
static void libre(void) {              /* attendre que la boîte aux lettres soit libre */
    while (ControlPort.command)
        ;
}

static uint8_t tampon[2 * 256];                 /* deux moitiés de 256 échantillons */
static uint16_t manques;                        /* sous-alimentations, lues par etat() */

static void creneau(uint8_t moitie) {           /* écrit un créneau : 16 échantillons hauts, 16 bas */
    uint8_t *p = tampon + moitie * 256;
    for (uint16_t i = 0; i < 256; i++)
        p[i] = (i & 16) ? 0xC0 : 0x40;          /* $80 = silence */
}

static uint8_t lancer(uint16_t adresse) {       /* 8,11 : 256 octets par moitié, 11025 Hz, volume 100 */
    libre();
    ControlPort.params[0] = adresse & 0xFF;     /* P0-P1 : adresse du tampon */
    ControlPort.params[1] = adresse >> 8;
    ControlPort.params[2] = 0;                  /* P2-P3 : taille d'une moitié */
    ControlPort.params[3] = 1;
    ControlPort.params[4] = 11025 & 0xFF;       /* P4-P5 : échantillons par seconde */
    ControlPort.params[5] = 11025 >> 8;
    ControlPort.params[6] = 100;                /* P6 : volume */
    api(8, 11);                                 /* Stream Start */
    return ControlPort.error;
}

static uint8_t etat(void) {                     /* 8,13 : bit 7 joue, bits 0 et 1 moitiés à remplir */
    libre();
    api(8, 13);
    manques = ControlPort.params[1] | ControlPort.params[2] << 8;
    return ControlPort.params[0];
}

static void arreter(void) {                     /* 8,12 */
    libre();
    api(8, 12);
}

int main(void) {
    creneau(0);
    creneau(1);
    lancer((uint16_t)tampon);                   /* 8,11 */
    uint8_t avant = etat();
    arreter();                                  /* Stream Stop : le tampon est libre */
    printf("Avant : %u, apres : %u\n", avant, etat());
    return 0;
}
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Avant : 128, apres : 0
Mad Pascal
// Arrêter un flux d'échantillons (groupe 8, fonction 12).
program streamstop;
uses crt, neo6502;
var tampon: array[0..511] of byte;              // deux moitiés de 256 échantillons
    manques: word;                              // sous-alimentations, lues par etat

procedure creneau(moitie: byte);                // écrit un créneau : 16 échantillons hauts, 16 bas
var i: word;
begin
  for i := 0 to 255 do
    if i and 16 <> 0 then tampon[moitie * 256 + i] := $C0
    else tampon[moitie * 256 + i] := $40;       // $80 = silence
end;

function lancer(adresse: word): byte;           // 8,11 : 256 octets par moitié, 11025 Hz, volume 100
begin
  NeoMessage.params[0] := lo(adresse);          // P0-P1 : adresse du tampon
  NeoMessage.params[1] := hi(adresse);
  NeoMessage.params[2] := 0;                    // P2-P3 : taille d'une moitié
  NeoMessage.params[3] := 1;
  NeoMessage.params[4] := lo(11025);            // P4-P5 : échantillons par seconde
  NeoMessage.params[5] := hi(11025);
  NeoMessage.params[6] := 100;                  // P6 : volume
  NeoSendMessage(8, 11);                        // Stream Start
  NeoWaitMessage;
  result := NeoMessage.error;
end;

function etat: byte;                            // 8,13 : bit 7 joue, bits 0 et 1 moitiés à remplir
begin
  NeoSendMessage(8, 13);
  NeoWaitMessage;
  result := NeoMessage.params[0];
  manques := NeoMessage.params[1] + NeoMessage.params[2] shl 8;
end;

procedure arreter;                              // 8,12
begin
  NeoSendMessage(8, 12);
  NeoWaitMessage;
end;
var avant: byte;

begin
  creneau(0);
  creneau(1);
  lancer(word(@tampon));                        // 8,11
  avant := etat;
  arreter;                                      // Stream Stop : le tampon est libre
  writeln('Avant : ', avant, ', apres : ', etat);
end.
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Avant : 128, apres : 0
NeoBASIC
10 ' Arreter un flux d'echantillons (groupe 8, fonction 12)
20 a = alloc(512) : b = a : gosub 1000 : b = a + 256 : gosub 1000
30 gosub 1100 : ' 8,11
40 gosub 1200 : avant = s
50 gosub 1300 : ' Stream Stop : le tampon est libre
60 gosub 1200 : print "Avant : ";avant;", apres : ";s
70 end
1000 ' creneau dans la moitie qui commence en b : 16 echantillons hauts, 16 bas ($80 = silence)
1010 for i = 0 to 255 : x = $40 : if i & 16 then x = $c0
1020 poke b + i, x : next i
1030 return
1100 ' 8,11 : tampon en a, 256 octets par moitie, 11025 Hz, volume 100 ; erreur dans e
1110 while peek($ff00) : wend
1120 doke $ff04, a : doke $ff06, 256 : doke $ff08, 11025 : poke $ff0a, 100
1130 poke $ff01, 11 : poke $ff00, 8
1140 while peek($ff00) : wend
1150 e = peek($ff02)
1160 return
1200 ' 8,13 : etat dans s (bit 7 joue, bits 0 et 1 moities a remplir), sous-alimentations dans u
1210 while peek($ff00) : wend
1220 poke $ff01, 13 : poke $ff00, 8
1230 while peek($ff00) : wend
1240 s = peek($ff04) : u = deek($ff05)
1250 return
1300 ' 8,12 : arret du flux
1310 while peek($ff00) : wend
1320 poke $ff01, 12 : poke $ff00, 8
1330 while peek($ff00) : wend
1340 return
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Avant : 128, apres : 0
65C02 assembly (64tass)
; Arrêter un flux d'échantillons (groupe 8, fonction 12).
        .cpu "65c02"
        * = $0800
ControlPort    = $FF00             ; +0 groupe, +1 fonction, +2 erreur, +4.. paramètres
WaitMessage    = $FFF4             ; attend que la boîte aux lettres soit libre
WriteCharacter = $FFF1             ; affiche le caractère de A

start:   lda #0                     ; remplir les deux moitiés avant de lancer
        jsr creneau
        lda #1
        jsr creneau
        lda #<lance
        ldx #>lance
        jsr envoi
        jsr etat
        sta avant
        lda #<arret
        ldx #>arret
        jsr envoi
                                   ; Stream Stop : le tampon est libre
        lda #<t0
        ldx #>t0
        jsr ecrire
        lda avant
        jsr decimal
        lda #<t1
        ldx #>t1
        jsr ecrire
        jsr etat
        jsr decimal
        lda #13
        jsr WriteCharacter
        rts

; Écrit un créneau dans la moitié A (0 ou 1) : 16 échantillons hauts, 16 bas ($80 = silence).
creneau:
        clc
        adc #>tampon
        sta ptr+1
        lda #<tampon
        sta ptr
        ldy #0
_r:     tya
        and #16
        beq _bas
        lda #$C0
        bra _ecr
_bas:   lda #$40
_ecr:   sta (ptr),y
        iny
        bne _r
        rts

; État du flux (8,13) dans A et etat_ ; sous-alimentations (octet bas) dans manques.
etat:   jsr WaitMessage
        lda #8
        ldx #13                    ; 8,13 : Stream Status
        jsr appel
        lda ControlPort+5          ; P1-P2 : sous-alimentations
        sta manques
        lda ControlPort+4          ; P0 : bit 7 joue, bits 0 et 1 moitiés à remplir
        sta etat_
        rts
etat_:  .byte 0
manques: .byte 0

lance:  .byte 8, 11, 7              ; 8,11 : Stream Start
        .word tampon, 256, 11025   ; adresse, taille d'une moitié, échantillons par seconde
        .byte 100                  ; volume
arret:  .byte 8, 12, 0             ; 8,12 : Stream Stop
t0:     .text "Avant : ", 0
t1:     .text ", apres : ", 0
avant:  .byte 0
        .align 256
tampon: .fill 512                  ; deux moitiés de 256 échantillons

; Appel décrit par un bloc dont l'adresse est dans A (poids faible) et X (poids fort) :
; groupe, fonction, nombre de paramètres, puis les paramètres (P0, P1...).
envoi:  sta blk
        stx blk+1
        jsr WaitMessage
        ldy #2
        lda (blk),y                ; nombre de paramètres
        clc
        adc #3
        sta fin
        ldy #3
_copie: cpy fin
        beq _lance
        lda (blk),y
        sta ControlPort+1,y        ; octet 3 du bloc -> P0 ($FF04)...
        iny
        bra _copie
_lance: ldy #1
        lda (blk),y
        tax
        lda (blk)
        jmp appel

; Nombre de notes en file sur le canal A (8,6), rendu dans A.
statut: pha
        jsr WaitMessage
        pla
        sta ControlPort+4
        lda #8
        ldx #6                     ; 8,6 : Sound Status
        jsr appel
        lda ControlPort+4
        rts

; Attend que le canal 0 se taise.
attendre:
        lda #0
        jsr statut
        bne attendre
        rts

; Affiche la chaîne terminée par 0 dont l'adresse est dans A (poids faible) et X (poids fort).
ecrire: sta ptr
        stx ptr+1
        ldy #0
_e:     lda (ptr),y
        beq _f
        jsr WriteCharacter
        iny
        bne _e
_f:     rts

; Affiche A (0-255) en décimal, sans zéros de tête.
decimal:
        phx
        phy
        ldx #0
        ldy #100
        jsr _chiffre
        ldy #10
        jsr _chiffre
        ora #'0'
        jsr WriteCharacter
        ply
        plx
        rts
_chiffre:
        sty diviseur
        ldy #0
_div:   cmp diviseur
        bcc _fini
        sbc diviseur
        iny
        bne _div
_fini:  pha
        tya
        bne _ecrit
        cpx #0
        beq _saute
_ecrit: ora #'0'
        jsr WriteCharacter
        ldx #1
_saute: pla
        rts

; Appel : A = groupe, X = fonction (les paramètres sont déjà en place).
appel:  stx ControlPort+1
        sta ControlPort            ; écrit en dernier : lance l'appel
        jmp WaitMessage

ptr      = $80
blk      = $82
diviseur: .byte 0
fin:     .byte 0
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Avant : 128, apres : 0

8,13 Stream Status Trinity

Gives the state of the stream: whether it plays, which halves wait to be refilled, and how many times it ran short of samples.

Inputs: none.

Outputs
BytesTypeMeaning
P0uint8bit 7: the stream plays; bit 0: half 0 to refill; bit 1: half 1 to refill; 0: stopped
P1-P2uint16underruns since the start (65535 at most)

An underrun is a half reached before it was marked full: the stream then goes silent until it is, without replaying it. The example refills nothing: after both halves, the state is 131 ($83) and the counter 1, then it stays there.

Firmware documentation text

Returns in Parameter:0 the state of the stream (Trinity T-79) : bit 7 set while it plays, bit 0 set when half 0 has been played and waits to be refilled, bit 1 the same for half 1 ; 0 when stopped. Parameters:1,2 count the underruns since the start (a half reached before it was marked full, 65535 at most).

C (llvm-mos)
/* Laisser un flux se tarir et lire son état (groupe 8, fonction 13). */
#include <stdio.h>
#include <neo6502.h>
#include <neo/api.h>

/* Appel brut de la boîte aux lettres : fonction puis groupe (écrit en dernier, il lance l'appel). */
static void api(uint8_t groupe, uint8_t fonction) {
    ControlPort.function = fonction;
    ControlPort.command = groupe;
    while (ControlPort.command)        /* attendre la fin */
        ;
}
static void libre(void) {              /* attendre que la boîte aux lettres soit libre */
    while (ControlPort.command)
        ;
}

static uint8_t tampon[2 * 256];                 /* deux moitiés de 256 échantillons */
static uint16_t manques;                        /* sous-alimentations, lues par etat() */

static void creneau(uint8_t moitie) {           /* écrit un créneau : 16 échantillons hauts, 16 bas */
    uint8_t *p = tampon + moitie * 256;
    for (uint16_t i = 0; i < 256; i++)
        p[i] = (i & 16) ? 0xC0 : 0x40;          /* $80 = silence */
}

static uint8_t lancer(uint16_t adresse) {       /* 8,11 : 256 octets par moitié, 11025 Hz, volume 100 */
    libre();
    ControlPort.params[0] = adresse & 0xFF;     /* P0-P1 : adresse du tampon */
    ControlPort.params[1] = adresse >> 8;
    ControlPort.params[2] = 0;                  /* P2-P3 : taille d'une moitié */
    ControlPort.params[3] = 1;
    ControlPort.params[4] = 11025 & 0xFF;       /* P4-P5 : échantillons par seconde */
    ControlPort.params[5] = 11025 >> 8;
    ControlPort.params[6] = 100;                /* P6 : volume */
    api(8, 11);                                 /* Stream Start */
    return ControlPort.error;
}

static uint8_t etat(void) {                     /* 8,13 : bit 7 joue, bits 0 et 1 moitiés à remplir */
    libre();
    api(8, 13);
    manques = ControlPort.params[1] | ControlPort.params[2] << 8;
    return ControlPort.params[0];
}

static void arreter(void) {                     /* 8,12 */
    libre();
    api(8, 12);
}

int main(void) {
    creneau(0);
    creneau(1);
    lancer((uint16_t)tampon);                   /* 8,11 : les deux moitiés comptent comme pleines */
    uint8_t e0 = etat();                        /* Stream Status */
    while (etat() != 131)                       /* rien n'est rempli à nouveau : 128 + 1 + 2 */
        ;
    printf("Au depart : %u, tari : %u, sous-alimentations : %u\n", e0, etat(), manques);
    arreter();
    printf("Arrete : %u\n", etat());
    return 0;
}
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Au depart : 128, tari : 131, sous-alimentations : 1
Arrete : 0
Mad Pascal
// Laisser un flux se tarir et lire son état (groupe 8, fonction 13).
program streamstatus;
uses crt, neo6502;
var tampon: array[0..511] of byte;              // deux moitiés de 256 échantillons
    manques: word;                              // sous-alimentations, lues par etat

procedure creneau(moitie: byte);                // écrit un créneau : 16 échantillons hauts, 16 bas
var i: word;
begin
  for i := 0 to 255 do
    if i and 16 <> 0 then tampon[moitie * 256 + i] := $C0
    else tampon[moitie * 256 + i] := $40;       // $80 = silence
end;

function lancer(adresse: word): byte;           // 8,11 : 256 octets par moitié, 11025 Hz, volume 100
begin
  NeoMessage.params[0] := lo(adresse);          // P0-P1 : adresse du tampon
  NeoMessage.params[1] := hi(adresse);
  NeoMessage.params[2] := 0;                    // P2-P3 : taille d'une moitié
  NeoMessage.params[3] := 1;
  NeoMessage.params[4] := lo(11025);            // P4-P5 : échantillons par seconde
  NeoMessage.params[5] := hi(11025);
  NeoMessage.params[6] := 100;                  // P6 : volume
  NeoSendMessage(8, 11);                        // Stream Start
  NeoWaitMessage;
  result := NeoMessage.error;
end;

function etat: byte;                            // 8,13 : bit 7 joue, bits 0 et 1 moitiés à remplir
begin
  NeoSendMessage(8, 13);
  NeoWaitMessage;
  result := NeoMessage.params[0];
  manques := NeoMessage.params[1] + NeoMessage.params[2] shl 8;
end;

procedure arreter;                              // 8,12
begin
  NeoSendMessage(8, 12);
  NeoWaitMessage;
end;
var e0, e: byte;

begin
  creneau(0);
  creneau(1);
  lancer(word(@tampon));                        // 8,11 : les deux moitiés comptent comme pleines
  e0 := etat;                                   // Stream Status
  repeat until etat = 131;                      // rien n'est rempli à nouveau : 128 + 1 + 2
  e := etat;
  writeln('Au depart : ', e0, ', tari : ', e, ', sous-alimentations : ', manques);
  arreter;
  writeln('Arrete : ', etat);
end.
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Au depart : 128, tari : 131, sous-alimentations : 1
Arrete : 0
NeoBASIC
10 ' Laisser un flux se tarir et lire son etat (groupe 8, fonction 13)
20 a = alloc(512) : b = a : gosub 1000 : b = a + 256 : gosub 1000
30 gosub 1100 : ' 8,11 : les deux moities comptent comme pleines
40 gosub 1200 : e0 = s : ' Stream Status
50 repeat : gosub 1200 : until s = 131 : ' rien n'est rempli a nouveau : 128 + 1 + 2
60 print "Au depart : ";e0;", tari : ";s;", sous-alimentations : ";u
70 gosub 1300 : gosub 1200 : print "Arrete : ";s
80 end
1000 ' creneau dans la moitie qui commence en b : 16 echantillons hauts, 16 bas ($80 = silence)
1010 for i = 0 to 255 : x = $40 : if i & 16 then x = $c0
1020 poke b + i, x : next i
1030 return
1100 ' 8,11 : tampon en a, 256 octets par moitie, 11025 Hz, volume 100 ; erreur dans e
1110 while peek($ff00) : wend
1120 doke $ff04, a : doke $ff06, 256 : doke $ff08, 11025 : poke $ff0a, 100
1130 poke $ff01, 11 : poke $ff00, 8
1140 while peek($ff00) : wend
1150 e = peek($ff02)
1160 return
1200 ' 8,13 : etat dans s (bit 7 joue, bits 0 et 1 moities a remplir), sous-alimentations dans u
1210 while peek($ff00) : wend
1220 poke $ff01, 13 : poke $ff00, 8
1230 while peek($ff00) : wend
1240 s = peek($ff04) : u = deek($ff05)
1250 return
1300 ' 8,12 : arret du flux
1310 while peek($ff00) : wend
1320 poke $ff01, 12 : poke $ff00, 8
1330 while peek($ff00) : wend
1340 return
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Au depart : 128, tari : 131, sous-alimentations : 1
Arrete : 0
65C02 assembly (64tass)
; Laisser un flux se tarir et lire son état (groupe 8, fonction 13).
        .cpu "65c02"
        * = $0800
ControlPort    = $FF00             ; +0 groupe, +1 fonction, +2 erreur, +4.. paramètres
WaitMessage    = $FFF4             ; attend que la boîte aux lettres soit libre
WriteCharacter = $FFF1             ; affiche le caractère de A

start:   lda #0                     ; remplir les deux moitiés avant de lancer
        jsr creneau
        lda #1
        jsr creneau
        lda #<lance
        ldx #>lance
        jsr envoi
        jsr etat                   ; Stream Status
        sta e0
_tari:  jsr etat                   ; rien n'est rempli à nouveau : 128 + 1 + 2
        cmp #131
        bne _tari
        lda #<t0
        ldx #>t0
        jsr ecrire
        lda e0
        jsr decimal
        lda #<t1
        ldx #>t1
        jsr ecrire
        lda etat_
        jsr decimal
        lda #<t2
        ldx #>t2
        jsr ecrire
        lda manques
        jsr decimal
        lda #13
        jsr WriteCharacter
        lda #<arret
        ldx #>arret
        jsr envoi
        lda #<t3
        ldx #>t3
        jsr ecrire
        jsr etat
        jsr decimal
        lda #13
        jsr WriteCharacter
        rts

; Écrit un créneau dans la moitié A (0 ou 1) : 16 échantillons hauts, 16 bas ($80 = silence).
creneau:
        clc
        adc #>tampon
        sta ptr+1
        lda #<tampon
        sta ptr
        ldy #0
_r:     tya
        and #16
        beq _bas
        lda #$C0
        bra _ecr
_bas:   lda #$40
_ecr:   sta (ptr),y
        iny
        bne _r
        rts

; État du flux (8,13) dans A et etat_ ; sous-alimentations (octet bas) dans manques.
etat:   jsr WaitMessage
        lda #8
        ldx #13                    ; 8,13 : Stream Status
        jsr appel
        lda ControlPort+5          ; P1-P2 : sous-alimentations
        sta manques
        lda ControlPort+4          ; P0 : bit 7 joue, bits 0 et 1 moitiés à remplir
        sta etat_
        rts
etat_:  .byte 0
manques: .byte 0

lance:  .byte 8, 11, 7              ; 8,11 : Stream Start
        .word tampon, 256, 11025   ; adresse, taille d'une moitié, échantillons par seconde
        .byte 100                  ; volume
arret:  .byte 8, 12, 0             ; 8,12 : Stream Stop
t0:     .text "Au depart : ", 0
t1:     .text ", tari : ", 0
t2:     .text ", sous-alimentations : ", 0
t3:     .text "Arrete : ", 0
e0:     .byte 0
        .align 256
tampon: .fill 512                  ; deux moitiés de 256 échantillons

; Appel décrit par un bloc dont l'adresse est dans A (poids faible) et X (poids fort) :
; groupe, fonction, nombre de paramètres, puis les paramètres (P0, P1...).
envoi:  sta blk
        stx blk+1
        jsr WaitMessage
        ldy #2
        lda (blk),y                ; nombre de paramètres
        clc
        adc #3
        sta fin
        ldy #3
_copie: cpy fin
        beq _lance
        lda (blk),y
        sta ControlPort+1,y        ; octet 3 du bloc -> P0 ($FF04)...
        iny
        bra _copie
_lance: ldy #1
        lda (blk),y
        tax
        lda (blk)
        jmp appel

; Nombre de notes en file sur le canal A (8,6), rendu dans A.
statut: pha
        jsr WaitMessage
        pla
        sta ControlPort+4
        lda #8
        ldx #6                     ; 8,6 : Sound Status
        jsr appel
        lda ControlPort+4
        rts

; Attend que le canal 0 se taise.
attendre:
        lda #0
        jsr statut
        bne attendre
        rts

; Affiche la chaîne terminée par 0 dont l'adresse est dans A (poids faible) et X (poids fort).
ecrire: sta ptr
        stx ptr+1
        ldy #0
_e:     lda (ptr),y
        beq _f
        jsr WriteCharacter
        iny
        bne _e
_f:     rts

; Affiche A (0-255) en décimal, sans zéros de tête.
decimal:
        phx
        phy
        ldx #0
        ldy #100
        jsr _chiffre
        ldy #10
        jsr _chiffre
        ora #'0'
        jsr WriteCharacter
        ply
        plx
        rts
_chiffre:
        sty diviseur
        ldy #0
_div:   cmp diviseur
        bcc _fini
        sbc diviseur
        iny
        bne _div
_fini:  pha
        tya
        bne _ecrit
        cpx #0
        beq _saute
_ecrit: ora #'0'
        jsr WriteCharacter
        ldx #1
_saute: pla
        rts

; Appel : A = groupe, X = fonction (les paramètres sont déjà en place).
appel:  stx ControlPort+1
        sta ControlPort            ; écrit en dernier : lance l'appel
        jmp WaitMessage

ptr      = $80
blk      = $82
diviseur: .byte 0
fin:     .byte 0
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Au depart : 128, tari : 131, sous-alimentations : 1
Arrete : 0

8,14 Stream Filled Trinity

Hands a half of the buffer back to the stream, once its new samples are written.

Inputs
BytesTypeMeaning
P0uint8half (0 or 1)
Outputs
BytesTypeMeaning
erreuruint81 if the half is neither 0 nor 1, or the stream is stopped

The loop reads the state (8,13), rewrites each played half then hands it back, six times, without an underrun. A half of 256 samples lasts 23 ms at 11025 Hz: NeoBASIC is too slow to rewrite it in time (six underruns on the bench), so the NeoBASIC example hands it back without rewriting it, and the same sound loops.

Firmware documentation text

Marks half Parameter:0 (0 or 1) of the stream as refilled (Trinity T-79), after the program has written its samples. Sets the error flag if the half is not 0 or 1 or the stream is stopped.

C (llvm-mos)
/* Entretenir un flux en remplissant chaque moitié rendue (groupe 8, fonction 14). */
#include <stdio.h>
#include <neo6502.h>
#include <neo/api.h>

/* Appel brut de la boîte aux lettres : fonction puis groupe (écrit en dernier, il lance l'appel). */
static void api(uint8_t groupe, uint8_t fonction) {
    ControlPort.function = fonction;
    ControlPort.command = groupe;
    while (ControlPort.command)        /* attendre la fin */
        ;
}
static void libre(void) {              /* attendre que la boîte aux lettres soit libre */
    while (ControlPort.command)
        ;
}

static uint8_t tampon[2 * 256];                 /* deux moitiés de 256 échantillons */
static uint16_t manques;                        /* sous-alimentations, lues par etat() */

static void creneau(uint8_t moitie) {           /* écrit un créneau : 16 échantillons hauts, 16 bas */
    uint8_t *p = tampon + moitie * 256;
    for (uint16_t i = 0; i < 256; i++)
        p[i] = (i & 16) ? 0xC0 : 0x40;          /* $80 = silence */
}

static uint8_t lancer(uint16_t adresse) {       /* 8,11 : 256 octets par moitié, 11025 Hz, volume 100 */
    libre();
    ControlPort.params[0] = adresse & 0xFF;     /* P0-P1 : adresse du tampon */
    ControlPort.params[1] = adresse >> 8;
    ControlPort.params[2] = 0;                  /* P2-P3 : taille d'une moitié */
    ControlPort.params[3] = 1;
    ControlPort.params[4] = 11025 & 0xFF;       /* P4-P5 : échantillons par seconde */
    ControlPort.params[5] = 11025 >> 8;
    ControlPort.params[6] = 100;                /* P6 : volume */
    api(8, 11);                                 /* Stream Start */
    return ControlPort.error;
}

static uint8_t etat(void) {                     /* 8,13 : bit 7 joue, bits 0 et 1 moitiés à remplir */
    libre();
    api(8, 13);
    manques = ControlPort.params[1] | ControlPort.params[2] << 8;
    return ControlPort.params[0];
}

static void arreter(void) {                     /* 8,12 */
    libre();
    api(8, 12);
}

int main(void) {
    creneau(0);
    creneau(1);
    lancer((uint16_t)tampon);                   /* 8,11 */
    uint8_t remplies = 0;
    while (remplies < 6) {
        uint8_t e = etat();
        for (uint8_t m = 0; m < 2; m++)
            if (e & (1 << m)) {                 /* moitié m jouée : la remplir, la rendre */
                creneau(m);
                libre();
                ControlPort.params[0] = m;
                api(8, 14);                     /* Stream Filled */
                remplies++;
            }
    }
    etat();
    arreter();
    printf("Moities rendues : %u, sous-alimentations : %u\n", remplies, manques);
    return 0;
}
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Moities rendues : 6, sous-alimentations : 0
Mad Pascal
// Entretenir un flux en remplissant chaque moitié rendue (groupe 8, fonction 14).
program streamfilled;
uses crt, neo6502;
var tampon: array[0..511] of byte;              // deux moitiés de 256 échantillons
    manques: word;                              // sous-alimentations, lues par etat

procedure creneau(moitie: byte);                // écrit un créneau : 16 échantillons hauts, 16 bas
var i: word;
begin
  for i := 0 to 255 do
    if i and 16 <> 0 then tampon[moitie * 256 + i] := $C0
    else tampon[moitie * 256 + i] := $40;       // $80 = silence
end;

function lancer(adresse: word): byte;           // 8,11 : 256 octets par moitié, 11025 Hz, volume 100
begin
  NeoMessage.params[0] := lo(adresse);          // P0-P1 : adresse du tampon
  NeoMessage.params[1] := hi(adresse);
  NeoMessage.params[2] := 0;                    // P2-P3 : taille d'une moitié
  NeoMessage.params[3] := 1;
  NeoMessage.params[4] := lo(11025);            // P4-P5 : échantillons par seconde
  NeoMessage.params[5] := hi(11025);
  NeoMessage.params[6] := 100;                  // P6 : volume
  NeoSendMessage(8, 11);                        // Stream Start
  NeoWaitMessage;
  result := NeoMessage.error;
end;

function etat: byte;                            // 8,13 : bit 7 joue, bits 0 et 1 moitiés à remplir
begin
  NeoSendMessage(8, 13);
  NeoWaitMessage;
  result := NeoMessage.params[0];
  manques := NeoMessage.params[1] + NeoMessage.params[2] shl 8;
end;

procedure arreter;                              // 8,12
begin
  NeoSendMessage(8, 12);
  NeoWaitMessage;
end;
var remplies, e, m: byte;

begin
  creneau(0);
  creneau(1);
  lancer(word(@tampon));                        // 8,11
  remplies := 0;
  while remplies < 6 do begin
    e := etat;
    for m := 0 to 1 do
      if e and (1 shl m) <> 0 then begin        // moitié m jouée : la remplir, la rendre
        creneau(m);
        NeoMessage.params[0] := m;
        NeoSendMessage(8, 14);                  // Stream Filled
        NeoWaitMessage;
        inc(remplies);
      end;
  end;
  etat;
  arreter;
  writeln('Moities rendues : ', remplies, ', sous-alimentations : ', manques);
end.
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Moities rendues : 6, sous-alimentations : 0
NeoBASIC
10 ' Entretenir un flux en rendant chaque moitie jouee (groupe 8, fonction 14)
20 a = alloc(512) : b = a : gosub 1000 : b = a + 256 : gosub 1000
30 gosub 1100 : ' 8,11
40 r = 0
50 repeat
60 gosub 1200
70 if s & 1 then m = 0 : gosub 400 : ' moitie 0 jouee : la rendre
80 if s & 2 then m = 1 : gosub 400
90 until r >= 6
100 gosub 1200 : gosub 1300
110 print "Moities rendues : ";r;", sous-alimentations : ";u
120 end
400 ' meme son en boucle : trop lent en BASIC pour reecrire 256 echantillons a temps
410 while peek($ff00) : wend
420 poke $ff04, m : poke $ff01, 14 : poke $ff00, 8 : ' Stream Filled
430 while peek($ff00) : wend
440 r = r + 1
450 return
1000 ' creneau dans la moitie qui commence en b : 16 echantillons hauts, 16 bas ($80 = silence)
1010 for i = 0 to 255 : x = $40 : if i & 16 then x = $c0
1020 poke b + i, x : next i
1030 return
1100 ' 8,11 : tampon en a, 256 octets par moitie, 11025 Hz, volume 100 ; erreur dans e
1110 while peek($ff00) : wend
1120 doke $ff04, a : doke $ff06, 256 : doke $ff08, 11025 : poke $ff0a, 100
1130 poke $ff01, 11 : poke $ff00, 8
1140 while peek($ff00) : wend
1150 e = peek($ff02)
1160 return
1200 ' 8,13 : etat dans s (bit 7 joue, bits 0 et 1 moities a remplir), sous-alimentations dans u
1210 while peek($ff00) : wend
1220 poke $ff01, 13 : poke $ff00, 8
1230 while peek($ff00) : wend
1240 s = peek($ff04) : u = deek($ff05)
1250 return
1300 ' 8,12 : arret du flux
1310 while peek($ff00) : wend
1320 poke $ff01, 12 : poke $ff00, 8
1330 while peek($ff00) : wend
1340 return
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Moities rendues : 6, sous-alimentations : 0
65C02 assembly (64tass)
; Entretenir un flux en remplissant chaque moitié rendue (groupe 8, fonction 14).
        .cpu "65c02"
        * = $0800
ControlPort    = $FF00             ; +0 groupe, +1 fonction, +2 erreur, +4.. paramètres
WaitMessage    = $FFF4             ; attend que la boîte aux lettres soit libre
WriteCharacter = $FFF1             ; affiche le caractère de A

start:   lda #0                     ; remplir les deux moitiés avant de lancer
        jsr creneau
        lda #1
        jsr creneau
        lda #<lance
        ldx #>lance
        jsr envoi
        stz rendues
_boucle:
        jsr etat
        lsr a                      ; bit 0 : moitié 0 jouée
        bcc _m1
        lda #0
        jsr rendre
_m1:    lda etat_
        and #2                     ; bit 1 : moitié 1 jouée
        beq _suite
        lda #1
        jsr rendre
_suite: lda rendues
        cmp #6
        bcc _boucle
        jsr etat
        lda #<arret
        ldx #>arret
        jsr envoi
        lda #<t0
        ldx #>t0
        jsr ecrire
        lda rendues
        jsr decimal
        lda #<t1
        ldx #>t1
        jsr ecrire
        lda manques
        jsr decimal
        lda #13
        jsr WriteCharacter
        rts

; Remplit la moitié A et la rend au firmware (8,14).
rendre: pha
        jsr creneau
        jsr WaitMessage
        pla
        sta ControlPort+4          ; P0 : moitié
        lda #8
        ldx #14                    ; 8,14 : Stream Filled
        jsr appel
        inc rendues
        rts

; Écrit un créneau dans la moitié A (0 ou 1) : 16 échantillons hauts, 16 bas ($80 = silence).
creneau:
        clc
        adc #>tampon
        sta ptr+1
        lda #<tampon
        sta ptr
        ldy #0
_r:     tya
        and #16
        beq _bas
        lda #$C0
        bra _ecr
_bas:   lda #$40
_ecr:   sta (ptr),y
        iny
        bne _r
        rts

; État du flux (8,13) dans A et etat_ ; sous-alimentations (octet bas) dans manques.
etat:   jsr WaitMessage
        lda #8
        ldx #13                    ; 8,13 : Stream Status
        jsr appel
        lda ControlPort+5          ; P1-P2 : sous-alimentations
        sta manques
        lda ControlPort+4          ; P0 : bit 7 joue, bits 0 et 1 moitiés à remplir
        sta etat_
        rts
etat_:  .byte 0
manques: .byte 0

lance:  .byte 8, 11, 7              ; 8,11 : Stream Start
        .word tampon, 256, 11025   ; adresse, taille d'une moitié, échantillons par seconde
        .byte 100                  ; volume
arret:  .byte 8, 12, 0             ; 8,12 : Stream Stop
t0:     .text "Moities rendues : ", 0
t1:     .text ", sous-alimentations : ", 0
rendues: .byte 0
        .align 256
tampon: .fill 512                  ; deux moitiés de 256 échantillons

; Appel décrit par un bloc dont l'adresse est dans A (poids faible) et X (poids fort) :
; groupe, fonction, nombre de paramètres, puis les paramètres (P0, P1...).
envoi:  sta blk
        stx blk+1
        jsr WaitMessage
        ldy #2
        lda (blk),y                ; nombre de paramètres
        clc
        adc #3
        sta fin
        ldy #3
_copie: cpy fin
        beq _lance
        lda (blk),y
        sta ControlPort+1,y        ; octet 3 du bloc -> P0 ($FF04)...
        iny
        bra _copie
_lance: ldy #1
        lda (blk),y
        tax
        lda (blk)
        jmp appel

; Nombre de notes en file sur le canal A (8,6), rendu dans A.
statut: pha
        jsr WaitMessage
        pla
        sta ControlPort+4
        lda #8
        ldx #6                     ; 8,6 : Sound Status
        jsr appel
        lda ControlPort+4
        rts

; Attend que le canal 0 se taise.
attendre:
        lda #0
        jsr statut
        bne attendre
        rts

; Affiche la chaîne terminée par 0 dont l'adresse est dans A (poids faible) et X (poids fort).
ecrire: sta ptr
        stx ptr+1
        ldy #0
_e:     lda (ptr),y
        beq _f
        jsr WriteCharacter
        iny
        bne _e
_f:     rts

; Affiche A (0-255) en décimal, sans zéros de tête.
decimal:
        phx
        phy
        ldx #0
        ldy #100
        jsr _chiffre
        ldy #10
        jsr _chiffre
        ora #'0'
        jsr WriteCharacter
        ply
        plx
        rts
_chiffre:
        sty diviseur
        ldy #0
_div:   cmp diviseur
        bcc _fini
        sbc diviseur
        iny
        bne _div
_fini:  pha
        tya
        bne _ecrit
        cpx #0
        beq _saute
_ecrit: ora #'0'
        jsr WriteCharacter
        ldx #1
_saute: pla
        rts

; Appel : A = groupe, X = fonction (les paramètres sont déjà en place).
appel:  stx ControlPort+1
        sta ControlPort            ; écrit en dernier : lance l'appel
        jmp WaitMessage

ptr      = $80
blk      = $82
diviseur: .byte 0
fin:     .byte 0
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Moities rendues : 6, sous-alimentations : 0

8,15 Get Master Volume Trinity

Gives the master volume (0 to 16) and tells whether the sound is muted.

Inputs: none.

Outputs
BytesTypeMeaning
P0uint8master volume, 0 to 16 (16 at start-up)
P1uint81 if the sound is muted

The master volume applies to everything that goes out: the 4 channels and the stream. The media keys of a USB keyboard change it: Mute toggles muting, Volume - and Volume + move it by 2 and unmute. These keys can still be read with 2,22.

Firmware documentation text

Returns the master volume (0-16, 16 at reset) in Parameter:0 and 1 in Parameter:1 if the sound is muted (Trinity T-86). The master volume scales everything the sound system outputs, the four channels and the stream. The Mute, Volume - and Volume + media keys of a USB keyboard change it : Mute toggles, each Volume key moves the volume by 2 and unmutes. The keys are still returned by Function 2,22.

C (llvm-mos)
/* Lire le volume général (groupe 8, fonction 15). */
#include <stdio.h>
#include <neo6502.h>
#include <neo/api.h>

/* Appel brut de la boîte aux lettres : fonction puis groupe (écrit en dernier, il lance l'appel). */
static void api(uint8_t groupe, uint8_t fonction) {
    ControlPort.function = fonction;
    ControlPort.command = groupe;
    while (ControlPort.command)        /* attendre la fin */
        ;
}
static void libre(void) {              /* attendre que la boîte aux lettres soit libre */
    while (ControlPort.command)
        ;
}

static uint8_t maitre, coupe;                   /* 8,15 : volume général (0-16) et coupure */

static void lire_maitre(void) {
    libre();
    api(8, 15);                                 /* Get Master Volume */
    maitre = ControlPort.params[0];
    coupe = ControlPort.params[1];
}

int main(void) {
    lire_maitre();
    printf("Volume general : %u, coupe : %u\n", maitre, coupe);
    return 0;
}
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Volume general : 16, coupe : 0
Mad Pascal
// Lire le volume général (groupe 8, fonction 15).
program getmaster;
uses crt, neo6502;
var maitre, coupe: byte;                        // 8,15 : volume général (0-16) et coupure

procedure lire_maitre;
begin
  NeoSendMessage(8, 15);                        // Get Master Volume
  NeoWaitMessage;
  maitre := NeoMessage.params[0];
  coupe := NeoMessage.params[1];
end;

begin
  lire_maitre;
  writeln('Volume general : ', maitre, ', coupe : ', coupe);
end.
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Volume general : 16, coupe : 0
NeoBASIC
10 ' Lire le volume general (groupe 8, fonction 15)
20 gosub 1000 : ' Get Master Volume
30 print "Volume general : ";g;", coupe : ";k
40 end
1000 ' 8,15 : volume general (0-16) dans g, coupure dans k
1010 while peek($ff00) : wend
1020 poke $ff01, 15 : poke $ff00, 8
1030 while peek($ff00) : wend
1040 g = peek($ff04) : k = peek($ff05)
1050 return
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Volume general : 16, coupe : 0
65C02 assembly (64tass)
; Lire le volume général (groupe 8, fonction 15).
        .cpu "65c02"
        * = $0800
ControlPort    = $FF00             ; +0 groupe, +1 fonction, +2 erreur, +4.. paramètres
WaitMessage    = $FFF4             ; attend que la boîte aux lettres soit libre
WriteCharacter = $FFF1             ; affiche le caractère de A

start:  jsr lire
        lda #<t0
        ldx #>t0
        jsr ecrire
        jmp montrer

; Volume général (8,15) dans maitre, coupure dans coupe.
lire:   jsr WaitMessage
        lda #8
        ldx #15                    ; 8,15 : Get Master Volume
        jsr appel
        lda ControlPort+4
        sta maitre
        lda ControlPort+5
        sta coupe
        rts
maitre: .byte 0
coupe:  .byte 0

; Affiche « maitre, coupe : coupe » et un retour à la ligne.
montrer:
        lda maitre
        jsr decimal
        lda #<tc
        ldx #>tc
        jsr ecrire
        lda coupe
        jsr decimal
        lda #13
        jmp WriteCharacter
tc:     .text ", coupe : ", 0

t0:     .text "Volume general : ", 0

; Appel décrit par un bloc dont l'adresse est dans A (poids faible) et X (poids fort) :
; groupe, fonction, nombre de paramètres, puis les paramètres (P0, P1...).
envoi:  sta blk
        stx blk+1
        jsr WaitMessage
        ldy #2
        lda (blk),y                ; nombre de paramètres
        clc
        adc #3
        sta fin
        ldy #3
_copie: cpy fin
        beq _lance
        lda (blk),y
        sta ControlPort+1,y        ; octet 3 du bloc -> P0 ($FF04)...
        iny
        bra _copie
_lance: ldy #1
        lda (blk),y
        tax
        lda (blk)
        jmp appel

; Nombre de notes en file sur le canal A (8,6), rendu dans A.
statut: pha
        jsr WaitMessage
        pla
        sta ControlPort+4
        lda #8
        ldx #6                     ; 8,6 : Sound Status
        jsr appel
        lda ControlPort+4
        rts

; Attend que le canal 0 se taise.
attendre:
        lda #0
        jsr statut
        bne attendre
        rts

; Affiche la chaîne terminée par 0 dont l'adresse est dans A (poids faible) et X (poids fort).
ecrire: sta ptr
        stx ptr+1
        ldy #0
_e:     lda (ptr),y
        beq _f
        jsr WriteCharacter
        iny
        bne _e
_f:     rts

; Affiche A (0-255) en décimal, sans zéros de tête.
decimal:
        phx
        phy
        ldx #0
        ldy #100
        jsr _chiffre
        ldy #10
        jsr _chiffre
        ora #'0'
        jsr WriteCharacter
        ply
        plx
        rts
_chiffre:
        sty diviseur
        ldy #0
_div:   cmp diviseur
        bcc _fini
        sbc diviseur
        iny
        bne _div
_fini:  pha
        tya
        bne _ecrit
        cpx #0
        beq _saute
_ecrit: ora #'0'
        jsr WriteCharacter
        ldx #1
_saute: pla
        rts

; Appel : A = groupe, X = fonction (les paramètres sont déjà en place).
appel:  stx ControlPort+1
        sta ControlPort            ; écrit en dernier : lance l'appel
        jmp WaitMessage

ptr      = $80
blk      = $82
diviseur: .byte 0
fin:     .byte 0
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Volume general : 16, coupe : 0

8,16 Set Master Volume Trinity

Sets the master volume (0 to 16) and mutes or unmutes the sound.

Inputs
BytesTypeMeaning
P0uint8master volume, 0 to 16
P1uint8not zero: sound muted
Outputs
BytesTypeMeaning
erreuruint81 if the volume is above 16 (nothing changes)

The setting survives 8,1 and the end of the program: the example restores 16 and unmutes before returning. While muted, notes play (the queue empties) but nothing comes out: on the bench, the recorded sound stays silent. The example causes an error by asking for 17.

Firmware documentation text

Sets the master volume to Parameter:0 (0-16) and mutes the sound if Parameter:1 is not zero (Trinity T-86). Sets the error flag if the volume is above 16.

C (llvm-mos)
/* Couper le son, jouer une note, puis rétablir (groupe 8, fonction 16). */
#include <stdio.h>
#include <neo6502.h>
#include <neo/api.h>

/* Appel brut de la boîte aux lettres : fonction puis groupe (écrit en dernier, il lance l'appel). */
static void api(uint8_t groupe, uint8_t fonction) {
    ControlPort.function = fonction;
    ControlPort.command = groupe;
    while (ControlPort.command)        /* attendre la fin */
        ;
}
static void libre(void) {              /* attendre que la boîte aux lettres soit libre */
    while (ControlPort.command)
        ;
}

static uint8_t maitre, coupe;                   /* 8,15 : volume général (0-16) et coupure */

static void lire_maitre(void) {
    libre();
    api(8, 15);                                 /* Get Master Volume */
    maitre = ControlPort.params[0];
    coupe = ControlPort.params[1];
}

static uint8_t regler(uint8_t vol, uint8_t muet) {   /* 8,16 : rend l'indicateur d'erreur */
    libre();
    ControlPort.params[0] = vol;                /* P0 : 0-16 */
    ControlPort.params[1] = muet;               /* P1 : 1 coupe le son */
    api(8, 16);                                 /* Set Master Volume */
    return ControlPort.error;
}

int main(void) {
    regler(8, 1);                               /* volume 8, coupé */
    lire_maitre();
    printf("Regle : %u, coupe : %u\n", maitre, coupe);
    neo_sound_queue(0, 440, 20, 0, 0);          /* 8,4 : cette note ne s'entend pas */
    while (neo_sound_status(0) != 0)
        ;
    printf("17 : erreur %u\n", regler(17, 0));  /* au-delà de 16 : refusé */
    regler(16, 0);                              /* rétablir : volume 16, son ouvert */
    lire_maitre();
    printf("Retabli : %u, coupe : %u\n", maitre, coupe);
    return 0;
}
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Regle : 8, coupe : 1
17 : erreur 1
Retabli : 16, coupe : 0
Mad Pascal
// Couper le son, jouer une note, puis rétablir (groupe 8, fonction 16).
program setmaster;
uses crt, neo6502;
var maitre, coupe: byte;                        // 8,15 : volume général (0-16) et coupure

procedure lire_maitre;
begin
  NeoSendMessage(8, 15);                        // Get Master Volume
  NeoWaitMessage;
  maitre := NeoMessage.params[0];
  coupe := NeoMessage.params[1];
end;
var e: byte;

function regler(vol, muet: byte): byte;         // 8,16 : rend l'indicateur d'erreur
begin
  NeoMessage.params[0] := vol;                  // P0 : 0-16
  NeoMessage.params[1] := muet;                 // P1 : 1 coupe le son
  NeoSendMessage(8, 16);                        // Set Master Volume
  NeoWaitMessage;
  result := NeoMessage.error;
end;

begin
  regler(8, 1);                                 // volume 8, coupé
  lire_maitre;
  writeln('Regle : ', maitre, ', coupe : ', coupe);
  NeoQueueNote(0, 440, 20, 0, 0);               // 8,4 : cette note ne s'entend pas
  repeat until NeoGetQueueLen(0) = 0;
  e := regler(17, 0);                           // au-delà de 16 : refusé
  writeln('17 : erreur ', e);
  regler(16, 0);                                // rétablir : volume 16, son ouvert
  lire_maitre;
  writeln('Retabli : ', maitre, ', coupe : ', coupe);
end.
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Regle : 8, coupe : 1
17 : erreur 1
Retabli : 16, coupe : 0
NeoBASIC
10 ' Couper le son, jouer une note, puis retablir (groupe 8, fonction 16)
20 v = 8 : m = 1 : gosub 1100 : ' volume 8, coupe
30 gosub 1000 : print "Regle : ";g;", coupe : ";k
40 sound 0,440,20 : ' cette note ne s'entend pas
50 repeat : until notes(0) = 0
60 v = 17 : m = 0 : gosub 1100 : print "17 : erreur ";e : ' au-dela de 16 : refuse
70 v = 16 : m = 0 : gosub 1100 : ' retablir : volume 16, son ouvert
80 gosub 1000 : print "Retabli : ";g;", coupe : ";k
90 end
1000 ' 8,15 : volume general (0-16) dans g, coupure dans k
1010 while peek($ff00) : wend
1020 poke $ff01, 15 : poke $ff00, 8
1030 while peek($ff00) : wend
1040 g = peek($ff04) : k = peek($ff05)
1050 return
1100 ' 8,16 : volume general v (0-16), m = 1 coupe le son ; erreur dans e
1110 while peek($ff00) : wend
1120 poke $ff04, v : poke $ff05, m : poke $ff01, 16 : poke $ff00, 8
1130 while peek($ff00) : wend
1140 e = peek($ff02)
1150 return
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Regle : 8, coupe : 1
17 : erreur 1
Retabli : 16, coupe : 0
65C02 assembly (64tass)
; Couper le son, jouer une note, puis rétablir (groupe 8, fonction 16).
        .cpu "65c02"
        * = $0800
ControlPort    = $FF00             ; +0 groupe, +1 fonction, +2 erreur, +4.. paramètres
WaitMessage    = $FFF4             ; attend que la boîte aux lettres soit libre
WriteCharacter = $FFF1             ; affiche le caractère de A

start: lda #<coupe8
        ldx #>coupe8
        jsr envoi
        jsr lire
        lda #<t0
        ldx #>t0
        jsr ecrire
        jsr montrer
        lda #<n1
        ldx #>n1
        jsr envoi
        jsr attendre
        lda #<trop
        ldx #>trop
        jsr envoi
        lda ControlPort+2          ; au-delà de 16 : refusé
        sta e
        lda #<t1
        ldx #>t1
        jsr ecrire
        lda e
        jsr decimal
        lda #13
        jsr WriteCharacter
        lda #<ouvre
        ldx #>ouvre
        jsr envoi
        jsr lire
        lda #<t2
        ldx #>t2
        jsr ecrire
        jmp montrer

; Volume général (8,15) dans maitre, coupure dans coupe.
lire:   jsr WaitMessage
        lda #8
        ldx #15                    ; 8,15 : Get Master Volume
        jsr appel
        lda ControlPort+4
        sta maitre
        lda ControlPort+5
        sta coupe
        rts
maitre: .byte 0
coupe:  .byte 0

; Affiche « maitre, coupe : coupe » et un retour à la ligne.
montrer:
        lda maitre
        jsr decimal
        lda #<tc
        ldx #>tc
        jsr ecrire
        lda coupe
        jsr decimal
        lda #13
        jmp WriteCharacter
tc:     .text ", coupe : ", 0

coupe8: .byte 8, 16, 2, 8, 1       ; 8,16 : Set Master Volume, volume 8, coupé
trop:   .byte 8, 16, 2, 17, 0      ; volume 17
ouvre:  .byte 8, 16, 2, 16, 0      ; rétablir : volume 16, son ouvert
n1: .byte 8, 4, 8, 0     ; 8,4 : canal 0 : cette note ne s'entend pas
        .word 440, 20, 0
        .byte 0
t0:     .text "Regle : ", 0
t1:     .text "17 : erreur ", 0
t2:     .text "Retabli : ", 0
e:      .byte 0

; Appel décrit par un bloc dont l'adresse est dans A (poids faible) et X (poids fort) :
; groupe, fonction, nombre de paramètres, puis les paramètres (P0, P1...).
envoi:  sta blk
        stx blk+1
        jsr WaitMessage
        ldy #2
        lda (blk),y                ; nombre de paramètres
        clc
        adc #3
        sta fin
        ldy #3
_copie: cpy fin
        beq _lance
        lda (blk),y
        sta ControlPort+1,y        ; octet 3 du bloc -> P0 ($FF04)...
        iny
        bra _copie
_lance: ldy #1
        lda (blk),y
        tax
        lda (blk)
        jmp appel

; Nombre de notes en file sur le canal A (8,6), rendu dans A.
statut: pha
        jsr WaitMessage
        pla
        sta ControlPort+4
        lda #8
        ldx #6                     ; 8,6 : Sound Status
        jsr appel
        lda ControlPort+4
        rts

; Attend que le canal 0 se taise.
attendre:
        lda #0
        jsr statut
        bne attendre
        rts

; Affiche la chaîne terminée par 0 dont l'adresse est dans A (poids faible) et X (poids fort).
ecrire: sta ptr
        stx ptr+1
        ldy #0
_e:     lda (ptr),y
        beq _f
        jsr WriteCharacter
        iny
        bne _e
_f:     rts

; Affiche A (0-255) en décimal, sans zéros de tête.
decimal:
        phx
        phy
        ldx #0
        ldy #100
        jsr _chiffre
        ldy #10
        jsr _chiffre
        ora #'0'
        jsr WriteCharacter
        ply
        plx
        rts
_chiffre:
        sty diviseur
        ldy #0
_div:   cmp diviseur
        bcc _fini
        sbc diviseur
        iny
        bne _div
_fini:  pha
        tya
        bne _ecrit
        cpx #0
        beq _saute
_ecrit: ora #'0'
        jsr WriteCharacter
        ldx #1
_saute: pla
        rts

; Appel : A = groupe, X = fonction (les paramètres sont déjà en place).
appel:  stx ControlPort+1
        sta ControlPort            ; écrit en dernier : lance l'appel
        jmp WaitMessage

ptr      = $80
blk      = $82
diviseur: .byte 0
fin:     .byte 0
Output observed in the Phosphoneo emulator, Trinity firmware 0.16.69:
Regle : 8, coupe : 1
17 : erreur 1
Retabli : 16, coupe : 0

← The Neo6502 API