PARTE 8: MODULACIÓN POR ANCHO DE PULSOS
A medida que continuamos buscando una mayor variedad en el repertorio sonoro del Atari, nos encontraremos lidiando con formas de onda cada vez más complejas. El modo de funcionamiento normal del Atari nos proporciona probablemente la forma de onda más simple, una onda cuadrada. En las últimas columnas hemos jugado con el diseño de formas de onda personalizadas, como ondas sinusoidales y de rampa. Ahora vamos a dar un paso más hacia las junglas del wacka-wacka: la modulación dinámica de formas de onda.
La característica de la forma de onda que se modifica más fácilmente es probablemente el ancho del pulso. Tomemos, por ejemplo, una forma de onda de pulso simple. El patrón podría verse así:
donde un 1 significa que el altavoz está empujado a cierta distancia y un 0 significa que el altavoz está en reposo. Este tipo de forma produce un tono muy fino, parecido a una nariz, porque el altavoz está la mayor parte del tiempo en reposo y sólo ocasionalmente sale. Para alargar el ancho del pulso, simplemente alargaríamos el tiempo en que se expulsó el altavoz:
Esta forma suena más completa porque el altavoz pasa la misma cantidad de tiempo en cada posición. Cuando hablamos de ancho de pulso, comúnmente nos referimos al porcentaje de la relación entre entrada y salida. Por lo tanto, el patrón de forma de onda que se acaba de describir se denomina pulso de ciclo de trabajo del 50 por ciento. Cincuenta por ciento es el pulso más amplio que podemos obtener, porque un ciclo de trabajo del 75 por ciento (11111100) suena igual que un ciclo de trabajo del 25 por ciento (11000000).
Si esto no tiene ningún sentido, tenga en cuenta que el factor importante en el sonido no es la amplitud en sí sino el cambio de amplitud. Un altavoz en la posición máxima no hace más ruido que un altavoz en reposo: es el movimiento del altavoz lo que crea ruido, no su posición. Ahora regrese y observe las ondas del 75 por ciento y del 25 por ciento y observe que si invierte cada una de las posiciones (convierte 1 en 0 y viceversa) obtiene el mismo patrón. Nuestros oídos no podrán detectar ninguna diferencia.
"Ah", dirá, "¿Pero al ampliar el pulso no cambiamos el tono?" ¡No es tan así! En todos los casos mostrados hasta ahora, el patrón requiere ocho pasos para repetirse: por tanto, la frecuencia seguirá siendo la misma.
Dado que cambiar el ancho del pulso afecta la calidad subjetiva del tono, ahora podemos predecir qué sucedería si, mientras reproducimos la forma de onda, estiramos simultáneamente su forma. Si la forma de onda comenzara como un pulso estrecho (como el primer ejemplo) y termina como una onda cuadrada (el segundo), escucharemos que el tono oscila desde un sonido fino y aflautado hasta un tono con mucho cuerpo. Este es un efecto interesante y útil que recuerda vagamente al sonido de esas bobinas de Tesla de las primeras películas de Frankenstein. (¿Recuerda el pequeño y divertido dispositivo que hacía que un arco eléctrico de alto voltaje subiera y bajara?) Es más, como no estamos haciendo nada extraño con el tono, la modulación de ancho de pulso también es musicalmente útil.
Y, en caso de que no lo haya adivinado, eso es lo que vamos a hacer.
El Listado En Lenguage Ensamblador. El código de máquina del listado 1 configura la misma interrupción controlable por frecuencia que comentamos la última vez. La única diferencia es na inicialización adicional y, por supuesto, lo que sucede dentro de la rutina de interrupción. Lo más seguro que se puede hacer aquí es hacer que el procedimiento de configuración inserte 255 en el registro de frecuencia de audio para que cuando se habilite la interrupción la máquina no se bloquee debido a una frecuencia demasiado alta.
Esto es seguro, pero significa que no tiene elección sobre el tono que suena cuando se llama por primera vez a la rutina establecida. Para corregir esto, se agregó un control de volumen crudo en la ubicación 203 (decimal), y esta ubicación se inicializa en 0 mediante el procedimiento de configuración. Introducir un 15 en este registro hará que la forma de onda se reproduzca a su volumen más alto. Tenga en cuenta que introducir un 0 en este registro detiene el tono pero no detiene la interrupción, ¡y no reemplaza el procedimiento de borrado de la interrupción!
La rutina de sonido utiliza manipulación de bits para obtener el efecto de modulación por ancho de pulsos. A continuación se ofrece una explicación del código Ensamblador, pero también se debe estudiar el listado. Si es un programador ensamblador principiante, preste mucha atención a la forma en que se utiliza el registro de acarreo.
STEP realiza un seguimiento de qué parte de la forma de onda está reproduciendo actualmente la rutina y contiene solo un bit que se mueve perpetuamente hacia la derecha. A este puntero se le aplica un AND con la variable de forma de onda WIDTH para derivar el valor que se enviará al altavoz. Para obtener la onda de rampa, simplemente reemplazamos la amplitud arbitraria "activada" discutida anteriormente con una derivada de la posición del paso de onda de la rutina (STEP nuevamente). Esto significa que la primera vez que se ejecute la rutina, la amplitud de encendido será 8, luego 4, y así sucesivamente. Esto es todo lo que necesitamos para reproducir la forma de onda; el resto se encarga de la modulación.
Luego actualizamos un contador para controlar la tasa de modulación. Cuando el contador llega a 0, lo primero que hacemos es comprobar si la tasa de modulación ha alcanzado su valor máximo, 80$; si no es así, el ritmo se ralentiza. Esta parte se añadió para hacer la modulación más interesante que una velocidad de barrido constante. Cuando se introduce un 1 en MRATE, el ancho del pulso cambia a un ritmo vertiginoso y eventualmente disminuye hasta convertirse en un barrido lento y constante. El ejemplo en BASIC utiliza esto para proporcionar cierta cantidad de expresión. Tenga en cuenta también que si presiona MRATE con un número mayor que $80, la velocidad de barrido simplemente se establece en esa tasa. La demostración BASIC usa esto en la línea 55 para iniciar la música con un cambio muy lento.
Lo único que queda por hacer es cambiar el ancho del pulso. Esto se hace examinando el tercer bit desde la izquierda del byte WIDTH e invirtiendo su estado, luego rotando todos los bits hacia la izquierda para que el nuevo bit entre desde el costado. Esto nos da un desfile de bits: pasan los primeros 3 unos, luego 3 ceros, luego 3 unos nuevamente. Se eligió el ancho máximo de 3 bits para garantizar que siempre hubiera un cambio de estado en algún lugar de la forma de onda, evitando así espacios de silencio en el tono.
Finalmente, si va a reproducir música con modulación por ancho de pulsos, necesita saber qué valores producen una escala igualmente templada. El Listado 2 le permitirá imprimir un cuadro de estos valores. La fórmula para determinar la frecuencia de la interrupción es la misma que la discutida anteriormente, pero tenga en cuenta que la velocidad de llamada de la rutina de sonido y el tono producido por esa rutina son dos cosas diferentes. En este caso, la rutina debe llamarse 6 veces antes de que se complete la forma de onda (3 ceros y luego 3 unos), por lo que la frecuencia de salida se divide por 6.
El Listado 3 ofrece un uso bastante obvio de las rutinas. La única característica inusual es el uso de especificaciones de tono negativo: cada vez que se lee un tono negativo de la tabla, esto se interpreta como una señal para ajustar la velocidad de modulación, lo que a su vez provoca una explosión de actividad tonal. Los valores de frecuencia también se utilizan para determinar el color y la posición de las líneas dibujadas en pantalla. Una vez más, el ejemplo BASIC pretende ser sólo un trampolín, para animarle a utilizar la sustancia real en el listado Ensamblador y experimentar con él.
La próxima vez, cambiaremos un poco el ritmo y echaremos un vistazo a una muy buena alternativa a la codificación en BASIC o ensamblador: el lenguaje C.
TITLE 'PWM 1.0' ; ;Pulse Width Modulation Demo ;*Assembler Listing* ; ;OS equates IRQEN = $D20E ;IRQ enable POKMSK = $10 ;shadow for IRQEN AUDF = $D200 ;frequency register AUDC = $0201 ;volume register VTIMR1 = $210 ;timer vector RANDOM = $D20A ;random # register ; ;Zero page variables VOLUME = $CB ;master volume MRATE = VOLUME + 1 ;modulation rate MODC = MRATE + 1 ;modulation count WIDTH = MODC + 1 ;pulse width STEP = WIDTH + 1 ;wave step count ORG $600 ; ;Refer to the July '83 Atari Sound ;column for a description of the method ;used to obtain an interrupt-driven ;sound routine. ; ;Enter here to start sound SET SEI LDX #$FF ;ensure no lockup STX AUDF ;upon start INX STX VOLUME ;zero volume INX STX STEP ;initialize step STX WIDTH ;and pulse width LDA #LOW PWM STA VTIMR1 LDA #HIGH PWM STA VTIMR1 + 1 LDA POKMSK ORA #1 STA POKMSK STA IRQEN BNE SET2 ;Enter here to kill sound CLEAR SEI LDA POKMSK AND #$FE STA POKMSK STA IRQEN SET2 CLI PLA RTS ; ;Here's the new pulse width modulation ;sound routine: ; PWM LDA STEP ;get position count LSR A ;update it BCC NWRAP ;if bit falls off, LDA #8 ;replenish it WRAP STA STEP ;save updated pot AND WIDTH ;examine waveform AND VOLUME ;clip bits for vol ORA #$10 ;set forced output STA AUDC ;poke speaker DEC MODC ;check if time BNE NOMOD ;to modulate LDA MRATE ;recharge count STA MODC ;with mod rate BMI NACCL ;if > $7F, skip INC MRATE ;slow down mod rate NACCL CLC ;change pulse width LDA WIDTH ;examine d2 AND #4 ;… if set. BNE CCLR ;then carry clear SEC ;else set carry CCLR ROL WIDTH ;rotate carry in NOMOD PLA ;ta-da RTI ; END
Listado 1.
5 REM THIS PROGRAM WILL PRINT 10 REM ALL THE EQUALLY TEMPERED 15 REM NOTES AVAILABLE FROM THE 20 REM PWM ASSEMBLER DEMO 25 DIM N$(25) 30 ? "TURN ON PRINTER, PRESS RETURN" 35 INPUT N$ 40 LPRINT "NOTE","VALUE","FREQUENCY" 45 LPRINT 50 REM NOTE PLACEMENT OF SPACES 55 N$="C C#D D#E F F#G G#A A#B " 60 FOUT=8.1759375:SKEY=1 65 CLOCK=63921 70 AUDF=INT((CLOCK/(FOUT*12))-0.5) 75 IF AUDF>255 THEN 90 80 IF AUDF<23 THEN 110 85 LPRINT N$(SKEY,SKEY+1),AUDF,INT(FOUT*100)/100 90 SKEY=SKEY+2 95 IF SKEY=25 THEN SKEY=1 100 FOUT=FOUT*1.05946309 105 GOTO 70 110 END
Listado 2.
5 REM **************** 10 REM * PWM DEMO * 15 REM * * 20 REM **************** 25 REM 30 GOSUB 235 35 REM START UP INTERRUPT 40 Q=USR(ION) 45 RESTORE 50 A=243:COL=15 55 POKE PWM,255 60 POKE AUDF,ABS(A):POKE VOL,15 65 IF A<0 THEN POKE PWM,20:COL=15 70 SETCOLOR 2,COL,0:IF COL>0 THEN COL=COL-3 75 X=ABS(A)/1.5:Y=Y+8-COL/2 80 IF Y>191 THEN Y=0 85 PLOT 0,0:DRAWTO X,Y:DRAWTO 160,96:DRAWTO 319-X,Y:DRAWTO 319,0 90 PLOT 0,191:DRAWTO X,191-Y:DRAWTO 160,96:DRAWTO 319-X,191-Y:DRAWTO 319,191 95 READ A 100 IF A<>0 THEN 60 105 REM NOTE TABLE 110 DATA 204,172,-144,102,85,72 115 DATA 60,-45,42,-35,37,45,47 120 DATA -33,35,42,45,26,31,-37,45 125 DATA -53,31,37,45,57,57,-42,67 130 DATA 57,67,-85,136,-114,136 135 DATA 172,172,172,172,172,172 140 DATA 172,172,0 145 REM TURN OFF INTERRUPT 150 Q=USR(IOFF) 155 GOTO 155 160 REM MACHINE CODE FOLLOWS 165 DATA 120,162,255,142,0,210,232 170 DATA 134,203,232,134,207,134 175 DATA 206,169,48,141,16,2,169,6 180 DATA 141,17,2,165,16,9,1,133 185 DATA 16,141,14,210,208,10,120 190 DATA 165,16,41,254,133,16,141 195 DATA 14,210,88,104,96,165,207 200 DATA 74,144,2,169,8,133,207,37 205 DATA 206,37,203,9,16,141,1,210 210 DATA 198,205,208,18,165,204,133 215 DATA 205,48,2,230,204,24,165 220 DATA 206,41,4,208,1,56,38,206 225 DATA 104,64 230 DATA -1 235 REM POKE IN MACHINE CODE 240 GRAPHICS 24:COLOR 1 245 SETCOLOR 1,0,13 250 RESTORE 160 255 FOR L=1536 TO 1625 260 READ A 265 POKE L,A 270 S=S+A 275 SETCOLOR 2,0,14-INT(S/736) 280 NEXT L 285 READ A 290 IF A<>-1 OR S<>10309 THEN ? "ERROR IN MACHINE CODE":STOP 295 REM MACHINE CODE ENTRY POINTS 300 ION=1536:IOFF=1571 305 REM SPECIAL REGISTER EQUATES 310 VOL=203:PWM=204:AUDF=53760 315 RETURN
Listado 3.
Leer la siguiente y última columna sin título.
Publicado en revista Softline Volumen 3 de Septiembre y Octubre del 1983, páginas 27 y 28.