Reconnaissance de caractères en BASIC

Sur une machine avec un « vrai » mode texte, lorsqu’on affiche du texte, le micro-processeur se contente de copier les codes ASCII de chaque caractère dans zone définie de la mémoire vidéo, et le processeur graphique se charge de convertir ces codes ASCII en caractères visibles sur l’écran.

Le ZX-Spectrum, comme plusieurs micros de la même époque, ne dispose pas d’un mode d’affichage par caractères « matériel ». Cela veut dire que lorsqu’on affiche du texte, celui-ci est affiché en mode graphique par le micro-processeur.
C’est légèrement plus lent car il faut au minimum modifier 8 octets dans la mémoire vidéo au lieu d’un seul(*), mais en général ça ne se remarque pas trop.

Un avantage est qu’on peut sans problème mélanger texte et graphiques à l’écran, mais il peut être compliqué pour un programme de retrouver quel caractère se trouve à une position donnée de l’écran.

Certains micros, comme le TRS-80 Model 100(**) conservent une « fausse » ram vidéo qui contient le code ASCII de chaque caractère affiché. Le micro-processeur doit toujours « dessiner » chaque caractère en entier en mode graphique, mais si on veut savoir quel caractère se trouve à une position donnée il suffit de lire cette mémoire vidéo pour trouver son code ASCII(***).

Sur le ZX-Spectrum ce n’est pas le cas.
Il y a pourtant une fonction BASIC appelée SCREEN$ qui renvoie le caractère affiché à une position donnée de l’écran.
Comment cette fonction procède-t-elle pour retourner cette information ?

Je soupçonnais la présence d’une zone mémoire conservant les caractères affichés comme pour le Model 100, mais cela ne fonctionnait pas avec les caractères redéfinissables.

En effet le ZX-Spectrum dispose de 21 caractères redéfinissables (codes ASCII entre 144 et 164). Après un reset, ces caractères contiennent les graphiques des caractères « A » à « U ».
Si on entre les deux lignes suivantes :

PRINT CHR$ 144
PRINT ASC SCREEN$(0,0)

nous récupérons un « A » (le graphique par défaut du caractère 144) suivi de la valeur… 65 ! (le code ASCII du « vrai » A).

Que ce passe-t-il donc ?
Pour vérifier j’ai écrit le petit programme ci-dessous :

Ce programme affiche un « F » dans la première case de l’écran (à la position 0,0), lit le code ASCII correspondant avec SCREEN$(0,0) (70, valeur ASCII de F) puis traces 1 pixel dans cette même case avant de relire son code ASCII (maintenant 0 puisque ce caractère n’existe pas) avant de tracer une droite pour transformer le caractère « F » en « E », puis de lire une dernière fois la valeur de SCREEN$(0,0), qui est maintenant 69 (c’est à dire le code ASCII du « E »).

Est-ce que cette fonction reconnaît vraiment dynamiquement tous les symboles du jeu de caractères du ZX-Spectrum ?

Je pensais qu’une simple implémentation serait d’utiliser une fonction de hashage sur les 8 octets qui composent l’image du caractère, puis de regarder dans une table de lookup pour trouver le code ASCII correspondant à l’image, mais après avoir regardé le code source de cette fonction, il semble qu’elle compare effectivement les 8 octets de l’image avec les images du jeu de caractère !

Cette reconnaissance fonctionne pour toutes les couleurs, ce qui peut poser quelques problèmes. En effet, à cause de cette fonctionnalité, SCREEN$ retourne la même valeur pour un espace et pour un bloc graphique plein, mais heureusement il y a des solutions pour contourner ce problème.

Décidément, ce bon vieux ZX-Spectrum est plein de surprises dès qu’on le regarde d’un peu plus près.

(*) Plus si l’affichage est en couleurs
(**) Sur lequel je suis en train de taper cet article :o)
(***) Ca lui sert aussi à redessiner l’écran après un scrolling vertical

Enregistrer sur K7 avec un Omni 128HQ

L’Omni 128HQ est une machine très polyvalente côté émulation, et intègre des extensions bien pratiques comme des ports Joystick ou un (même deux) lecteurs de SD cards compatibles DivMMC, mais ce dernier ne peut fonctionner que lors de l’émulation d’un ZX-Spectrum (48K, 128K ou +2e, mais un Spectrum).

Cela pose problème lors de l’utilisation des autres machines comme le ZX-81 ou le Jupiter ACE. Heureusement il reste le port K7, que je vais utiliser ici.

Premier problème, il faut trouver le bon câble. Si les micro-ordinateurs d’origine avaient deux prises 3.5mm mono (une pour lire et une pour enregistrer, sur l’Omni 128HQ elles ont été remplacées par une seule prise 3.5mm stéréo (la seconde prise 3.5mm est toujours là mais elle permet d’écouter la sortie audio du chip AY-3-8910).

N’ayant pas pu trouver le bon type de câble, et ne voulant pas en faire un, j’ai utilisé un câble 3.5mm stéréo vers deux RCA mono, avec deux adaptateurs RCA vers 3.5mm mâle.

En guise de magnétophone, j’ai utilisé un TASCAM DR-05 avec la configuration suivante :

  • Format : WAV 24 bits
  • Sample : 44.1k
  • Type : Mono
  • MIC POWER : OFF
  • LOW CUT : OFF
  • PRE REC : OFF
  • AUTO-TONE : OFF
  • AUTO-REC : OFF

Il ne reste plus qu’à s’assurer que le contrôle du niveau d’enregistrement automatique est désactivé (menu « Quick » pendant que l’enregistrement est en pause) et à mettre le niveau d’enregistrement à 0 (le niveau en sortie de l’Omni 128HQ est assez élevé, mais ça fonctionne).

J’ai testé avec le mode Jupiter ACE et tout fonctionne très bien. Il m’est arrivé quelques fois que le Jupiter ACE plante à la fin de la sauvegarde, mais dans tous les cas, la sauvegarde a bien fonctionné.

Les commandes principales en Forth pour utiliser le lecteur de K7 sont :

SAVE fichier
VERIFY fichier
LOAD fichier

Notez que sur l’Omni 128HQ, la première fois que vous allez utiliser une de ces commandes, vous allez avoir un menu permettant de choisir le périphérique à utiliser. Il faut choisir l’option « Cassette » (ce menu n’apparaît pas sur un vrai Jupiter ACE).

Contrairement à l’équivalent BASIC, LOAD sans argument ne chargera pas le premier fichier trouvé, mais affichera les noms de tous les fichiers rencontrés (pressez la touche espace pour revenir au prompt). Il faut impérativement fournir le nom du fichier pour que celui soit chargé (et il n’y a pas de guillemets autour du nom, qui est limité à 8 caractères).

De plus, la commande LOAD est en fait l’équivalent de MERGE, donc si vous chargez un programme alors que d’autres sont déjà en mémoires, ceux-ci seront ajouté au dictionnaire.

Fichier corrompu !

Après quelques jours de développement de mon Space Invaders en BASIC ZX-Spectrum, j’ai eu la désagréable surprise de constater des lignes étranges à la fin du programme.

La ligne 8960 à la fin du programme est corrompue (en plus, il y a deux lignes avec le même numéro)

Non seulement leur contenu semble corrompu, mais leur numéro n’étant pas dans l’ordre, l’éditeur du BASIC ne veut même pas les éditer…

Lorsque j’ai commencé ce programme, je voulais profiter à fond de l’expérience « 80s » et tout faire sur la « vraie » machine, sans utiliser d’émulateurs ou d’outils modernes (à part l’utilisation du DivMMC comme sauvegarde car je n’avais pas encore de lecteur de K7 compatible).
Le programme ne dépassait pas quelques dizaines de lignes, mais si je voulais les retaper il faudrait que je commence par les recopier à la main (pas d’imprimante disponible) et ça ne me tentait pas trop.

Heureusement comme je l’ai mentionné dans un autre billet de blog la documentation du BASIC est plus que complète, et contient en particulier la structure des programmes en mémoire, ainsi que l’endroit où ces programmes se trouvent.

Je fais un reset du ZX-Spectrum pour partir d’une situation stable, et je commence à taper le programme BASIC d’exemple de la documentation qui dumpe la mémoire correspondant au programme BASIC en mémoire.
Si j’arrive à décoder à la main ces quelques lignes, il est évident qu’avec un programme de la taille de Space Invaders ça va être très très long si je ne trouve pas une meilleure solution.
Comme la partie corrompue se trouve à la fin du programme, je décide de n’afficher que les numéros de lignes que je décode en mémoire, et l’adresse où la ligne commence. Comme ça une fois que je trouve l’adresse de la première ligne corrompue, je n’aurai qu’à poker la valeur de fin du programme pour éliminer ces lignes.
Etrangement, cette valeur n’est pas indiquée dans le guide du BASIC mais après quelques tests il s’avère que la fin du programme est indiquée par une ligne avec le numéro 24832 (le plus grand numéro de ligne valide pour le ZX-Spectrum est 9999).

J’ai écrit ce programme en commençant à la ligne 4000 car c’est un espace libre dans mon Space Invaders. L’idée étant de faire un « MERGE » dans le programme corrompu, puis un « RUN 4000 » pour l’exécuter.

Le programme qui liste les numéros de ligne du programme BASIC, et leur adresse en mémoire

Le problème auquel je n’avais pas pensé est que UnoDOS ne fournit pas de commande « .merge » et la commande standard « MERGE » utilise le lecteur de K7.

Heureusement, Unodos permet de « monter » un fichier « .TAP » qui sera utilisé à la place du lecteur de K7 par les commandes du BASIC.
Seul problème (de plus :o) ) je ne trouve nulle part comment obtenir un « .TAP » vide…
Après un peu de recherche, je fini par trouver une page qui indique qu’il est possible de concatener plusieurs « .TAP » entre eux. Du coup il est probable que ce format n’ai pas d’entête particulier et que je puisse donc utiliser un fichier vide comme « .TAP ».
Il ne me reste donc qu’à créer un fichier vide avec :
.bsave rescue.tap,0,0

Puis à monter ce fichier en sortie et à sauver mon programme basic dedans :
.tapeout rescue.tap
SAVE "r4000"

Note : J’ai découvert plus tard que .tapeout crée automatiquement un fichier vide si le fichier spécifié n’existe pas… donc le .bsave était inutile

Il ne reste plus qu’à recharger mon Space Invader corrompu, monter le .tap en lecture, puis faire un merge et lancer la ligne 4000 :
.load si.bas
.tapein rescue.tap
MERGE ""
RUN 4000

La ligne corrompue commence à l’adresse 25347

Une paire de pokes plus tard, tout est rentré dans l’ordre !

J’espère juste que ce genre de problème ne se produira pas trop souvent :o)

Personnalisation d’UnoDOS 3

L’Omni128-HQ (et probablement le DivMMC) peut utiliser deux « OS » différents pour la gestion des cartes SD : ESXDOS et UnoDOS
Les deux ont leurs avantages et limitations, mais j’ai commencé à utiliser UnoDOS.

Une partie de la page d’aide de UnoDOS 3

Je ne suis pas sûr d’avoir fait le meilleur choix car le repository sur Github ne semble pas avoir évolué depuis un bon moment mais il a l’avantage d’être open-source, et d’être facile à utiliser une fois qu’on a compris comment il fonctionne.

Avec le système de saisie des ordinateurs Sinclair où une seule touche permet d’entrer un mot clé complet il n’est pas simple d’ajouter de nouvelle commandes.
Le truc utilisé est de commencer ces nouvelles commandes par le caractère point « . » qui passe le clavier en mode « L » (lettres minuscules) et permet de taper des commandes.
Par exemple .dir va lister le contenu du répertoire courant de la carte SD.

La documentation n’est par contre pas super claire sur ces commandes et leur format. Il m’a fallu un petit moment pour réaliser qu’il ne fallait pas utiliser de guillemets autour des noms de fichier, contrairement à l’instruction SAVE du BASIC par exemple. Donc pour sauver un programme BASIC sur la carte SD il faut entrer :

.save monprog.bas

Oui, il faut spécifier l’extension, mais elle peut avoir n’importe quelle valeur (en fait, vous n’êtes même pas obligés d’en utiliser une mais c’est bien pratique pour retrouver vos fichiers).

La première limitation de ce système est qu’il n’est pas possible d’utiliser par exemple une variable chaîne de caractères pour spécifier le nom du fichier, ce qui peut être gênant si on utilise une telle commande dans un programme.

L’autre problème est que certaines de ces commandes sont un peu longue à taper sur le clavier « authentique » des ZX-Spectrum (Le seul clavier sur lequel l’utilisation du système « 1 touche = 1 commande » semble une bonne idée vu la difficulté de taper à une vitesse acceptable).

Une des commandes disponible est « .help » qui affiche la liste des commandes disponibles et pour certaines d’entre elles donne quelques détails supplémentaires. Grace à cette dernière, je me suis aperçu que certaines commandes avaient plusieurs noms (par exemple « .del » et « .erase« ).

En fouillant un peu, je me suis rendu compte que toutes ces commandes étaient des exécutables placés dans le répertoire DOS à la racine de la carte SD, et surtout que la manière utilisée pour avoir plusieurs noms pour une même commande est très simple : Le même exécutable est copié sous plusieurs noms différents dans ce répertoire !

Partant de là, il devient très simple de créer vos propres commandes. Si comme moi vous êtes habitués à Linux, vous pouvez simplement entrer les lignes suivantes pour vous sentir comme chez vous :

.cd /dos
.copy copy cp
.cp del rm
.cp dir ls
.cp help man

J’ai été tenté de créer également des aliases « L » et « S » pour LOAD et SAVE, mais je pense plutôt essayer de créer mes propres commandes qui ajouteront automatiquement l’extension « .bas » lorsqu’elle n’est pas spécifiée (chaque touche compte avec ce clavier ;o) )

Notez que les sources en assembleur des commandes de base sont fournies, ce qui est un bon point de départ pour ajouter toutes sortes de nouvelles fonctionnalités.

Comme d’habitude pour ce genre de bricolages, pensez bien à faire une copie de sauvegarde de votre carte SD avant de commencer à modifier les fichiers système.

Le BASIC du ZX-Spectrum

Pris d’une envie subite de lire un vieux manuel utilisateur, et d’utiliser mon Omni128-HQ Laptop j’ai téléchargé le manuel du BASIC du ZX Spectrum sur archive.org

ZX-Spectrum BASIC Manual's cover
Le manuel « BASIC Programming » fourni avec le ZX-Spectrum

Je n’ai jamais eu de ZX-Spectrum mais je garde de bons souvenirs de la lecture du manuel BASIC du ZX81 dans les années 80, même si à l’époque je n’avais pas tout compris.

Ce manuel ne fait pas exception, et si il commence par une partie destinée aux débutants qui n’ont jamais touché un ordinateur de leur vie (c’était courant à cette époque) il va très loin dans les détails d’implémentation, ce qui permet de faire des choses assez sympas.

Après avoir expliqué comment utiliser le clavier (qui comme d’habitude chez Sinclair peut avoir jusqu’à 5 fonctions par touche) et les bases de l’éditeur de code, le manuel va guider le lecteur via de courts programmes de plus en plus complexes, et utilisant une ou deux instructions de plus à chaque chapitre.

Le BASIC de Sinclair est assez classique si on excepte l’utilisation du mot clé LET obligatoire pour manipuler les variables, et une gestion un peu particulière des chaînes de caractères.

J’ai bien apprécié au chapitre 13 le détail du comportement des fonctions AND et OR, et qui explique comment simplifier certaines expressions.
Par exemple, au lieu d’écrire :

IF x<31 THEN LET X=X+1

vous pouvez écrire :

LET X=X+(1 AND x<31)

(parce que AND retourne son premier argument quand le second est non-zéro, ou zéro dans les autres cas)

Il y a aussi tout un chapitre qui liste des séquences d’échappement pour pouvoir déplacer le curseur au milieu d’une chaîne de caractères. Malheureusement après avoir fait quelques tests il s’avère que cela est beaucoup plus lent que l’utilisation des fonctions équivalentes TAB ou AT donc ce ne sera pas aussi utile que cela en avait l’air.

C’est au chapitre 23 que les choses sérieuses commencent avec une description des instructions IN et OUT destinées à lire/écrire les ports d’entrées/sorties du Z80, qui va jusqu’à donner le brochage du port d’extension de la machine, et donner quelques trucs pour choisir les adresses de vos propres cartes d’extensions.

Le chapitre 24 est encore plus intéressant car non seulement il donne la carte mémoire du ZX Spectrum, mais il décrit en grand détails l’encodage des programmes BASIC en RAM, ainsi que les formats de stockage des différents types de variable.
Cela permet de se rendre compte par exemple que les variables avec un nom d’un seul caractère prennent beaucoup moins de place en mémoire (et aussi qu’il ne semble pas y avoir de limite sur la longueur du nom des variables numériques), et que les variables de type tableau, chaîne de caractères ou utilisées comme index d’une boucle FOR ont des noms limités à un seul caractère.

Le chapitre suivant donne une longue liste d’adresses utiles si vous voulez jouer avec peek et poke, et enfin le chapitre 26 donne quelques exemples d’appel de routines en langage machine depuis le BASIC.

Bref, c’était une lecture très intéressante, qui m’a donné envie d’écrire un programme un peu plus conséquent en BASIC, ne serait-ce que pour voir si 30+ années d’expérience supplémentaires me permettraient d’obtenir de meilleurs résultats que ce que je faisais à l’époque sur mon ZX-81 :o)

Mon premier « vrai » programme de jeu en BASIC était une version très simplifiée de Space Invaders : En gros il y avait un seul vaisseau qui traversait l’écran sur la ligne du haut, et il fallait presser une touche au bon moment pour le toucher avec un missile tiré du bas de l’écran.
Du coup, je me suis dit qu’il serait intéressant de voir si je pouvais coder un vrai Space Invaders avec toutes les options. Je doute que la vitesse du BASIC permette d’y arriver mais je suis curieux de voir jusqu’où il est possible de s’en rapprocher…

Mais ça fera l’objet d’un autre article :o)

En attendant, cette petite lecture m’a permis d’apprendre quelques raccourcis bien utiles pour utiliser un ZX-Spectrum de manière optimale.

Le truc le plus utile que j’ai appris :

Pour éditer une ligne BASIC existante, il faut la sélectionner avec les flèches haut et bas, puis utiliser la touche EDIT (Shift+1), mais ça vous le savez déjà si vous avez utilisé un micro-ordinateur Sinclair :o)

Le problème est que la navigation avec les flèches est très lente (chaque pression rafraîchit tout l’écran). Le truc est que si vous entrez un numéro de ligne qui n’existe pas, la ligne existante suivante est automatiquement sélectionnée. Si par exemple vous êtes au début de votre programme et que vous voulez éditer la ligne 650, il suffit de taper :

6 4 9 ENTER SHIFT+1

C’est le genre de petits trucs qui peut vous faire gagner facilement quelques heures sur le développement d’un programme de quelques centaines de lignes !

Col-Tracker, part XI

La première partie de cette série est disponible ici.

Deux ans plus tard

Et oui, encore un trou de deux ans avec presque aucune évolution dans Col-Tracker.

Bon, j’ai bien fait quelques évolutions mineures sur les retours de mon béta-testeur, et il a pu composer la musique du jeu Muncher Mouse mais pas grand chose de plus n’a changé.

Nous sommes maintenant en 2020 et à la fin de l’année l’applet Flash utilisé par Col-Tracker ne fonctionnera plus (comme tous les autres applets Flash), et je ne suis pas super motivé pour le ré-écrire en Javascript, donc je ferais sûrement un correctif minimal pour que l’éditeur reste utilisable, mais sans possibilité d’écouter les musiques à part en passant par le « ROM player« .

En attendant il vous reste environ un mois si vous voulez l’essayer :

http://www.fantasy-lands.net/Col-Tracker/

Notez qu’une fois votre clavier configuré il faudra appuyer sur « F3 » pour faire apparaître l’applet Flash, puis cliquer sur l’icône « Flash » pour autoriser l’exécution.

La version actuelle de Col-Tracker

Présentation de l’Omni128HQ Laptop

L’Omni 128HQ est un clone moderne du ZX Spectrum qui « émule » plusieurs ordinateurs de cette époque qui partagent une architecture similaire :

  • Le ZX-Spectrum (48Ko, 128Ko (version de 1985 ou 1986), +2e)
  • Le ZX81
  • Le Jupiter Ace
Tout neuf dans sa boite

Contrairement à la plupart des versions modernes de nos vieux micros, celui-ci n’est pas basé sur un FPGA mais utilise un vrai Z80, et dispose de tous les ports d’extensions d’origine (avec quelques modifications).

Il dispose également de quelques périphériques intégrés bien pratiques comme par exemple un lecteur de cartes SD (et micro SD) compatible DivMMC, et deux ports joystick (l’arrière est compatible « Sinclair » et l’avant compatible « Kempston« ).

De gauche à droite, les deux ports SD, les DIP, le bouton NMI et les deux ports joystick

Au niveau des différences, la sortie audio/vidéo RF a été remplacée par une sortie RGB + audio propriétaire (avec câble péritel fourni), et les deux jacks mono utilisés pour brancher un magnétophone à cassettes sont maintenant stéréos : Un est toujours utilisé pour le magnétophone à cassette (« In » et « Out » correspondent aux canaux droite et gauche) tandis que le second est utilisé pour la sortie audio stéréo du Yamaha AY-3-8910.
Le port d’alimentation est différent de celui des Spectrums d’origine, mais ce n’est pas plus mal car ça empêche de le connecter par erreur dans un des jacks audio.
Notez que la machine dispose d’une sortie HDMI mais celle-ci n’est pas active par défaut (probablement pour ne pas avoir à payer de royalties au consortium HDMI).

Il y a un port d’extension complet mais la documentation n’est pas très claire sur la compatibilité avec les périphériques d’époque donc il vaut mieux être prudent avant de connecter quelque chose dessus (quoi qu’avec les ports joystick et le DivMMC intégrés il n’y a pas besoin d’ajouter quoi que ce soit pour avoir tout le confort moderne).

Cette machine est vraiment compacte d’origine, mais elle devient vraiment portable avec l’option « Laptop » qui rajoute un écran LCD 9″ qui vient se fixer sous la machine avec une paire de charnières.
La construction est un peu artisanale, et probablement pas aussi robuste qu’un laptop moderne mais l’intégration est assez bien faite, en particulier grâce au connecteur vidéo/audio propriétaire qui permet de ne connecter qu’un seul câble entre le moniteur et l’Omni 128HQ. L’écran est au format 16×9, mais il est possible de le configurer en 4×3 afin de respecter le format d’origine.

l’Omni128HQ avec son écran LCD

Pour faire bonne mesure, il est possible de mettre à l’intérieur deux batteries rechargeables standard (environ 15 euros la paire sur eBay) ce qui donne environ 3 heures d’autonomie avec l’écran.
Il n’y a pas d’indicateur du niveau de charge, mais une LED rouge s’allume pendant la charge et devient violette lorsque les batteries sont chargées. Lors de l’utilisation, le moniteur étant plus gourmand que l’Omni, celui-ci va commencer à clignoter lorsque les batteries sont presque déchargées, ce qui devrait vous laisser amplement le temps de sauvegarder ce que vous êtes en train de faire.
Un seul petit bémol : Si vous essayez de charger les batteries pendant que l’ordinateur est allumé, celui-ci va chauffer (environ 60º au niveau du circuit de recharge) donc il vaut mieux le charger éteint.

Un autre point négatif est que le DivMMC ne fonctionne que pour le ZX-Spectrum, et uniquement en mode « USR0 » donc si vous voulez programmer sur le ZX81 ou le Jupiter Ace il va falloir brancher un lecteur de K7, comme au bon vieux temps.
C’est également le cas si vous voulez utiliser les modes « modernes » des ZX-Spectrum (c’est à dire le mode par défaut des machines 128 Ko où il faut entrer les mots clés BASIC lettre par lettre au lieux d’utiliser les touches dédiées).

A part ça, c’est une machine plutôt sympa à utiliser. En particulier contrairement à la plupart des micros de cette époque qui nécessitent de trouver un écran compatible, les câbles qui vont avec, une table assez grande pour caser tout ça, etc…, il suffit de le mettre en marche pour commencer à l’utiliser, puis de replier l’écran pour le ranger.

Col-Tracker, part X

La première partie de cette série est disponible ici.

Premier utilisateur !

Depuis quelques semaines, j’ai un utilisateur (salut Cyril ;o) ) qui a bien voulu essuyer les plâtres en essayant d’utiliser Col-Tracker pour écrire la musique du prochain jeu de mon ami Youki.

Bon, comme il utilise un MacBook il a fallu que j’ajoute le support des claviers sans pavé numérique, et que je repense les raccourcis claviers (ainsi qu’une interface pour changer de clavier car pour l’instant je faisais ça à la main dans le code) mais ça n’a pas été trop dur.

Ca fait un long moment que je n’ai plus mis ce blog à jour, mais il y a eu quelques évolutions dans Col-Tracker.

D’abord j’ai corrigé le problème du bruit affreux au début du canal 2. En fait il semble que ce soit causé par le fait que le canal commence par une commande « rest ». Ca ne se posait pas sur le canal 3 car comme la pause était beaucoup plus longue, Col-Tracker n’utilisait pas une commande « rest » mais une note traditionnelle avec un volume de 0 (ça prend moins de mémoire que d’utiliser plusieurs « rest » l’un derrière l’autre).

Je n’ai pas trouvé de moyen élégant d’intégrer ça dans mon code, mais je suppose que maintenant que tout fonctionne il devrait être temps de faire un petit refactoring pour profiter du recul acquis après 4 années de développement.

J’ai aussi implémenté un « ROM player« , qui permet en un clic de générer une ROM au format Colecovision contenant la musique prête à être jouée sur un émulateur, afin de vérifier que la tout fonctionne bien.

Ca m’a permis grâce à Cyril de découvrir un bug de priorité des canaux dans certains cas… mais il semble s’agir d’un bug du BIOS donc je ne peux pas faire grand chose (en gros il faut changer à la main les priorités des canaux sinon un ou plusieurs d’entre eux restent silencieux). J’ai passé quelques heures à tracer le problème sous le débogueur de mon émulateur, et si toutes les données sont au bon endroit, la routine du BIOS essaie de jouer les notes sur le mauvais canal :o(

Je suis assez tenté de remplacer le BIOS par mes propres routines (ce qui me permettrait d’encoder les musiques de manière plus compacte) mais je crains que ça prenne beaucoup de temps avant d’être stable donc je vais éviter pour l’instant.

La onzième partie de cette série est disponible ici.

Col-Tracker, part IX

La première partie de cette série est disponible ici.

Faire des pauses

En fait, si vous donnez une priorité au canal 1 supérieure à celle du canal 2, le canal 2 ne jouera aucune note, et ainsi de suite… donc ma solution au problème des 0xFF de la semaine dernière ne fonctionne pas.

J’ai donc simplement modifié l’algo de génération des pauses pour le canal 3. En fait ce n’est pas si gênant : le surcoût en mémoire sera juste d’un octet dans les cas où on a une pause de 31, 61 ou 62 frames.

A ce point, tout semble fonctionner parfaitement, y compris le mod de test que j’ai composé spécialement pour inclure tous les cas tordus auxquels je pouvais penser !

…enfin jusqu’à ce que j’essaie d’utiliser le mod généré dans un programme directement sur la Colecovision :

Le mod de test ne contenait que 2 patterns jouant une gamme montante, puis descendante (Do Ré Mi Fa Sol La Si Do Si La Sol Fa Mi Re Do), mais pour rendre ça plus intéressant ça jouait « Do Ré Mi » sur le canal 1, « Fa Sol La » sur le canal 2 et « Si Do » sur le canal 3 :

Premier module de test : Une gamme qui monte et redescend

Le fichier ASM généré contenait exactement ce à quoi je m’attendais, mais à l’exécution il y avait un gros son affreux dès que le canal 2 commençait à jouer des notes :o(

Je suis toujours en train de chercher la cause du problème mais il semblerait que le canal 2 ne puisse pas commencer par une pause (étrangement, ça a l’air de fonctionner pour le canal 3)

La dixième partie de cette série est disponible ici.

Col-Tracker, part VIII

La première partie de cette série est disponible ici.

Le silence c’est compliqué

L’éditeur d’instruments avec stages séparés fonctionne, et on peut maintenant jouer une pattern sur 2 canaux (le 1 et le 2).

Pour le 3ème canal, il reste un problème à cause des silences…

J’ai écrit un algorithme pour générer des silences entre les notes de la façon la plus optimale (c’est à dire qui prend le moins d’octets sur la cartouche de la Colecovision) et ça m’a appris beaucoup sur la façon dont le BIOS décode les notes.

Il y a une commande « rest » qui sert à faire des pauses. Celle-ci utilise 5 bits pour la durée de la pause (en nombre de frames, c’est à dire en 1/50 secondes pour une console PAL et en 1/60 secondes pour NTSC). Je pensais que lorsque ces 5 bits étaient à zéro la pause serait de 32 frames (25=32), mais en fait cela fait une pause de 256 frames ce qui est à la fois surprenant et totalement non-documenté(*).

Petit détail amusant, j’avais constaté ce comportement en regardant les sources de l’applet Flash de Daniel Bienvenu, et je pensais qu’il avait fait une erreur (désolé Daniel :o) ). Heureusement c’était un bug facile à corriger, donc maintenant tout devrait fonctionner, non ?

Non… Ca fonctionne bien sur les canaux 1 et 2, mais pas sur le canal 3.

Le problème vient également de la commande « rest« .
Le format de celle-ci est :  » <nº de canal sur 2 bits> 1 <durée sur 5 bits> « .

Lorsqu’on veut faire une pause de 31 frames sur le canal 3, ça donne « 11111111« , ou 0xFF en hexadécimal. Hors, 0xFF est aussi utiliser pour indiquer qu’un canal n’a aucune note.

J’ai supposé en lisant la documentation technique que ça ne fonctionnait que lorsque 0xFF était au début des données du canal, mais en fait dès qu’un 0xFF est présent dans un canal (à la place d’une note) celui-ci s’arrête.

J’ai d’abord pensé à faire un algorithme différent et un peu moins optimisé pour le canal 3 afin de ne jamais avoir de 0xFF mais j’ai réalisé qu’il me faudrait aussi un algorithme différent pour la canal 0 (celui des bruits blancs)(**).

A la place, j’ai essayé une solution un peu plus exotique : Puisque chaque canal « logique » a une priorité, et peut jouer des notes sur plusieurs canaux « hardware« , je vais générer toutes les pauses sur le canal « hardware » 1.

Si je donne au canal « logique » 1 la priorité la plus grande, quand un des autres canaux va essayer de jouer un « rest« , ils vont être suspendus jusqu’à ce que ce canal « hardware » 1 soit disponible. Ca devrait fonctionner cette fois, non ?

Après un test rapide ça a l’air de fonctionner mais il faut que j’implémente cette solution pour pouvoir tester tous les cas. Ca ne fonctionne pas du tout dans l’applet Flash par contre, mais je peux toujours générer des données différentes pour celle-ci (par exemple en remplaçant « 0xFF » par « 0xF 0xE1 » (pause de 30 frames, puis pause de 1 frame)).

Essayons ! :o)

La neuvième partie de cette série est ici.

(*) En fait, je pense que c’est un effet de bord de la manière dont le code à été écris : La valeur sur 5 bits est copiée dans un registre 8 bits (les 3 bits supérieurs sont mis à zéro), puis une pause d’une frame est effectuée et le registre est décrémenté ; la commande se termine quand le registre contient zéro, mais si on part de zéro, celui-ci sera décrémenté avant le premier test, et contiendra alors 255, ce qui entraîne une durée de 256 frames.

(**) En effet ce canal ne supporte pas les « Frequence sweep » que j’utilise beaucoup dans les silences car ça permet d’avoir une pause de 4096 frames pour seulement 6 octets.