Table des matières

Retourner au sommaire

Les SID-voices

Rédigé pour le Quasar Net par OffseT.

Rahhh ! Enfin ! D'aucuns avaient sans doute déjà renoncé à l'idée de voir enfin un jour l'article sur les SID-voices, pourtant, il est bien là comme en témoigne le filet de bave qui coule de la bouche du petit blond à lunettes.

Oui, mais ne soyons pas si impatients de commencer. Car même si ce type de son est bien connu de la plupart des gens de la scène, n'en oublions pas pour autant les profanes ! Et avançons pas à pas dans cette caverne sombre et humide1).

Or donc, un SID-voice c'est quoi ?

Un peu d'histoire

Le C64, la référence absolue

De tout temps, le C64 a été jalousé pour les étonnantes sonorités de ses somptueuses musiques de jeux et de démos2). Eh oui, alors que la plupart des machines 8 bits de l'époque se voyaient affublées d'un générateur de son primitif, le C64 disposait du processeur SID ! Et ce chipset audio conçu spécialement pour la machine est un bijou disposant de capacités bien supérieures à tout ce que pourra jamais produire un AY.

Oui, car sur CPC nous ne faisons pas mieux que les Oric et autres ZX Spectrum côté chipset son, tout comme eux nous avons un AY (ou pour certains la variante YM qui ne vaut guère mieux). Ce misérable chipset ne dispose que d'une seule forme d'onde : le carré (fort heureusement, en polyphonie et avec un contrôle relativement fin du volume).

En quoi cela est complètement ridicule comparé au SID du C64 ? À peu près en tout à vrai dire. Le SID dispose également de trois canaux indépendants, mais il offre le choix de la forme d'onde (carré, triangle, dents de scie ou bruit blanc), le contrôle du rapport cyclique de ces formes élémentaires et l'application de filtres ! Ne cherchez pas, on ne peut pas lutter.

Le ST à la conquête du SID

D'aucuns ont la basse native ! Si historiquement cette supériorité du processeur SID par rapport au AY n'a que peu révolté les possesseurs de CPC, il en était tout autrement des possesseurs d'Atari ST. Eh oui, cet ordinateur 16/32 bits qui espérait rivaliser avec l'Amiga3) (sic) était lui aussi doté d'un YM… imaginez la honte.

Le C64, cette petite machine 8 bits était capable de produire des sons outrageusement variés et des basses dignes de Cliff Burton alors que le ST, tout comme le CPC, devait se contenter des « bips » classiques dévolus aux AY. Pathétique. Certes, le YM, tout comme notre brave AY, permet de créer des sons un peu différents en y ajoutant du bruit (pour faire des percussions) ou en utilisant les enveloppes hard à haute fréquence (pour faire ce que l'on appelle des basses hard). Toutefois, c'était clairement insuffisant pour oser regarder droit dans les yeux un C64 en train de faire vibrer les haut-parleurs de la sono des parents.

Les codeurs ST se sont donc mis au travail et ont inventé ce que tout le monde appelle désormais les sons SID (ou SID-voices en anglais). L'idée était de moduler le triste signal carré du YM par une enveloppe de volume très rapide afin de le transformer en modifiant son rapport cyclique. On est bien loin de la palette de sons d'un vrai processeur SID, mais le simple fait de pouvoir faire cracher au YM ces petits sons cristallins fit le tour de la scène ; et bien vite, tous les musiciens ST se mirent à « faire du SID ».

C'est donc cet effet sonore que nous allons étudier afin de comprendre comment le reproduire sur CPC avec notre bon vieil AY.

Le principe

Comme je m'en doutais, le petit blond à lunettes veut faire du SID mais n'a rien compris au concept. Commençons donc par les bases.

Les sons carrés

Si l'on fait abstraction des basses hard et du générateur bruit, notre AY est uniquement capable de produire des sons carrés. Comme expliqué en détail dans l'article sur le PSG, on en contrôle fort heureusement la fréquence et le volume grâce aux registres 0-1, 2-3, 4-5 (tonalité sur les canaux A, B et C) et 8, 9 et 10 (volume sur ces mêmes trois canaux). Mais on aura beau faire des portamentos ou des vibratos (variations de la tonalité dans le temps), des arpeggios (changement rapide de la note afin de simuler un accord) ou des tremolos (changement du volume au cours du temps), il n'en demeure pas moins que nos sons sont désespérément carrés. Difficile dans ces conditions de produire une musique aux sonorités variés où le thème, l'accompagnement et la basse donneraient l'impression d'être joués par trois instruments différents.

Dans sa palette sonore, le C64 (ou plus exactement son processeur SID) a lui aussi les sons carrés. Oui mais voilà, s'il peut tout comme nous contrôler la fréquence et le volume, il peut surtout choisir le rapport cyclique. Pour ceux qui, comme le petit blond à lunettes, ont séché les cours de physique au lycée, le rapport cyclique d'un son carré représente le ratio entre la durée de sa partie haute et sa période. Sur AY ce rapport est fixe ; il est de 50%. Les sons sont rigoureusement carrés, la demi-période basse dure exactement le même temps que la demi-période haute.

Le petit blond à lunettes commence à décrocher et demande un exemple. Le voici :

Cas d'un signal au rapport cyclique de 50%

Dans ce premier cas, H/T = 0,5 soit 50%; notre signal est parfaitement carré.

Cas d'un signal au rapport cyclique de 70%

Ici on voit bien que l'état haut dure plus longtemps que la partie basse, nous avons un rapport cyclique de l'ordre de H/T = 0,7 soit 70%.

Mais qu'est-ce qu'il nous embête avec le rapport cyclique d'un son ? Eh bien même si visuellement il n'y paraît pas grand chose, au niveau acoustique cette petite différence change à peu près tout. En effet, modifier le rapport cyclique d'un son agit tout simplement sur son timbre. Bon, je vois que certains sont déjà en train de chercher les tarifs postaux de 1983 ; je suis désolé, mais je ne vais pas développer tout ça ici puisque Zik l'a déjà fait .

Les sons SID

Un son SID, au sens du AY, est donc tout simplement un signal carré dont on va modifier le rapport cyclique… à la main ! Eh oui, comme notre AY ne permet pas de le contrôler directement, nous allons agir dessus en reprogrammant très rapidement (en « temps-réel » comme on dit dans le milieu) le registre de volume pendant qu'un son se joue. On dirait qu'une étincelle d'intelligence vient de briller dans les yeux du petit blond à lunettes ! Il vient de comprendre l'idée. En faisant varier le volume entre 0 et la valeur souhaitée pour le son, nous allons tout simplement hacher notre signal carré maussade pour le métamorphoser en un magnifique rectangle au son cristallin : nous avons un son SID !

Petit schéma :

En rouge le son qui sort du AY (programmé via les registres de période), en bleu la valeur du volume sur le canal associé au son (programmé via les registres de volume), en vert le son final (le son SID !)

On voit que le son de base est filtré par le son de hachage afin d'obtenir un nouveau son. Le son de hachage devra généralement être à la même fréquence que celle du son de base, mais pourra légèrement osciller autour de celle-ci afin d'ajouter des modulations au son final. Enfin, lorsque le déphasage des sons de base et de hachage évolue, le son final verra son rapport cyclique évoluer. Ce déphasage étant de fait permanent4), nous avons notre son sid.

À noter que plutôt que d'agir sur le registre de volume, on peut aussi utiliser le registre 7 (le mixeur) pour ouvrir et fermer le canal devant jouer du SID à la-dite fréquence. Le résultat sera sensiblement le même, mais c'est généralement moins souple, surtout si l'on veut produire plusieurs SID-voices à partir des AY-lists du CPC+.

La technique

Vous l'aurez compris, cette technique est assez lourde puisqu'il va falloir reprogrammer le AY très rapidement et différemment selon la fréquence du son à produire. Bonjour l'angoisse.

Sur Atari ST

Sur Atari ST la chose se fait assez simplement à l'aide de leurs deux timers. Sous interruption, ils vont reprogrammer les registres de volume des deux canaux qu'ils souhaitent transformer en SID-voices. Et par la même occasion vous venez de comprendre pourquoi les musiques ST sont généralement limitées à deux SID-voices… ce n'est pas une contrainte de leur YM, mais simplement une limitation de leurs timers qui ne sont qu'au nombre de deux (bien sûr, en programmant plus finement on peut facilement passer outre cette limitation).

Sur CPC

Je vais te faire cracher le morceau ! Sur CPC, d'une part nous n'avons pas de timer programmable, et surtout, le AY n'est programmable qu'au travers du PPI via un nombre de OUT affolant. On voit de suite poindre le problème de performance. Bien sûr, rien n'empêche de faire des sons SID façon « split-raster ». Mais on voit tout de suite la limite du procédé ; ceci explique pourquoi personne n'a jamais sorti de musique qui utilise vraiment des SID-voices sur CPC5) (même si les initiés ont déjà vu des previews jouant des sons SID parfaits en même temps que des rasters par exemple). L'autre gros soucis, c'est que l'on ne pourra raisonnablement produire qu'un seul SID-voice à la fois. Imaginez l'enfer pour synchroniser la reprogrammation de deux SID-voices à des fréquences différentes, le tout en temps-réel. Non merci. Bien sûr, on peut tout arrondir à 64 micro-secondes près et poser des boucles synchronisées avec la vidéo, mais c'est tellement couteux que ça ne vaut pas vraiment le coup au final.

Sur CPC+

Sur CPC+ les choses sont notablement différentes, car nous avons les DMA audio. Nous pouvons les utiliser de deux manières différentes. Soit comme des timers (comme sur Atari ST) qui vont déclencher nos routines de reprogrammation du AY (via la ribambelle de OUT sur le PPI) ; soit comme des copper-lists (comme sur Amiga) qui vont directement reprogrammer les registres du AY.

Par DMA INT (interruption)

L'avantage de cette technique est la précision. Même si les DMA audio ne sont précis qu'à 64 micro-secondes près, il est ensuite aisé de faire l'ajustement à la micro-seconde près dans la routine d'interruption elle-même. On peut donc contrôler très finement le rapport cyclique et avoir des sons SID quasi-parfaits (par rapport à l'Atari ST).

Mais bien sûr, la précision a un coût. D'abord, toutes ces interruptions, ça prend du temps, beaucoup de temps. Et plus le son sera aigu, plus les interruptions seront nombreuses ; le Z80 sera très rapidement saturé. Ensuite, toujours à cause de la vitesse de notre brave Z80, il sera impossible d'avoir plus d'un SID-voice à la fois. Le traitement des interruptions ne sera pas assez rapide.

Cette méthode permet de rejouer à la perfection les musiques ST n'utilisant qu'un seul SID-voice. Elle fut utilisée, par exemple, dans Larsen (pour une musique de Scavenger) et dans la compo de Kris de l'Amstrad Expo 2009 (avec une musique de Mad Max).

Par DMA LOAD (programmation directe)

Ici, l'idée est de créer des AY-lists qui vont directement reprogrammer les registres sur AY. Nous sommes limités par la précision des DMA, soit 64 micro-secondes et il faudra bien gérer les arrondis si on ne veut pas faire pleurer les musiciens6). En revanche, ceux-ci pourront allègrement avoir trois SID-voices simultanément sans grand soucis.

À partir de là, nous avons deux façons de procéder.

Par boucle DMA

On peut simplement faire une boucle DMA qui va alternativement changer le volume. Notre AY-list ressemblerait à ceci :

REPEAT &FFF
PAUSE n
LOAD R8,0
PAUSE n-1
LOAD R8,vol
LOOP

Il suffit ensuite de modifier n et vol à chaque frame en fonction de la fréquence et du volume du son. C'est très peu couteux en temps processeur (la génération de l'AY-list est très rapide) et les calculs des SID voices ne dépendent plus de la fréquence des sons joués. Mais -eh oui, il y a toujours un mais- nous ne sommes pas très précis et jouer des sons SID aigus par ce biais est tout simplement impossible. Ceci étant, si n est bien calculé, il n'en demeure pas moins qu'il s'agit d'un excellent compromis, surtout dans le cas de musiques composées sur CPC plutôt que converties. Le Soundtracker DMA de Zik utilise d'ailleurs cette méthode (mais en programmant le registre 7 plutôt que les registres de volume).

Par DMA déroulés

Alors là, c'est du lourd. Plutôt que d'avoir une AY-list qui boucle et que l'on modifie à la volée, nous allons calculer une AY-list complète frame après frame. C'est couteux en temps processeur -tout de même pas autant que par interruption-, mais c'est presque aussi précis que par interruption. En réalité, seuls les puristes remarqueront une légère différence de timbre entre un SID-voice à « DMA déroulé » et un SID-voice par interruption.

Au final, nos AY-lists sont ici très simples :

LOAD R8,0
PAUSE n1
LOAD R8,vol
PAUSE n1
LOAD R8,0
PAUSE n2
LOAD R8,vol
PAUSE n2
LOAD R8,0
PAUSE n3
LOAD R8,vol
PAUSE n3
...

Comme les players sont généralement à la frame, cette liste devra simplement durer une frame. On voit tout de suite qu'il faudra poker une multitude de valeurs : toutes les pauses (n1, n2, n3, etc.) et aussi le volume (vol, autant de fois que nécessaire). De plus, on comprend bien que plus un son sera aigu, plus il faudra poker de valeurs (l'AY-list sera plus longue !).

En pratique, cette technique n'a qu'une seule utilité : permettre de rejouer fidèlement des musiques SID composées sur ST en limitant la charge processeur par rapport aux interruptions, et en autorisant les musiques ayant deux voire trois SID-voices. C'est ce type de player que j'avais codé pour Iron et qu'il a intégré dans la très controversée Killmax.

La pratique

Face à l'insistance du petit blond à lunettes, je vais vous livrer un petit programme d'exemple qui permet de jouer des musiques SID. Ce player est une variante de celui utilisé dans Killmax et est conçu de façon très modulaire (et peu optimale) afin d'être facile à lire et à comprendre. Il fonctionne exclusivement sur CPC+ en utilisant des AY-lists DMA déroulées comme décrit précédemment pour jouer les sons SID.

Les fichiers musicaux

Les musiques utilisées par le player sont dans un format dédié (que j'ai simplement appelé AY+). Il est assez similaire au format YM5/6 mais a été adapté pour le CPC. Les flux de registres sont compressés indépendamment à l'aide de PuCrunch et stockés de façon plus optimale.

Ces flux contiennent donc les valeurs des pseudo-registres du PSG au fil des frames. Certains sont directement des registres du AY, d'autres sont des registres composites qui seront décodés avant d'être utilisés. En outre, ce format ne se limite pas à la gestion des “SID-voices” mais permet de stocker les informations d'autres effets spéciaux comme les “sinus-SID”, les “synch-buzzer” et les “digidrums” qui sont également utilisés sur ST (le player ci-après ne les gère toutefois pas).

Le format AY+

Les flux compressés sont stockés dans l'ordre dans le fichier qui est sauvé en RAW sans aucune forme de header (un fichier de configuration décrit ci-après joue le rôle de header).

Voici donc la description des données extraites de chaque flux :

Flux Codage Présence Description
0 HardA SidA DrumA SinA BuzzA D10 D9 D8 Toujours D8-D10 : bits de poids fort de la période sur le canal A (registre 0)
1 D7 D6 D5 D4 D3 D2 D1 D0 Toujours D7-D0 : bits de poids faible de la période sur le canal A (registre 0)
2 HardB SidB DrumB SinB BuzzB D10 D9 D8 Toujours D8-D10 : bits de poids fort de la période sur le canal B (registre 1)
3 D7 D6 D5 D4 D3 D2 D1 D0 Toujours D7-D0 : bits de poids faible de la période sur le canal B (registre 1)
4 HardC SidC DrumC SinC BuzzC D10 D9 D8 Toujours D8-D10 : bits de poids fort de la période sur le canal C (registre 2)
5 D7 D6 D5 D4 D3 D2 D1 D0 Toujours D0-D7 : bits de poids faible de la période sur le canal C (registre 2)
6 D4 D3 D2 D1 D0 Toujours D0-D4 : niveau de bruit (registre 6)
7 D5 D4 D3 D2 D1 D0 Toujours D0-D5 : mixeur (registre 7)
8 D3 D2 D1 D0 Toujours D0-D3 : Volume sur le canal A (registre 8)
9 D3 D2 D1 D0 Toujours D0-D3 : Volume sur le canal B (registre 9)
10 D3 D2 D1 D0 Toujours D0-D3 : Volume sur le canal C (registre 10)
11 H2 H1 H0 D4 D3 D2 D1 D0 Si HardEnv7) D0-D4 : bits de poids fort de la période de l'enveloppe hard (registre 11), H0-H2 : forme de l'enveloppe hard8)
12    D7       D6       D5       D4       D3       D2       D1       D0    Si HardEnv9) D7-D0 : bits de poids faible de la période de l'enveloppe hard (registre 12)
13 D7 D6 D5 D4 D3 D2 D1 D0 Si FX A10) D7-D6 : bits de poids fort de la période11) de l'effet spécial sur le canal A
14 D7 D6 D5 D4 D3 D2 D1 D0 Si FX A12) D7-D6 : bits de poids faible de la période13) de l'effet spécial sur le canal A
15 D7 D6 D5 D4 D3 D2 D1 D0 Si FX B14) D7-D6 : bits de poids fort de la période15) de l'effet spécial sur le canal B
16 D7 D6 D5 D4 D3 D2 D1 D0 Si FX B16) D7-D6 : bits de poids faible de la période17) de l'effet spécial sur le canal B
17 D7 D6 D5 D4 D3 D2 D1 D0 Si FX C18) D7-D6 : bits de poids fort de la période19) de l'effet spécial sur le canal C
18 D7 D6 D5 D4 D3 D2 D1 D0 Si FX C20) D7-D6 : bits de poids faible de la période21) de l'effet spécial sur le canal C

Codage de l'enveloppe hard (flux 11) :

H0-H2 Registre 13
0 0, 1, 2, 3 ou 9
1 4, 5, 6, 7 ou 15
2 8
3 12
4 10
5 14
6 11
7 13

Les fichiers de configuration

Tous les flux ne sont pas forcément présents dans chaque fichier AY+. Par exemple une musique qui n'utilise pas les enveloppes hard aura un fichier dans lequel le flux des registres 11, 12 et 13 sera absent. L'absence ou la présence des flux est déterminée à partir d'un fichier de configuration du player qui est fourni avec chaque fichier de données.

Ces fichiers contiennent les informations sur la taille de chaque flux (ce qui permet de les localiser dans le fichier AY+) ainsi que sur les effets sonores utilisés par la musique (ce qui permet d'identifier les flux éventuellement absents et au player de s'assembler différemment selon le cas).

Au final, il s'agit simplement d'un bout de code assembleur destiné à être inclus dans le player avant d'être assemblé (sous Maxam 1.5).

Voici un exemple :

; 
; Configuration de la musique 
; 
let SIDOnA=0 
let SIDOnB=1 
let SIDOnC=1 
let HardEnv=1 
; 
; Table contenant la taille des streams 
; 
StreamsSize 
	dw  1112,1761, 143, 693, 147,1882, 200, 253 
	dw   229, 247, 567, 206,1635+24+24, 468, 679, 990,1748

La décompression

La bibliothèque de décompression (qui permet de sortir les registres des flux compressés pour chaque frame) que je vous fournis ici a été développée à partir de PuCrunch dont l'implémentation a été modifiée pour pouvoir gérer du streaming avec un dictionnaire de 256 octets. Elle n'est pas performante et aucun effort d'optimisation n'a été effectué dessus ; c'est de toute manière un peu hors sujet au regard du sujet de cet article. Si vous voulez en savoir plus sur PuCrunch, je vous invite à allez jeter un coup d'œil ici22). Pour le reste, libre à vous d'optimiser cette bibliothèque de streaming, ou mieux, d'implémenter la votre car il est possible de faire beaucoup plus performant tant en terme de compression que de temps machine utilisé pour la décompression.

Ceci étant, si vous désirez créer des fichiers AY+ par vous même, voici la ligne de commande qu'il vous faudra utiliser pour compresser chacun des flux afin que le décompresseur utilisé ici puisse fonctionner correctement :

pucrunch -c0 -d -r256 <fichier d'entrée contenant les données brutes> <fichier de sortie compressé>

Ensuite, concaténez simplement les fichiers compressées de chaque flux en un seul gros fichier et créez un fichier de configuration correspondant (avec la taille de chaque flux compressé et les autres informations pour le player).

Le player

Comme vous l'aurez compris, le player n'est pas générique mais s'assemble conditionnellement en fonction du fichier de configuration qui vient avec chaque musique au format AY+. Le tout est prévu pour s'assembler avec Maxam 1.5. Tout comme pour la bibliothèque de décompression, le code n'est absolument pas optimisé ce qui lui permet de rester relativement facile à comprendre (si vous vous essayez à l'exercice d'optimisation, vous devriez sans problème parvenir à diviser le temps machine nécessaire par deux).

Le principe de fonctionnement est assez élémentaire. Une fois les flux décompressés, le player va donc simplement prendre un registre de chaque puis reprogrammer le PSG et calculer les AY-lists pour les SID pour la frame en cours. La partie qui nous intéresse ici le plus est bien évidemment celle qui décode et gère les pseudo-registres des effets spéciaux pour les “SID-voices”.

Les toutes premières lignes permettent de configurer la musique avec :

Le petit blond à lunettes est circonspect concernant ce dernier point. Je m'explique. Le player est en effet en temps constant avec un système de buffer dynamique qui se remplit à l'avance pendant les phases où jouer la musique prend moins de temps. Ainsi, il commence à s'exécuter à la première ligne et fera le maximum de travail jusqu'à la ligne indiquée avant d'être interrompu jusqu'à la prochaine frame. Ce prebuffer a une taille de 256 octets par flux.

Lorsque la musique se joue, des rasters permettent de visualiser le temps machine pris par chaque partie du code dans la limite du temps maximum configuré (player en jaune, le gestionnaire de flux et le générateur d'AY-lists en rouge, le décompresseur en magenta). Le fond d'écran change également de couleur et tend plus ou moins vers le rouge selon la marge disponible dans le buffer de prebuffering (si c'est noir tout va bien, et plus c'est rouge vif, plus le buffer risque d'être vidé trop tôt). Si le décompresseur a pris trop de retard par rapport au player (c'est-à-dire s'il n'a pas réussi à alimenter le buffer assez rapidement au fil de frames) alors un octet à &FF est poké en &C000 (en haut à gauche de l'écran). Si le générateur d'AY-lists n'a pas pu terminer son travail dans cette même limite de temps, alors le border clignote. Dans ces deux cas il faut augmenter le numéro de ligne maximum d'exécution pour le player !

Enfin, je ne vais donc pas vous en détailler tout le fonctionnement ici, il est de toute façon largement commenté.

Amusez-vous bien !

Listings

Le player AY+

Télécharger le listing au format Maxam 1.5

; AY+ DMA Player
; OffseT/Futurs' pour Iron
; Mai 2007 - Octobre 2007
;   -> Version initiale
; Mai 2009
;   -> Ajout d'une bufferisation dynamique
;   -> Maitrise du temps CPU max du stream
 
	Org &9000
	Nolist
	Let entry=$
	jp start
 
	read"cave.cfg"		; Nom de la musique
 
Bank		Equ &c4		; Bank de la musique
StreamsStart	Equ &4000	; Adresse de la musique
 
Buffers		Equ &d000	; Adresse des n buffers de &100 octets
				; n dépend de la musique, voir le fichier .cfg
				; min=13 et max=17 (19 en théorie mais bon...)
BreakLine	Equ 80		; Numéro de ligne limite pour le player
 
; ###################################################################
; Ici commencent les inits du programme principal
; ###################################################################
Start	ld a,tableint/256
	ld i,a
	ld a,tableint mod 256
	or 1
	ld hl,&6805
	ld bc,&7fb8
	ld ix,&6c02
	ld e,&a0
 
	di
	im 2
	out (c),c
	ld (hl),a
	ld (ix+0),0
	ld (ix+4),0
	ld (ix+8),0
	out (c),e
	ei
 
	exx
	ex af,af'
	push af
	push bc
	push de
	push hl
 
	ld bc,&7f00+bank
	out (c),c
	ld c,&8e
	out (c),c
 
	call initplayer
 
; ###################################################################
; Ici commence le programme principal
; ###################################################################
; Boucle principale
 
Loop	ld bc,&f500
Synch	in a,(c)
	rra
	jp nc,synch
 
	ld a,(alertbuffer)		; Récupération du flag d'erreur buffer
	ld (&c000),a
 
	ld a,(prebufferingcount+1)	; Colorisation en fonction du buffer
	ld c,a
	ld a,prebuffers
	sub c
	ld bc,&7fb8
	out (c),c
	sla a:sla a:sla a:sla a
	ld (&6400),a
	xor a
	ld (&6401),a
	ld bc,&7fa0
	out (c),c
 
	ld bc,&7f10
	out (c),c
 
	ld bc,&7fb8	; On place une synchro pour appeler "runplayer"
	out (c),c
	ld a,1
	ld (&6800),a
	ld bc,&7fa0
	out (c),c
	halt
 
	ld bc,&7f00+74
	out (c),c
	call runplayer	; Doit etre stable sur la frame !
	ld bc,&7f00+76
	out (c),c
	call getframeplayer
	ld bc,&7f00+79
	out (c),c
	ld a,breakline	; Numéro de ligne à ne pas dépasser
	call getbuffersplayer
	ld bc,&7f00+84
	out (c),c
 
; Test clavier
 
	ld bc,&f40e
	out (c),c
	ld bc,&f6c0
	out (c),c
	xor a
	out (c),a
	ld bc,&f792
	out (c),c
	ld bc,&f648
	out (c),c
	ld b,&f4
	in a,(c)
	ld bc,&f782
	out (c),c
	ld bc,&f600
	out (c),c
	rra
	rra
	rra
	jr nc,broken
EndLoop	jp loop
 
; ###################################################################
; Ici commence la fin du programme principal
; ###################################################################
Broken	di
	ld bc,&7fb8
	out (c),c
 
	ld a,&f0
	ld (&6c0f),a
	xor a
	ld (&6800),a
 
	ld bc,&7fa0
	out (c),c
	ei
 
	ld bc,&7fc0
	out (c),c
 
	pop hl
	pop de
	pop bc
	pop af
	exx
	ex af,af'
 
	call &bca7
	im 1
	ret
 
; ###################################################################
; A partir d'ici, on ne touche plus à rien c'est le player
; ###################################################################
;
; Configuration du player
;
let AutoSIDA=0	; Calcul SID automatique (ne pas utiliser le timer ST)
let AutoSIDB=0
let AutoSIDC=0  
let SyncSID=0	; Synchroniser l'attaque du SID avec le AY (expérimental)
;
; Flag d'alerte, doit toujours valoir zéro
;
AlertBuffer	db 0
;
; Initialisations globales
;
StreamsCount	Equ 11+hardenv+hardenv+sidona+sidona+sidonb+sidonb+sidonc+sidonc
PreBuffers	Equ 256/streamscount-1
;
; Initialisation et prébufferisation des streams
;
InitPlayer
	ld a,prebuffers
	ld (prebufferingcount+1),a
	xor a
	ld (alertbuffer),a
	ld (currentstreamcount+1),a
	ld (framebufferpointer+1),a
	ld hl,streamscontextadr
	ld (currentstreamcontextadr+2),hl
	ld hl,goprebuffering
	ld (pcstack),hl
	ld hl,regstack
	ld (workstack+1),hl
	ld hl,regsoldvalues
	ld de,regsoldvalues+1
	ld (hl),&ff
	ld bc,31
	ldir
 
IF sidona
	xor a
	ld (whichdmalista+1),a
	ld hl,primarysidlistapredelay
	ld (dmalista+1),hl
	ld hl,primarysidlista
	ld de,primarysidlista+4
	ld bc,312-4
	ldir
	ld hl,secondarysidlista
	ld de,secondarysidlista+4
	ld bc,312-4
	ldir
ENDIF
IF sidonb
	xor a
	ld (whichdmalistb+1),a
	ld hl,primarysidlistbpredelay
	ld (dmalistb+1),hl
	ld hl,primarysidlistb
	ld de,primarysidlistb+4
	ld bc,312-4
	ldir
	ld hl,secondarysidlistb
	ld de,secondarysidlistb+4
	ld bc,312-4
	ldir
ENDIF
if sidonc
	xor a
	ld (whichdmalistc+1),a
	ld hl,primarysidlistcpredelay
	ld (dmalistc+1),hl
	ld hl,primarysidlistc
	ld de,primarysidlistc+4
	ld bc,312-4
	ldir
	ld hl,secondarysidlistc
	ld de,secondarysidlistc+4
	ld bc,312-4
	ldir
ENDIF
	ld de,buffers
	ld hl,streamsstart
	ld ix,streamscontextadr
	ld iy,streamssize
	ld b,streamscount
LoopInitStreams
	push bc
	push hl
	push de
	call initstream
	pop hl
	ld bc,bufferssize
	add hl,bc
	ex de,hl
	pop hl
	ld b,(iy+1)
	ld c,(iy+0)
	add hl,bc
	inc iy
	inc iy
	ld bc,streamscontextsize
	add ix,bc
	pop bc
	djnz loopinitstreams
 
	call getframeplayer
	ret
 
;
; Mise à jour des buffers
;
 
	ds 8
RegStack
	dw 0,0,0,0,0,0
PCStack	dw goprebuffering
 
; A = ligne max jusqu'à laquelle travailler
GetBuffersPlayer
	di
	ld bc,&7fb8
	out (c),c
	ld (&6800),a
	ld bc,&7fa0
	out (c),c
	ld hl,stopbufferinginterrupt
	ld (intdma0),hl
	ld (intrast),hl
 
	ld a,(prebufferingcount+1)
	dec a		; Soucis si a était à zéro ! (buffer vide)
	cp &ff
	jr nz,goonbuffersok
	ld (alertbuffer),a
GoOnBuffersOK
	ld (prebufferingcount+1),a
	ld (restoresp+1),sp
WorkStack
	ld sp,regstack
	pop af
	pop bc
	pop de
	pop hl
	pop ix
	pop iy
	ei
	ret
 
StopBufferingInterrupt
	push iy
	push ix
	push hl
	push de
	push bc
	push af
	ld hl,rasterinterrupt
	ld (intdma0),hl
	ld (intrast),hl
	ld (workstack+1),sp
RestoreSP
	ld sp,0
	ei
	ret
 
GoPreBuffering
	ei
	nop
	di
PreBufferingCount
	ld a,prebuffers
	cp prebuffers
	jr z,goprebuffering
	ei
	ld a,(prebufferingcount+1)
	inc a
	ld (prebufferingcount+1),a
 
CurrentStreamContextAdr
	ld ix,streamscontextadr
	call getnextbytes
CurrentStreamCount
	ld a,0
	inc a
	cp streamscount
	jr nz,updatetonextstream
ResetToFirstStream
	xor a
	ld ix,streamscontextadr-streamscontextsize
UpdateToNextStream
	ld (currentstreamcount+1),a
	ld bc,streamscontextsize
	add ix,bc
	ld (currentstreamcontextadr+2),ix
 
	jr goprebuffering
 
;
; Récupération des données du framedata courant dans les buffers
;
GetFramePlayer
	ld hl,framedata
	ld d,buffers/256
FrameBufferPointer
	ld e,0
	ld b,streamscount
LoopGetFrameData
	ld a,(de)
	ld (hl),a
	inc hl
	inc d
	djnz loopgetframedata
 
; Mise à jour du pointeur de buffer
 
	ld a,(framebufferpointer+1)
	inc a
	ld (framebufferpointer+1),a
 
; Construction de la table des regsvalues à partir du framebuffer
 
	ld ix,framedata
	ld de,regsvalues
 
	ld bc,&8007
	ld a,(ix+f_R0):		ld (de),a:inc e	; R0
	ld a,(ix+f_R1):and c:	ld (de),a:inc e	; R1
	ld a,(ix+f_R2):		ld (de),a:inc e	; R2
	ld a,(ix+f_R3):and c:	ld (de),a:inc e	; R3
	ld a,(ix+f_R4):		ld (de),a:inc e	; R4
	ld a,(ix+f_R5):and c:	ld (de),a:inc e	; R5
	ld a,(ix+f_R6):		ld (de),a:inc e	; R6
	ld a,(ix+f_R7):		ld (de),a:inc e	; R7
	ld a,(ix+f_R1):and b
	srl a:srl a:srl a:ld h,a
	ld a,(ix+f_R8):or h:	ld (de),a:inc e	; R8
	ld a,(ix+f_R3):and b
	srl a:srl a:srl a:ld h,a
	ld a,(ix+f_R9):or h:	ld (de),a:inc e	; R9
	ld a,(ix+f_R5):and b
	srl a:srl a:srl a:ld h,a
	ld a,(ix+f_R10):or h:	ld (de),a:inc e	; R10
IF hardenv
	ld a,(ix+f_R11):	ld (de),a:inc e	; R11
	ld a,(ix+f_R12)
	ld h,a:and c:		ld (de),a:inc e	; R12
	ld a,h:and &f8
	srl a:srl a:srl a:srl a:srl a
	ld c,a:ld b,0
	ld hl,tablereg13:add hl,bc
	ld a,(hl):		ld (de),a:inc e	; R13
	ld c,&7
ENDIF
	ld a,(ix+f_R1):and &70:	ld (de),a:inc e	; FXA
IF sidona
	ld hl,putfxa_vol	; Désactive volume A si SID A
	bit 6,a
	jr nz,skipr8
	ld hl,putpsg2
SkipR8	ld (poker8+1),hl
IF autosida
	ld a,(ix+f_R1):and c:ld h,a:ld l,(ix+f_R0)
	ld a,7:and l:ld b,a
	srl h:rr l:srl h:rr l:srl h:rr l
	ld a,l:			ld (de),a:inc e	; FreqFXA High
	ld a,b
	sla a:sla a:sla a:	ld (de),a:inc e	; FreqFXA Low
ELSE
	ld a,(ix+f_FXA_H)
	ld (de),a:inc e				; FreqFXA High
	ld a,(ix+f_FXA_L)
	ld (de),a:inc e				; FreqFXA Low
ENDIF
ENDIF
	ld a,(ix+f_R3):and &70:	ld (de),a:inc e	; FXB
IF sidonb
	ld hl,putfxb_vol	; désactive volume B si SID B
	bit 6,a
	jr nz,skipr9
	ld hl,putpsg2
SkipR9	ld (poker9+1),hl
IF autosidb
	ld a,(ix+f_R3):and c:ld h,a:ld l,(ix+f_R2)
	ld a,7:and l:ld b,a
	srl h:rr l:srl h:rr l:srl h:rr l
	ld a,l:			ld (de),a:inc e	; FreqFXB High
	ld a,b
	sla a:sla a:sla a:	ld (de),a:inc e	; FreqFXB Low
ELSE
	ld a,(ix+f_FXB_H)
	ld (de),a:inc e				; FreqFXB High
	ld a,(ix+f_FXB_L)
	ld (de),a:inc e				; FreqFXB Low
ENDIF
ENDIF
	ld a,(ix+f_R5):and &70:	ld (de),a:inc e	; FXC
IF sidonc
	ld hl,putfxc_vol	; désactive volume C si SID C
	bit 6,a
	jr nz,skipr10
	ld hl,putpsg2
SkipR10	ld (poker10+1),hl
IF autosidc
	ld a,(ix+f_R5):and c:ld h,a:ld l,(ix+f_R4)
	ld a,7:and l:ld b,a
	srl h:rr l:srl h:rr l:srl h:rr l
	ld a,l:			ld (de),a:inc e	; FreqFXC High
	ld a,b
	sla a:sla a:sla a:	ld (de),a:inc e	; FreqFXC Low
ELSE
	ld a,(ix+f_FXC_H)
	ld (de),a:inc e				; FreqFXC High
	ld a,(ix+f_FXC_L)
	ld (de),a:inc e				; FrefFXC Low
ENDIF
ENDIF
 
IF sidona
; appel de la routine de préparation de l'ay list sid A
	ld a,(regsvalues+FXA)
	bit 6,a		; Check SID
	call nz,createdmalista
ENDIF
IF sidonb
; appel de la routine de préparation de l'ay list sid B
	ld a,(regsvalues+FXB)
	bit 6,a		; Check SID
	call nz,createdmalistb
ENDIF
IF sidonc
; appel de la routine de préparation de l'ay list sid C
	ld a,(regsvalues+FXC)
	bit 6,a		; Check SID
	call nz,createdmalistc
ENDIF
	ret
 
;
; Programmation du PSG et DMA à partir des regsvalues
;
RunPlayer
; appel de la routine de lancement de l'ay list sid
	di
IF sidona
	ld a,(regsvalues+FXA)
	call putfxa
ENDIF
IF sidonb
	ld a,(regsvalues+FXB)
	call putfxb
ENDIF
IF sidonc
	ld a,(regsvalues+FXC)
	call putfxc
ENDIF
	ei
 
	ld de,regsvalues		; DE=regvalues
	ld h,d
	ld l,regsoldvalues MOD 256	; HL=oldregvalues
 
	ld a,(de):cp (hl):call nz,putpsg2	; R0
	inc e:inc l
	ld a,(de):cp (hl):call nz,putpsg2	; R1
	inc e:inc l
	ld a,(de):cp (hl):call nz,putpsg2	; R2
	inc e:inc l
	ld a,(de):cp (hl):call nz,putpsg2	; R3
	inc e:inc l
	ld a,(de):cp (hl):call nz,putpsg2	; R4
	inc e:inc l
	ld a,(de):cp (hl):call nz,putpsg2	; R5
	inc e:inc l
	ld a,(de):cp (hl):call nz,putpsg2	; R6
	inc e:inc l
	ld a,(de):cp (hl):call nz,putpsg2	; R7
	inc e:inc l
	ld a,(de):cp (hl)			; R8
PokeR8	call nz,putpsg2:inc e:inc l
	ld a,(de):cp (hl)			; R9
PokeR9	call nz,putpsg2:inc e:inc l
	ld a,(de):cp (hl)			; R10
PokeR10	call nz,putpsg2:inc e:inc l
IF hardenv
	ld a,(de):cp (hl):call nz,putpsg2	; R11
	inc e:inc l
	ld a,(de):cp (hl):call nz,putpsg2	; R12
	inc e:inc l
	ld a,(de):cp (hl):call nz,putpsg2	; R13
ENDIF
	ret
 
; E=Reg
; A=Val
; Sauve A dans (HL)
PutPSG2	ld (hl),a
	ld b,&f4
	di
	out (c),e
	ld bc,&f6c0
	out (c),c
	ld c,0
	out (c),c
	ld b,&f4
	out (c),a
	ld a,&80
	ld b,&f6
	out (c),a
	out (c),c
	ei
	ret
 
IF sidona
PutFXA_Vol
	ld (hl),a
	ret
; E=FXA
; A=Val
PutFXA	bit 6,a		; Check SID, check reg7 ?
	jp nz,startdmalista
StopFXA	ld bc,&7fb8
IF sidonb+sidonc	; On compense en temps constant
	ds 25		; uniquement si d'autres DMA viennent
ENDIF			; après
	out (c),c
	ld a,(&6c0f)	; Stop DMA0
	and &fe
	ld (&6c0f),a
	ld c,&a0
	out (c),c
IF sidonb+sidonc
	ds 22
ENDIF
	ret
ENDIF
IF sidonb
PutFXB_Vol
	ld (hl),a
	ret
; E=FXB
; A=Val
PutFXB	bit 6,a		; Check SID, check reg7 ?
	jp nz,startdmalistb
StopFXB	ld bc,&7fb8
IF sidonc		; On compense en temps constant
	ds 25		; uniquement si d'autres DMA
ENDIF			; viennent après
	out (c),c
	ld a,(&6c0f)	; Stop DMA1
	and &fd
	ld (&6c0f),a
	ld c,&a0
	out (c),c
IF sidonc
	ds 22
ENDIF
	ret
ENDIF
IF sidonc
PutFXC_Vol
	ld (hl),a
	ret
; E=FXC
; A=Val
PutFXC	bit 6,a		; Check SID, check reg7 ?
	jp nz,startdmalistc
StopFXC	ld bc,&7fb8	; Pas de compensation en temps constant comme pour
	out (c),c	; A et B car aucun autre DMA ne viendra après
	ld a,(&6c0f)	; Stop DMA2
	and &fb
	ld (&6c0f),a
	ld c,&a0
	out (c),c
	ret
ENDIF
 
IF sidona
; Routines de creation des l'AYList
; A = volume SID
; DE = frequence SID
CreateDMAListA
	ld a,(regsvalues+FXA_H)
	ld d,a				; D=nbre ligne periode sid
IF syncsid
	ld a,(regsoldvalues+FXA_H)
	cp d
	jr z,nonewfxa_h
	ld a,d
	ld (regsoldvalues+FXA_H),a
	xor a
	ld (runtimeoffseta+1),a
NoNewFXA_H
ENDIF
	ld a,(regsvalues+FXA_L)
	ld e,a				; E=retenue en nops
IF syncsid
	ld a,(regsoldvalues+FXA_L)
	cp e
	jr z,nonewfxa_l
	ld a,e
	ld (regsoldvalues+FXA_L),a
	xor a
	ld (runtimeoffseta+1),a
NoNewFXA_L
ENDIF
	ld a,(regsvalues+R8)		; On recupere le volume courant et
	ld (pokeswitchvolumea+1),a	; on le met en switch dans la dma loop
	ld b,a				; B=volume du canal
DMAListA
	ld hl,primarysidlistapredelay	; AYList a utiliser
PreDelayA
	ld a,255		; On recupere le nombre de lignes qui restaient
	ld (hl),a		; a attendre de la frame precedente et on le
	inc hl			; poke dans la phase d'init de l'AYList
	inc hl
	inc a
	ex af,af'
SIDVolumeA			; On recupere l'etat du volume SID et
	ld a,15			; le poke dans la phase d'init de l'AYList
	ld (hl),a
	inc hl
	inc hl
	or a			; si le volume courant du canal a change depuis
	jr z,nonewvola		; la frame precedente on le RAZ
	ld a,b
NoNewVolA
	ex af,af'		; A'=volume SID courant
;
; On crée l'AYList
;
; IN	; A'=volume SID courant
;	; A=nombre de lignes deja traitee en init
;	; D,E=periode SID en base 64
;	; (pokeswichvolume+1)=volume canal courant
;	; (dmacarry+1)=retenue periode SID en cours
; OUT	; A=nombre de lignes SID debordant de la frame
;	; A'=volume SID courant
MakeDMAListA
	exx                     
	ld h,0			; HL'=compteur de ligne
	ld l,a
	ld b,h			; B'=0 (constante)
	ld e,56			; E'=312 modulo 256 (constante)
LoopDMA_A
	exx
	ld b,d			; B=nombre de lignes a attendre
RunTimeOffsetA
	ld a,0			; A=retenue du calcul precedent
PokeA_1	add a,e
	ld c,a			; C=retenue globale
	and &3f
	ld (runtimeoffseta+1),a	; Est-ce qu'on a plus d'une
	cp c			; ligne de retenue ?
	jr z,nodmaoverflowa
DMAOverflowA
	inc b
NoDMAOverflowA			; a une ligne moins a attendre
	ld a,b
	dec b
	ld (hl),b		; Pokage de l'unite de pause
	inc hl
;ld (hl),&10
	inc hl
	ex af,af'
PokeSwitchVolumeA		; Calcul du volume SID courant
	xor 15
	ld (hl),a		; Pokage du volume SID
	ex af,af'
	inc hl
;ld (hl),8
	inc hl
	exx
	ld c,a			; Test de fin de frame
	add hl,bc
	bit 0,h
	jr z,loopdma_a
	ld a,l
	sub a,e
	jr c,loopdma_a
	exx
;ld (hl),&20
;inc hl
;ld (hl),&40
	dec a			; Correction du nombre de lignes
				; en overflow (255=pas d'overflow)
	ld (predelaya+1),a	; On recupere le nombre de ligne qui debordent de
				; la frame pour les mettre au debut de la suivante
	ex af,af'		; On recupere le volume SID courant pour
	ld (sidvolumea+1),a	; reprendre au bon etat a la frame suivante
	ret
;
; Routines de lancement des AYLists A
;
StartDMAListA
	ld e,2
	ld bc,4
	ld hl,(dmalista+1)		; On ne lance l'AYList du debut
	ld a,(hl)			; que si on avait une pre-attente
	cp 255				; sinon si on n'avait pas de pre-attente
	jr z,startwithnopredelaya	; alors on saute toute la phase d'init
;	or a				; sinon si on avait juste une ligne de
;	jr nz,startwithpredelaya	; pre-attente on saute uniquement
	ld c,e				; l'instruction de pause
StartWithNoPredelayA
	add hl,bc
StartWithPredelayA			; Ici HL=adresse AYList a lancer
	ld bc,&7fb8
	out (c),c
	ld (&6c00),hl	; adr DMA0
	ld a,(&6c0f)
	or 1
	ld (&6c0f),a	; run DMA0
	ld c,&a0
	out (c),c
	ld hl,primarysidlistapredelay	; On switch les AYLists de travail
	ld de,secondarysidlistapredelay
WhichDMAListA
	ld a,0
	xor 1
	ld (whichdmalista+1),a
	jp z,startwithprimarya
StartWithSecondary
	ld (dmalista+1),de
	ret
StartWithPrimaryA
	nop
	ld (dmalista+1),hl
	ret
ENDIF
IF sidonb
; Routines de creation des AYLists B
; A = volume SID
; DE = frequence SID
CreateDMAListB
	ld a,(regsvalues+FXB_H)
	ld d,a				; D=nbre ligne periode sid
IF syncsid
	ld a,(regsoldvalues+FXB_H)
	cp d
	jr z,nonewfxb_h
	ld a,d
	ld (regsoldvalues+FXB_H),a
	xor a
	ld (runtimeoffsetb+1),a
NoNewFXB_H
ENDIF
	ld a,(regsvalues+FXB_L)
	ld e,a				; E=retenue en nops
IF syncsid
	ld a,(regsoldvalues+FXB_L)
	cp e
	jr z,nonewfxb_l
	ld a,e
	ld (regsoldvalues+FXB_L),a
	xor a
	ld (runtimeoffsetb+1),a
NoNewFXB_L
ENDIF
	ld a,(regsvalues+R9)		; On recupere le volume courant et
	ld (pokeswitchvolumeb+1),a	; on le met en switch dans la dma loop
	ld b,a				; B=volume du canal
DMAListB
	ld hl,primarysidlistbpredelay	; AYList a utiliser
PreDelayB
	ld a,255		; On recupere le nombre de lignes qui restaient
	ld (hl),a		; a attendre de la frame precedente et on le
	inc hl			; poke dans la phase d'init de l'AYList
	inc hl
	inc a
	ex af,af'
SIDVolumeB			; On recupere l'etat du volume SID et
	ld a,15			; le poke dans la phase d'init de l'AYList
	ld (hl),a
	inc hl
	inc hl
	or a			; si le volume courant du canal a change depuis
	jr z,nonewvolb		; la frame precedente on le RAZ
	ld a,b
NoNewVolB
	ex af,af'		; A'=volume SID courant
;
; On crée l'AYList B
;
; IN	; A'=volume SID courant
;	; A=nombre de lignes deja traitee en init
;	; D,E=periode SID en base 64
;	; (pokeswichvolume+1)=volume canal courant
;	; (runtimeoffsetb+1)=retenue periode SID en cours
; OUT	; A=nombre de lignes SID debordant de la frame
;	; A'=volume SID courant
MakeDMAListB
	exx                     
	ld h,0			; HL'=compteur de ligne
	ld l,a
	ld b,h			; B'=0 (constante)
	ld e,56			; E'=312 modulo 256 (constante)
LoopDMA_B
	exx
	ld b,d			; B=nombre de lignes a attendre
RunTimeOffsetB
	ld a,0			; A=retenue du calcul precedent
PokeB_1	add a,e
	ld c,a			; C=retenue globale
	and &3f
	ld (runtimeoffsetb+1),a	; Est-ce qu'on a plus d'une
	cp c			; ligne de retenue ?
	jr z,nodmaoverflowb
DMAOverflowB
	inc b
NoDMAOverflowB			; a une ligne moins a attendre
	ld a,b
	dec b
	ld (hl),b		; Pokage de l'unite de pause
	inc hl
ld (hl),&10
	inc hl
	ex af,af'
PokeSwitchVolumeB		; Calcul du volume SID courant
	xor 15
	ld (hl),a		; Pokage du volume SID
	ex af,af'
	inc hl
;ld (hl),9
	inc hl
	exx
	ld c,a			; Test de fin de frame
	add hl,bc
	bit 0,h
	jr z,loopdma_b
	ld a,l
	sub a,e
	jr c,loopdma_b
	exx
ld (hl),&20
inc hl
ld (hl),&40
	dec a			; Correction du nombre de lignes
				; en overflow (255=pas d'overflow)
	ld (predelayb+1),a	; On recupere le nombre de ligne qui debordent de
				; la frame pour les mettre au debut de la suivante
	ex af,af'		; On recupere le volume SID courant pour
	ld (sidvolumeb+1),a	; reprendre au bon etat a la frame suivante
	ret
;
; Routines de lancement des AYLists B
;
StartDMAListB
	ld e,2
	ld bc,4
	ld hl,(dmalistb+1)		; On ne lance l'AYList du debut
	ld a,(hl)			; que si on avait une pre-attente
	cp 255				; sinon si on n'avait pas de pre-attente
	jr z,startwithnopredelayb	; alors on saute toute la phase d'init
;	or a				; sinon si on avait juste une ligne de
;	jr nz,startwithpredelayb	; pre-attente on saute uniquement
	ld c,e				; l'instruction de pause
StartWithNoPredelayB
	add hl,bc
StartWithPredelayB			; Ici HL=adresse AYList a lancer
	ld bc,&7fb8
	out (c),c
	ld (&6c04),hl	; adr DMA1
	ld a,(&6c0f)
	or 2
	ld (&6c0f),a	; run DMA1
	ld c,&a0
	out (c),c
	ld hl,primarysidlistbpredelay	; On switch les AYLists de travail
	ld de,secondarysidlistbpredelay
WhichDMAListB
	ld a,0
	xor 1
	ld (whichdmalistb+1),a
	jp z,startwithprimaryb
StartWithSecondaryB
	ld (dmalistb+1),de
	ret
StartWithPrimaryB
	nop
	ld (dmalistb+1),hl
	ret
ENDIF
IF sidonc
; Routines de creation des AYLists C
; A = volume SID
; DE = frequence SID
CreateDMAListC
	ld a,(regsvalues+FXC_H)
	ld d,a				; D=nbre ligne periode sid
IF syncsid
	ld a,(regsoldvalues+FXC_H)
	cp d
	jr z,nonewfxc_h
	ld a,d
	ld (regsoldvalues+FXC_H),a
	xor a
	ld (runtimeoffsetc+1),a
NoNewFXC_H
ENDIF
	ld a,(regsvalues+FXC_L)
	ld e,a				; E=retenue en nops
IF syncsid
	ld a,(regsoldvalues+FXC_L)
	cp e
	jr z,nonewfxc_l
	ld a,e
	ld (regsoldvalues+FXC_L),a
	xor a
	ld (runtimeoffsetc+1),a
NoNewFXC_L
ENDIF
	ld a,(regsvalues+R10)		; On recupere le volume courant et
	ld (pokeswitchvolumec+1),a	; on le met en switch dans la dma loop
	ld b,a				; B=volume du canal
DMAListC
	ld hl,primarysidlistcpredelay	; AYList a utiliser
PreDelayC
	ld a,255		; On recupere le nombre de lignes qui restaient
	ld (hl),a		; a attendre de la frame precedente et on le
	inc hl			; poke dans la phase d'init de l'AYList
	inc hl
	inc a
	ex af,af'
SIDVolumeC			; On recupere l'etat du volume SID et
	ld a,15			; le poke dans la phase d'init de l'AYList
	ld (hl),a
	inc hl
	inc hl
	or a			; si le volume courant du canal a change depuis
	jr z,nonewvolc		; la frame precedente on le RAZ
	ld a,b
NoNewVolC
	ex af,af'		; A'=volume SID courant
;
; On crée l'AYList C
;
; IN	; A'=volume SID courant
;	; A=nombre de lignes deja traitee en init
;	; D,E=periode SID en base 64
;	; (pokeswichvolume+1)=volume canal courant
;	; (runtimeoffsetc+1)=retenue periode SID en cours
; OUT	; A=nombre de lignes SID debordant de la frame
;	; A'=volume SID courant
MakeDMAListC
	exx                     
	ld h,0			; HL'=compteur de ligne
	ld l,a
	ld b,h			; B'=0 (constante)
	ld e,56			; E'=312 modulo 256 (constante)
LoopDMA_C
	exx
	ld b,d			; B=nombre de lignes a attendre
RunTimeOffsetC
	ld a,0			; A=retenue du calcul precedent
PokeC_1	add a,e
	ld c,a			; C=retenue globale
	and &3f
	ld (runtimeoffsetc+1),a	; Est-ce qu'on a plus d'une
	cp c			; ligne de retenue ?
	jr z,nodmaoverflowc
DMAOverflowC
	inc b
NoDMAOverflowC			; a une ligne moins a attendre
	ld a,b
	dec b
	ld (hl),b		; Pokage de l'unite de pause
	inc hl
;ld (hl),&10
	inc hl
	ex af,af'
PokeSwitchVolumeC		; Calcul du volume SID courant
	xor 15
	ld (hl),a		; Pokage du volume SID
	ex af,af'
	inc hl
;ld (hl),10
	inc hl
	exx
	ld c,a			; Test de fin de frame
	add hl,bc
	bit 0,h
	jr z,loopdma_c
	ld a,l
	sub a,e
	jr c,loopdma_c
	exx
;ld (hl),&20
;inc hl
;ld (hl),&40
	dec a			; Correction du nombre de lignes
				; en overflow (255=pas d'overflow)
	ld (predelayc+1),a	; On recupere le nombre de ligne qui debordent de
				; la frame pour les mettre au debut de la suivante
	ex af,af'		; On recupere le volume SID courant pour
	ld (sidvolumec+1),a	; reprendre au bon etat a la frame suivante
	ret
;
; Routines de lancement des AYLists C
;
StartDMAListC
	ld e,2
	ld bc,4
	ld hl,(dmalistc+1)		; On ne lance l'AYList du debut
	ld a,(hl)			; que si on avait une pre-attente
	cp 255				; sinon si on n'avait pas de pre-attente
	jr z,startwithnopredelayc	; alors on saute toute la phase d'init
;	or a				; sinon si on avait juste une ligne de
;	jr nz,startwithpredelayc	; pre-attente on saute uniquement
	ld c,e				; l'instruction de pause
StartWithNoPredelayC
	add hl,bc
StartWithPredelayC			; Ici HL=adresse AYList a lancer
	ld bc,&7fb8
	out (c),c
	ld (&6c08),hl	; adr DMA2
	ld a,(&6c0f)
	or 4
	ld (&6c0f),a	; run DMA2
	ld c,&a0
	out (c),c
	ld hl,primarysidlistcpredelay	; On switch les AYLists de travail
WhichDMAListC				; Ici pas besoin de temps constant
	ld a,0				; comme les routines DMA A et B
	xor 1				; car on ne lancera rien après
	ld (whichdmalistc+1),a
	jr z,startwithprimaryc
StartWithSecondaryC
	ld hl,secondarysidlistcpredelay
StartWithPrimaryC
	ld (dmalistc+1),hl
	ret
ENDIF
 
IF hardenv
TableReg13
	db 0,4,8,12,10,14,11,13
ENDIF
FrameDATA
	ds streamscount
 
	Let CurrentPC=$
	Org 0:Nocode
f_R1 db 0:f_R0 db 0
f_R3 db 0:f_R2 db 0
f_R5 db 0:f_R4 db 0
f_R6 db 0:f_R7 db 0
f_R8 db 0:f_R9 db 0:f_R10 db 0
IF hardenv:f_R12 db 0:f_R11 db 0:ENDIF
IF sidona:f_FXA_H db 0:f_FXA_L db 0:ENDIF
IF sidonb:f_FXB_H db 0:f_FXB_L db 0:ENDIF
IF sidonc:f_FXC_H db 0:f_FXC_L db 0:ENDIF
	Org CurrentPC:Code
 
	Read"streamv4.inc"
 
	Let CurrentPC=$+1
	Org CurrentPC and &fffe
 
RasterInterrupt
	ei
	ret
 
DMAInterrupt1		; Spécifique DMA1
	push af
	push bc
	ld bc,&7fb8
	out (c),c
	ld a,(&6c0f)
	or &20
	and &25		; ack&stop DMA1
	ld (&6c0f),a
	ld c,&a0
	out (c),c
	pop bc
	pop af
	ei
	ret
 
DMAInterrupt2		; Spécifique DMA2
	push af
	push bc
	ld bc,&7fb8
	out (c),c
	ld a,(&6c0f)
	or &10
	and &13		; ack&stop DMA2
	ld (&6c0f),a
	ld c,&a0
	out (c),c
	pop bc
	pop af
	ei
	ret
 
	Let CurrentPC=$+&100	; Multiple de &100 car E=numéro de registre
	Org CurrentPC and &ff00
 
RegsValues	ds 32
RegsOldValues	ds 32,&aa
 
	Let CurrentPC=$		; Offset des registres AY
	Org 0:NoCode
R0 db 0:R1 db 0:R2 db 0:R3 db 0:R4 db 0:R5 db 0
R6 db 0:R7 db 0
R8 db 0:R9 db 0:R10 db 0
IF hardenv:R11 db 0:R12 db 0:R13 db 0:ENDIF
FXA db 0:IF sidona:FXA_H db 0:FXA_L db 0:ENDIF
FXB db 0:IF sidonb:FXB_H db 0:FXB_L db 0:ENDIF
FXC db 0:IF sidonc:FXC_H db 0:FXC_L db 0:ENDIF
	Org CurrentPC:Code
 
	Let CurrentPC=$+8	; Multiple de &8 car lié à IVR
	Org CurrentPC and &fff8
 
TableInt
IntDMA2	dw dmainterrupt2
IntDMA1	dw dmainterrupt1
IntDMA0	dw rasterinterrupt
IntRast	dw rasterinterrupt
 
	Let CurrentPC=$+1	; Multiple de 2 car AY List
	Org CurrentPC and &fffe
 
; SID AYLists
IF sidona
PrimarySIDListAPredelay
	dw &1000	; Pause NN
	dw &080F	; LD VolA,NN
PrimarySIDListA
	dw &1000	; Pause NN
	dw &080F	; LD VolA,NN
	ds 312-4
	dw &4020	; STOP
SecondarySIDListAPredelay
	dw &1000	; Pause NN
	dw &080F	; LD VolA,NN
SecondarySIDListA
	dw &1000	; Pause NN
	dw &080F	; LD VolA,NN
	ds 312-4
	dw &4020	; STOP
ENDIF
IF sidonb
PrimarySIDListBPredelay
	dw &1000	; Pause NN
	dw &090F	; LD VolB,NN
PrimarySIDListB
	dw &1000	; Pause NN
	dw &090F	; LD VolB,NN
	ds 312-4
	dw &4020	; STOP
SecondarySIDListBPredelay
	dw &1000	; Pause NN
	dw &090F	; LD VolB,NN
SecondarySIDListB
	dw &1000	; Pause NN
	dw &090F	; LD VolB,NN
	ds 312-4
	dw &4020	; STOP
ENDIF
IF sidonc
PrimarySIDListCPredelay
	dw &1000	; Pause NN
	dw &0A0F	; LD VolC,NN
PrimarySIDListC
	dw &1000	; Pause NN
	dw &0A0F	; LD VolC,NN
	ds 312-4
	dw &4020	; STOP
SecondarySIDListCPredelay
	dw &1000	; Pause NN
	dw &0A0F	; LD VolC,NN
SecondarySIDListC
	dw &1000	; Pause NN
	dw &0A0F	; LD VolC,NN
	ds 312-4
	dw &4020	; STOP
ENDIF
	List
EndAdr
	Nolist
	Org entry

La bibliothèque de décompression

Télécharger le listing au format Maxam 1.5

; Routine de décompression
; version modifiée du pucrunch
; pour pouvoir g{rer du streaming
; par packets de taille fixe
 
; OffseT - Mai 2007
 
BuffersSize		Equ &100
BufferSize		Equ streamscount
 
; ****** Unpack pucrunch data ******
; Entry HL = Source packed data
;	DE = Destination for unpacked data
 
; HL = InPtr
; D = bitstr
; E = X
; BC = temps
 
InitStream
	ld a,buffersize*prebuffers
	ld (buffercount),a
	ld (ix+OutPtr),e
	ld (ix+OutPtr+1),d
	ld (ix+StartPtr),l
	ld (ix+StartPtr+1),h
 
; Read the file header & setup variables
 
RestartStream
	ld bc,6
	add hl,bc
 
	ld a,(hl)
	ld (ix+escPu),a
 
	inc hl
	inc hl
	inc hl
 
	ld a,(hl)
	ld (ix+EscBits),a
 
	ld a,8
	sub (hl)
	ld (ix+Esc8Bits),a
 
	inc hl
 
	ld a,(hl)
	ld (ix+MaxGamma),a
 
	ld a,9
	sub (hl)
	ld (ix+Max8Gamma),a
 
	inc hl
 
	ld a,(hl)
	ld (ix+Max1Gamma),a
 
	add a,a
	dec a
	ld (ix+Max2Gamma),a
 
	inc hl
 
	ld a,(hl)
	ld (ix+ExtraBits),a
 
	inc hl
	inc hl
	inc hl
 
	ld c,(hl)	; Get lenght of RLE table (max = 31)
	inc c		; (B=0)
	ld (ix+tablePu),l
	ld (ix+tablePu+1),h
	add hl,bc	; Go to the start of data
 
	ld d,&80
	jp main
 
newesc	ld b,a
	ld a,(ix+escPu)
	ld (ix+regy),a
 
	ld a,(ix+EscBits)
	ld e,a
	ld a,b
	inc e
	call getchk
	ld (ix+escPu),a
	ld a,(ix+regy)
 
; Fall through and get the rest of the bits.
noesc	ld b,a
	ld a,(ix+Esc8Bits)
	ld e,a
	ld a,b
	inc e
	call getchk
 
; Write out the escaped/normal byte
 
	ld c,(ix+OutPtr)
	ld b,(ix+OutPtr+1)
	ld (bc),a
	inc c
	ld (ix+OutPtr),c
 
	ld a,(buffercount)
	dec a
	ld (buffercount),a
	jr nz,main
 
	ld (ix+returntype),0
	ld (ix+saved),d
	ld (ix+savel),l
	ld (ix+saveh),h
	ret
 
; Fall through and check the escape bits again
 
main 	ld a,(ix+EscBits)
	ld e,a
	xor a
	ld (ix+regy),a
	inc e
	call getchk	; X=2 -> X=0
	ld b,a
	ld a,(ix+escPu)
	cp b
	ld a,b
	jr nz,noesc	; Not the escape code -> get the rest of the byte
 
; Fall through to packed code
 
	call getval	; X=0 -> X=0
	ld (ix+lzpos),a	; xstore - save the length for a later time
	srl a		; cmp #1
			; LEN == 2 ? (A is never 0)
	jp nz,lz77	; LEN != 2 -> LZ77
	call get1bit	; X=0 -> X=0
	srl a		; bit -> C, A = 0
	jp nc,lz772	; A=0 -> LZPOS+1	LZ77, len=2
 
; e..e01
	call get1bit	; X=0 -> X=0
	srl a		; bit -> C, A = 0
	jr nc,newesc	; e..e010 New Escape
 
; e..e011		Short/Long RLE
	ld a,(ix+regy)	; Y is 1 bigger than MSB loops
	inc a
	ld (ix+regy),a
	call getval	; Y is 1, get len,	X=0 -> X=0
	ld (ix+lzpos),a	; xstore - Save length LSB
	ld c,a
	ld a,(ix+Max1Gamma)
	ld b,a
	ld a,c
	cp b		; ** PARAMETER 63-64 -> C set, 64-64 -> C clear..
	jr c,chrcode	; short RLE, get bytecode
 
; Otherwise it's long RLE
longrle	ld b,a
	ld a,(ix+Max8Gamma)
	ld e,a	 ; ** PARAMETER	111111xxxxxx
	ld a,b
	call getbits	; get 3/2/1 more bits to get a full byte, X=2 -> X=0
	ld (ix+lzpos),a	; xstore - Save length LSB
	call getval	; length MSB, X=0 -> X=0
	ld (ix+regy),a	; Y is 1 bigger than MSB loops
 
chrcode	call getval	; Byte Code,	X=0 -> X=0
	ld e,a
	ld a,(ix+tablepu)
	add a,e
	ld c,a
	ld a,(ix+tablepu+1)
	adc a,0
	ld b,a
	ld a,e
	cp 32		; 31-32 -> C set, 32-32 -> C clear..
	ld a,(bc)
	jr c,less32	; 1..31
 
; Not ranks 1..31, -> 11111 # xxxxx (32..64), get byte..
	ld a,e		; get back the value (5 valid bits)
	ld e,3
	call getbits	; get 3 more bits to get a full byte, X=3 -> X=0
 
less32	ld (ix+savel),l
	ld (ix+saveh),h
	ld (ix+saved),d
 
	ld b,(ix+lzpos)	; xstore - get length LSB
	inc b		; adjust for cpx#$ff;bne -> bne
	ld c,(ix+regy)
	jr z,rlenodecc
	dec c
RLENoDecC
	ld d,a
 
	ld l,(ix+OutPtr)
	ld h,(ix+OutPtr+1)
RLEBufferManager	; D=fill byte, CB=size, HL=dest pointer
	xor a
	cp c
	jr nz,rlebufferoverflowbig
	ld a,(buffercount)
	cp b
	jr c,rlebufferoverflowsmall
RLEBufferOK
	sub a,b
RLEBufferOKLoop
	ld (hl),d
	inc l
	djnz rlebufferokloop
	ld (ix+OutPtr),l
	or a
	jr z,rlebufferokreturn
RLEBufferOKGoMain
	ld (buffercount),a
	ld d,(ix+saved)
	ld l,(ix+savel)
	ld h,(ix+saveh)
	jp main
RLEBufferOKReturn
	ld (ix+returntype),0
	ret
RLEBufferOverflowSmall
	ld c,b
	ld b,a
RLEBufferOverflowSmallLoop
	ld (hl),d
	inc l
	djnz rlebufferoverflowsmallloop
	ld (ix+OutPtr),l
	ld b,a
	ld a,c
	ld c,b
	sub a,c
	ld (ix+data1),a		; remaining size
	ld (ix+data2),0
	ld (ix+returntype),1
	ret
RLEBufferOverflowBig
	ld a,(buffercount)
	ld e,a
	ld a,b
	ld b,e
	sub a,b
	jr nc,rlebufferoverflowbigno16boverflow
RLEBufferOverflowBig16bOverflow
	dec c
RLEBufferOverflowBigNo16bOverflow
	ld (ix+data1),a
	ld (ix+data2),c
	ld b,e
RLEBufferOverflowBigLoop
	ld (hl),d
	inc l
	djnz rlebufferoverflowbigloop
	ld (ix+OutPtr),l
	ld (ix+returntype),1
	ret
 
Exit	ld l,(ix+StartPtr)
	ld h,(ix+StartPtr+1)
	jp restartstream
 
lz77	call getval	; X=0 -> X=0
	ld b,a
	ld a,(ix+Max2Gamma)
	cp b		; end of file?
	jr z,exit	; yes, exit
 
	ld a,(ix+ExtraBits) ; ** PARAMETER (more bits to get)
	ld e,a
	ld a,b
	dec a		; subtract 1	(1..126 -> 0..125)
	inc e
	call getchk	; clears Carry, X=0 -> X=0
 
lz772	ld e,8		; offset MSB (lzlen+1) but always = 0
	call getbits	; clears Carry, X=8 -> X=0
 
; Note; Already eor;ed in the compressor..
	ld b,(ix+lzpos)	; xstore - LZLEN (read before it's overwritten)
	inc b		; adjust for cpx#$ff;bne -> bne
 
	add a,(ix+OutPtr)	; -offset -1 + curpos (C is clear)
	ld (ix+lzpos),a
 
;	Write decompressed bytes out to RAM
	ld (ix+saved),d
	ld (ix+savel),l
	ld (ix+saveh),h
 
LZBufferManager			; A=src oft, B=lz size
	ld e,(ix+OutPtr)	; copy X+1 number of chars from LZPOS to OUTPOS
	ld d,(ix+OutPtr+1)
	ld l,a
	ld h,d
	ld a,(buffercount)
	cp b			; b > buffercount ?
	jr c,lzbufferoverflow
LZBufferOK
	ld c,a
	sub a,b
	cp c
	jr z,lzbufferoverflow	; b=0 -> b=256
	ld c,a
LZBufferOKLoop
	ld a,(hl)
	ld (de),a
	inc l
	inc e
	djnz lzbufferokloop
	ld (ix+outptr),e
	ld a,c
	or a
	jr z,lzbufferokreturn
LZBufferOKGoMain
	ld (buffercount),a
	ld d,(ix+saved)
	ld l,(ix+savel)
	ld h,(ix+saveh)
	jp main
LZBufferOKReturn
	ld (ix+returntype),0
	ret
LZBufferOverflow
	ld c,a
	ld a,b
	ld b,c
	sub a,b
	ld (ix+data1),a
LZBufferOverflowLoop
	ld a,(hl)
	ld (de),a
	inc l
	inc e
	djnz lzbufferoverflowloop
	ld (ix+outptr),e
	ld (ix+data2),l
	ld (ix+returntype),2
	ret
 
GetNextBytes
	ld a,buffersize
	ld (buffercount),a
	ld a,(ix+returntype)
	or a
	jr z,returntype0
	cp 1
	jr z,returntype1
	cp 2
	jr z,returntype2
	ret
ReturnType0		; Simple return
	ld d,(ix+saved)
	ld l,(ix+savel)
	ld h,(ix+saveh)
	jp main
ReturnType1		; RLE copy management
	ld l,(ix+OutPtr)
	ld h,(ix+OutPtr+1)
	dec l
	ld d,(hl)
	inc l
	ld b,(ix+data1)
	ld c,(ix+data2)
	jp rlebuffermanager
ReturnType2		; LZ77 copy management
	ld b,(ix+data1)
	ld a,(ix+data2)
	jp lzbuffermanager
 
; getval; Gets a 'static huffman coded' value
; ** Scratches X, returns the value in A **
getval	ld a,1		; X must be 0 when called!
	ld e,a
loop0	sla d
 
	jr nz,loop1
 
	ld d,(hl)
	inc hl
	rl d		; Shift in C=1 (last bit marker) bitstr initial value = &80 == empty
loop1	jr nc,getchk	; got 0-bit
 
	inc e
	ld b,a		; save a
	ld a,(ix+MaxGamma)
	cp e
	ld a,b		; restore a
 
	jr nz,loop0
	jr getchk
 
 
; getbits; Gets X bits from the stream
; ** Scratches X, returns the value in A **
 
get1bit	inc e
getbits	sla d
	jr nz,loop3
 
	ld d,(hl)
	inc hl
	rl d		; Shift in C=1 (last bit marker) bitstr initial value = &80 == empty
loop3	rla
getchk	dec e
	jr nz,getbits
	ret
 
BufferCount
	db 0
 
; Data Index
 
	Let CurrentPC=$
	Org 0
	Nocode
escPu		db 0
OutPtr		dw 0
lzpos		db 0
EscBits		db 0
Esc8Bits	db 0
MaxGamma	db 0
Max1Gamma	db 0
Max2Gamma	db 0
Max8Gamma	db 0
ExtraBits	db 0
tablePu		dw 0
regy		db 0
StartPtr	dw 0
SaveL		db 0
SaveH		db 0
SaveD		db 0
Data1		db 0
Data2		db 0
ReturnType	db 0
StreamsContextSize
		db 0
 
	Org CurrentPC
	Code
 
StreamsContextAdr
	ds streamscount*streamscontextsize

Musiques AY+ d'exemple

Les musiques d'exemple ci-après ont été converties à partir de fichiers YM 5/6 (également fournis). Le programme de conversion ne tournant pas sur CPC et n'ayant pas d'intérêt au regard de cet article sur les sons SID26), je n'ai pas jugé bon de le publier (de toute manière c'est un horrible mix entre C, AmigaDOS et AREXX dont vous ne sauriez que faire).

En jouant ces fichiers AY+ avec le player que je vous fournis ici, vous constaterez que leur rendu sur CPC+ est quasiment identique à celui de la musique originale sur Atari ST. Toutefois, les sons SID sont légèrement moins purs (à cause de la précision des DMA qui est inférieure à celle des timers du ST, mais on peut passer outre en jouant les SID par interruption plutôt que via des boucles DMA). Enfin, certains sons hard ne sonnent pas aussi juste (le YM du ST est à 2MHz, il est donc deux fois plus précis que le AY à 1MHz du CPC ce qui se ressent sur les sons aigus) ou ne sont pas à la même octave (le convertisseur de YM passe en effet les sons hard à l'octave inférieure s'il estime que ça sonnerait trop faux, toujours à cause de la différence de précision entre le YM à 2Mhz et le AY à 1MHz).

Titre de la musique Auteur Fichier AY+ Fichier YM d'origine
Lemon Sqeezers Dream Jess LEMONSQ.AY+ Lemon Sqeezers Dream.ym
Lin Wu's Challenge Tao LINWU.AY+ linwu1.ym
Reality27) Big Alec REALITY.AY+ Reality.ym
SID Tune 1 Mad Max MAD-MAX1.AY+ Sid Music #1.ym
Stay Away Tao STAY.AY+ Stay Away.ym
Synergy DBA Music Disk (4) Scavenger FLIPOINT.AY+ flipointro.ym
Synergy DBA Music Disk (5) Scavenger SYNER5.AY+ scaven5.ym
The Cave Tao CAVE.AY+ cave.ym
The Delegate Tao JACIT.AY+ JACIT.YM
Virtual Escape (Equinox) Main Tune Mad Max VIRTEST2.AY+ Virtual Escape Main.ym
Virtual Escape (Equinox) End Tune Mad Max VIRTEST3.AY+ Vitual Escape Final.ym

Télécharger le DSK contenant les fichiers AY+.

Télécharger l'archive contenant les fichiers YM d'origine.

1) oui, celle-là même où rôde le Grumly
2) Et non pas pour son abominable clavier brun crasseux
3) l'Amiga disposait quant à lui d'un autre processeur audio dédié, Paula, qui n'avait absolument rien à envier au SID
4) et on n'a de facto aucun moyen de le contrôler
5) À noter que la Backtro d'Overflow offre une très belle tentative !
6) n'est-ce pas Ast ?
7) , 9) c'est-à-dire s'il y a au moins un canal qui utilise une enveloppe hard
8) voir le tableau qui suit pour la correspondance avec les valeurs réelles du registre 13
10) , 12) , 14) , 16) , 18) , 20) c'est-à-dire s'il y a au moins un effet spécial utilisé sur le canal (Sid, Drum, Sin ou Buzz)
11) , 13) , 17) , 19) , 21) attention, sur une base de 64µs (vitesse des DMA) et non pas de 8µs comme pour les périodes des registres du PSG !
15) attention, sur une base de 64µs (vitesse des DMA) et non pas de 8µs comme pour les périodes des regist res du PSG !
22) et en bonus je vous propose une version MorphOS qui n'est pas disponible sur le site officiel.
23) Attention, certaines des musiques d'exemple font plus de &4000 et ne rentrent pas entièrement dans une bank et déborderont en &8000.
24) Dans l'exemple ils sont en &D000 (en mémoire graphique), libre à vous de les déplacer où vous voulez sur une adresse multiple de &100 en faisant bien attention à leur taille.
25) la valeur par défaut de 80 est largement suffisante pour la plupart des musiques, sauf pour “Stay Away” de Tao et “Virtual Escape Final” de Mad Max qui ont des pointes jusqu'à la ligne 100
26) il ne s'agit que de décoder et réordonner un fichier YM avant de le compresser
27) Dédicace spéciale pour Overflow !