Travaux pratiques avec l'Ensoniq

               SOMMAIRE

     1. INTRODUCTION
     2. INTERFACE LOGICIELLE DE BASE
     3. INTERFACE LOGICIELLE NORMALISEE
     4. LES REGISTRES INTERNES GENERAUX
     5. LE REGISTRE INTERNE DE CONVERSION A/N
     6. LES REGISTRES INTERNES D'OSCILLATEURS

     Rédacteur : Marc BAVANT
                                         Mai 1988

1. INTRODUCTION

L'ordinateur Apple II GS (désigné par 2GS dans ce qui suit) est muni d'un circuit spécialisé qui lui confère toutes ses possibilités sonores. Il s'agit du DOC (Digital Oscillator Chip) 5503 de la firme Ensoniq. Ce circuit, utilisé dans des synthétiseurs professionnels, contient 32 oscillateurs numériques, divers registres de commande et un convertisseur analogique-numérique à 8 bits. De plus, ce circuit est associé à une mémoire vive de 64Ko qui lui est réservée.
Ces exercices de travaux pratiques ont pour but de familiariser l'étudiant avec les possibilités du DOC et son utilisation dans la configuration de l'Apple II GS. On se limitera à des opérations élémentaires du point de vue logiciel et on ne s'aventurera pas trop dans les dédales de la boite à outils (indispensable pour les programmes d'application plus ambitieux).

matériel requis
* un 2GS muni d'un clavier et d'un moniteur (lecteur de disquette et DOS pas indispensables)
* un poste de radio muni d'une sortie casque (de préférence avec volume et tonalité réglables)
* quelques composants électroniques de base :  résistances, condensateurs, alimentation TTL (piles, alimentation variable ou récupérer alimentation du 2GS récupérée sur le connecteur de jeux), le moyen de les implanter (planche à contacts, soudure...), un petit voltmêtre et un peu de cable électrique gainé souple.

connaissances requises
* programmation de base en 6502 et notions de 65816
* utilisation du moniteur intégré du 2GS (fonctions de base)
* bon sens et prudence pour manipuler l'électronique : vous êtes seuls responsables de vos actes ! (cet avertissement ne sera pas renouvelé)

bibliographie de référence
* Apple II GS hardware reference
* Ensoniq DOC ERS
* Sound Tools ERS

2. INTERFACE LOGICIELLE DE BASE

généralités

Le principe de communication entre le processeur 65816 du 2GS et le DOC est identique à celui employé avec tous les autres organes d'entrée-sortie, à savoir l'utilisation de "registres" spécialisés situés dans l'espace $C000-$CFFF.

L'interface est constituée
. d'un registre de commande $C03C qui permet d'une part de passer certaines commandes au DOC et d'autre part de modifier la signification du registre d'adresse,
. d'un registre d'adresse $C03E-$C03F qui permet de spécifier l'adresse de RAM ou le registre interne concerné,
. d'un registre de donnée $C03D qui permet de lire ou d'écrire à l'endroit pointé par le registre d'adresse.
contenu du registre de commande
                    b7 : DOC occupé
                    b6 : accès RAM
                    b5 : autoincrémentation
                    b4 : réservé
                    b3..b0 : volume
DOC occupé : si ce bit est à 1, le circuit ne prend pas en compte les demandes d'accès à ses registres internes, il faut donc attendre que ce bit passe à 0 (est-ce que cela arrive vraiment en pratique ?).
accès RAM : si ce bit est à 1, l'adresse présente dans le registre d'adresse (16 bits) désigne un octet de la RAM privative du DOC sinon, le premier octet du registre d'adresse sera interprété comme un numéro de registre interne du DOC pour toute opération de lecture ou d'écriture.
autoincrémentation : si ce bit est à 1, le registre d'adresse est incrémentée à chaque fois que le 65816 accède au registre de données (en lecture ou en écriture).
volume : ces quatre bits indiquent le volume sonore de sortie ; ils correspondent aux seize graduations du réglage de volume accessible par le tableau de bord  (ce réglage est disponible à l'adresse $E100CA) mais ils fournissent toujours le résultat $F en lecture.

mise en pratique

Mettez le 2GS sous tension puis appelez le moniteur.

Consultez le contenu de $C03C. Vous trouverez vraisemblablement $1F, ce quisignifie DOC non occupé, accès aux registres internes, pas d'autoincrémentation. Ecrivez alors la valeur $1F dans $C03C, en apparence rien ne devrait avoir changé mais pour percevrez un souffle plus intense dans le haut-parleur, signe que le volume sonore demandé ($F) est plus important qu'auparavant. Si vous tapez CONTROL-G ou que vous faites une erreur de syntaxe, le traditionnel BIP se transformera en un GONG assourdissant. Tout rentrera dans l'ordre si vous modifiez de nouveau le registre de commande en tenant compte du volume imposé par le tableau de bord ($E100CA).

*****   accès à la RAM

Essayons de transférer quelques octets dans la RAM du DOC. Pour cela positionnons le registre de commande à $7x (o| x est le volume, par exemple 3) ce qui signifie accès RAM et autoincrémentation. Positionnons le registre d'adresse à la valeur $80FE :
     C03C : 73
     C03E : FE 80    (inversion des octets)

attention : toute erreur de manipulation peut entrainer des résultats apparemment aberrants ; en particulier, à chaque fois que vous commettrez une erreur qui occasionne un BIP, il vous faudra repositionner le registre de commande.

Puis insérons successivement les valeurs $55, $66, $77, $88 dans le registre de données
     C03D : 55
     C03D : 66
     C03D : 77
     C03D : 88

Observons alors le registre d'adresse, on trouve
     C03D : 02 81

ce qui illustre bien l'autoincrémentation de ce registre à chaque accès au registre de données.

Essayons maintenant de relire ces octets, repositionnons donc le pointeur d'adresse à $80FE puis lisons alternativement le registre de données et celui d'adresse à l'aide du petit programme suivant :
     *!
     !300:LDA C03D
     ! STA 2000
     ! LDA C03E
     ! STA 2001
     ! LDA C03F
     ! STA 2002
     ! RTS
     !
     *

Il vient alors :
     * C03C : 73 (deux précautions ...)
     * C03E : FE 80
     * 300G 2000.2002
     =* 00/02000 : xx FF 80  (xx quelconque)
     * 300G 2000.2002
     =* 00/02000 : 55 00 81
     * 300G 2000.2002
     =* 00/02000 : 66 01 81
     * 300G 2000.2002
     =* 00/02000 : 77 02 81
     * 300G 2000.2002
     =* 00/02000 : 88 03 81

nota : on a fait précéder les réponses du moniteur par le signe = afin de les différencier des commandes.

Etonnant, non ? En fait, le fonctionnement est plus cohérent qu'il n'y parait : l'opération de lecture du registre par le 65816 a pour effet de transmettre la commande de lecture à la logique d'interface puis d'incrémenter le pointeur d'adresse, mais la donnée recherchée n'estpas immédiatement disponible et le 65816 obtient la valeur correspondant à la requête précédente.

Pourquoi avoir utilisé un programme et pas le moniteur, vous demandez-vous ? Eh bien, faisons-le, on obtiendra quelque chose comme :
     * C03C : 73
     * C03E : FE 80
     * C03D.C03F
     =* 00/C03D : xx FF 80    (xx quelconque)
     =* 00/C03D : 66 01 81
     =* 00/C03D : 88 03 81

il semble donc que l'on saute une valeur sur deux, peut-être parce que le moniteur lit ces registres deux fois pour avoir la valeur recherchée et que double lecture implique doucle incrémentation.

exercice 2.1 : essayez de relire la RAM avec le moniteur mais en n'utilisant pas l'option d'autoincrémentation; conclusion ?

*****   accès aux registres internes

Le fonctionnement est tout à fait analogue pour adresser les 256 registres internes du DOC et nous pouvons nous lancer dans un programme qui permet de récupérer leur valeur dans la page $2000-$2100.
     *!
     !300:LDA #13  ; accès registre + pas autoincrémentation
     ! STA C03C
     ! LDX #0
     !307:STX C03E ; X est le numéro de registre concerné
     ! LDA C03D
     ! LDA C03D ; cette répétition est nécessaire
     ! STA 2000,X
     ! INX
     ! BNE 307
     ! RTS
     !*300G 2000.20FF

exercice 2.2 : faites la mème chose en utilisant l'autoincrémentation.

3. INTERFACE LOGICIELLE NORMALISEE

généralités

Nous avons réussi à accéder au DOC sans difficulté jusqu'à présent, mais cette interface est un peu trop proche du niveau physique et pas assez compacte. C'est pourquoi Apple fournit une interface normalisée dite "de bas niveau" (au sens des programmes d'applications complexes) sous la forme de routine en ROM. Les points d'entrée de ces routines dépendent de la version de ROM utilisée et on doit avoir recours à l'outil GetTableAdress pour récupérer un pointeur sur une table des points d'entrée.

mise en pratique

Appelez cet outil grace à la commande moniteur U :
     *\4 4 0 0 0 0 B 8\U

le résultat est $00FF407B avec la version 01 des ROM.

              
routineoffsetpoint d'entrée
Read Register00$FF40A3
Write Register04$FF40B4
Read RAM08$FF40A8
Write RAM0C$FF40B9
Read Next10$FF40AF
Write Next14$FF40BC

Vous avez tous les éléments pour désassembler ces routines très simples, comprendre leur mode de fonctionnement et leur interface.

remarque 1 : prêtez attention aux problèmes de gestion de l'incrémentation et de l'accès séquentiel (Xxxx Next).

remarque 2 : dans l'interface de ces routines, le registre A est court alors que le registre X est tantot long (accès RAM) tantot indifférent (accès registres).

remarque 3 : nous utiliserons dans ce qui suit les points d'entrée trouvés ci-dessus, ce qui n'est pas la manière légale de faire et, à l'occasion, nous reviendrons à l'interface de base pour les programmes qui imposent des contraintes sur le nombre de cycles processeur (en effet, l'inconvénient de cette interface normalisée est qu'elle ne spécifie pas la durée d'exécution de ces routines).

Nous sommes maintenant à mème de réécrire le petit programme de lecture de tous les registres sous une forme abrégée :
     *!
     !300:LDX #0
     ! JSL FF40A3 ; ReadReg
     !306:STA 2000,X
     ! JSL FF40AF ; ReadNxt
     ! INX
     ! BNE 306
     ! RTS
     !*

Ecrivons maintenant un programme inverse qui nous permette de positionner tous les registres internes désignés à la valeur voulue à l'aide d'une table de couples (registre,valeur) terminée par $FF et commen\ant en $340 par exemple :
     !320:LDY #0
     !322:LDA 340,Y
     ! CMP #FF
     ! BEQ 337
     ! TAX
     ! INY
     ! LDA 340,Y
     ! INY
     ! JSL FF40B4 ; WritReg
     ! BRA 322
     !337:RTS
     !*

Vérifions tout \a sur un exemple :
     * 340 : 80 88 FF  (mettre 88 dans le registre 80)
     * 320G 300G 2080
     =* 00/2080 : 88 ....

Reste maintenant à connaitre la signification de tous ces registres !

4. LES REGISTRES INTERNES GENERAUX

On a déjà eu l'occasion de dire que le DOC possède 256 registres internes numérotés de $00 à $FF. Les registres de numéro compris entre $00 et $DF permettent la commande de chacun des 32 oscillateurs et se répartissent donc en tranches de 32 ($00,$20,$40,$60,$80,$A0,$C0 et $D0). Les positions de $E3 à $FF sont inutilisées.

Le registre $E2 concerne la conversion analogique-numérique et les registres $E0 et $E1 font l'objet du présent chapitre.

registre d'autorisation $E1 (oscillator enable). Ce registre à pour but d'indiquer au DOC combien d'oscillateurs sont en fonction simultanément à un instant donné. Ce paramêtre à une influence directe sur la fréquence du son produit par le DOC comme nous le verrons plus loin. L'utilisation de ce registre n'est pas des plus évidentes :
en écriture : introduisez la valeur 2*n-2 ou 2*n-1 modulo 64 pour autoriser n oscillateurs (ex : $02, $03, $42 -> 2 oscillateurs),
en lecture : vous obtenez la valeur $C0+2*n-1 lorsque n oscillateurs sont autorisés (ex : $C3 -> 2, $FF -> 32).

A la mise sous tension ou après un démarrage à froid, les 32 oscillateurs sont prêts à fonctionner comme l'atteste la valeur $FF du registre $E1. Décidons de restreindre le nombre d'oscillateurs autorisés à 16. On introduira pour cela la valeur 2*16-2=$1E dans le registre $E1. Si on exécute à présent notre programme de lecture de tous les registres, on s'apercevra que tous les registres correspondant à des oscillateurs non autorisés sont égaux à $FF.

Essayons de modifier la valeur du registre $90=$80+16 qui correspond à l'oscillateur numéro 16 (c'est-à-dire le 17ème...).
     * 340: 90 88 FF
     * 320G 300G 2090
     =* 00/2090 : FF ....

la valeur qu'on voulait imposer n'a pas été prise en compte. Regardons les choses un peu plus en détail :
     *!
     !800:STZ 820
     ! LDA #88
     ! LDX #90
     ! JSL FF40B4 ; WritReg
     !80B:JSL FF40A3 ; ReadReg
     ! CMP #88
     ! BNE 818
     ! INC 820
     ! BRA 80B
     !818:RTS
     !
     * 800G 820
     =* 00/820 : nn ...    (nn <> 0)

Ce programme nous permet de voir que la valeur donnée est conservée un certain temps avant que le DOC ne se souvienne que l'oscillateur en question n'est pas autorisé et efface le registre.

Essayons maintenant l'opération inverse, c'est-à-dire d'augmenter le nombre d'oscillateurs autorisés d'une unité. Et là une suprise nous attend :

                    UNCLAIMED SOUND INTERRUPT

Ce message signifie qu'une interruption issue du DOC est arrivée sur la ligne IRQ du 65816 et que le vecteur d'interruption correspondant est resté à sa valeur par défaut, à savoir l'affichage de ce message et l'arrêt du traitement.Un reset nous sortira provisoirement d'affaire avant d'approfondir cette notion d'interruption.

registre d'interruptions $E0 (oscillator interrupt)
Chacun des 32 oscillateurs est capable de générer une interruption dans certaines phases de son fonctionnement afin de permettre au processeur de modifier les caractéristiques de ce fonctionnement pour une phase ultérieure.
Les interruptions peuvent être autorisées ou non pour chaque oscillateur i par l'intermédiaire du registre $A0+i. Nous verrons plus bas que c'est le bit 3 de ce registre qui autorise l'interruption.
Lorsque qu'un oscillateur est en situation de générer une interruption, le DOC met à 0 le bit 7 du registre $E0, indique dans ce mème registre le numéro de l'oscillateur concerné (bits 1 à 4) et active une interruption sur la ligne IRQ du 65816.
Si d'autres interruptions surviennent avant l'acquittement de la première, le numéro de l'oscillateur concerné est mis dans une file d'attente spécifique.

Lors d'une interruption IRQ issue du DOC, le 65816 est censé exécuter une routine qui acquittera l'interruption grace à une lecture du registre $E0. Le bit 7 passera alors à zéro s'il n'y a pas d'autre interruption en attente.

remarque : le reset qui a suivi notre dernière expérience a acquitté l'interruption "non réclamée" provoquée vraisemblablement par l'oscillateur 16. Rappelons que ce dernier avait été mis en fonctionnement alors que tous ses registres valaient $FF ce qui, entre autres choses, a d| l'autoriser à générer une interruption.

La petite expérience suivante permettra de vérifier cette conjecture :
décidons d'autoriser un nouvelle oscillateur (le numéro 17) en ayant pris soin de modifier juste auparavant le registre $A0+17=$B1 pour lui interdire les interruptions (une expérience précédente nous a prouvé que la modification de registre d'un oscillateur non autorisé restait effective un certain temps) :
     *!
     !800:LDA #0  ; interruption inhibée
     ! LDX #B1
     ! JSL FF40BA ; WritReg
     ! LDA #22    ; -> 18 oscillateurs autorisés
     ! LDX #E1
     ! JSL FF40BA
     ! RTS
     !
     *

Cette fois-ci, on entendra peut-être une court bruit dans le haut parleur mais on a évitera l'erreur fatale.

5. LE REGISTRE INTERNE DE CONVERSION A/N

Nous commen\ons par cette fonction du DOC car c'est la plus simple et elle semble totalement indépendante des autres fonctions.

Le signal à convertir est présenté sur deux broches du connecteur réservé au son, après quoi la lecture du registre interne $E2 déclenche un processus de conversion qui va durer environ 32 microsecondes (d'ou une fréquence d'échantillonnage de  31.25 kHz). La valeur trouvée est alors disponible dans ce mème registre. On note qu'il y a encore un décalage entre la valeur lue et la valeur recherchée (on lit toujours la valeur précédente) mais il ne faut pas effectuer deux lectures consécutives dans l'espoir de résoudre ce problème puisque chaque lecture réinitialise le processus de conversion (d'ou également l'impossibilité d'utiliser les routines Read Register et Read Next à cet effet !).

brochage du connecteur réservé au son

Ce connecteur est un connecteur Molex qui se présente sous la forme d'une barrette de sept pointes de section carrée et d'un centimêtre de haut environ. Ce connecteur se trouve à l'avant droit de l'appareil, tout près du connecteur de haut-parleur.  Le brochage est décrit dans les documents cités en référence, mais il faut savoir que les broches sont numérotées de l'avant vers l'arrière. Cela donne :
               7, 6, 5, 4     indications de canal pour démultiplexage
               3              sortie analogique
               2              masse
               1              entrée analogique

Il est spécifié que l'entrée ne doit pas dépasser 2,5 V et 3000 ohmsd'impédance.

Il peut paraitre difficile à première vue d'utiliser ce connecteur sans posséder un connecteur femelle correspondant, mais en fait c'est très simple. Il suffit de prendre un conducteur souple gainé de plastique, proprement coupé pour présenter une section sans barbes de cuivre (sources potentielles de court-circuits), et de se servir de la gaine tubulaire comme d'un gant pour coiffer la broche. Enfoncer suffisamment pour que cela tienne en faisant attention toutefois à ne pas tordre les broches. Traiter de cette manière les broches 1 et 2 qui seules nous servent pour le moment.

étalonnage du convertisseur

Nous savons peu de chose sur ce convertisseur sinon qu'il accepte des tensions inférieures à 2,5 V. Construisons-nous un petit programme qui se charge d'interroger le convertisseur et de stocker 256 résultats.
     *!
     !800:LDA #13
     ! STA C03C
     ! LDA #E2
     ! STA C03E
     ! LDX #0
     !80C:LDA C03D
     ! DEC 0 ; histoire de passer 5 cycles
     ! DEC 0
     ! DEC 0
     ! DEC 0
     ! DEC 0
     ! DEC 0
     ! STA 2000,X
     ! INX
     ! BNE 80C
     ! RTS
     !
     *

Pour fonctionner comme souhaité, ce programme doit être lancé en vitesse lente (1 MHz, faites un reset pour être plus sur). En vitesse rapide, il faudrait rajouter des délais pour atteindre les 32 microsecondes indispensables.

Vérifier à l'aide d'un voltmêtre que le circuit ne délivre aucune tension entre les broches 1 et 2 (si c'était le cas, vous vous seriez trompé de broches !) et court-circuitez-les. La valeur trouvée par le convertisseur est alors 0. Bricolez une alimentation variable avec une pile et un potentiomêtre. Reliez la masse au pole moins de la pile et appliquez prudemment et successivement des tensions de 0 à 2.5 V de manière à étalonner le convertisseur. Pour ma part, aux erreurs demesure près je trouve :

tension (V)valeur numérique
0.000
0.216
0.42C
0.642
0.858
1.06E
1.280
1.496
1.6AA
1.8BE
2.0D2
2.2E8
2.4FC
  

Ces résultats montrent de manière flagrante que le convertisseur est linéaire sur la totalité de son domaine, avec une pente constante d'environ 110/V. Cette gamme de tensions admissibles va nous poser un petit problème car la plupart des équipements de reproduction musicale (radio, magnétophones) fournissent un signal de valeur moyenne nulle, signal qu'il faudra donc polariser autour de 1,2 V si on veut l'utiliser en entrée du convertisseur. Et ceux qui fournissent un signal polarisé n'ont pas de raison particulière d'avoir la tension moyenne que nous recherchons. Un simple pont diviseur et un couplage par condensateur se révéleront très satisfaisants dans tous les cas.

écoute directe du son numérisé et écrêté

Ce paragraphe va certainement rappeler des souvenirs à ceux qui avaient utilisé l'entrée cassette de leur vieil Apple II pour numériser des messages qu'on pouvait lui faire restituer d'une voix nasillarde, mais parfaitement intelligible et naturelle, à tout moment. La technique est ici la mème : on ne regarde que le signe du signal d'entrée (par rapport à sa valeur moyenne) et on actionne un déplacement de la membrane du haut-parleur à chaque changement de signe.
     *!
     !800:LDA #13
     ! STA C03C
     ! LDA #E2
     ! STA C03E
     !80A:LDA C03D       ; 4 cycles
     ! JSR 830           ; 12 + n*2  (n = nombre de NOP)
     ! TXA               ; 2
     ! EOR 0             ; 3
     ! BPL 80A           ; 3
     ! STX 0
     ! STA C030          ; haut-parleur
     ! BIT C061          ; bouton ou pomme ouverte
     ! BPL 80A
     !830:NOP
     ! NOP
     ! NOP
     ! NOP
     ! RTS

une fois encore, les temps sont ajustés pour un fonctionnement à 1 MHz.

Testons tout cela. Choisissez une station de radio, de préférence en modulation de fréquence à cause de la meilleure qualité du son, et au moment des informations pour pouvoir évaluer l'intelligibilité.

Récupérez le signal de la prise casque, par exemple grace à un jack monophonique avec un éventuel adaptateur, et enfin injectez ce signal à l'entrée du montage polarisateur après avoir pris soin de baisser le réglage de volume du poste. Il est prudent de garder un voltmêtre à courant continu en parallèle sur l'entrée du DOC pour surveiller les tensions appliquées.

Lancez le programme ci-dessus, ajustez le volume juste au-dessus du seuil limite, ne lésinez pas sur les aigus et appréciez la qualité de la parole écrêtée c'est-à-dire, dans notre cas, numérisée avec un seul bit par échantillon. Si tout fonctionne correctement, la parole doit être clairement intelligible et ressembler à ce qui peut sortir d'un alkie-talkie, d'un poste CB ou autres gadgets pas précisément renommés pour leurs qualités musicales. Si ca ne marche pas cherchez l'erreur !

L'avantage de ce type de numérisation est bien entendu la compacité du codage qu'il est possible d'atteindre (pour mémoire, les systèmes similaires sur Apple II permettent de stocker une petite dizaine de secondes de parole dans 16K octets mais cela dépend beaucoup de la qualité du son). La fréquence d'échantillonnage est toutefois un peu faible sur le DOC pour ce genre de technique (l'entrée cassette était beaucoup mieux adaptée aux échantillonnages rapides sur un bit)

Quand vous en avez assez, frappez la touche pomme ouverte ou faites reset.

numérisation et stockage du son dans la RAM privative

L'expérience précédente nous aura surtout permis de vérifier que notre montage fonctionne correctement. Nous allons maintenant préparer l'étape suivante en stockant les valeurs numérisées dans la RAM privative du DOC. On peut noter au passage qu'il est dommage que le processeur soit obligé d'intervenir pour cette opération qu'on aimerait bien voir le DOC effectuer seul. Le programme qui suit est une décalque du précédent légèrement compliquée par la valse des registres.

     *
     *!800:LDA #00
     ! STA C03E
     ! STA C03F
     ( acquérir l'échantillon précédent et relancer la numérisation)
     !808:LDA C03E            ; 4 + k cycles
     ! PHA                    ; 3
     ! LDA #13                ; 2
     ! STA C03C               ; 4 + k
     ! LDA #E2                ; 2
     ! STA C03E               ; 4 + k
     ! LDX C03D               ; 4 + k
     ! BNE 8xx                ; 3  (remplacer les 0 par des 1)
     ! INX                    ; 2 - 1
     ( insérer l'échantillon dans la RAM privative )
     !8xx:LDA #73             ; 2
     ! STA C03C               ; 4 + k
     ! PLA                    ; 4
     ! STA C03E               ; 4 + k
     ! STX C03D               ; 4 + k
     ( continuer jusqu'à 64 K échantillons )
     ! LDA C03E               ; 4 + k
     ! BEQ 8yy                ; 3
     ! NOP                    ; 2 - 1
     ! JMP 8zz                ; 3  (astuce pour équilibrer les temps de cycle)
     !8yy:LDA C03F            ; 4 - 1 + k
     !8zz:BNE 80A             ; 3
     ! RTS

Lancez ce programme par 800G, le prompteur du moniteur réapparaitra après un court instant quand toute la RAM privative aura été remplie.

remarque : la valeur 0 qui correspond à une tension rigoureusement nulle a la mauvaise propriété d'être également un ordre d'arrêt pour les oscillateurs qui essaient de la produire, d'ou notre INX.

remarque : on a procédé à une astuce pour éviter que la durée de la période d'échantillonnage ne diffère selon la branche empruntée par le programme après le test de début de page. Ceci n'a sans doute pas grand effet sur la qualité du son et présente l'inconvénient d'allonger cette période dans tous les cas.

exercice 5.1 : on arrive à une période d'échantillonnage de 59 ou 60 cycles si je ne me trompe pas, ce qui est un peu trop pour un fonctionnement du microprocesseur à 1 MHz et pas assez en principe pour 2.8 MHz. Néanmoins, on verra que grace aux ralentissements dus à l'accès à la mémoire lente, ce programme fonctionne également en vitesse "rapide". En supposant que l'accès à la mémoire lente se fait exactement à 1 MHz, calculez le nombre équivalent k de cycles à 2.8 MHz "rajoutés" aux instructions STore et LoaD. (réponse : k=11 environ)

exercice 5.2 : calculez la fréquence d'échantillonnage correspondant à ce programme tournant à 1 MHz. Calculez le débit binaire moyen de transfert dans la RAM des données numérisées. Calculez le temps de remplissage de la RAM.
Mesurez-le et comparez.

exercice 5.3 : mesurez le temps de remplissage pour l'horloge à 2.8 MHz. Déduisez-en la période et la fréquence d'échantillonnage.

Conclusion.

Nous pouvons évidemment faire fonctionner ce programme mais, à ce stade, nous n'aurons pas d'assurance sonore que tout s'est bien passé.

Contentons-nous d'observer une page de la RAM privative et de vérifier qu'elle contient des données plausibles.
     *!
     !300:LDX #0
     ! JSL FF40A8 ; ReadRam
     !306:STA 2000,X
     ! JSL FF40AF ; ReadNxt
     ! INX
     ! BNE 306
     ! RTS
     !
     * 300G 2000.20FF

5. LES REGISTRES D'OSCILLATEURS

Voici l'heure du plat de résistance ! Les lecteurs pressés peuvent sauter directement aux expériences mais il ne comprendront pas tout.
Il ne sera pas question ici de rentrer dans tous les détails, mais seulement d'essayer d'éclairer la documentation du constructeur sans la paraphraser. La lecture intégrale de cette documentation (Ensoniq DOC ERS, 11 pages) n'est pas nécessaire pour comprendre ce chapitre, mais je la recommande fortement à ceux que le sujet intéresse.

généralités sur les oscillateurs

Le DOC est un processeur synchronisé grace à une horloge à 7.14 MHz (je ne garantis pas cette valeur sur laquelle la documentation semble hésitante). Le cycle de traitement interne du DOC est 8 fois plus long que le cycle d'horloge. Pendant chaque cycle de traitement, le DOC "donne la parole" à un oscillateur, par ordre de numéro et jusqu'à concurrence du nombre d'oscillateurs autorisés (si quatre oscillateurs sont autorisés, le DOC donnera successivement la parole au 0, au 1, au 2, au 3, au 0 etc.). Il consacre ensuite deux cycles au rafraichissement de la RAM. La durée qui sépare deux interrogations successives du mème oscillateur dépend donc du nombre d'oscillateurs autorisés. Par commodité, on lui donnera le nom de période d'échantillonnage du DOC (ne pas confondre cet échantillonnage de sortie avec l'échantillonnage de 32 microsecondes réalisé par le convertisseur d'entrée !)

Chaque oscillateur est en fait un générateur d'adresse, c'est-à-dire que lorsque le DOC lui "donne la parole", il élabore une adresse de 16 bits à l'aide des informations de commandes que l'utilisateur a placées dans ses registres.  Vous aurez surement deviné que cette adresse désigne un octet de la RAM privative du DOC et que c'est justement cet octet-là qui va ensuite  passer dans le convertisseur numérique-analogique qui précède la sortie analogique du circuit.

la génération d'adresse

Entrons un peu dans les détails de la génération d'adresse. Tout d'abord, notons que les calculs d'adresse se font sur 24 bits.

L'accumulateur de l'unité arithmétique du DOC est chargé avec la valeur courante relative à l'oscillateur courant (ex : 04FF80). On lui ajoute le contenu concaténé de deux registres de 8 bits dits registres de fréquence (ex:0002) correspondant à ce mème oscillateur.
Il faut ensuite procéder à un savant mixage pour obtenir l'adresse recherchée.

Cela se fait à l'aide de trois paramêtres :
          . la résolution,
          . la page de début de la "table d'onde",
          . la longueur de la "table d'onde".

Le paramêtre de résolution nous permet déja de nous débarasser de 8 bits superflus dans l'accumulateur (ex : $04FF82).

On peut garder les bits
          . de b23 à b8 :  $04FF   (résolution 7)
          . de b22 à b7 :  $09FF   (résolution 6)
            etc.
          . de b16 à b1 :  $7FA1   (résolution 0)

notez que dans tous les cas, le bit 0 est sacrifié (le bit 1 aussi d'ailleurs à cause de ce qui suit). Notez tout de mème que le résultat de l'opération reste bien dans l'accumulateur, dont le contenu est sauvegardé, et donc que le bit 0 n'est pas du tout inutile !

La "table d'onde" (wavetable) est tout simplement un segment de lamémoire RAM privative auquel on veut cantonner l'oscillateur.

Autrement dit, on ne l'autorise pas à générer des adresses en dehors de ce segment. Pour simplifier les calculs du DOC nous sommes astreints à définir des "tables d'onde" commencant impérativement en début d'une page (i.e avec une adresse divisible par $100) et dont la longueur est un nombre entier de pages, puissance de deux comprise entre 1 et 128 (autrement dit des tailles de 2**8 à 2**15 octets).

Notre bonheur ne serait pas complet si on n'ajoutait pas à cela une restriction liant ces deux paramêtres, à savoir que le début d'une "table d'onde" doit avoir une adresse multiple de sa longueur.

Illustration :
     . un segment d'une page peut commencer à n'importe quel début de page,
     . un segment de 16 pages ne peut commencer qu'à une adresse de la forme $n000,
     . un segment de 128 pages ne peut commencer qu'à l'adresse $0000 ou $8000,
     . un segment de 2**L pages ne peut commencer qu'à une adresse de la forme n*2**(L+8).

Nous avons presque terminé la description du calcul d'adresse. Une fois déterminés les bits significatifs du registre de début de "table d'onde" en fonction de sa longueur (ex: les quatres premiers bits de $A0 pour une "table d'onde" de 16 pages) il nous reste à extraire 12 bits de l'accumulateur qui n'en contient déjà plus que 16 après l'opération de résolution (ex: la résolution la plus fine $04FF82 ->$7FA1). On choisit les 12 bits de poids fort ( $7FA0 -> $7FA ). D'ou l'adresse finale $A7FA. Tout ceci est admirablement résumé dans le tableau de la page 11 de la documentation.

quelques formules pour mieux saisir

On aura compris que la fréquence du son issu du DOC est une fonction des nombreux paramêtres décrits ci-dessus. Commencons par exprimer la fréquence d'échantillonnage (inverse de la période d'échantillonnage) désignée dans la documentation par SR.

          SR = CLK / (8*(OSC + 2))

ou l'on retrouve CLK la fréquence d'horloge, 8 le nombre de cycles d'horloge pour un cycle de traitement, OSC le nombre d'oscillateurs autorisés et 2 le nombre de cycles de rafraichissement.

On peut mettre ceci en rapport avec la fréquence d'échantillonnage d'entrée :

          SRin = CLK / 8*26

ou l'on retrouve 26, le nombre de cycles de traitements par numérisation.

Imaginons maintenant que nous définissions une "table d'onde" en RAM contenant une période unique d'un signal périodique quelconque. Nous pouvons demander à un oscillateur de parcourir cette table ndéfiniment en bouclant. Le son produit sera donc égalementpériodique et de période égale à la durée de parcours de cette table (période du fondamental). Calculons cette durée :
à chaque période d'échantillonnage, l'oscillateur incrémente son accumulateur de N

          N = FC  (mod. 2**(16+RES))

et l'adresse qu'il produit d'une quantité n qui dépend de tous les paramêtres définis :

          n = N / (2**(9+RES-L))

ou FC est le contenu des registres de fréquence, RES est la résolution et 2**L est la longueur de la table en pages.

La période du fondamental est donc

          T = 2**(L + 8) / (n * SR) = 2**(17+RES) / (FC * SR)

Ce n'est pas un hasard si la longueur de la table n'intervient plus dans cette dernière formule : c'est une conséquence directe des mécanismes compliqués de calcul d'adresse. C'est mème vraisemblablement leur cause ! De cette manière, vous avez le choix du pas d'échantillonnage de votre signal : soit vous choisissez un pas très fin et alors vous aurez une "table d'onde" très longue, soit vous choisissez un pas grossier et alors la mème portion du mème signaltiendra dans une "table d'onde" plus courte.

Si les paramêtres FC, RES et OSC sont identiques dans les deux cas, vous obtiendrez en sortie du DOC un signal de mème fréquence mais de qualité sonore peut-être différente.

les registres dits de commande de fréquence $00-$1F et $20-$3F

Le premier contient les bits de poids faible et le second les bits de poids fort de la valeur ajoutée à l'accumulateur à chaque cycle de traitement.
ex: $05 contient $40 et $25 contient $02 -> incrément de $240 pour l'oscillateur numéro 5

les registres de volume $40-$5F

En plus du volume général de la sortie commandé par les 4 bits de poids faible du registre $C03C, on peut commander individuellement et sur une échelle de 0 (mini) à 255 (maxi) le volume de sortie de chaque oscillateur. C'est cela qui permet à la boite à outils d'offrir des définitions d'enveloppe.

les registres d'échantillon  $60-$7F

Chaque registre contient la dernière valeur lue dans la RAM par l'oscillateur concerné.

les registres d'adresse  $80-$9F

Chaque registre contient le numéro de la première page de la "table d'onde" à laquelle l'oscillateur est rattaché.

les registres de commande  $A0-$BF

Chaque registre de cette série est composé de plusieurs champs :
b7 à b4 : canal (ces bits seront présentés sur les broches 4 à 7 du connecteur Molex à chaque fois que l'oscillateur en question sera actif)
b3      : autorisation de générer une interruption à la fin de chaque cycle de parcours de la "table d'onde" associée
b2 à b1 : mode de fonctionnement (voir plus bas)
b0      : arrêt de l'oscillateur (spontané ou commandé par forcage de ce bit), rappelons que l'oscillateur s'arrête s'il génère l'adresse d'une mémoire de contenu 0.

Les modes sont 00 (boucle infinie), 01 (une seule itération), 10 (un oscillateur commande le volume ou la durée de la boucle d'un oscillateur de numéro adjacent), 11 (deux oscillateurs de numéros adjacents prennent la main à tour de role et indéfiniment).

les registres de longueur/résolution  $C0-$DF

Chaque registre de cette série est composé de plusieurs champs :
b7 à b6 : non utilisés sur 2GS
b5 à b3 : paramêtre L (2**L = nombre de pages de la "table d'onde")
b2 à b0 : paramêtre RES

Nous ne revenons pas sur leur signification.

mise en pratique

Nous allons nous contenter da déterminer par le raisonnement les paramêtres à utiliser pour que le DOC nous "rejoue" le contenu de la mémoire que nous venons d'y insérer grace au programme de numérisation.
Tout d'abord, on remarque qu'un seul oscillateur ne peut pas parcourir les 64 K d'un coup. On pourrait penser à lui faire parcourir les 32 premiers K, lui demander de générer une interruption, traiter cette interruption en modifiant ses registres pour lui faire parcourir le reste de la RAM. C'est faisable mais il existe une technique beaucoup plus naturelle pour le DOC, à savoir l'utilisation du mode relais (swap mode, %11) qui permet d'enchainer le fonctionnement de deux oscillateurs sans aucune discontinuité. Le premier oscillateur travaillera donc sur les adresses $0000 à $7FFF et le second sur $7FFF à $FFFF.

remarque : ce mode nécessite que le premier oscillateur soit de numéro pair et que le second soit de numéro immédiatement supérieur.

Nous voulons que la période d'échantillonnage en sortie soit identique à celle que nous avons utilisée en entrée. En supposant que l'on a échantillonné l'entrée avec la fréquence maximale SRin, on obtient :
          OSC = 24

Nous voulons également qu'à chaque période d'échantillonnage, l'adresse produite augmente de n=1. Comme L=7, il vient :
          FC = 2**(2 + RES)

Nous pouvons donc choisir ces deux derniers paramêtres avec un degré de liberté : RES=0 et FC=$0004 par exemple. Le volume de chaque oscillateur sera réglé à une valeur identique, $FF par exemple.
     *340 : E1 2E                     (24 oscillateurs)
            40 FF 41 FF               (volume)
            00 04 20 00 01 04 21 00   ("fréquence")
            80 00 81 80               (page de début)
            C0 38 C1 38               (longueur/résolution)
            A0 06 A1 07               (commande)
            FF                        (fin des directives)
     *320G                            (chargement des registres)
     *300G 2000.20FF                  (pour relire et vérifier)

Normalement, dès l'exécution de la routine de chargement des registres vous devriez entendre le contenu sonore de la RAM privative répété sans relache.

Si cela vous convient, vous pouvez continuer à travailler en musique (un peu lancinant tout de mème) ou arrêter le DOC, soit par un reset, soit par

     *340 : A0 01 A1 01 A0 01 A1 01 FF
     *320G

La répétition semble nécessaire dans certains cas.

Remarque : en dépit des savants calculs ci-dessus, la fréquence restituée par le DOC est trop grande.  Ceci est du au fait que nous avons supposé que la fréquence d'échantillonnage est égale à la fréquence max, ce qui est approximatif dans le cas d'une horloge à 2.8 MHz et carrément faux dans le cas de l'horloge à 1 MHz. Ajuster la fréquence par l'intermédiaire des paramêtres RES ou FC ne peut pas suffire car la modification de l'un de ces paramêtres revient toujours à doubler ou diviser par deux la fréquence du son.

On peut, en revanche, jouer sur le nombre d'oscillateurs pour régler finement la fréquence. Les paramêtres suivants sont satisfaisants :

     2.8 MHz       OSC=27    RES=0     FC=$0004
     1 MHz          OSC=22    RES=0     FC=$0002

exercice 6.1 : calculez k (ralentissement) en fonction des résultats ci-dessus.(résultat : k=10 environ)

exercice 6.2 : essayez différentes valeurs de RES et de FC satisfaisant à l'égalité FC = 2**(2 + RES).

exercice 6.3 : utilisez quatre oscillateurs (2 paires) en choisissant leurs numéros assez éloignés pour obtenir un léger décalage entre les deux "voies" (ex: 0-1 et 13-14). Vous constaterez surement une amélioration de la qualité. Comment produire un décalage plus important ?

exercice 6.4 : modifier la routine de numérisation pour qu'elle boucle indéfiniment sur elle mème (prévoir quand mème une sortie par appui sur pomme ouverte) et lancer cette routine tandis que les oscillateurs sont en fonctionnement. Qu'obtient-on ? Observez les conséquences du léger désajustement des fréquences d'échantillonnage de lecture et de numérisation.