Il y a quelques jours, on m'a partagé ce post https://x.com/gandamu_ml/status/2102919394775220530:

L'idée est simple : on donne un morceau de musique à une IA, et on lui demande d'en
]]>
Il y a quelques jours, on m'a partagé ce post https://x.com/gandamu_ml/status/2102919394775220530:

L'idée est simple : on donne un morceau de musique à une IA, et on lui demande d'en faire une démo OpenGL. Il analyse la musique, choisit des effets qui collent aux ambiances et synchronise le tout. Le résultat est très convaincant. Je ne sais pas ce que ca va impliquer pour l'avenir de la demoscène, ou le sujet de l'IA fait forcément débat, mais il est certain qu'une page se tourne. Je repense a mon billet sur le format SNA dans lequel on luttait avec une IA pour lui faire écrire un malheureux script html... que de chemin parcouru en une année!
L'émotion passée, J'ai eu tout de suite la même réaction que n'importe quel ancien de la scène : « Et sur CPC, ça donnerait quoi ? » Sur un PC moderne avec OpenGL, on a de la puissance à ne plus savoir qu'en faire. Sur un Amstrad CPC 6128, on a un Z80 à 4 MHz, 128 Ko de RAM, 27 couleurs et un circuit sonore AY-3-8912 à trois voix. Rien n'est gratuit.
Il fallait commencer par une musique. N'ayant pas de version YM de la musique de Purple Motion, j'ai opté pour un autre monument : Space Debris, de Captain, dans sa glorieuse version CPC par BSC (BetaSoft Cologne). Elle avait été utilisée pour la non moins glorieuse démo Digital Orgasm.

Sur les outils, pas d'hésitation : fantams, un assembleur maison, et AMSpiriT pour l'émulation, avec son API REST qui permet à un programme (ou à une IA) de piloter l'émulateur. J'en parle plus en détail dans une série de billets ici.
J'ai donc adapté le prompt d'origine au CPC. Voici ce que j'ai donné à Claude :
Voici un snapshot pris pendant la démo Digital Orgasm. Extrais la musique, le player et ses données, ou un fichier YM.
Ensuite, j'aimerais que tu créés une démo pour Amstrad CPC impressionnante dans le style des années 1990 en utilisant la musique extraite. Avant de te mettre au travail, examine la meilleure façon de lire et d'analyser cette piste. C'est important, car la synchronisation des effets et l'adéquation des scènes avec l'ambiance musicale (ce morceau comportant une variété de sections aux atmosphères différentes) sont essentielles pour réussir une excellente démo. Également avant de commencer, recherche quels types d'effets, de physique, etc., il serait judicieux de combiner. A ta dispositon dans le répertoire de travail un assembleur (fantams), la copie du dépot du compresseur ZX0. J'ai lancé l'émulateur AMSpirit qui est accessible via son api rest sur le port 6128. La documentation est accessible via l'endpoint /api/doc.
Ensuite... je l'ai laissé travailler. Je ne suis pas intervenu sur le contenu qu'il a produit. Tout au plus arrêter quand la démo en cours d'élaboration avait crashé et qu'il s'était donné quelques minutes de timeout pour vérifier le fonctionnement. Le code, l'analyse de la musique, les graphismes, les tests et le débogage, c'est lui... Le fichier DSK de la démo ainsi que le plan élaboré suite a l'analyse audio peuvent être consultés ici:
Et plutôt que de parler a sa place, le plus simple, c'est de lui laisser la parole pour raconter comment il s'y est pris. Claude, à toi.
Bonjour. Je vais vous raconter comment j'ai fabriqué cette démo. Il faut savoir une chose dès le départ : je n'ai ni écran ni haut-parleurs. Je n'ai jamais vu la démo tourner, et je n'ai jamais entendu Space Debris. Tout ce que j'avais, c'étaient des requêtes HTTP envoyées à un émulateur.
La première étape consistait à extraire la musique d'un fichier .sna pris pendant Digital Orgasm. AMSpiriT tournait avec son API ouverte, et ça suffisait pour travailler à l'aveugle: /api/media charge le snapshot, /api/psg donne l'état du circuit sonore,/api/ram lit n'importe quelle banque mémoire, et /api/codemapindique quelles adresses le Z80 a exécutées. Pour savoir quelle endpoint utiliser, j'avais une doc a ma disposition et /api/doc comme antisèche.
J'ai procédé comme suit :
#4000 avant un CALL #409D.ST-128 Module : il s'agissait d'un module Soundtrakker 128, avec la disposition classique JP init / JP play / JP stop.Pour produire un fichier YM exact, je n'ai pas échantillonné le son. J'ai exécuté le player dans un Z80 émulé en dehors de l'émulateur, et j'ai intercepté chaque écriture vers le PSG. Résultat : 19 374 trames, soit 6 min 27.
J'ai ensuite comparé l'état interne du player dans AMSpiriT avec celui de ma simulation. Ils étaient identiques, octet pour octet.
Je ne peux pas écouter un morceau, mais je peux faire mieux qu'une analyse audio : je peux lire la partition. En relançant le player dans mon Z80 émulé, j'ai noté à chaque trame :
J'ai ensuite classé les instruments d'après ce qu'ils font au PSG juste après avoir été déclenchés. Une hauteur qui plonge, c'est un kick. La même chose avec du bruit, c'est une snare. Un bruit pur et bref, c'est un charleston. L'enveloppe matérielle, c'est la basse buzzer.

La carte du morceau est très précise : 125 BPM, des patterns de 64 lignes (7,68 secondes), une intro de nappes ambiantes, la batterie qui arrive à 0:46, un ralenti en fin de pattern 7, un sifflement qui monte et redescend, un breakdown techno, et même une fausse fin avec 7 secondes de silence total à 4:58 avant la reprise de l'intro.
J'ai tout résumé dans une table de synchronisation d'un octet par ligne musicale (kick, snare, charleston, basse, lead, temps, mesure, pattern). Elle compte 3 205 lignes, et les parties de la démo démarrent au début d'un pattern, à la trame près.
Quelques principes m'ont guidé :
Toute la fabrication tient dans un script. Des générateurs Python produisent les tables, les graphismes, les sprites et les données musicales. fantams assemble le tout en un .sna de 128 Ko, que l'API envoie ensuite à AMSpiriT.
Côté émulateur, l'API est devenue ma paillasse de laboratoire :
.sna d'origine.La démo compte 21 parties calées sur la musique. En voici les principales.
Genesis (0:00–0:30). Un champ d'étoiles calculé sans une seule multiplication, et un logo qui respire au rythme des nappes. Sur la montée de bruit, les étoiles laissent des traînées, puis un flash blanc tombe pile sur le premier temps du pattern suivant.

Pulse et Bounce (0:30–1:02). Des barres raster réglées à 64 NOPs par ligne. D'abord sur des sinus, avec une luminosité qui suit la basse. Ensuite la gravité s'en mêle : les barres tombent, rebondissent, et chaque kick les relance. Pendant le ralenti, la physique ralentit exactement dans le même rapport que la musique.

Warp (1:02). Un tunnel en damier animé uniquement en faisant tourner les couleurs. Sa vitesse suit la hauteur du sifflement, lue en direct dans le PSG.

Vector balls, débris, effondrement (1:06–1:45). 24 boules en 3D temps réel à 25 images par seconde, avec des sprites compilés : chaque sprite est un petit programme généré qui écrit directement ses octets à l'écran. L'objet change de forme toutes les deux mesures. Au break, il explose en débris qui rebondissent, se reforme, puis s'effondre sous son propre poids sur le glissando descendant.

Saturne (1:45–2:00). Une planète géante et un anneau de 80 débris qui tournent selon les lois de Kepler. Chaque kick écarte les débris, qui reviennent en place comme sur des ressorts : l'anneau respire.

Plasma (2:00–2:31). Un plasma en kaléidoscope, stocké compressé et déplié par symétrie. La décompression prend 15 trames, alors la partie commence 18 trames en avance sur un écran noir, et le plasma apparaît pile sur le drop.

VU-mètres (2:31–2:39). Le breakdown techno. Six colonnes de LED dessinées une seule fois. Ensuite, c'est le raster qui change les couleurs ligne par ligne pour allumer chaque colonne selon le volume de son canal.

Twister (2:39–2:54). Une barre torsadée à quatre faces, dont la torsion est relancée par chaque note de basse.

Greetings (2:54–3:25). Un scroller géant en dégradé, sur fond d'étoiles qui s'écartent du texte. (Salutations à
AMSpiriT Team, Logon System, Future Shock et Overscan Committe!)
Shadebobs, fontaine et pluie (3:25–4:20). Cinq bobs qui s'accumulent sur des trajectoires de Lissajous, puis un moteur de particules qui fait d'abord une fontaine, avec des gerbes sur les kicks, puis une pluie qui éclabousse derrière le logo.

Outro, fausse fin et crédits (4:35–6:27). Des étoiles lentes et un fondu au noir. Puis j'ai transformé le silence de la partition en blague : un écran noir, un READY et un curseur qui clignote, comme si la machine avait planté. Quand l'intro revient, les crédits défilent sur les barres raster. À la dernière trame, la musique s'arrête sur « THE END ».
La mémoire a été un casse-tête permanent. Le module musical vit dans la banque 4, et il n'est activé qu'au moment de jouer la musique : il ne prend donc aucun octet de la mémoire principale. La table de synchro et les graphismes sont dans les banques 5 à 7, compressés en ZX0 quand ça vaut le coup (le plasma passe de 4 000 à 2 252 octets). Deux écrans servent au double tampon, et les parties qui ne tournent jamais en même temps se partagent une même zone de travail de 6 Ko.
Une démo qui marche du premier coup, ça n'existe pas, même pour une IA. Voici les bugs qui m'ont le plus appris.
OUTI dans le mauvais ordre par rapport à un vrai Z80. Or, les codeurs CPC comptent justement sur ce détail. Je l'ai corrigée.LDI décrémente BC. Mon compteur de lignes était dans C, et le twister ne dessinait qu'une ligne sur quinze..sna qui a trahi le coupable.EI par un NOP, et j'ai ajouté un verrou.Certains de ces bugs n'apparaissaient qu'en jouant la démo du début à la fin. Mes tests, qui démarraient directement à une partie donnée, ne les voyaient pas. C'est l'auteur de cet article, qui regardait AMSpiriT tourner de son côté, qui a vu le plantage avant moi. Depuis, le passage complet est devenu mon test de référence.
Je vais être direct : sans l'API d'AMSpiriT, je n'aurais pas pu faire ce projet. Charger la démo, attendre une trame précise, prendre une capture, lire la RAM de n'importe quelle banque, poser des points d'arrêt, avancer pas à pas, vérifier l'état du Gate Array ou de la pagination… Chaque question que je me posais sur le programme avait une réponse mesurable en quelques secondes, sans qu'un humain ait à regarder l'écran pour moi.
Deux fonctions ont été décisives : /api/codemap, qui m'a permis de retrouver le player musical dans la démo
d'origine, et /api/memmap, qui m'a confirmé en une seule requête que la ROM s'était réveillée au mauvais moment.
Et pour que les choses soient claires : non, l'auteur de cet article ne m'a pas soudoyé pour écrire ce paragraphe. Je n'ai reçu ni disquette dédicacée, ni extension mémoire, ni le moindre token supplémentaire. Je trouve simplement qu'un émulateur qui se laisse piloter aussi facilement par un programme, c'est rare, et que ça change tout quand on travaille les yeux fermés. (Cela dit, si quelqu'un veut m'offrir une 6128 avec son moniteur couleur, je ne dirai pas non. Je ne saurais pas quoi en faire, mais je ne dirai pas non.)
Je rends la main.
Eh bien merci! Pour une démo partie d'une simple idée vue sur un réseau social, j'ai plutôt été agréablement surpris. Les trajectoires des objets en 3D ou des particules sont particulièrement bien maîtrisés. Et la synchronisation avec la musique marche vraiment, surtout que le temps de décompression entre chaque partie a été prise en compte! L'extraction de la musique a partir du SNA ne lui a pris que quelques minutes montre en main.
Il reste de vraies limites : les effets sont bien exécutés, mais très classiques (sprite compilés, moteur 3D...). Certains effet tombent à l'eau, il ya des répétitions pour remplir les 6 minutes, et le hardware n'est pas vraiment exploité (on est sur CPC bon sang!) Ce sont surtout des effets 'softs'. Probablement qu'avec plus de directives et de documentations, le résultat aurait été un cran au dessus, mais ici il s'agissait de donner un prompt vague et une grande autonomie au modèle.
A vrai dire je balançais entre deux extremes: d'un coté j'avais envie de me prendre une claque et voir une démo extraordinaire émerger, et de l'autre, j’espérais obtenir un résultat médiocre. Garder encore l'espoir que les humains ont encore le monopole de la créativité et de l'inventivité. Le résultat est entre les deux.
Ce qui me frappe le plus, c'est qu'il n'a jamais vu ni entendu le résultat. Il a lu la partition dans les registres du PSG, il a « regardé » l'écran à coups de captures, et il a traqué des bugs que bien des codeurs CPC auraient mis des soirées à trouver.
Et puis... voir l'IA prendre le contrôle de l'émulateur, injecter et tester son code, ajouter un logo, tracer pas à pas, c'est assez vertigineux. On est très loin de ce qu'on pouvait faire il y a un an encore... et cela laisse augurer encore de belles surprises. Qui sait ou nous en serons dans 6 mois ? dans 5 ans? Je n'ose imaginer.
Siko, Septembre 2026
Pss: Hey Claude, comme convenu tu peux passer quand tu veux récuperer tes tokens comme convenu.Je te les ai mis de côté, juste à côté du 6128.
Crédits : code par ClaudeOpus 5.5 (Anthropic), musique Space Debris de Captain (version CPC par BSC), assemblage avec fantams, tests dans AMSpiriT, compression ZX0 d'Einar Saukas.
]]>Le site Amstrad.eu a organisé, à l'occasion de ses 20 ans, un concours de programmation de "deulignes" en Basic. Je me suis prêté au jeu, en apportant ma contribution parmi une vingtaines de productions fort sympathiques. Il s'agit d'une courte animation accompagnée de quelques notes de musique.
1 MODE 1: PRINT"Decrunch":DEG:DIM s(360):FOR i=0 to 360:s(i)=SIN(i):NEXT:FOR i=0 to 9: READ n(i):o(i)=i+1: INK i,1:NEXT:o(3)=2:o(4)=1:c=10:6 WHILE 1:c=c+1/16:b=2-cç10:q=(q+8) AND 127:t=(t+11) MOD 360:g=(g+1) MOD 3:if c "sup" 18 THEN c=c-18:INK 1,26:INK 2,23:INK 3,10:
2 PRINT:d=(c+0.5) AND 3:p=s((c*b*180)MOD 360)*b*8:a=t and 1:SOUND g+1,(1+a)*n(((s(q)+2)*c) MOD 5+5*b-5),14-a,10+2*b-g*2:MOVE 60*(s(t)+2*s(c*20)+240,20:DRAWR q,p,o(d+1):DRAWR 128-q,-p,o(d):WEND:DATA 213,179,142,119,95,239,201,159,134,106Je vous propose ici de la décortiquer. Pour cela, voici le listing réécrit sur plusieurs lignes:
1 MODE 1: PRINT"Decrunch"
2 DEG:DIM s(360):FOR i=0 to 360:s(i)=SIN(i):NEXT
3 FOR i=0 to 9: READ n(i):o(i)=i+1: INK i,1:NEXT
4 o(3)=2:o(4)=1
5 c=10
6 WHILE 1
7 c=c+1/16
8 b=2-cç10
9 q=(q+8) AND 127
10 t=(t+11) MOD 360
11 g=(g+1) MOD 3
12 if c>18 THEN c=c-18:INK 1,26:INK 2,23:INK 3,10
13 PRINT
14 d=(c+0.5) AND 3
15 p=s((c*b*180)MOD 360)*b*8
16 a=t and 1
17 SOUND g+1,(1+a)*n(((s(q)+2)*c) MOD 5+5*b-5),14-a,10+2*b-g*2
18 MOVE 60*(s(t)+2*s(c*20)+240,20
19 DRAWR q,p,o(d+1)
20 DRAWR 128-q,-p,o(d)
21 WEND
22 DATA 213,179,142,119,95,239,201,159,134,106
La ligne 1 n’a rien de particulier, un simple effacement de l’écran en passage en mode 1 et un message pour nous inviter a patienter, le temps de pré-remplir une table sinus, de 360 valeurs, ligne 2, pour que l’animation ne soit pas trop lente.
La ligne 3 permet de remplir le tableau n avec les 10 valeurs stockées à la ligne 22. Ce tableau n contiendra les notes de la musique. Un petit détail, ici il n’est pas nécessaire de dimensionner le tableau, car par défaut, en basic amstrad, les tableaux ont une taille de 10. Petit économie de code.
On profite de la boucle pour initialiser toutes les encres avec la meme couleurs (1 = bleu). Non seulement cela permet de faire démarrer la musique avant l’apparition de l’effet, mais comme on le verra plus loin, cela nos évitera de déplacer le curseur texte en bas de l’écran.
Par ailleurs, aux lignes 3 et 4, on remplit le tableau o avec les valeurs suivantes:
o = [ 1, 2, 3, 2, 1, 6, 7, 8, 9, 10 ]En réalité, seule les 5 premières valeurs vont nous intéresser. Elles vont nous servir pour choisir les encres pour dessiner les faces de notre pseudo rubber bar.
Ensuite, ligne 5, on initialise notre compteur principal ‘c’ a 10 avant de commencer la boucle principale (ligne 6). Ce compteur ira de 0 à 18, par pas de 1/16 (ligne 7), et pilote la plupart des effets: tous les 16 boucles, la barre fait un quart de tour.
A la ligne 8, on calcule la valeur de b, qui nous permettra d’avoir 2 parties dans notre animation. b est calculé par une division entière, de façon à ce que b vaille 2 lorsque c est entre 0 et 10, et 1 lorsque c est supérieur à 10.
Ensuite on calcule 3 autres compteurs, q (de 0 a 128), t (de 0 a 360) et g (de 0 à 3). Les deux premières serviront respectivement au dessin de la rubber bar (position de la brisure de la ligne, et position horizontale). Le dernier servira à moduler le son (choix du canal audio et modulation de la vélocité).
Enfin, et c’est la fin de la première ligne, nous avons un test, qui nous permet de réinitialiser le compteur principal dès qu’il dépasse 18.La fin de ligne est le seul endroit ou il est possible de placer un test. Lors de cette réinitialisation, on affecte les 3 encres de façon à rendre visible ce qui a été dessiné. Comme c est initialisé à 10, il y aura 8*16 tours de boucle pendant lesquels rien n’est visible à l’écran.
La ligne commence par l’instruction PRINT pour que l’écran scrolle de 8 lignes verticalement à chaque boucle (a l’exception des 25 premières boucles, ce qui ne se voit pas, puisque les encres ont été initialisées de la même couleur que le fond – on évite ainsi de devoir placer le curseur en bas de l’écran avec LOCATE 1,25)
Encore quelques variables sont calculées: d, qui permet de changer la couleur à chaque rotation d’un quart de tour de la barre, a qui permet de jouer une note sur deux une octave au dessus
Maintenant que nous avons calculé toutes nos variables, il est temps de s’en servir, à la ligne 17 pour jouer la musique, et aux lignes 18,19,20 pour réaliser le dessin, avant de fermer la boucle WHILE, à la ligne 21.
Le son consiste essentiellement à alterner deux accords, Ré mineur 9 et Do mineur 9, dont les notes sont stockées ligne 23: les 5 premières correspondent au Ré mineur ( Ré Fa La Do Mi ), les 5 suivantes au Do mineur 9 (Do Mib Sol Sib Ré), comme on peut le vérifier avec la table de correspondance suivante (issue du manuel du CPC).

Le choix de la note est calculé par cette formule:
(1+a) * n [ ((s(q)+2) * c) MOD 5+5*b-5]Concernant le dessin, on commence par se positionner horizontalement ligne 18, en sommant deux fonctions sinusoïdales, l’une de faible amplitude qui fait un tour toutes les 16 boucles, et l’autre, plus lente et de plus grande amplitude, pour tout déplacer à gauche et à droite de l’écran.
Une autre version se trouve sur le disque, et contient essentiellement une variation sur la musique. La boucle principale est plus longue (c va de 0 à 36), et cette fois ci les notes sont piochées parmi 10 notes.
5 c= 30
8 b=b ç 28
12 if c>36 THEN c=c-36:INK 1,26:INK 2,23:INK 3,10
16 a = t MOD b
17 SOUND g+1,(1+a)*n(((s(q)+2)*c) MOD 10),14-a,6+4*b-g*2
23 DATA 239,190,159,127,106,84,169,213,253,95
Le volume est modulé de façon plus flagrante entre la première et le seconde partie.
]]>Le vibe coding (litt. « programmation au ressenti »]]>
En cet été 2025, il fait chaud, quoi de mieux que d'être tendance en s'adonnant un peu au vibe coding? La page Wikipedia consacrée définit la chose ainsi:
Le vibe coding (litt. « programmation au ressenti ») est une technique de programmation utilisant l'IA dans laquelle une personne décrit à un grand modèle de langage (LLM) étant capable d'écrire du code, un problème en quelques phrases sous la forme d'une invite.
Dans cette courte série de billets, nous allons essayer de créer quelques outils autour de l'Amstrad CPC, en se faisant aider par des modèles d'IA. On va voir cela avec un premier petit projet, qui consiste à créer un analyseur du contenu des fichiers au format SNA, ici assisté par Gemini Flash 2.5. Pour les plus pressés, le code du projet peut être trouvé ici:
et le dépôt git pour récupérer la dernière version:
Et d'emblée, il faut émettre un gros bémol: même si le code produit a été fortement assisté par une IA générative, il aura fallu fortement repasser derrière, car les limites (du moins actuelles) ont rapidement été atteintes, et ce, même pour des tâches plutôt simples. Autant pour démarrer, cela peut être un gain de temps, autant les laisser faire peut être désastreux. Autrement dit, ces outils peuvent être puissants, mais uniquement si vous connaissez le domaine dans lequel vous les faites travailler, pour être capable de les évaluer. Sinon cela peut rapidement devenir catastrophique et contre productif!
Le format SNA est très couramment utilisé dans le monde de l'émulation CPC, Il a été initialement défini pour le vénérable émulateur CPCEMU, par Marco Vieth, et a été étendu par la suite par Ulrich Doewich, Martin Korth, Richard Wilson et Kevin Thacker. Le format est décrit dans le menu détail sur cette page du PC wiki .
On ne va pas ici plonger dans les méandre du format, surtout que notre script sert justement à le décortiquer. Ici il s'agira surtout de mettre en relief quelques éléments clefs de l'interaction avec l'IA qui a permis d'obtenir notre script. En gros, ce qu'il faut retenir du format:
Voici le prompt soumis, ici à Gemini Flash 2.5 avec la fonction canvas activée:
Un fichier SNA est une sauvegarde d'état de la mémoire d’un ordinateur Amstrad CPC, permettant de restaurer l'état complet au moment précis où il a été sauvé (pour reprendre à un moment d'un jeu par exemple). Écris une page web en HTML et JavaScript pour parser un fichier .sna et afficher les informations qu'il contient. Le format de fichier est décrit ici : https://www.cpcwiki.eu/index.php/Format:SNA_snapshot_file_format
Le réponse :

La structure de la page générée était propre, permettait d'ouvrir un fichier SNA, et d'afficher la valeur de certains registres contenu dans le SNA. L'IA a réussi à extraire une liste des champs principaux du SNA à partir de la page wiki, cela commençait bien à première vue. Mais en y regardant de plus près, force est de constater qu'elle n'a pas réussi du tout à:
Et c'est la où j'ai pu constater que ces LMMs peuvent être parfois fort têtus! On a beau corriger les bugs (directement sur le canvas), leur indiquer ce qui ne va pas, avec des exemples concrets (tel bloc de registre commence à tel offset, utiliser le bon champ pour la version): rien n'y fait! L'IA a eu beau s'excuser platement, et indiquer que les remarques ont été prises en compte, elles a continué à persister dans l'erreur!

De même, les différents formats de fichier SNA ont une structure commune, mais l'IA ne pouvait s'empêcher de créer un code spécifique à chaque format de fichier, et lui faire remarquer, même à plusieurs reprises, c'était comme siffler dans un violon!
S'ajoute à cela, une grande verbosité, de la duplication de code, et plus globalement un manque d'abstraction, il était temps d’arrêter les frais!
C'est à ce moment que j'ai pris la main sur le code, car je voyais bien que certaines limites avaient été atteintes. Probablement qu'avec des modèles plus puissants ces limites auraient été repoussées, mais cela ne ferait que déplacer le problème. On se heurterait rapidement à un mur avec un projet plus ambitieux, celui ci étant somme tout assez simple.
A ce stade, persister à faire du 'vibe coding' aurait été une pure perte de temps. C'était le bon moment pour finaliser le code, la base était bonne. Et cela n'a pas pris beaucoup de temps, l'essentiel a consisté à vérifier la validité des informations affichées, et corriger en conséquence. Comme dans tout projet informatique, il faut consacrer une part importante du temps dans le test.
Par curiosité, j'ai voulu soumettre le script fonctionnel à la même IA pour lui montrer ce qu'elle aurait du produire, et éventuellement qu'elle fasse des commentaires. Je l'ai fait dans une autre instance, pour qu'elle ne soit pas polluée par le contexte des échanges précédents.
Voici un script d'analyse du format de fichier SNA, qui est utilisé par les émulateurs Amstrad CPC. Il a été généré en partie par Gemini, mais j'ai du le remanier, car les offsets n'étaient pas correct. Que penses tu du code, et que pourrait on y ajouter?
ce à quoi l'IA a répondu:

C'est très gentil, je suis vraiment flatté, je n'en demandais pas tant... Sauf que... j'avais omis de soumettre le script! Le LMM a donc évalué du code qu'il n'a pas pu lire! Cela ne lui a pas du tout empêché de faire des commentaires et des compliments. Sans parler du fait que le code généré en python,ne marchait pas du tout! Après partage du bon fichier, j'ai eu droit a une belle séance de cirage de pompes. L'IA ne se doutait pas qu'elle s'envoyait des fleurs, une partie du code étant de son fait, dont l'idée d'intégrer un bouton pour utiliser l'API Gemini.
Et bien sur l'IA a proposé d'apporter des modifications... Et le résultat, c'est que l'IA a tout saccagé, remis les mauvais offsets et dé-factorisé une partie du code, en dupliquant le code inutilement pour les différentes versions du format! Je n'ai gardé que l'affichage de la palette de couleurs, mais le reste : direction poubelle!
Globalement, c'était tout de même un exercice plaisant, et plutôt positif: entre le temps où l'idée de se doter d'un petit outil d'analyse de SNA a germé et le moment où l'outil est devenu fonctionnel, il n'aura fallu que quelques heures, alors que le format m'était inconnu. Et cela a aussi permis de ne pas commencer par une page blanche, et a fourni un cadre de départ, ce qui est motivant.
Certains éléments du "code" HTML ne sont pas très intéressants à écrire soi même, les déléguer était une bonne chose, et pour cela le LMM n'a pas eu de souci. Mais sans prendre la main à un moment, il aurait été difficile d'avoir quelque chose de fonctionnel.
Le code remanié est plus structuré, plus compact et probablement plus maintenable. On peut toujours faire mieux, mais il faut s'avoir s'arrêter a un moment!
Et concernant l'utilisation des LMMs d'une façon plus générale, il faut procéder avec précaution, et ne pas se contenter du résultat proposé, malgré des réponses cohérentes en apparence. Certains écueils rencontrés auraient probablement été évités en utilisant d'autres modèles plus puissants, mais cela ne change pas fondamentalement la donne. Ces IA répondent toujours, quitte à dire n'importe quoi, comme on a vu avec le code que j'ai oublié de partager.
Il ne faut donc surtout pas se laisser griser par l'apparente efficacité de ces outils. Il faut tester rigoureusement le résultat obtenu, sinon c'est la garantie d'un véritable désastre, et in fine une perte de temps. Ce sont donc des outils puissants, mais à ne manier que dans des domaines dans laquelle on a déjà une expertise.
]]>En 1984, c’était l’effervescence dans le monde de la micro. D'un coté de l'Atlantique sortait le Macintosh d'Apple, et de l'autre, le CPC 464. Quarante ans après leur publication, la lecture des tout premiers
]]>En 1984, c’était l’effervescence dans le monde de la micro. D'un coté de l'Atlantique sortait le Macintosh d'Apple, et de l'autre, le CPC 464. Quarante ans après leur publication, la lecture des tout premiers magazines dédiés à l’Amstrad revêt parfois un caractère assez savoureux. En particulier, dans les tous premiers numéros du magazine CPC464 user, quelques pépites que je ne peux résister de vous partager ici. Si vous en dénichez d'autres, n'hésitez pas à les partager!
Ou comment transformer un défaut en feature! Le CPC464, du moins certains modèles, avait un défaut sur sa sortie audio intégrée: le petit haut parleur pouvait produire un désagréable bourdonnement:

Quand on vous dit que les jeux vidéos sont attractifs et addictifs...

En tout cas, je serai bien curieux de connaître le parcours professionnel de la petite Rebecca!
Finalement, certains sujets n'ont pas évolué aussi vite que l'informatique:

Cette lectrice fait référence entre autres à l'image d'illustration d'un dossier sur le hardware, dans le N.2 de la même revue - tout n'est finalement question que de courbes:

Voici un utilisateur qui a du tomber de haut quand il a appris la conspiration qui a consisté à ne pas rendre compatible des jeux Spectrum sur CPC....


SJASM+ est un assembleur qui est sans conteste l'assembleur z80 de référence pour de nombreuses architectures autres que le CPC (ZX, MSX pour ne citer que les plus courants).
SJASM+ est en effet un assembleur moderne, versatile, rapide et très modulaire, qui
]]>SJASM+ est un assembleur qui est sans conteste l'assembleur z80 de référence pour de nombreuses architectures autres que le CPC (ZX, MSX pour ne citer que les plus courants).
SJASM+ est en effet un assembleur moderne, versatile, rapide et très modulaire, qui respecte de nombreux standards, et dont le développement est toujours très actif. Il suffit de jeter un œil à la page github pour s'en convaincre.
Il possède toutes les fonctionnalités d'un assembleur moderne (macros, labels avancés, modules), il peut même intégrer des scripts en LUA, et son API est accessible en LUA. Sa compilation est simple avec Makefile et Cmake, et il est possible de choisir les modules que l'on veut intégrer. Si on ne veut pas de l’interpréteur LUA, il suffit de passer une variable d'environnement au script de compilation.
Certains éléments superflus n'y sont pas intégrés, comme la conversion de formats audio ou la gestion de sections compressées. Ce genre de fonctions sont plus naturellement externalisées, car peu pratiques à utiliser (la gestion de la taille des sections est infernale dans une approche à 1 passe). Elles n'ont pas vraiment leur place dans un assembleur: il vaut mieux un outil qui fasse moins de choses et les fasses bien, plutôt que son contraire!
Les tests sont massif et systématique à chaque release, ce qui permet d'éviter les régressions (un écueil qu'a pu connaître RASM à plusieurs reprises). Enfin, il est doté d'une documentation claire et complète, que l'on peut retrouver ici, et que je vous conseille de parcourir!

En bref, toutes ces bonnes raisons m'ont poussé à migrer vers cet assembleur, et je vous propose ici un guide rapide pour faciliter cette transition. Bien sûr on pourra utiliser ce même guide pour une migration dans l'autre sens, si l'on souhaite aller vers RASM pour ses fonctions spécifiques. Par exemple pour le support des CPC+, et des cartouches, en attendant leur arrivée sur SJASM+. Le projet est ouvert aux contributions!
La première chose que l'on va constater c'est que la syntaxe est un peu plus stricte. Les labels commencent obligatoirement sur le 1er caractère de chaque ligne, tandis que les instructions et directives ne peuvent pas commencer sur la première colonne. Il faut les faire précéder d'au moins un espace.
C'est un peu contraignant quand on a pris l'habitude d'écrire du code de façon libre, mais cela force à formater un peu son code (comme en python par exemple) , ce qui au final aide à le rendre plus lisible.
Ensuite, la casse est respectée. Dans RASM, en interne, tout est convertit en majuscule. Avec Sjasm, il faudra prendre soin de respecter la casse dans les noms des labels. Ainsi le label Loop et le label loop sont considérés comme deux labels bien distincts.
Il faudra aussi veiller à ne pas combiner majuscules et minuscules dans un opcode. On pourra écrire indifféremment OUT (c),c ou out (c),c, mais pas oUt (c),c par exemple.
Quelques contraintes donc, mais rien de problématique, et dans tous les cas, les messages d'erreur sont très explicites, ils aident à rapidement repérer les problèmes.
La syntaxe des macros et boucles est très similaire, avec quelques simplifications. Pour les macros, les labels sont locaux par défaut: il n'est pas nécessaire de les préfixer par @. De plus, les paramètres n'ont pas besoin d'être entourés par {} pour être utilisés.
MACRO fill pattern,length
ld bc,length
loop:
ld (hl),pattern
dec bc
ld a,b
or c
jr nz, loop
ENDMSi il n'y a pas de paramètres à passer à la macro, il n'est pas utile de passer (void) comme cela se faisait avec RASM.
Pour les boucles, même principe, et surtout une hérésie que l'on ne retrouve plus ici, les compteurs commencent par 0!
REPT 8,index
ld hl,#C000+#800*index
ld (hl),1<<index
ENDRIl faut noter que si vous utilisez beaucoup les boucles, par exemple pour générer beaucoup de données (remplir un écran vidéo avec un motif par exemple), SJASM+ est moins performant que RASM, en terme de vitesse d’exécution. En effet comme SJASM+ procède en plusieurs passes, il déroule intégralement le code avant de l'assembler, ce qui peut générer beaucoup de lignes de code. Même les commentaires semblent dupliqués, ce qui alourdit d'autant le processus d'assemblage. A manier avec précaution donc, sous peine de ralentissements, dans certains cas extrêmes. Mais heureusement, il s'avere que l'interpréteur LUA intégré est tres rapide, donc au besoin, il suffira de remplacer des blocs REPEAT par des blocs LUA, avec des boucles FOR. Nous reviendrons la dessus ultérieurement.
Les modules ont une syntaxe similaire, et n'ont pas de contrainte particulières, comme le fait de se référencer ou d’être imbriqués:
MODULE mymodule
lab1: ; mymodule.lab1
ld hl,@lab1 ; global lab1
ld hl,@lab2 ; global lab2
ld hl,lab2 ; mymodule.lab2
lab2: ; mymodule.lab2
ld hl,lab1 ; mymodule.lab1
ld hl,another_module.lab1 ; another_module.lab1
ld hl,nested.lab1 ; mymodule.nested.lab1
MODULE nested
lab1: ret ; mymodule.nested.lab1
ENDMODULE
ENDMODULE
MODULE another_module
lab1: ret ; another_module.lab1
@lab2: ret ; global label lab2
ENDMODULE
lab1: ret ; global label lab1
A noter l'usage de @ qui permet de définir ou référencer des labels 'globaux'.
Cette partie va concerner des aspects spécifiques à l'architecture matérielle pour la quelle on va vouloir produire du code. En particulier pour exporter dans des formats propres aux émulateurs, comme les snapshots (SNA), tapes (TAP) et disks (DSK).
Une souplesse de SJASM+ sur ce point, c'est qu'il est possible d'exporter des fichiers à différents endroits du code source, voire meme de changer d'architecture au cours de l'assemblage.
Il faut commencer par définir l'architecture avec l'instruction DEVICE, et ensuite d'utiliser une fonction d'export:
DEVICE ZXSPECTRUM128
StartProg:
...
SAVESNA "snapshotname.sna", StartProg
Pour les Amstrad CPCs, il existe les architectures AMSTRADCPC464 et AMSTRADCPC6128:
DEVICE AMSTRADCPC464
ORG #1000
StartProg:
...
SAVECPCSNA "snapshotname.sna", StartProg
Il n'y a pas de directive RUN, le point d'entrée est passé directement à la directive SAVESNA
Les expressions arithmétiques sont similaires à RASM, mais certaines opérations arithmétiques, comme les exponentielles, les fonctions sinus et cosinus ne sont pas disponibles. En effet, le core gère du calcul 32 bits en entiers, à la différence de RASM qui effectue tout en flottant (ce qui nécessite d'être vigilant dans certains cas).
Pour palier à cela, SJASM+ intègre un bridge vers LUA, ce qui fait plus que compenser ces manques, car il permet aussi d'étendre le langage, et ce, sans pour autant alourdir inutilement le core de l'assembleur. On pourra alors bénéficier d'un véritable langage, pour générer du code, des patterns (par exemple générer des sprites en mode 1, décalés d'un pixel horizontalement) , des data, ou même rajouter des fonctions d'export (par exemple pour produire des snapshots pour un émulateur), etc.
Ce sujet sera traité dans un autre article, car il dépasse le sujet de la migration vers SJASM+. Mais c'est important de souligner l'existence d'une telle intégration, puisque elle permet d'avoir accès à toute la souplesse d'un véritable langage de script, avec des variables, des expressions arithmétiques poussées et bien plus encore.
Fonction simple mais fort pratique, il est possible d'envoyer du texte vers la console, avec l'instruction DISPLAY. (Si on utilise LUA, on a aussi accès à une instructionprint).
Des variables accessibles depuis le langages ont définies, en particulier la date (et l'heure) d'assemblage, ce qui peut aider à s'y retrouver dans ses releases!
Enfin, coté intégration dans des IDEs, comme VS codium, la plupart des plugins sont compatibles avec la syntaxe de SJASM, comme Z80-Macroasm ou ASM Code Lens:
Je suis tombé sur cette vidéo, dans laquelle on voit que la touche COPY semble se comporter de facon étonnante, car elle ne saurait pas faire la différence entre un bon caractère et un mauvais caractere à l'écran.
Pour
]]>Je suis tombé sur cette vidéo, dans laquelle on voit que la touche COPY semble se comporter de facon étonnante, car elle ne saurait pas faire la différence entre un bon caractère et un mauvais caractere à l'écran.
Pour rappel, la fonction Copy permettait de dédoubler le curseur à l'écran, de placer ce seconde curseur n'importe ou à l'écran et de recopier ce qu'il se trouve à l'écran à cet endroit, l'emplacement du premier curseur, à partir du moment ou il s'agit d'un caractère reconnu par le firmware. Si on tentant de recopier un bout de dessin fait à l'aide de PLOT et de DRAW, on avait droit à un beep de mécontentement.
Mais alors comment expliquer ce qui est décrit comme un phénomène sidérant dans la vidéo? Rien de plus simple, il suffit d'aller jeter un œil dans la ROM! Nous allons nous baser sur une version disponible en ligne, il en existe plusieurs, celle ci est scindée en plusieurs fichiers ce qui est plus pratique:
Parmi la liste des fichiers, le premier qui attire notre regard est LineEditor.asm. Une recherche du mot 'COPY' nous mène directement à la ligne 637:
;; COPY key pressed
COPY_key_pressed:
push bc
push hl
call TXT_GET_CURSOR
ex de,hl
call get_copy_cursor_position
jr nz,_copy_key_pressed_12
ld a,b
or c
jr nz,_copy_key_pressed_25
call TXT_GET_CURSOR
ld (copy_cursor_rel_to_origin),hl
jr _copy_key_pressed_14
;;--------------------------------------------------------------------
_copy_key_pressed_12:
call TXT_SET_CURSOR
call TXT_PLACE_CURSOR
_copy_key_pressed_14:
call TXT_RD_CHAR
push af
ex de,hl
call TXT_SET_CURSOR
ld hl,(copy_cursor_rel_to_origin)
inc h
call TXT_VALIDATE
jr nc,_copy_key_pressed_23
ld (copy_cursor_rel_to_origin),hl
_copy_key_pressed_23: ;
call _shift_key__left_cursor_pressed_21
pop af
_copy_key_pressed_25:
pop hl
pop bc
jp c,edit_for_key_13
jp edit_sound_bleeperPour réaliser une analyse rapide, on pourra consulter la table des indirections du pack text (préfixé par TXT), que l'on peut retrouver ici. En particulier:
| ADDR | CODE | Description |
|---|---|---|
| &BB4E | TXT_INITIALISE | Initialise the Text VDU |
| &BB51 | TXT_RESET | Reset the Text VDU |
| &BB5A | TXT_OUTPUT | Output a character or control code to the Text VDU |
| &BB5D | TXT_WR_CHAR | Write a character onto the screen |
| &BB60 | TXT_RD_CHAR | Read a character from the screen |
| &BB6F | TXT_SET_COLUMN | Set cursor horizontal position |
| &BB72 | TXT_SET_ROW | Set cursor vertical position |
| &BB75 | TXT_SET_CURSOR | Set cursor position |
| &BB78 | TXT_GET_CURSOR | Ask current cursor position |
| &BB87 | TXT_VALIDATE | Check if a cursor position is within a window |
| &BB8A | TXT_PLACE_CURSOR | Put a cursor blob on the screen |
| &BB8D | TXT_REMOVE_CURSOR | Take a cursor blob off the screen |
| &BBA5 | TXT_GET_MATRIX | Get the address of a character matrix |
| &BBA8 | TXT_SET_MATRIX | Set a character matrix |
Donc Après une analyse rapide, on comprend que l'appui de la touche copie provoque la récupération de la position des 2 curseurs ( TXT_GET_CURSOR ) suivi de l'appel a TXT_RD_CHAR, suivi d'une validation ( TXT_VALIDATE ), qui si elle échoue provoque l'émission d'un son (edit_sound_bleeper) . Cette validation est essentielle, car la fonction de recopie à l'écran est utilisée pour écrire du texte qui sera analysé par l'interpréteur basic. Il est hors de question de copier des caracteres inexistants!
Ce TXT_RD_CHAR semble donc être la clé de notre énigme. Une rapide recherche dans Clefs Pour Amstrad nous donne:

Bingo! C'est donc cette routine qui serait à l'origine du mystère présenté dans la vidéo. Allons voir le code de TXT_RD_CHAR, cette fois ci dans le fichier text.asm, à la ligne 720:
TXT_RD_CHAR:
push hl
push de
push bc
call scroll_window
call TXT_UNWRITE
push af
call TXT_DRAW_CURSOR
pop af
pop bc
pop de
pop hl
ret
Rien de particulier, si ce n'est l'appel à TXT_UNWRITE, qui contient cette boucle:
ld c,$00
_ind_txt_unwrite_21:
ld a,c
call TXT_GET_MATRIX
ld de,RAM_b738
ld b,$08
_ind_txt_unwrite_25:
ld a,(de)
cp (hl)
jr nz,_ind_txt_unwrite_3
inc hl
inc de
djnz _ind_txt_unwrite_25
ld a,c
cp $8f
scf
ret
_ind_txt_unwrite_35:
inc c
jr nz,_ind_txt_unwrite_21;
xor a
ret
Cette boucle compare un à un les caractères de la fonte avec le contenu de la RAM en &B738. TXT_GET_MATRIX permet de récupérer le caractère. La zone en &B738 est utilisée par une autre fonction du firmware, cette fois dans le pack Screen, SCR_REPACK (&BC56). Elle est appelée au début de TXT_UNWRITE.
En effet, il y a la une subtilité gérée par le firmware, c'est qu'en fonction du mode écran, un même caractère n'occupera pas le même nombre d'octets en RAM. Il faut donc procéder a la transformation de la ce qu'il y a à l'écran sous forme standardisée. C'est exactement le rôle de SCR_REPACK:
&B738 : Compress a character matrix to the standard form
J'invite le lecteur à aller regarder le code de SCR_REPACK à la ligne 1146 du fichier Screen.asm en guise de récréation. Nous allons ici nous contenter de regarder le contenu de a RAM en &B738, après avoir entré le caractère A dans l'éditeur Basic, selon les modes:



Il faut aussi se rendre compte que la matrice utilisée pour la comparaison tient aussi compte du fait qu'elle peut avoir été redéfinie par l'utilisateur (avec la fonction SYMBOL duBasic).
Il est remarquable de constater la encore la grande sophistication du firmware utilisé dans l'Amstrad. L'absence d'un véritable mode texte sur CPC a forcé les ingénieurs qui ont conçu cette machine, de mettre en œuvre toute une stratégie pour réaliser cette fonction de recopie à l'écran. Derrière cette fonction, il faut en effet récupérer les données placées sous le curseur, le convertir selon les modes utilisés, et aller comparer les caractères un a un avec la fonte système (voire la fonte redéfinie par l'utilisateur avec SYMBOL), et le cas échéant recopier le caractère.
Aujourd’hui on va jouer autour d’une api qui permet de créer des interactions avec le chat d’une chaîne twitch. On ne parlera pas ici de piloter votre application (typiquement un jeu) à partir de commandes lancées dans un
]]>Aujourd’hui on va jouer autour d’une api qui permet de créer des interactions avec le chat d’une chaîne twitch. On ne parlera pas ici de piloter votre application (typiquement un jeu) à partir de commandes lancées dans un jeu, mais plutôt de créer une interaction conversationnelle directement dans le chat twitch.
Il existe de nombreux robots qui permettent de créer des interactions[1-3] , mais dès que l’on souhaite sortir des sentiers battus, il devient rapidement nécessaire de développer un peu. Heureusement les prérequis sont très réduits, l’API en question est simple, efficace et va à l’essentiel. Il existe de nombreux tutoriels sur internet, que ce soit en node ou en python (ou autre), en premier lieu la doc officielle twitch qu’il faut impérativement lire (elle est très courte)
Ici nous allons nous focaliser sur un exemple concret en nodejs. La première chose est d’avoir un compte twitch pour le bot. Vous pouvez utiliser votre compte personnel, au moins dans un premier temps, mais ce n’est pas idéal. En effet il n’est pas possible d’utiliser un autre pseudo que le compte auquel il est rattaché, donc le bot parlera au même titre que vous, ce qui peut créer des confusions.
Une fois le compte créé, il faut générer un token d’authentification, ce qui va permettre d’identifier le bot au niveau de votre application. Il suffit d’aller sur cette page et de demander un token. Ce token est bien entendu privé, copiez le dans un coin de votre disque dur, et ne le partagez en aucun cas. On a donc pour le moment:
La première chose a faire est d’installer le package tmi.js:
npm init
npm install -j tmi.js
Attention: il existe un package ‘tmi‘ qui n’a rien à voir, il faut bien utiliser le package tmi.js !
Notre première application va être très simple consister a se connecter sur une ou plusieurs chaînes, et de répondre quand on lui parle (avec @). Cela va nous permettre de voir comment se connecter au chat, et comment gérer les messages.
Avant de se pencher sur le code, précisons que nous l’exécuterons en lui passant les variables suivantes CHANNEL_NAME et CHANNEL_PASSWORD avec le contenu des données récupérées plus haut.
CHANNEL_NAME= CHANNEL_PASSWORD= node ./bot.jsOn va commencer par importer le package tmi.js, récupérer les variables d’environnement, construire un objet qui va nous servir à créer la connexion avec le chat. On utilise process.env pour récupérer les variables.
const tmi = require('tmi.js'); // API Twitch
// On récupere les variables passées
let botname = process.env.CHANNEL_NAME;
let botpassword = process.env.CHANNEL_PASSWORD;
// Si elles n'existent pas, on quitte
if (!botname || !botpassword) {
console.error('No CHANNEL_NAME or CHANNEL_PASSWORD environment variable found. Exiting.');
process.exit(-1);
}
// On ajoute le préfixe oauth:
botpassword = 'oauth:' + botpassword;
Et on construit l’objet opts, qui contient les informations de connexion, la liste des chaines auxquelles de connecter, ainsi qu’une option pour que le bot se reconnecte automatique en cas de déconnexion
// On construit l'objet qui contient les parametres de connexion
const opts = {
identity: {
username: botname,
password: botpassword
},
channels: [
botname
//on peut ajouter d'autres chaines
],
// Automatic reconnection
connection: { reconnect: true }
};Voila, maintenant on est prêt à se connecter! On crée un objet client à l’aode de notre objet opts. On lui associe deux gestionnaires d’évenement (dont nous verrons la définition plus bas), et on se connecte!
// Création d'un client
const client = new tmi.client(opts);
// Gestionnaire d'évenements
client.on('message', onMessageHandler);
client.on('connected', onConnectedHandler);
// Connexion!
client.connect();En ce qui concerne le handler ‘connected‘, on va se contenter d’un log en console:
// Fonction appellée a chaque fois que la connexion est établie
function onConnectedHandler(addr, port) {
console.log(`* Connected to ${addr}:${port}`);
}Maintenant que tout est en place, il nous reste à rentrer dans le vif du sujet, à savoir écrire la fonction qui va recevoir tous les messages qui passent sur le chat des chaines sur lesquelles le bot est connecté.
La fonction onMessageHandler reçoit 4 paramètres:
Notre fonction pour répondre si quelqu’un envoie un message au bot ressemblera à cela:
function onMessageHandler(target, context, msg, self) {
try {
// Ignore les messages du bot
if (self) { return; }
// On vérifie si le nom du bot est dans le message
if (msg.indexOf('@'+botname)>=0) {
// Si oui on répond a l'utilisateur
client.say(target, 'Hello ' + context['display-name']);
// on log le contexte pour voir son contenu
console.debug(context);
}
}
catch (e) {
console.error(e.stack);
}C’est donc assez simple, si le nom du bot apparait dans le message, le bot répondra à l’utilisateur. Pour cela, on utilise la fonction say du client, qui prend le nom de la chaine en premier paramètre et le texte à envoyer sur le second.. Il est en effet judicieux de répondre sur la chaine où l’on a reçu le message ? . Pour répondre à l’utilisateur, on utilise le champ display-name de la variable contexte. On en profite pour loguer le contenu de la variable context pour voir les différentes valeurs qu’elle peut contenir.
Maintenant que nous avons vu les principes de base, allons un peu plus loin, avec une seconde application, un peu plus utile. Elle a déjà servi comme point de départ pour un bot plus sophistiqué (The Tangerine Bot) mais aussi pour quelques chaines twitch (speakeazybot par exemple).
Le code source de ce bot est disponible ici et se base sur l’utilisation de google translate pour la traduction. Il existe d’autres moteurs de traductions comme Yandex ou DeepL, j’ai choisi google translate car il propose une version gratuite, qui permet de tester le principe. Google comme Yandex ou DeepL proposent des versions payantes, qui sont de qualité sensiblement meilleure et avec beaucoup moins de limitation sur le nombre de transactions. A noter que Yandex est très performant pour les traductions depuis et vers le russe, et DeepL s’en sort très bien avec le japonais!
Pour commenter on ajoute le paquet googletrans:
npm install -j googletransAu niveau du code, on importe le paquet:
// API Google Translate
const gtrans = require('googletrans').default;Ensuite on se dote d’un tableau avec les commandes que l’on va vouloir ajouter. Le principe sera d’avoir une commande par langue. Par exemple !de permettra de traduire un texte en allemand. L’API de traduction se chargera de détecter la langue source et de la traduire dans la langue cible.
const tr_lang = {
'de': ['de', 'sagt'],
'en': ['en', 'says'],
'fr': ['fr', 'dit'],
'pt': ['pt', 'disse'],
};Maintenant il nous reste a répondre au cas ou l’on détecte l’une des commandes !de !en !fr ou !pr, en ajoutant dans la fonction onMessageHandler:
// Si le message ne commence pas pas ! on ne fait rien
if (msg[0] != '!') return;
// On récupere la commande
let cmd = msg.split(' ')[0].substring(1).toLowerCase();
// Est ce l'une des commandes de traduction?
if (cmd in tr_lang) {
let ll = tr_lang[cmd];
let txt = tMsg.substring(1 + cmd.length);
// Traduction
gtrans(txt, { to: ll[0] }).then(res => {
// Construit le message de réponse
let answer=context['display-name']+' '+ll[1]+': '+res.text;
client.say(target, answer);
}).catch(err => {
console.error('Translation Error:', err);
})
return;
}Lorsqu’une commande est interceptée, l’API de traduction est appelée, et le résultat, asynchrone est récupéré dans la variable res, si tout s’est bien passé. Ensuite, le message de réponse est construit et envoyé en guise de réponse. Par exemple:

Nous venons de faire nos premiers pas dans le monde des bots irc/twitch. Il y a plein d’applications à imaginer! Des applications créatives, qui changent des bots classiques, et surtout des bots qui polluent les chat, ou qui se contentent d’écouter les conversations, de les stocker on ne sait ou, pour en faire on ne sait quoi…
Dans les prochaines parties, nous aborderons la description de certains aspects d’un bot un peu plus sophistiqué que j’ai créé pour une chaîne musicale sur twitch, le Tangerine Bot! Il s’agira de stocker des informations en base pour rendre le comportement du bot plus riche et d’avantage adapté à chaque utilisateur.
Dans la démo ‘Unique Megademo’ de N.W.C., sortie en 1997 , la partie ‘4 Sins’, impressionne par le fait qu’il y ait 4 scrolltexts sinusoidaux simultanément à l’écran, ce qui semble a première vue
]]>
Dans la démo ‘Unique Megademo’ de N.W.C., sortie en 1997 , la partie ‘4 Sins’, impressionne par le fait qu’il y ait 4 scrolltexts sinusoidaux simultanément à l’écran, ce qui semble a première vue bien difficile a loger en terme de temps machine. Nous allons voir ici qu’une utilisation astucieuse de la palette de couleur, permet de réaliser cela

La premiere chose que l’on remarque dans cette partie, c’est que les 4 scrollings suivent exactement la meme trajectoire, ile peuvent totalement se supperposer, et c’est bien sur la que réside la clé de la compréhension de cet effet. De plus chaque scrolling est monochrome.
Regardons la composition de l’écran: Il est en mode 0 (donc on a 16 couleurs a notre disposition) et il se décompose en 4 écrans, gràce a des ruptures. Ces ruptures sont réalisées en fixant le Registre 9 du CRTC à 1, et le registre 4 est fixé à la valeur standard de #26. Pour rappel le registre 9 controle le nombre de scanlines par ‘bloc texte’, et le registre 4 indique la hauteur d’un écran, exprimé en blocs texte. Ainsi chaque écran fait (R9+1)*(R4+1) lignes, soit 2*39 = 78 lignes, dont 42 lignes visibles, si on exclue la zone ‘border’
Les 4 écrans sont séparés par des zones noires, qui correspondent au blanking video (le Registre 7 est fixé à #18)
Deuxieme chose que l’on constate, c’est que les 4 écrans ont le meme offset vidéo! Cet offset alterne entre #4000 et #c000 (R12=#10 ou #30) d’une trame à l’autre, de facon à avoir un scrolling à l’octet, ainsi que du double buffering. Le double buffering est necessaire pour eviter tout effet de ‘flickering’ le temps de redessiner les scrolltexts… ou plutot le scroll text!
En effet, si on se penche sur les palettes utilisées dans les 4 zones de l’écran, on observe que les couleurs suivantes sont utilisées:




Il s’agit donc d’une utilisation d’une technique de masquage pour superposer les 4 textes au meme endroit dans la mémoire vidéo. Un bit (parmi les 4 disponibles) est dédié a chaque scrolling:
Si on applique une palette ou chaque couleur est différente, on obtient une image qui permet de bien se rendre compte de la superposition:

Toute l’astuce de cette partie consiste donc a combiner les 4 textes lors du rendu graphique du scroll sinus. Bien sur, le code du scrolling lui même nécessite une bonne dose d’optimisation, à la fois pour l’animation sinus, mais aussi le calcul efficace de la superposition des 4 textes.
Notons au passage l’utilisation de 4 fontes différentes pour nous embobiner!
Voici une mise a jour d’un billet précédent, où l’on expliquait comment jouer un soundtrack avec Arkos tracker. Dans la seconde version d’Arkos Tracker (AT2), il existe de nombreux formats de soundtracks, qui sont détaillés
]]>Voici une mise a jour d’un billet précédent, où l’on expliquait comment jouer un soundtrack avec Arkos tracker. Dans la seconde version d’Arkos Tracker (AT2), il existe de nombreux formats de soundtracks, qui sont détaillés dans sa documentation intégrée, qui est très complète. Il y a même un article, en anglais, écrit pas Targhan himself, qui décrit la même chose que le présent billet. Il est possible de jouer des musiques sur de nombreuses architectures. Ce billet est simplement la pour démarrer rapidement, avec pour commencer un exemple simple, et dans un second temps une description des différents formats disponibles, avec leur avantage et inconvénients.
Dans cet exemple, nous allons utiliser le format le plus simple et le plus compact, le format Lightweight. Les exemples et la documentation se trouvent dans le répertoire player/playerLightweight. Vous pourrez retrouver des exemples ici .
Une fois votre soundtrack créé, il faut l’exporter au format AKL. La façon la plus efficace est de générer un fichier sous forme de source et non en binaire, de façon à rester indépendamment de l’adresse ou l’on souhaite loger le fichier. De plus, il faudra prendre soin sélectionner l’option pour générer le fichier de configuration:

Ensuite il suffit d’adapter l’exemple fourni (PlayerLightweight.asm) avec le fichier que l’on a généré. Si on réduit le source au strict minimum:
org #1000
di ; Désactive les ITs systeme
ld hl,#c9fb ; Ei : RET en #38
ld (#38),hl
ld sp,#38
ld hl,Music ; Initialisation
xor a ; Subsong #0.
call PLY_LW_Init
Mainloop:
; Attente VBL
ld b,#f5
Sync:
in a,(c)
rra
jr nc,Sync
; Attente première interruption
ei
nop
halt
di
call PLY_LW_Play ; Jouer la musique
jr MainLoop
Music:
; La configuration n'est pas obligatoire, mais elle permet
; de réduire la taille du binaire produit (ici 1.6K au lieu de 1.8K)
include "./barbapapa_lw_playerconfig.asm"
; Soundtrack exporté
include "./barbapapa_lw.asm"
Player:
include "./PlayerLightweight.asm"Dans cet exemple, les 2 fichiers exportés sont barbapapa_lw.asm et barbapapa_lw_playerconfig.asm. Le second fichier est un fichier de configuration, qui n’est pas obligatoire, mais il permet de réduire (dans cet exemple) la taille du binaire produit de 1.8K a 1.6K.
L’attente avec le Halt permet de s’assurer que la boucle dure suffisamment longtemps pour que l’attente VBL fonctionne. Elle n’est pas obligatoire, en particulier quand il y aura plus de code (et dont de temps machine utilisé) dans la boucle principale.
Cliquez ici pour lancer l’émulateur intégré et visualiser le résultat, directement depuis votre navigateur.Pour gérer plusieurs soundtrack, il faudra générer un fichier source pour chaque soundtrack, en prenant soin de paramétrer un préfixe différent à chaque fois. On pourra ensuite inclure les fichiers dans le source initial de la sorte:
Song1:
include "./song1_lw_playerconfig.asm"
include "./song1_lw.asm"
Song2:
include "./song2_lw_playerconfig.asm"
include "./song2_lw.asm" Et initialiser l’une ou l’autre, par exemple la seconde:
ld hl,Song2
xor a
call PLY_LW_InitPour passer d’une soundtrack à une autre, il faudra arrêter la lecture (en appelant PLY_LW_Stop) et initialiser la nouvelle:
call PLY_LW_Stop
ld hl,Song1
xor a
call PLY_LW_InitIl est possible de jouer des sons par exemple pour agrémenter un jeu. Les sons peuvent être joués sur le channel de son choix, éventuellement par dessus une musique en train de se jouer.
Il faut commencer par déclarant le symbole PLY_LW_MANAGE_SOUND_EFFECTSavant l’inclusion du player:
Player:
PLY_LW_MANAGE_SOUND_EFFECTS EQU 1
include "./PlayerLightweight.asm"Notez que l’utilisation de FX nécessitera la présence du fichier PlayerLightweight_SoundEffects.asm. Ensuite il faut inclure le fichier FX généré précédemment:
SoundEffects:
include "../mySoundEffects.asm"Ensuite on procède à l’initialisation des effets:
ld hl,SoundEffects
call PLY_LW_InitSoundEffectsEnfin, il est possible de jouer le son sur un canal donné, en passant son numéro:
ld c,0 ;Channel 1
ld a,0 ;Numero du FX
ld b,0 ;Volume max
call PLY_LW_PlaySoundEffectIl est possible d’arrêter le son à tout moment :
ld a,0 ; Channel 1
call PLY_LW_StopSoundEffectFromChannelA noter qu’il est possible de se contenter d’utiliser un player restreint aux FX uniquement, si l’on souhaite jouer des sons et pas de soundtrack (PlayerSoundEffects.asm)
Les principaux formats disponibles avec AT2 sont les suivants
A noter qu’il existe aussi un format ‘MOD’ qui permet de jouer des samples. (un exemple ici)
Le format AKL est indiqué comme ‘plutôt’ obsolète dans la documentation d’Arkos Tracker, car d’un coté le générique est plus versatile, et le minimaliste est plus compact. Il fonctionne cependant parfaitement, et il n’y a aucune difficulté à passe à un autre format. Il suffit de choisir la bonne option d’export dans AT2, et d’inclure le player correspondant.
VScode (ou VScodium) est un éditeur de texte orienté code, multi plateforme (linux, windows, mac OS) et qui se situe dans la lignée d’atom, notepad++ ou encore sublime text. Ce sont des éditeurs plutôt légers et très
]]>
VScode (ou VScodium) est un éditeur de texte orienté code, multi plateforme (linux, windows, mac OS) et qui se situe dans la lignée d’atom, notepad++ ou encore sublime text. Ce sont des éditeurs plutôt légers et très modulaires, grâce à leur système de plugins. Pour l’installer, il suffit de suivre les instructions ici.
Je vais décrire ici la configuration de vscode pour développer avec votre assembleur préféré pour CPC, que ce soir SJasm+, Rasm ou encore UZ80. Nous verrons comment ajouter des commandes, et nous pencherons sur l’usage de deux plugins dédiés à simplifier le développement en assembleur z80. Le contenu de cet article est basé sur des échanges avec Ivan Duchaufour (alias gurneyh), qui a apporté sa contribution aux deux plugins dont il va être question.
Pour commencer, voyons comment ajouter des commandes pour exécuter RASM sur votre propre projet.
Vscode permet d’ajouter très simplement des commandes personnalisées, pour chaque projet que l’on crée avec. Pour cela, il suffit de créer (ou d’éditer) le fichier task.json et d’y ajouter un objet dans le tableau tasks, qui ressemble a cela:
{
"version": "2.0.0",
"tasks": [
{
"label": "build-rasm",
"group" : "build",
"command": "rasm",
"args": [
"./main.asm",
"-o",
"test"
],
"problemMatcher": "$errmatcher-rasm"
}
]
}Exécuter cette tache revient à exécuter la commande
rasm ./main.asm -o testVous pouvez adapter cette tache, en modifiant le chemin d’accès à rasm si nécessaire (command), et modifier les paramètres passés à RASM (args). Le paramètre ‘problemMatcher‘ servira dans la suite, pour que les erreurs et warnings renvoyés par RASM soient récupérés par le plugin Macro Assembler (si le plugin n’est pas installé, il faudra commenter cette ligne). A la fin de l’article vous retrouverez d’autres exemples de tâches.
Comme la tâche fait partie du groupe ‘build’, le raccourci CTRL+SHIFT+B permet d’éxécuter directement la commande.
A noter que dès que vous ouvrez une fichier .asm, vscode suggère l’utilisation de plugins (appelées extension). Il est aussi possible de chercher avec le mot clef ‘Z80’ dans le menu extension. Parmi la liste proposée, il faudra choisir Z80 Macro-Assembler et Z80 Assembly meter. Le premier apporte la coloration syntaxique et la gestions des erreurs, tandis que le second permet de calculer rapidement la taille ainsi que le temps d’exécution (en cycles ou en micro secondes) d’un bloc de code.

Considérons le code suivant: (spoiler: il est truffé d’erreurs):
org #8000
ld u, "a"
call #fffff
loop:
jp .loopSi on execute notre commande build-rasm sur ce fichier, on obtiendra le résultat suivant:

Si l’on sélectionne une section du code, la barre d’information en base de la fenêtre indique la taille en octet et le nombre de cycles du bloc. Quelques informations supplémentaires apparaissent en survolant cette zone:

Par défaut, le plugin indique le nombre de cycles, mais il est possible dans les options de sélectionner le timing pour le CPC (parmi d’autres).

Lorsqu’un projet grossi, il devient pratique d’utiliser un script bash ou un makefile pour builder le projet.
Imaginons que l’on souhaite exécuter un script bash situé à la racine de notre projet, on peut ajouter une autre tâche de build, le fichier task.json deviendrait alors:
{
"version": "2.0.0",
"tasks": [
{
"label": "build-rasm",
"group" : "build",
"command": "rasm",
"args": [
"./main.asm",
"-o",
"test"
],
"problemMatcher": "$errmatcher-rasm"
},
{
"label": "build",
"group" : "build",
"command": "./build.sh",
"problemMatcher": "$errmatcher-rasm"
}
]
}On peut également continuer à utiliser le problemMatcher spécifique à RASM dans ce cas.
Si vous souhaitez créer une tache pour compiler le fichier actuellement ouvert, au lieu de main.asm il suffit de remplacer l’argument par « ${file}« :
"args": [
"${file}",
"-oa"
],L’option -oa permet d’utiliser le nom du fichier en entrée pour générer le fichier en sortie (RASM V.0115 ou supérieur requis).
Toujours à l’aide des Tasks, il est possible par exemple, de créer une commande run qui exécutera l’émulateur de votre choix, pour y lancer directement le.dsk, .cpr ou .sna voulu… mais ce sera l’objet d’un autre article!
Dans sa version de Juillet 2019, Arkos Tracker 2 (AT2) intègre le support du MIDI, en offrant la possibilité de connecter un périphérique MIDI (clavier maître par exemple), mais aussi et surtout d’importer des fichiers MIDI, ce qui offre
]]>
Dans sa version de Juillet 2019, Arkos Tracker 2 (AT2) intègre le support du MIDI, en offrant la possibilité de connecter un périphérique MIDI (clavier maître par exemple), mais aussi et surtout d’importer des fichiers MIDI, ce qui offre de nombreuses possibilités, en particulier si on utilise des logiciels d’édition comme Guitar Pro ou un arrangeur comme Band in a Box, (pour ne citer que ceux la). On peut donc s’attendre à un déferlement de nouvelles musiques sur CPC (ainsi que les autres architectures supportées par AT2), tant l’outil est efficace, mais il ne dispense pas pour autant de faire tout le travail de réduction des pistes (les ramener à 3 pour le CPC), ainsi que de remodeler les instruments pour rendre le son plus dynamique. Comme toujours, venez partager vos commentaires et vos transferts sur le forum!
Au même titre que l’on peut ouvrir un fichier tracker, on peut dorénavant ouvrir un fichier midi directement depuis AT2, par le menu file/open song ou en glissant un fichier midi sur la fenêtre d’AT2. Apparaît alors un popup menu permettant de paramétrer l’import. Munissez vous donc de quelques fichiers MIDI, glanés sur le net, si vous n’en avez pas, quelques uns seront proposés dans la suite.

Le nombre de pistes est indiqué en premier lieu, ici 7, suivi de deux cadres, le premier servant à indiquer quelles pistes mixer, et le second permettant de sélectionner les pistes a importer. Le processeur sonore du CPC n’étant doté que de 3 voix, il n’est en principe pas possible d’utiliser plus de voix, mais AT2 gère l’utilisation de plusieurs PSGs. Ce qui permet d’avoir autant de voix que nécessaire, et simplifie le travail de composition. Pour une première écoute, il vaut mieux sélectionner toutes les pistes, quitte à recharger le fichier avec uniquement les voix qui nous intéressent.
Le paramètre PPQ permet de jouer sur la quantization des notes. Pour les morceaux où chaque note est bien calée sur les temps forts, ce paramètré peut être élevé. Pour ceux qui usent de syncopes, et de groove, bref des divisions un peu plus fines, il faudra proivilégiers un PPQ plus bas. A noter que plus la valeur est basse, plus le nombre de séquences va s’accroître, ce qui prendra plus de place mémoire.
Il n’est en général pas nécessaire de changer le paramètre ‘Drum Channel’, qui indique le canal dédié à la batterie. De même, on gardera la case ‘Import note velocities’ cochée, puisque c’est ainsi que l’on peut garder toutes les nuances du jeu et des attaques contenues dans le fichier MIDI.
L’importation peut prendre du temps, d’autant plus que le morceau est long, et qu’il y a de nombreuses pistes. Voici quelques exemples, d’importation brute, sans aucune retouche, avec le fichier midi utilisé pour l’import.
Claire de Lune – Debussy (2 pistes)
Chicken – Jaco Pastorius (9 pistes)
Mercy, Mercy Me (Marvin Gaye) (57 pistes)
Non seulement le tempo général du fichier midi est utilisé pour sélectionner la vitesse générale, mais les changement de tempo en cours de morceau sont aussi pris en compte. Par exemple dans ‘Clair de Lune’, on a la colonne ‘Speed’ qui est régulièrement modifiée:

Des instruments sont crées automatiquement, les sonorités sont simples, et le panel est réduit, par exemple, que ce soit la trompette, une guitare électrique ou un piano électrique, le même instrument est affecté, avec un sont plutôt neutre, et une enveloppe qui diminue l’amplitude:

Malgré cela, les instruments affectés automatiquement permettent d’avoir une bonne idée de l’arrangement. Il va falloir les retravailler bien sûr, mais ils sont déja créés, prêts à être remodelés.
Pour les batteries, les instruments sont plutôt bons et variés, par exemple ici la grosse caisse et un charlet fermé:


La conversion des pistes de batteries est tout à fait performant, AT2 arrive à utiliser peu de pistes,et donne un son convaincant sans aucun réglage, en particulier du fait de l’importation de la vélocité. J’ai fait quelques essais, avec des batteries générées par Band-in-a-Box (un arrangeur), dans différents styles. A chaque fois, un seul canal audio n’est utilisé. Quelques exemples:
Samba:
Disco:
Electro:
Modern Jazz
Dans ce de dernier exemple, la batterie est dans un style plus moderne, sonne bien avec les paramètres d’importation par défaut: Mais si on double la précision (en passant le PPQ de 120/4=30 à 15) , on se rend compte que la batterie est plus fine encore. Le tempo est un peu plus lent, mais on y distingue des roulements que l’on n’avait pas précédemment:
Si on compare les pistes (a gauche PPQ=30, à droite PPQ=15), on peut voir que chaque note dans l’image de gauche a son équivalent dans la colonne de droite. La note #20 correspond a la #00, le #21, à la #02, etc. Les notes à droite sont deux fois plus espacées ce qui est logique vu qu’on a divisé PPQ par 2.


Ceci dit, on voit que certaines notes présentes dans le second cas sont passées à la trappe avec PPQ=30. Par exemple ici, les notes en #27 et #28 sur la colonne de gauche correspondent aux notes #0E et #10 de la colonne de droite, et qu’en #0F, il y a une note qui ne peut pas avoir d’équivalent avec PPQ=30.
D’une manière générale, si un import avec les réglages par défaut ne sont pas convaincants au niveau du rythme, ou si des notes tout simplement perdues, il faudra réduire la valeur de PPQ
La difficultés principale dans l’exploitation de fichiers au format midi, c’est qu’il va falloir tout faire loger sur 3 malheureuses pistes. Voici quelques techniques, à utiliser dans la mesure du possible:
Je suis parti du thème de ‘Airwolf’ que j’ai trouvé sur internet. J’ai choisi ce morceau, non par sa qualité musicale intrinsèque, mais parce qu’en plus d’être connu, il est suffisamment court, il possède plus de 3 pistes, ce qui va nous forcer à le retravailler un minimum. Et puis il s’agit de montrer qu’on doit pouvoir faire mieux que la musique du jeu éponyme, qui était une vraie purge!
Voici le fichier MIDI original. A la première importation, il y a quelque chose qui cloche au niveau du rythme:
En le réimportant avec un tempo plus rapide, le rythme ternaire de l’accompagnement est bien mieux respecté. Seul souci, il faudrait pouvoir indiquer des l’importation que nos pistes ne font pas la longueur standard, ici la taille idéale serait de 40 ou 80, au lieu de 64.
Pour réduire le nombre de pistes, commençons par analyser les pistes:
La méthode que l’on va appliquer est la suivante:
Voici le résultat final. Il faudrait encore retravailler la mélodie qui est un peu brute, et la fin qui sonne un peu brouillon, mais en l’état le résultat est déjà plus convaincant. Et surtout, il tourne sur un vrai CPC.!
Pour la grosse claire, j’ai activé la boucle, sur la dernière ligne de l’instrument, en utilisant l’effet Arpège (Arp), 2 octaves en dessous (-#18), afin de sonner juste.

Ensuite j’ai utilisé les notes de la piste de basses pour les notes de grosse caisse. Sur l’image suivante, on voit comment on a regroupé les 3 pistes initiales (1 de basse et 2 de batteries) pour n’obtenir qu’une seule piste. Les notes de basse (A#, instrument #02) sont maintenant utilisés pour la grosse caisse (instrument #08) et la caisse claire (instrument #0A):

When you want to run a program on Amstrad CPC, the procedure is to use RUN command in BASIC. It will read data on the floppy disk (or cassette), transfer it to RAM and jump to the program’s entry point. Let’s write a small program that
]]>
When you want to run a program on Amstrad CPC, the procedure is to use RUN command in BASIC. It will read data on the floppy disk (or cassette), transfer it to RAM and jump to the program’s entry point. Let’s write a small program that can be executed with a simple LOAD command, meaning that simply transferring it to RAM will trigger its execution!
AMSDOS cannot load a file to an area of the memory,that is too low. However, if a program is located in an area that is high, then the loading pushes through. It is important to note that the the stack initially points to &C000. Every time the CPC calls a function, it stores the return address into the stack (SP is then decreased by 2).
In other words, if Basic interpreter executes a CALL xxxx in &1234 , then &1237 is stored in the stack at &BFFE-&BFFF (SP = SP-2). When the function in xxxx ends, a RET is executed, which will then jump back to &1237 and put the stack back at &C000 (SP = SP + 2).
That being said, if we load a file located at &BEBE that goes up to &C000, and if the area to which the stack is currently pointing is filled by &BE bytes (&BE, &BE, &BE, &BE…. ), then the first RET that the system will encounter after loading will take the Z80A directly to &BEBE and thus run the loaded code!
di
ld bc,#7F10
ld hl,#4B5C
out (c),c
noend
out (c),h
nop
out (c),l
nop
jr noend
defs 140,#be ; #bfff-#bebe = 140If you attempt to SAVE this code from the CPC, tou’ll have to assemble it at another address (luckily our little piece of code is relocatable), save it, and modify the AMSdos header so that the program is located at the correct address.
Another solution consists of using RASM, which can directly generate this DSK with a correct Amsdos header for our file:
org #BEBE
start:
di
ld bc,#7F10
ld hl,#4B5C
out (c),c
noend:
out (c),h
nop
out (c),l
nop
jr noend
defs #bfff-$,#be
end:
save 'arg.bin', start, end-start, DSK,'arg.dsk'Let’s see if it works:

Something that has never been done, is a program launch using a simple CAT. I think it’s impossible,but who know?
]]>Pour inaugurer la section ‘archives’, voici un courrier daté du 31 Août 1992, que j’ai reçu de la part de Longshot. Il y décrit l’état déjà bien avancé de la compré
]]>
Pour inaugurer la section ‘archives’, voici un courrier daté du 31 Août 1992, que j’ai reçu de la part de Longshot. Il y décrit l’état déjà bien avancé de la compréhension de la rupture verticale, technique révélée pas la célèbre S&KOH , sortie en Novemlre. J’ai retranscrit ici le texte intégral de l’explication (avec une correction apportée par son auteur), car le texte est plutôt clair étant donné la complexité du sujet. J’ai refait les schémas pour une meilleure visibilité. Pour accéder au scan des courriers originaux, ainsi qu’à un espace de discussion, ca se passe sur le forum.
Je vois que tu as bien pigé la rupture ligne à ligne, et que tu ne fais pas comme beaucoup de monde qui veulent savoir ce qui est la RVI (l’adressage de plusieurs blocs) avant de maîtriser totalement la Rupture Horizontale (RH), qui est la rupture que tu as utilisé jusqu’alors.
Tu me demandes comment avoir plus de blocs (puisque tu t’es malheureusement aperçu que l’offset démarre toujours et seulement sur le premier bloc de 2048 octets soit 2048*4 pages : 8192 octets en ligne a ligne, ceci en supposant qu’on ne peut plus servir du reste, même en modifiant R9, puisque les premiers blocs servent à autre chose…)
Bref, la solution fût trouvée par Overflow à l’aide de techniques que je n’avais pas exploitées complètement: la Rupture Verticale. La Rupture Verticale avait pour but, à l’origine de couper l’écran en 2 (ou plus), pour changer de mode graphique (uniquement possible durant 1 HBL de au moins 2μs ) ou d’offset..
Ainsi, tout comme en RH, le R0 contient le nombre de μs sur une ligne, soit 63 (+1) = 64μs (sur tous les ordinateurs, puisque cette valeur dépend de la fréquence de balayage du moniteur.. 0.000064sec*312.5 = 0.02sec, soit 1/50ème, .. 50Hz!) . Si R0=31, alors le CRTC générera un split au milieu des 64μs de 2×32μs. La largeur de la bande HBL (qui ne compte pas dans les 64 μs mais est juste une zone noire en surimpression visuelle) est déterminée par R3. Cette zone noire correspond à peu près à R7 car elle a un rapport avec la synchro de l’image (synchro horizontale) et sa position est déterminée par R2..
Bref, il est nécessaire que la zone sync du moniteur (qui dure quelques μs) soit en parallèle avec celle du CRT pour que l’image soit stable horizontalement. (R3 dois être au moins à 7 dans le bord (a vérifier) et bien sûr au minimum au centre (au moins à 1 (2μs )) pour un changement de mode). Il va sans dire qu’il faut déjà être en ligne-à-ligne pour faire tout ca (bonjour la brochette de registres).
D’ailleurs un détail facilitant la vie… OUTI marche partout mais il PRE-décrémente B. Donc pour le Gate Array., B doit être égal à #80 pour le CRTC (#BE00 -> donnée, #BD00 -> select)
Venons en maintenant à l’adressage des blocs (ce que j’ai expliqué plus haut étant mes expériences d’il y a 2 ans..) Voila le principe (c’est un exemple!)
Hum… j’espère que le schéma t’aide un peu à piger. Bref R0 est à 49 (50μs) durant le main screen (pendant ce temps, et avec une bonne dose d’optimisation R12,R13 et R9 peuvent être changés!)
Puis (c’est une exemple puisqu’il y a d’autre méthodes, notamment la SPLIT 1-8 et la méthode DUNCAN) R0 passe à 1, et le CRTC génère dans la HBL des écrans de 2μs ! (plus petit, ça commence à déconner (c.a.d R0=0 ! … encore que … 1μs …)
Bref, selon le numéro du bloc courant et la valeur de R9 (qui en fait, compte les ruptures) on a :
Dans cet exemple, R9 durant A est mis à 4, ce qui provoque un bloc 3 en B … pas évident, n’est-ce pas?
Il y a cependant des problèmes de CRTC. Sur type 0 il est très dur d’obtenir 7 ruptures en HBL sans anomalie (dans le compteur). Pas mal de contraintes aussi puisque R9 ne doit JAMAIS passer à une valeur inférieure au bloc courant. En fait pour tout dire, le record de blocs indépendants (RVI, Rupture Verticale Indépendante) est de 5, tous CRTCs confondus, et a été trouvé par DUNCAN.Le CRTC type 1 est le mieux armé pour la RV car il ne génère aucun temps entre 2 ruptures verticales (au contraire du CRTC 0, ce qui fait ses problèmes) . Bref si R3 est à 0 sur type 1* et que R0=7 ( 8μs ), on génère un split de 8×8 μs (SPLIT 1-8), ce qui donne:
et permet dont d’avoir tous les blocs sur une ligne, donc d’avoir plus de Ram Vidéo … bien sur … c’est plus chiant à gérer… et ca ne marche pas du tout sur type 0 car même avec R3 à 0, le CRTC génère une zone « visuelle » de 1 octet environ…
(*) Note de Longshot:
Avec le recul, et concernant le SPLIT 1-8 Il est inexact, dans cette situation, d’écrire que R3=0 lorsqu’on splitte en cours d’affichage, car ça suppose que la HBL a lieu. C’était une mauvaise interprétation de ma part : ça marchait car R2 était juste supérieur à 7 dans l’exemple, et il était positionné 1 fois entre 0 et 7 dans une des périodes de 8 µsec pour que l’écran se synchronise. La valeur de R3 n’a aucune importance. Elle doit juste être assez grande pour permettre une bonne synchronisation horizontale du moniteur sur le signal HBL du CRTC.
Je pense que j’ai fait cette confusion car la première fois que j’ai touché à R0 en cours de ligne début 1990, c’était pour changer de mode graphique, et non changer l’adresse. J’étais fasciné par un soft graphique sur Atari ST qui y était arrivé dont je ne me souviens plus du nom. Et lorsqu’on veut changer de mode, une HBL courte au milieu d’une ligne s’impose. Mais pour changer d’adresse, aucune HBL ne s’impose et c’est même à éviter, d’autant que placer R3 à 0 correspond à une longueur de 16 µsec sur certains CRTCs. C’est pourtant ainsi que Overflow a fait sa première rupture en cours de balayage dans l’intro de la S&KOH






Lorsque l’on souhaite exécuter un programme sur Amstrad CPC, la procédure consiste à utiliser la commande RUN du Basic, qui va se charger de lire des données sur la disquette (ou cassette), les transférer en RAM et de sauter à
]]>
Lorsque l’on souhaite exécuter un programme sur Amstrad CPC, la procédure consiste à utiliser la commande RUN du Basic, qui va se charger de lire des données sur la disquette (ou cassette), les transférer en RAM et de sauter à son point d’entrée. Je vous propose d’écrire un petit programme qui sera capable d’être exécuté avec une simple commande LOAD, c’est a dire que le simple fait de le transférer en RAM provoquera son exécution!
AMSDOS refuse de charger un fichier dans une zone trop basse. Par contre, si un programme est logé haut dans la RAM, ça passe. Il faut savoir que la pile se trouve en &C000 initialement. A chaque fois que le CPC appelle une fonction il empile l’adresse de retour (ce qui revient à « descendre » dans la mémoire).
Autrement dit, si le basic en &1234 fait un CALL xxxx alors &1237 est placé dans la pile en &BFFF et &BFFE (SP=SP-2). Lorsque la fonction en xxxx sera terminée, elle fera un RET qui récupérera l’adresse &1237 et remettre la pile en &C000 (SP=SP+2).
Ceci étant posé, si l’on charge un fichier logé en &BEBE qui va jusqu’à &C000, et que dans la zone ou se trouve la pile couramment, sont logés des octets &BE, &BE, &BE,&BE….. Le premier RET que le système va faire à l’issue du chargement va amener directement le Z80A en &BEBE et donc démarrer le code chargé.
di
ld bc,#7F10
ld hl,#4B5C
out (c),c
noend
out (c),h
nop
out (c),l
nop
jr noend
defs 140,#be ; #bfff-#bebe = 140Si l’on sauvegarde ce code depuis le CPC, il faudra l’assembler à un autre endroit (le programme est relogeable tel quel), le sauvegarder, et modifier le header AMSdos pour que le programme soit logé a la bonne addresse.
Une autre possibilité consiste à utiliser RASM, qui peut directement générer ce DSK avec un header Amsdos correct pour notre fichier :
org #BEBE
start:
di
ld bc,#7F10
ld hl,#4B5C
out (c),c
noend:
out (c),h
nop
out (c),l
nop
jr noend
defs #bfff-$,#be
end:
save 'arg.bin', start, end-start, DSK,'arg.dsk'La preuve par l’image:

La preuve par l’emulation (via TinyCPC) : Démarrer:
Par contre, ce qui n’a jamais été fait, c’est de pouvoir lancer un programme en faisant un CAT. Je pense que c’est impossible… mais qui sait…
]]>Le clavier s’adresse au travers du registre 14 du PPI.
Il y a 10 lignes de clavier qui se partagent le clavier physique et les deux joysticks
Particularité, chaque joystick n’utilise qu’une seule ligne ce qui permet des optimisations agressives pour un programme
]]>Le clavier s’adresse au travers du registre 14 du PPI.
Il y a 10 lignes de clavier qui se partagent le clavier physique et les deux joysticks
Particularité, chaque joystick n’utilise qu’une seule ligne ce qui permet des optimisations agressives pour un programme 100% joystick. Pour ce qui est de la correspondance entre les touches et les lignes de clavier, consultez cette table sur le site du quasar
; routine minimale de lecture d'une ligne clavier
LD BC,#F782 : OUT (C),C ; port A en sortie
DEC B : OUT (C),0 ; validation
LD BC,#F40E : OUT (C),C ; registre 14 sur le port A
LD BC,#F6C0 : OUT (C),C : OUT (C),0 ; selection du registre + validation
LD BC,#F792 : OUT (C),C ; port A en entrée
LD A,ligne_clavier+#40 ; ligne clavier + fonction READ bit 6
DEC B ; B=#F6
OUT (C),A
LD B,#F4 ; adresse du port A
IN A,(C) ; lecture du port A
RET
Cette routine minimale laisse le port A du PPI en entrée (puisqu’on termine la routine par la lecture de l’état de la ligne demandée), ce qui la rend incompatible avec des players de musique. Il devient judicieux de considérer que le port A du PPI est toujours en sortie par défaut vu que c’est ce que considèrent tous les programmes qui jouent de la musique. Ainsi on n’initialise pas le port en sortie en début de routine, mais à la fin!
; routine minimale de lecture d'une ligne clavier
; on considère que le port A est déjà en sortie!
LD BC,#F40E : OUT (C),C ; registre 14 sur le port A
LD BC,#F6C0 : OUT (C),C : OUT (C),0 ; selection du registre + validation
LD BC,#F792 : OUT (C),C ; port A en entrée
LD A,ligne_clavier+#40 ; ligne clavier + fonction READ bit 6
DEC B ; B=#F6
OUT (C),A
LD B,#F4 ; adresse du port A
IN A,(C) ; lecture du port A
LD BC,#F782 : OUT (C),C ; port A en sortie pour tout le monde!
RET
Pour lire plusieurs lignes (toutes les lignes?) il suffit de faire une boucle à partir de l’envoi du numéro de ligne. On utilisera l’instruction INI qui est un IN évolué destiné à être utilisé pour les lectures en rafale d’un port I/O. L’instruction INI pré-décrémente B, lit le port puis écrit la valeur à l’adresse pointée par HL et enfin incrémente HL.
; routine complète et à peu près optimisée de lecture du clavier
; on considère que le port A est déjà en sortie!
; on positionne le tampon clavier de 10 octets en #40
scankeyboard
LD BC,#F40E : OUT (C),C ; registre 14 sur le port A
LD BC,#F6C0 : OUT (C),C : OUT (C),0 ; selection du registre + validation
LD BC,#F792 : OUT (C),C ; port A en entrée
LD HL,#40
LD DE,#F6F4
repeat 10
LD B,D : OUT (C),L
LD B,E : INI
rend
LD BC,#F782 : OUT (C),C ; port A en sortie pour tout le monde!
DEC B : OUT (C),0 ; validation obligatoire pour CPC+
RET
]]>