Rebol pour émuler le PSG AY-3-8910
guest24-Feb-2007/22:16:53+1:00
alors voilà, dans mon projet de faire une émulation du MSX1, une machine à base de Z80, j'ai produit un petit script qui émule le PSG (la puce sonore du MSX) avec l'aide indispensable de Goldevill bien sûr

Sur internet il existe de nombreux programmes qui le font déjà et beaucoup mieux car le script rebol est assez rudimentaire pour le moment.
Je ne gère pas encore les enveloppes variables et le canal bruit.

Malgrès tout on arrive déjà à un résultat audible.

ici: http://perso.orange.fr/rebol/play2.r
Dans ce programme test, j'ai mis une musique célèbre assez longue du jeu "Auf wiedersehen Monty" parru en 1987

Pour ceux que ça intéresse, vous remarquerez que j'insère des portions de musique synthétisée dans le port sound de rebol toutes les 5 secondes.
Et on remarque un léger sautement toutes les 5 secondes. Ceci pour dire que Rebol réintialise probablement des trucs et que le streaming audio avec Rebol est donc probablement impossible, c'est dommage.
Si vous entendez que la musique s'arrête entre chaque injection, c'est probablement que votre machine est moins rapide que la mienne.
Il faut alors aller dans le script et augmenter la variable delay.
par défaut delay = 500 (5 secondes de décalage)

Autre remarque, le petite bruit de cloche qu'on entend correspond en principe à un bruit de batterie mais comme je n'ai pas encore géré le canal bruit (je sais je l'ai déjà dit) ben ça fait ce son bizarre.

Et pour finir, il n'y a aucunne tricherie, les données compressées de la variable song correspondent à une suite de valeurs des registres du PSG. Ce ne sont pas des wav compressés.

Pour preuve la musique dûre plusieurs secondes et dans le format wav elle n'aurait pu tenir aussi peu de place.

vala vala, bonne écoute...
guest24-Feb-2007/22:32:47+1:00
je voulais dire : la musique dure plusieurs minutes et pas plusieurs secondes.

Autre remarque en passant, lorsque la musique monte haut dans les aigues, ça devient faux.
probablement du à un effet de seuil que le script de gère pas encore.
guest28-Feb-2007/14:10:38+1:00
J'ai posté une démo sur Rebol.org avec une jolie animation.

http://www.rebol.org/cgi-bin/cgiwrap/rebol/download-a-script.r?script-name=demo-ay.r

Malheureusement cette version occupe beaucoup trop de mémoire (80 M chez moi), j'ai une version qui n'en prend qu'une 15zaine, je la mettrai à jour, à l'occasion
Goldevil8-Feb-2007/15:17:23+1:00
Ha! Toute la nostalgie des demos.

En tout cas le script est très concis : quand on enlève la partie graphique, les données de la musique et le texte qui défile (merci au passage pour le remerciement), on se rend compte qu'il reste que peu de lignes. Rebol et les développeurs Rebol arrivent encore à m'étonner chaque jour.
coccinelle12-Feb-2007/16:24:22+1:00
Hello Guest2,

J'ai jeté un coup d'oeil sur ton script et je me dis que si on entend un arrêt par moment, c'est que tu ne laisse pas assez de temps à Rebol pour composer sa musique.

Ceci vient je crois du wait dans la fonction mix mais je me trompe peut-être.

Ceci dit, je ne sais pas trop quoi te suggérer, désolé.
guest212-Feb-2007/19:50:10+1:00
non non, je crois pas que ce soit ça.
Justement, le fonction wait est là pour patienter jusqu'à ce que le port sound se libère (c'est une durée estimée).
Sinon lorsque on tente d'insérer des données sur le port sound alors qu'il n'est pas libre, le script se retrouve bloqué en attendant.

Le vrai problème, c'est que lorsqu'on reinjecte des données dans le port sound, certains trucs sont reinitialiés par rebol et d'après mes tests, c'est lui qui provoque ce "blanc" ainsi que ce micro bloquage.

J'ai eu beau essayer de calculer au plus juste le momment de réinjecter les nouvelles données du sample, il me fait toujours ça.

j'ai donc été obligé de rajouter un délai de 5 secondes entre chaque injection pour que ce soit quand même écoutable mais je pense qu'il est impossible de faire du streaming audio temps réel (j'ai essayé).
Je crois malheureusement que c'est Rebol qui déconne.

Ou alors j'ai fait un truc qui fallait pas dans mon script mais je vois pas quoi...
Didec13-Feb-2007/9:25:09+1:00
Et tu peux pas faire un 'wait du port sound (suggestion à la con, mais bon les ports et moi, je suis pas marin du tout) ?
guest213-Feb-2007/12:59:23+1:00
Et béh ! t'es en forme Didec !

j'ai testé le wait sur le port-sound, mais le problème est le même.
Je pense qu'il faudrait poster ça sur Rambo ou en discuter sur la mailing list.
De mon point de vue , le streaming audio ne fonctionne pas.
guest213-Feb-2007/14:10:06+1:00
d'ailleurs dans RAMBO, il semble que le problème ait déjà été relevé depuis longtemps mais pas traité.

RAMBO Ticket #3930


Id:
3930
Summary:
REBOL has problems playing sound
Type:
Bug
Class:
Code
State:
Reviewed
Importance:
High
Product:
All products
Category:
TBD
Version:
all
Platform:
XP
Created:
21-Oct-2005/4:24:11-4:00
Modified:
21-Oct-2005/4:24:11-4:00
Details:
If REBOL plays a sound, there are clicks on beginning and end of the sound. Another issue is that if you want two play two sounds in a series, there's always small gap, so it's not possible to stream the sound. This is very annoying.
Example:


Fix:


Response:

Submitter:
REBolek
Email:
(private)
Goldevil13-Feb-2007/15:29:49+1:00
Je pense aussi que le support des sons dans Rebol a des limitations connues. Je ne pense pas qu'il y a eu d'évolutions depuis plusieurs années à ce niveau.

Et puis le format wav c'est pas terrible. C'est le BMP des formats d'image.

J'ai proposé à Carl d'inclure le support de la librairie FLAC, un codec OpenSource qui gère le format FLAC : son compressé sans pertes (le format PNG du son).
Pour rebol, cela serait l'équivalent de la librairie AGG mais pour le son.
Il avait l'air emballé et donc on peu raisonnablement espérer l'avoir dans Rebol 3.0.
Goldevil13-Feb-2007/15:32:10+1:00
Tant que j'y pense, tu pourrais mettre ton émulateur MSX sur rebol.org aussi.
guest213-Feb-2007/16:37:19+1:00
pas fini, pas montrable
coccinelle18-Feb-2007/12:32:31+1:00
Je me suis dis que peut-être, mais effectivement, le port sound pose problème. J'ai seulement constaté qu'un wait sound-port avant l'insert sound-port améliore légèrement les transitions.

Comme il me titillait de comprendre, j'ai réécrit un peu différemment le script (sans animation).

J'ajuste proportionnellement la période et je construis une onde sans passer par la fréquence. C'est comme ça que j'ai compris la chose et ça m'a paru plus simple.

La fonction append-mix à un refinement /square qui permet de produire une onde carrée ou pas. Vous pouvez essayer, c'est assez amusant de voir la différence.

Le son est à peine différent d'autant plus que le bruit (noise) est aussi mixé. Voici ce que çà donne :
REBOL []

song: load decompress #{
...
reprendre les données du script ttp://www.rebol.org/cgi-bin/cgiwrap/rebol/download-a-script.r?script-name=demo-ay.r
...
}

; This is the mixer that mix 3 channel and noise.
current-a: 0 ; Current channel A position
current-b: 0 ; Current channel B position
current-c: 0 ; Current channel C position
current-n: 0 ; Current noise position

append-mix: func [
    "Mix channel a, b, c and noise, and append it to data"
    data [binary! string!] "Sound data"
    length [number!] "Sound length"
    channel-a [block!] "Channel A [Periode Amplitude]"
    channel-b [block!] "Channel B [Periode Amplitude]"
    channel-c [block!] "Channel C [Periode Amplitude]"
    noise [number!] "Noise periode (Amplitude is generated randomly)"
    /square "Produce a square wave"
][
    loop length [
        insert tail data to char! 128 + (
            channel-a/2 * either zero? channel-a/1 [0][
                current-a: current-a + (360 / channel-a/1)
                either square [sign? sine current-a][sine current-a]
            ]
        ) + (
            channel-b/2 * either zero? channel-b/1 [0][
                current-b: current-b + (360 / channel-b/1)
                either square [sign? sine current-b][sine current-b]
            ]
        ) + (
            channel-c/2 * either zero? channel-c/1 [0][
                current-c: current-c + (360 / channel-c/1)
                either square [sign? sine current-c][sine current-c]
            ]
        ) + (
            (-1 + random 32) * either zero? noise [0][
                current-n: current-n + (360 / noise)
                either square [sign? sine current-n][sine current-n]
            ]
        )
    ]
]

print "running"

; Set up the sound sample parameters:
sample: make sound [
    rate: 44200 
    channels: 1
    bits: 8
    volume: 1
    data: #{}
]

; Ratio and length
ratio: sample/rate / (3579545 / 16 / 2)
length: sample/rate / 100
max-length: sample/rate * 4

; open sound port
wait 0
sound-port: open/no-wait sound://

foreach [nb A0 A1 B0 B1 C0 C1 noise mixer volA volB volC Ev0 Ev1 Shape JoyA JoyB] song [
    loop nb [
        append-mix/square sample/data length
            reduce [ratio * (A1 and 15 * 256 + A0) 2 * (volA and 15)]
            reduce [ratio * (B1 and 15 * 256 + B0) 2 * (volB and 15)]
            reduce [ratio * (C1 and 15 * 256 + C0) 2 * (volC and 15)]
            ratio * (noise and 31)
    ]
    if max-length < length? sample/data [
        wait sound-port
        insert sound-port sample
        clear sample/data recycle
    ]
]
unless zero? length? sample/data [
    wait sound-port
    insert sound-port sample
    clear sample/data recycle
]

ask "Done"
guest218-Feb-2007/22:08:18+1:00
argh, je peux pas tester, j'ai plus de son sur mon portable.
le truc du wait avant le insert, effectivement j'avais déjà constaté que ça permettait de réduire le lag.

par contre suis impatient de voir ce que donne le canal bruit.
guest219-Feb-2007/16:35:47+1:00
Je viens aussi de me rendre compte que tu t'es peut-être servi de la version que j'avais postée sur le fofo au lieu de la version de rebol.org.
Je ne calcule plus de fréquence non plus, je calcule uniquement la demi periode pour les changements de phase dans le mixer.
coccinelle19-Feb-2007/18:47:51+1:00
fofo ??? quesaquo

Par contre, les calculs sont lents, à peine plus rapide que le son joué alors je vais essayer d'utiliser rebcode pour mixer les 3 cannaux. Je te dirais le résultat obtenus.

Je ne pensais pas un jour me remettre à quelque chose de proche de l'assembleur, mais bon, c'est ainsi.
guest219-Feb-2007/18:57:37+1:00
fofo = forum pour les intimes

Je ne pense pas que tu ais besoin d'utiliser rebcode.
Il y'a certaines optimisations à faire dans ton code. Accessoirement, en utilisant que des ondes carrées ce serait beaucoup plus rapide à calculer.
coccinelle20-Feb-2007/0:35:36+1:00
J'ai fait quelques essais avec rebcode. La vitesse est vraiment supérieur. Il me faut 32 secondes pour générer tout le morceau alors qu'avec le script quelque peu optimisé, il me faut (ben j'en ai marre d'attendre ...) 5 minutes et 21 secondes.

Je donnerai tout çà demain une fois que c'est bien au point.
coccinelle20-Feb-2007/8:56:34+1:00
Voilà le résultat, 31 secondes environ pour générer un morceau de 6 minutes 44. Ca laisse pas mal de temps pour faire autre chose dans le programme.

En utilisant rebcode pour générer les graphiques, on doit avoir quelque chose de fluide probablement.

Voici le script :
REBOL []

song: load decompress #{
...
reprendre les données du script ttp://www.rebol.org/cgi-bin/cgiwrap/rebol/download-a-script.r?script-name=demo-ay.r
...
}

; This is the mixer that mix 3 channel and noise.
current-a: 0 ; Current A position
current-b: 0 ; Current B position
current-c: 0 ; Current C position
current-n: 0 ; Current noise position

append-mix: rebcode [
    {Mix channel A, B, C and noise, and append it to data
        - Amplitude must be between 0 and 31
        - Square flag produce square wave}
    data [binary!] "Sound data"
    length [integer!] "Sound length"
    channel-a [block!] "Channel A [periode amplitude square-flag]"
    channel-b [block!] "Channel B [periode amplitude square-flag]"
    channel-c [block!] "Channel C [periode amplitude square-flag]"
    noise [block!] "Noise [periode max-amplitude square-flag] (Amplitude is generated randomly)"
    /local result value 
        step-a ampl-a sqre-a
        step-b ampl-b sqre-b
        step-c ampl-c sqre-c
        step-n maxi-n sqre-n ampl-n
][
; Initialize channel A value
; --------------------------
    pick step-a channel-a 1
    to-int step-a
    sett step-a ift [
        set value 72000
        div.i value step-a
        set.i step-a value
    ]
    pick ampl-a channel-a 2
    to-dec ampl-a
    pick sqre-a channel-a 3
    to-int sqre-a

; Initialize channel B value
; --------------------------
    pick step-b channel-b 1
    to-int step-b
    sett step-b ift [
        set value 72000
        div.i value step-b
        set.i step-b value
    ]
    pick ampl-b channel-b 2
    to-dec ampl-b
    pick sqre-b channel-b 3
    to-int sqre-b

; Initialize channel C value
; --------------------------
    pick step-c channel-c 1
    to-int step-c
    sett step-c ift [
        set value 72000
        div.i value step-c
        set.i step-c value
    ]
    pick ampl-c channel-c 2
    to-dec ampl-c
    pick sqre-c channel-c 3
    to-int sqre-c

; Initialize noise value
; ----------------------
    pick step-n noise 1
    to-int step-n
    sett step-n ift [
        set value 72000
        div.i value step-n
        set.i step-n value
    ]
    pick maxi-n noise 2
    to-int maxi-n
    pick sqre-n channel-n 3
    to-int sqre-n

; Generate the wave points for the given length
; ---------------------------------------------
    loop length [
        set result 128.0
; Channel A wave point      
        sett step-a ift [
            add.i current-a step-a
            rem.i current-a 72000
            set value current-a
            to-dec value
            div.d value 200.0
;           sin value ; !!! ne marche pas -> remplacé par la ligne suivante (apply)
            apply value sine [value]
            sett sqre-a ift [sign value to-dec value]
            mul.d value ampl-a
            add.d result value
        ]
; Channel B wave point      
        sett step-b ift [
            add.i current-b step-b
            rem.i current-b 72000
            set value current-b
            to-dec value
            div.d value 200.0
;           sin value ; !!! ne marche pas -> remplacé par la ligne suivante (apply)
            apply value sine [value]
            sett sqre-b ift [sign value to-dec value]
            mul.d value ampl-b
            add.d result value
        ]
; Channel C wave point      
        sett step-c ift [
            add.i current-c step-c
            rem.i current-c 72000
            set value current-c
            to-dec value
            div.d value 200.0
;           sin value ; !!! ne marche pas -> remplacé par la ligne suivante (apply)
            apply value sine [value]
            sett sqre-c ift [sign value to-dec value]
            mul.d value ampl-c
            add.d result value
        ]
; Noise wave point      
        sett step-n ift [
            add.i current-n step-n
            rem.i current-n 72000
            set value current-n
            to-dec value
            div.d value 200.0
;           sin value ; !!! ne marche pas -> remplacé par la ligne suivante (apply)
            apply value sine [value]
            sett sqre-n ift [sign value to-dec value]
            set.i ampl-n maxi-n
            add.i ampl-n 1
            randz ampl-n
            to-dec ampl-n
            mul.d value ampl-n
            add.d result value
        ]
; Append result to data
        to-int result
        tail data
        insert data result 1
    ]
]

print "running"

; Set up the sound sample parameters:
sample: make sound [
    rate: 44200 
    channels: 1
    bits: 8
    volume: 1
    data: #{}
]

; Ratio and length
ratio: sample/rate / (3579545 / 16 / 2)
length: to-integer sample/rate / 100
max-length: sample/rate * 4

; open sound port
wait 0
sound-port: open/no-wait sound://

s: now/time/precise
foreach [nb A0 A1 B0 B1 C0 C1 noise mixer volA volB volC Ev0 Ev1 Shape JoyA JoyB] song [
    loop nb [
        append-mix sample/data length
            reduce [ratio * (A1 and 15 * 256 + A0) 2 * (volA and 15) true]
            reduce [ratio * (B1 and 15 * 256 + B0) 2 * (volB and 15) true]
            reduce [ratio * (C1 and 15 * 256 + C0) 2 * (volC and 15) true]
            reduce [ratio * (noise and 31) 31 true]
    ]
    if max-length < length? sample/data [
        print "Wait"
        wait sound-port
        insert sound-port sample
        clear sample/data recycle
        print "Compose"
    ]
]

unless zero? length? sample/data [
    wait sound-port
    insert sound-port sample
    clear sample/data recycle
]
wait sound-port

ask "Done"


Pour vous faire une idée, la fonction de départ qui mettait autour de 5 minute 30 pour générer le 6 minutes 44 de musique était celle-ci
append-mix: func [
	{Mix channel a, b, c and noise, and append it to data
		- Amplitude must be between 0 and 31
		- Square flag produce square wave}
    data [binary!] "Sound data"
    length [integer!] "Sound length"
    channel-a [block!] "Channel A [Periode Amplitude Square]"
    channel-b [block!] "Channel B [Periode Amplitude Square]"
    channel-c [block!] "Channel C [Periode Amplitude Square]"
    noise [block!] "Noise [Periode Amplitude Square] (Amplitude is generated randomly)"
    /local step-a step-b step-c step-n
][
	step-a: either zero? channel-a/1 [0][360 / channel-a/1]
	step-b: either zero? channel-b/1 [0][360 / channel-b/1]
	step-c: either zero? channel-c/1 [0][360 / channel-c/1]
	step-n: either zero? noise/1 [0][360 / noise/1]
    loop length [
        insert tail data to char! 128 + (either zero? step-a [0][
            current-a: remainder current-a + step-a 360
            channel-a/2 * either channel-a/3 [sign? sine current-a][sine current-a]
        ]) + (either zero? step-b [0][
            current-b: remainder current-b + step-b 360
            channel-b/2 * either channel-b/3 [sign? sine current-b][sine current-b]
        ]) + (either zero? step-c [0][
            current-c: remainder current-c + step-c 360
            channel-c/2 * either channel-c/3 [sign? sine current-c][sine current-c]
        ]) + (either zero? step-n [0][
            current-n: remainder current-n + step-n 360
            (random noise/2) * either noise/3 [sign? sine current-n][sine current-n]
        ])
    ]
]
Didec20-Feb-2007/13:34:20+1:00
Tes "current-a" sont des entiers, que j'imagine bornés (0 à 360 si j'ai bien compris).

Une optimisation classique en 3D est de précalculer les sinus et cosinus de toutes les valeurs possibles dans une table et d'aller chercher le résultat pour la valeur souhaitée plutôt que de la calculer à chaque fois.

Je sais pas si ça peux améliorer la vitesse ?!
    ; Init de la table des sinus si pas déjà fait
    if empty? sin-table: [] [
        repeat sin 361 [insert tail sin-table sine sin - 1]
        ; Cette fonction va être utiliser à la place de la fonction 'sine de Rebol
        sin: func [v][pick sin-table v + 1]
    ]
guest220-Feb-2007/14:43:50+1:00
ouep ouep, surtout si tu fais l'accès en rebcode:

donc j'écrirais:

; Channel A wave point      
        sett step-a ift [
            add.i current-a step-a
            rem.i current-a 72000
            set.i value current-a
            div.i value 200
            pickz value sine-table value
            sett sqre-a ift [sign value to-dec value]
            mul.d value ampl-a
            add.d result value
        ]
coccinelle20-Feb-2007/14:47:16+1:00
Rebolek sur altMe m'a conseillé çà et j'ai gagné encore 5 secondes :
some example code (in C, but easy to rewrite):

float a = 2.f*(float)sin(Pi*frequency/samplerate);

float s[2];

s[0] = 0.5f;
s[1] = 0.f;

loop:
s[0] = s[0] - a*s[1];
s[1] = s[1] + a*s[0];
output_sine = s[0];
output_cosine = s[1]
guest220-Feb-2007/15:50:27+1:00
m'est avis que l'accès à une table est encore plus rapide.
coccinelle20-Feb-2007/17:26:23+1:00
J'ai mis le script là : http://www.rebol.org/cgi-bin/cgiwrap/rebol/view-script.r?script=psg-ay-3-8910-study.r

Je en sais pas si une table est vraiment plus rapide. Un petit essais me donne ceci
>> bench 1000000 [sine 90]
== 0:00:00.381
>> x: [1 2 3]
== [1 2 3]
>> bench 1000000 [x/2]
== 0:00:00.42
>> bench 1000000 [pick x 2]
== 0:00:00.311
>> bench 1000000 [sine 26]
== 0:00:00.391
>>
guest220-Feb-2007/18:06:30+1:00
hum cool cool, sauf que le script original est de moi.
enfin, il me semble...
coccinelle20-Feb-2007/19:25:56+1:00
Pardon, je t'ai confondu avec Goldevil, c'est corrigé.
Je t'ai aussi mis auteur de ce script sur rebol.org.
J'espère avoir rendu à Cesar ce qui est à César.

Désolé.
guest220-Feb-2007/20:21:48+1:00
Bé, y'a pas de mal, Goldevil m'a pas mal inspiré.
C'est un travail collégial

Login required to Post.


Powered by RebelBB and REBOL 2.7.8.4.2