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;
}Avant : 3 et 2, apres : 0 et 0Mad 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.Avant : 3 et 2, apres : 0 et 0NeoBASIC
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)Avant : 3 et 2, apres : 0 et 0Assembleur 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 0Avant : 3 et 2, apres : 0 et 08,2 Reset Channel Officiel
Coupe un canal : vide sa file et le fait taire, sans toucher aux autres.
| Octets | Type | Rôle |
|---|---|---|
P0 | uint8 | canal (0 à 3) |
| Octets | Type | Rôle |
|---|---|---|
erreur | uint8 | 1 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;
}Canal 0 : 0, canal 1 : 2Mad 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.Canal 0 : 0, canal 1 : 2NeoBASIC
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 aussiCanal 0 : 0, canal 1 : 2Assembleur 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 0Canal 0 : 0, canal 1 : 28,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;
}Bip termineMad 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.Bip termineNeoBASIC
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"Bip termineAssembleur 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 0Bip termine8,4 Queue Sound Officiel
Met une note en file sur un canal : onde carrée, volume 100.
| Octets | Type | Rôle |
|---|---|---|
P0 | uint8 | canal (0 à 3) |
P1-P2 | uint16 | fréquence en Hz (0 : silence) |
P3-P4 | uint16 | durée en centièmes de seconde |
P5-P6 | int16 | glissement en Hz par centième |
P7 | uint8 | « source » dans la documentation : le firmware ne la lit pas |
| Octets | Type | Rôle |
|---|---|---|
erreur | uint8 | 1 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;
}En file : 4 notes
FiniMad 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.En file : 4 notes
FiniNeoBASIC
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 returnEn file : 4 notes
FiniAssembleur 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 0En file : 4 notes
Fini8,5 Play Sound Officiel
Met en file l'un des 24 effets sonores du firmware (0 à 23) sur un canal.
| Octets | Type | Rôle |
|---|---|---|
P0 | uint8 | canal (0 à 3) |
P1 | uint8 | effet : 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 |
| Octets | Type | Rôle |
|---|---|---|
erreur | uint8 | 1 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;
}Notes en file : 4Mad 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.Notes en file : 4NeoBASIC
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 finNotes en file : 3Assembleur 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 0Notes en file : 48,6 Sound Status Officiel
Donne le nombre de notes qui restent sur un canal, celle qui joue comprise ; 0 quand il se tait.
| Octets | Type | Rôle |
|---|---|---|
P0 | uint8 | canal (0 à 3) |
| Octets | Type | Rôle |
|---|---|---|
P0 | uint8 | notes restantes |
erreur | uint8 | 1 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;
}Notes : 2 1 0Mad 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.Notes : 2 1 0NeoBASIC
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 printNotes : 2 1 0Assembleur 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 0Notes : 2 1 08,7 Queue Sound Extended Officiel
Met une note en file comme 8,4, en choisissant la forme d'onde et le volume.
| Octets | Type | Rôle |
|---|---|---|
P0 | uint8 | canal (0 à 3) |
P1-P2 | uint16 | fréquence en Hz (0 : silence) |
P3-P4 | uint16 | durée en centièmes de seconde |
P5-P6 | int16 | glissement en Hz par centième |
P7 | uint8 | forme d'onde : 0 carré, 1 bruit |
P8 | uint8 | volume, 0 à 100 |
| Octets | Type | Rôle |
|---|---|---|
erreur | uint8 | 1 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;
}Bruit en file, volume lu : 50Mad 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.Bruit en file, volume lu : 50NeoBASIC
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 returnBruit en file, volume lu : 50Assembleur 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 0Bruit en file, volume lu : 508,8 Get Channel Count Officiel
Donne le nombre de canaux sonores : 4.
Entrées : aucune.
| Octets | Type | Rôle |
|---|---|---|
P0 | uint8 | nombre 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;
}Canaux : 4Mad Pascal
// Demander le nombre de canaux sonores (groupe 8, fonction 8).
program channelcount;
uses crt, neo6502;
begin
writeln('Canaux : ', NeoGetChannels); // Get Channel Count
end.Canaux : 4NeoBASIC
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 : ";nCanaux : 4Assembleur 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 0Canaux : 48,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.
| Octets | Type | Rôle |
|---|---|---|
P0 | uint8 | canal (0 à 3) |
P1 | uint8 | volume visé, 0 à 100 |
P2-P3 | uint16 | durée du fondu en centièmes (0 : tout de suite) |
| Octets | Type | Rôle |
|---|---|---|
erreur | uint8 | 1 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;
}Volume : 100, puis 50, puis 0 en fondu
Canal 1 muet : erreur 1Mad 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.Volume : 100, puis 50, puis 0 en fondu
Canal 1 muet : erreur 1NeoBASIC
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 returnVolume : 100, puis 50, puis 0 en fondu
Canal 1 muet : erreur 1Assembleur 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 0Volume : 100, puis 50, puis 0 en fondu
Canal 1 muet : erreur 18,10 Get Channel Volume Trinity
Donne le volume actuel d'un canal, 0 quand il se tait.
| Octets | Type | Rôle |
|---|---|---|
P0 | uint8 | canal (0 à 3) |
| Octets | Type | Rôle |
|---|---|---|
P1 | uint8 | volume, 0 à 100 |
erreur | uint8 | 1 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;
}Canal 0 : 24
Canal 1 : 0
Canal 4 : erreurMad 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.Canal 0 : 24
Canal 1 : 0
Canal 4 : erreurNeoBASIC
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 returnCanal 0 : 24
Canal 1 : 0
Canal 4 : erreurAssembleur 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 0Canal 0 : 24
Canal 1 : 0
Canal 4 : erreur8,11 Stream Start Trinity
Lance un flux d'échantillons PCM 8 bits lus dans un tampon double de la mémoire du 6502.
| Octets | Type | Rôle |
|---|---|---|
P0-P1 | uint16 | adresse du tampon (deux moitiés à la suite) |
P2-P3 | uint16 | taille d'une moitié en octets |
P4-P5 | uint16 | échantillons par seconde (11025, 22050...) |
P6 | uint8 | volume, 0 à 100 |
| Octets | Type | Rôle |
|---|---|---|
erreur | uint8 | 1 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;
}Lance : 128
Moitie 0 jouee : 129
Tampon trop haut : erreur 1Mad 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.Lance : 128
Moitie 0 jouee : 129
Tampon trop haut : erreur 1NeoBASIC
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 returnLance : 128
Moitie 0 jouee : 129
Tampon trop haut : erreur 1Assembleur 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 0Lance : 128
Moitie 0 jouee : 129
Tampon trop haut : erreur 18,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;
}Avant : 128, apres : 0Mad 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.Avant : 128, apres : 0NeoBASIC
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 returnAvant : 128, apres : 0Assembleur 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 0Avant : 128, apres : 08,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.
| Octets | Type | Rôle |
|---|---|---|
P0 | uint8 | bit 7 : le flux joue ; bit 0 : moitié 0 à remplir ; bit 1 : moitié 1 à remplir ; 0 : arrêté |
P1-P2 | uint16 | sous-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;
}Au depart : 128, tari : 131, sous-alimentations : 1
Arrete : 0Mad 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.Au depart : 128, tari : 131, sous-alimentations : 1
Arrete : 0NeoBASIC
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 returnAu depart : 128, tari : 131, sous-alimentations : 1
Arrete : 0Assembleur 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 0Au depart : 128, tari : 131, sous-alimentations : 1
Arrete : 08,14 Stream Filled Trinity
Rend une moitié du tampon au flux, une fois ses nouveaux échantillons écrits.
| Octets | Type | Rôle |
|---|---|---|
P0 | uint8 | moitié (0 ou 1) |
| Octets | Type | Rôle |
|---|---|---|
erreur | uint8 | 1 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;
}Moities rendues : 6, sous-alimentations : 0Mad 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.Moities rendues : 6, sous-alimentations : 0NeoBASIC
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 returnMoities rendues : 6, sous-alimentations : 0Assembleur 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 0Moities rendues : 6, sous-alimentations : 08,15 Get Master Volume Trinity
Donne le volume général (0 à 16) et dit si le son est coupé.
Entrées : aucune.
| Octets | Type | Rôle |
|---|---|---|
P0 | uint8 | volume général, 0 à 16 (16 au démarrage) |
P1 | uint8 | 1 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;
}Volume general : 16, coupe : 0Mad 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.Volume general : 16, coupe : 0NeoBASIC
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 returnVolume general : 16, coupe : 0Assembleur 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 0Volume general : 16, coupe : 08,16 Set Master Volume Trinity
Règle le volume général (0 à 16) et coupe ou rouvre le son.
| Octets | Type | Rôle |
|---|---|---|
P0 | uint8 | volume général, 0 à 16 |
P1 | uint8 | différent de 0 : son coupé |
| Octets | Type | Rôle |
|---|---|---|
erreur | uint8 | 1 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;
}Regle : 8, coupe : 1
17 : erreur 1
Retabli : 16, coupe : 0Mad 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.Regle : 8, coupe : 1
17 : erreur 1
Retabli : 16, coupe : 0NeoBASIC
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 returnRegle : 8, coupe : 1
17 : erreur 1
Retabli : 16, coupe : 0Assembleur 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 0Regle : 8, coupe : 1
17 : erreur 1
Retabli : 16, coupe : 0