Tachibana
Langue : français

Groupe 8 — Son Officiel

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

Le son a 4 canaux (0 à 3). Chaque canal a une file de 32 notes au plus : une note mise en file (8,4 ou 8,7) joue dès que la précédente se termine, et le programme continue sans attendre. 8,6 dit combien de notes il reste, celle qui joue comprise. Une note au-delà de 32 est ignorée, sans erreur.

Une note a une fréquence en Hz, une durée en centièmes de seconde (comptée par pas de 2 cs, le firmware avançant à 50 Hz : 25 cs en durent 24), un glissement en Hz par centième, une forme d'onde (0 carré, 1 bruit) et un volume de 0 à 100. En glissant, la fréquence reste entre 100 et 1200 Hz : au-delà, elle repart 1100 Hz plus bas ou plus haut. Une fréquence de 0 fait un silence de la durée donnée.

Trinity ajoute le volume d'un canal pendant qu'il joue (8,9, 8,10), un flux d'échantillons PCM mélangé aux 4 canaux (8,11 à 8,14) et un volume général (8,15, 8,16). Les volumes 0-100 sont stockés de 0 à 127 : une valeur relue peut perdre une unité (25 revient en 24).

Au banc, le son de l'émulateur est enregistré et contrôlé : il doit s'entendre pour les exemples qui jouent, et rester muet quand le son est coupé (8,16). Les exemples ne se contentent pas de jouer : ils affichent ce que le firmware rapporte (notes en file, volumes, état du flux).

8,1 Reset Sound Officiel

Coupe tout le son : vide les files des 4 canaux et arrête le flux d'échantillons.

Entrées : aucune.

Sorties : aucune.

L'exemple met 3 notes en file sur le canal 0 et 2 sur le canal 1, puis les efface d'un coup. Le volume général (8,16) n'est pas touché. En NeoBASIC, sound clear fait cet appel.

Mots-clés NeoBASIC qui font cet appel : sound

Texte de la documentation du firmware (anglais)

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;
}
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 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.
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 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)
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 0.16.69 :
Avant : 3 et 2, apres : 0 et 0
Assembleur 65C02 (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
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 0.16.69 :
Avant : 3 et 2, apres : 0 et 0

8,2 Reset Channel Officiel

Coupe un canal : vide sa file et le fait taire, sans toucher aux autres.

Entrées
OctetsTypeRôle
P0uint8canal (0 à 3)
Sorties
OctetsTypeRôle
erreuruint81 si le canal n'existe pas

Les deux notes du canal 1 restent en file. En NeoBASIC, sound n clear fait cet appel ; sfx le fait aussi, avant l'effet.

Mots-clés NeoBASIC qui font cet appel : sfx, sound

Texte de la documentation du firmware (anglais)

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;
}
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 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.
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 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
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 0.16.69 :
Canal 0 : 0, canal 1 : 2
Assembleur 65C02 (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
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 0.16.69 :
Canal 0 : 0, canal 1 : 2

8,3 Beep Officiel

Joue le bip du démarrage : la 220 Hz pendant 0,5 s puis la 440 Hz pendant 0,25 s, sur le canal 0.

Entrées : aucune.

Sorties : aucune.

Les deux notes sont mises en file sur le canal 0, derrière celles qui y sont déjà. Le firmware ne joue ce bip que sur la carte (code réservé au Pico, dans le firmware officiel comme dans Trinity) : dans l'émulateur, l'appel ne fait rien et la file reste vide, d'où la sortie observée immédiate. NeoBASIC n'a pas de mot-clé pour cet appel.

Texte de la documentation du firmware (anglais)

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;
}
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 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.
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 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"
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 0.16.69 :
Bip termine
Assembleur 65C02 (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
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 0.16.69 :
Bip termine

8,4 Queue Sound Officiel

Met une note en file sur un canal : onde carrée, volume 100.

Entrées
OctetsTypeRôle
P0uint8canal (0 à 3)
P1-P2uint16fréquence en Hz (0 : silence)
P3-P4uint16durée en centièmes de seconde
P5-P6int16glissement en Hz par centième
P7uint8« source » dans la documentation : le firmware ne la lit pas
Sorties
OctetsTypeRôle
erreuruint81 si le canal n'existe pas

L'exemple joue do, mi, sol, puis un do aigu qui monte de 20 Hz par centième ; passé 1200 Hz, le glissement repart 1100 Hz plus bas. NeoBASIC n'a pas de mot-clé pour 8,4 : sound et noise passent par 8,7.

Texte de la documentation du firmware (anglais)

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;
}
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 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.
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 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
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 0.16.69 :
En file : 4 notes
Fini
Assembleur 65C02 (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
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 0.16.69 :
En file : 4 notes
Fini

8,5 Play Sound Officiel

Met en file l'un des 24 effets sonores du firmware (0 à 23) sur un canal.

Entrées
OctetsTypeRôle
P0uint8canal (0 à 3)
P1uint8effet : 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
Sorties
OctetsTypeRôle
erreuruint81 canal inexistant, 2 effet inexistant

La documentation du firmware, llvm-mos et Mad Pascal disent que l'effet part « immédiatement, en vidant la file ». Le code, officiel comme Trinity, ne vide pas la file : il ajoute les notes de l'effet derrière celles qui attendent. Ici, une note d'une seconde puis l'effet 6 (trois notes) font 4 notes en file ; pour un effet immédiat, appeler 8,2 d'abord. C'est ce que fait sfx en NeoBASIC (8,2 puis 8,5), d'où 3 notes seulement.

Mots-clés NeoBASIC qui font cet appel : sfx

Texte de la documentation du firmware (anglais)

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;
}
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 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.
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 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
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 0.16.69 :
Notes en file : 3
Assembleur 65C02 (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
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 0.16.69 :
Notes en file : 4

8,6 Sound Status Officiel

Donne le nombre de notes qui restent sur un canal, celle qui joue comprise ; 0 quand il se tait.

Entrées
OctetsTypeRôle
P0uint8canal (0 à 3)
Sorties
OctetsTypeRôle
P0uint8notes restantes
erreuruint81 si le canal n'existe pas

L'exemple affiche le compte à chaque changement, jusqu'au silence. Attendre 0 est la façon de savoir qu'un son est fini. En NeoBASIC, notes(n) fait cet appel.

Mots-clés NeoBASIC qui font cet appel : notes()

Texte de la documentation du firmware (anglais)

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;
}
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 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.
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 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
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 0.16.69 :
Notes : 2 1 0
Assembleur 65C02 (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
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 0.16.69 :
Notes : 2 1 0

8,7 Queue Sound Extended Officiel

Met une note en file comme 8,4, en choisissant la forme d'onde et le volume.

Entrées
OctetsTypeRôle
P0uint8canal (0 à 3)
P1-P2uint16fréquence en Hz (0 : silence)
P3-P4uint16durée en centièmes de seconde
P5-P6int16glissement en Hz par centième
P7uint8forme d'onde : 0 carré, 1 bruit
P8uint8volume, 0 à 100
Sorties
OctetsTypeRôle
erreuruint81 si le canal n'existe pas

L'exemple met en file un bruit à mi-volume et relit son volume avec 8,10. En NeoBASIC, sound c,f,d[,g] (onde carrée) et noise c,f,d[,g] (bruit) font cet appel, toujours au volume 100 ; l'exemple NeoBASIC écrit donc la boîte aux lettres lui-même.

Mots-clés NeoBASIC qui font cet appel : sound

Texte de la documentation du firmware (anglais)

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;
}
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 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.
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 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
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 0.16.69 :
Bruit en file, volume lu : 50
Assembleur 65C02 (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
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 0.16.69 :
Bruit en file, volume lu : 50

8,8 Get Channel Count Officiel

Donne le nombre de canaux sonores : 4.

Entrées : aucune.

Sorties
OctetsTypeRôle
P0uint8nombre de canaux

Le flux d'échantillons (8,11) s'y ajoute sans être compté. NeoBASIC n'a pas de mot-clé pour cet appel.

Texte de la documentation du firmware (anglais)

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;
}
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 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.
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 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
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 0.16.69 :
Canaux : 4
Assembleur 65C02 (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
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 0.16.69 :
Canaux : 4

8,9 Set Channel Volume Trinity

Change le volume de la note qui joue sur un canal, d'un coup ou en fondu, sans relancer l'onde.

Entrées
OctetsTypeRôle
P0uint8canal (0 à 3)
P1uint8volume visé, 0 à 100
P2-P3uint16durée du fondu en centièmes (0 : tout de suite)
Sorties
OctetsTypeRôle
erreuruint81 si le canal n'existe pas ou ne joue rien

Seule la note en cours change : les notes encore en file gardent leur volume, et la note suivante annule un fondu inachevé. Le fondu avance d'un pas entier à chaque 50e de seconde et peut finir un peu plus tard que prévu (de 63 à 0 en 50 cs : 2 par pas, 32 pas). L'exemple provoque une erreur sur le canal 1, muet.

Texte de la documentation du firmware (anglais)

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;
}
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 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.
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 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
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 0.16.69 :
Volume : 100, puis 50, puis 0 en fondu
Canal 1 muet : erreur 1
Assembleur 65C02 (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
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 0.16.69 :
Volume : 100, puis 50, puis 0 en fondu
Canal 1 muet : erreur 1

8,10 Get Channel Volume Trinity

Donne le volume actuel d'un canal, 0 quand il se tait.

Entrées
OctetsTypeRôle
P0uint8canal (0 à 3)
Sorties
OctetsTypeRôle
P1uint8volume, 0 à 100
erreuruint81 si le canal n'existe pas

Le résultat est en P1, pas en P0. Le volume, stocké de 0 à 127, est reconverti en 0-100 : la note demandée à 25 se relit 24. Pendant un fondu (8,9), la valeur change à chaque 50e de seconde.

Texte de la documentation du firmware (anglais)

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;
}
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 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.
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 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
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 0.16.69 :
Canal 0 : 24
Canal 1 : 0
Canal 4 : erreur
Assembleur 65C02 (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
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 0.16.69 :
Canal 0 : 24
Canal 1 : 0
Canal 4 : erreur

8,11 Stream Start Trinity

Lance un flux d'échantillons PCM 8 bits lus dans un tampon double de la mémoire du 6502.

Entrées
OctetsTypeRôle
P0-P1uint16adresse du tampon (deux moitiés à la suite)
P2-P3uint16taille d'une moitié en octets
P4-P5uint16échantillons par seconde (11025, 22050...)
P6uint8volume, 0 à 100
Sorties
OctetsTypeRôle
erreuruint81 si une taille ou la fréquence vaut 0, le volume dépasse 100, ou le tampon ne finit pas avant $FF00

Les échantillons sont non signés, $80 étant le silence. Les deux moitiés doivent être remplies avant l'appel : elles comptent comme pleines. Le firmware joue la moitié 0, puis la 1, puis la 0..., et rend chacune une fois jouée (8,13) ; le programme la remplit et la rend (8,14). L'exemple joue un créneau de 256 échantillons par moitié à 11025 Hz, attend que la moitié 0 soit jouée (état 129 = $81), puis montre l'erreur d'un tampon qui déborderait sur $FF00. NeoBASIC n'a pas de mot-clé pour le flux.

Texte de la documentation du firmware (anglais)

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;
}
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 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.
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 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
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 0.16.69 :
Lance : 128
Moitie 0 jouee : 129
Tampon trop haut : erreur 1
Assembleur 65C02 (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
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 0.16.69 :
Lance : 128
Moitie 0 jouee : 129
Tampon trop haut : erreur 1

8,12 Stream Stop Trinity

Arrête le flux d'échantillons tout de suite ; le tampon peut resservir.

Entrées : aucune.

Sorties : aucune.

L'état (8,13) passe de 128 ($80, le flux joue) à 0. 8,1 arrête aussi le flux.

Texte de la documentation du firmware (anglais)

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;
}
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 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.
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 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
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 0.16.69 :
Avant : 128, apres : 0
Assembleur 65C02 (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
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 0.16.69 :
Avant : 128, apres : 0

8,13 Stream Status Trinity

Donne l'état du flux : s'il joue, quelles moitiés attendent d'être remplies, et combien de fois il a manqué d'échantillons.

Entrées : aucune.

Sorties
OctetsTypeRôle
P0uint8bit 7 : le flux joue ; bit 0 : moitié 0 à remplir ; bit 1 : moitié 1 à remplir ; 0 : arrêté
P1-P2uint16sous-alimentations depuis le lancement (65535 au plus)

Une sous-alimentation, c'est une moitié atteinte avant d'avoir été rendue pleine : le flux se tait alors jusqu'à ce qu'elle le soit, sans la rejouer. L'exemple ne remplit rien : après les deux moitiés, l'état vaut 131 ($83) et le compteur 1, puis il ne bouge plus.

Texte de la documentation du firmware (anglais)

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;
}
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 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.
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 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
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 0.16.69 :
Au depart : 128, tari : 131, sous-alimentations : 1
Arrete : 0
Assembleur 65C02 (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
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 0.16.69 :
Au depart : 128, tari : 131, sous-alimentations : 1
Arrete : 0

8,14 Stream Filled Trinity

Rend une moitié du tampon au flux, une fois ses nouveaux échantillons écrits.

Entrées
OctetsTypeRôle
P0uint8moitié (0 ou 1)
Sorties
OctetsTypeRôle
erreuruint81 si la moitié n'est ni 0 ni 1, ou si le flux est arrêté

La boucle lit l'état (8,13), réécrit chaque moitié jouée puis la rend, six fois, sans sous-alimentation. Une moitié de 256 échantillons dure 23 ms à 11025 Hz : NeoBASIC est trop lent pour la réécrire à temps (six sous-alimentations au banc), l'exemple NeoBASIC la rend donc sans la réécrire, et le même son boucle.

Texte de la documentation du firmware (anglais)

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;
}
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 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.
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 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
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 0.16.69 :
Moities rendues : 6, sous-alimentations : 0
Assembleur 65C02 (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
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 0.16.69 :
Moities rendues : 6, sous-alimentations : 0

8,15 Get Master Volume Trinity

Donne le volume général (0 à 16) et dit si le son est coupé.

Entrées : aucune.

Sorties
OctetsTypeRôle
P0uint8volume général, 0 à 16 (16 au démarrage)
P1uint81 si le son est coupé

Le volume général s'applique à tout ce qui sort : les 4 canaux et le flux. Les touches multimédia d'un clavier USB le changent : Muet bascule la coupure, Volume - et Volume + le déplacent de 2 et rouvrent le son. Ces touches restent lisibles par 2,22.

Texte de la documentation du firmware (anglais)

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;
}
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 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.
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 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
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 0.16.69 :
Volume general : 16, coupe : 0
Assembleur 65C02 (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
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 0.16.69 :
Volume general : 16, coupe : 0

8,16 Set Master Volume Trinity

Règle le volume général (0 à 16) et coupe ou rouvre le son.

Entrées
OctetsTypeRôle
P0uint8volume général, 0 à 16
P1uint8différent de 0 : son coupé
Sorties
OctetsTypeRôle
erreuruint81 si le volume dépasse 16 (rien ne change)

Le réglage survit à 8,1 et à la fin du programme : l'exemple rétablit 16 et le son avant de rendre la main. Pendant la coupure, les notes jouent (la file se vide) mais rien ne sort : au banc, le son enregistré reste muet. L'exemple provoque une erreur en demandant 17.

Texte de la documentation du firmware (anglais)

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;
}
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 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.
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 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
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 0.16.69 :
Regle : 8, coupe : 1
17 : erreur 1
Retabli : 16, coupe : 0
Assembleur 65C02 (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
Sortie observée dans l'émulateur Phosphoneo, firmware Trinity 0.16.69 :
Regle : 8, coupe : 1
17 : erreur 1
Retabli : 16, coupe : 0

← L'API du Neo6502