El Sonido del Atari, por Bill Williams

0. Introducción

Traducción al español de la columna Atari Sound de Bill Williams publicada por la revista SOFTLINE en 1982-1983.

1. Pokey y los Agujeros Negros

Vivimos en un universo audible. Aunque la principal orientación sensorial del Homo Sapiens hacia el mundo físico es visual, la especie no habría durado mucho sin la capacidad de oír cómo se partía repentinamente una ramita, y es poco probable que hubiera llegado a mucho si hubiera tenido que negociar tratados por correo escrito solamente. Los duelos entre naves en Star Wars no le habrían mantenido despierto por mucho tiempo si realmente se hubieran librado en un espacio silencioso. Tampoco jugaría mucho tiempo en su computadora personal si los sonidos que salen del altavoz fueran superfluos, irritantes o, por lo demás, deficientes.


El sonido es un ingrediente esencial. La combinación correcta de lo visual y lo auditivo hace que los buenos juegos sean buenos. Si va a escribir un juego para computadora personal, debe saber cómo quieres que suene y cómo lograr que suene de esa manera.


La mayoría de las computadoras requieren tiempo de microprocesador para la generación de sonido, ya sea mediante codificación en lenguaje ensamblador o una llamada de función a alguna rutina de sonido proporcionada por el sistema operativo. En ambos casos el problema es el mismo: el procesamiento de gráficos y las rutinas de cálculo deben detenerse mientras dure el sonido. Teniendo en cuenta el tiempo que esto añade a la ejecución del programa, no es de extrañar que el programa promedio arroje al usuario a un agujero negro cada vez que está ocupado.


El Sistema De Sonido Del Atari. Cuando la gente de Atari diseñó su sistema de computadoras domésticas, no simplemente metieron un microprocesador 6502 en una caja. También crearon tres chips de soporte -Antic, Ctia y Pokey- para compartir las funciones que normalmente proporciona el 6502 por sí solo. La versatilidad integrada en estos chips es tremenda; Tienen desplazamiento (scrolling) fino horizontal y vertical, control de luminancia del color, generación de sonido, movimiento de gráficos de player/missile y detección de colisiones, y una serie de otras operaciones de E/S diversas. En cada uno de ellos, los chips de soporte reciben sus instrucciones del 6502 y luego las llevan a cabo mientras el microprocesador pasa a tareas más esotéricas.


La generación de sonido es un buen ejemplo del poder inherente a este diseño. Pokey maneja cuatro canales de sonido programables de forma independiente, cada uno con sus propios registros de frecuencia, volumen y tono. Los programas pueden pasar el parámetro correcto a estos registros y el hardware continúa produciendo el sonido solicitado hasta que el programa le da instrucciones diferentes. Las técnicas de programación avanzadas pueden ampliar la gama de tonos disponibles, proporcionar una resolución de frecuencia más alta, insertar filtros de paso alto en los canales 1 y 2 y omitir completamente los generadores de tonos para crear formas de onda diseñadas a medida.


Sin embargo, muchas aplicaciones pueden ser manejadas adecuadamente por el intérprete del Basic de Atari.


La Instrucción Sound. El Basic de Atari se comunica con Pokey mediante la instrucción Sound C,P,D,V donde C,P,D y V son expresiones para los siguientes parámetros:


C: Canal de sonido. Las voces están numeradas del 0 al 3.


P: Tono. 0 es la frecuencia más alta, 255 la más baja. Las expresiones pueden ser superiores a 255, pero el tono "se ajustará" a ese límite.


D: Distorsión. Puede ser cualquier número par entre 0 y 14. 10 es una onda cuadrada pura y los otros números mezclarán la información de tono con varias cantidades de ruido. Los motores, las explosiones y el sonido del surf se pueden encontrar fácilmente experimentando con los parámetros de distorsión y tono.


V: Volumen. El más ruidoso es el 15; 0 apaga el canal. Esto no debe considerarse simplemente como una alternativa al control de volumen de su televisor: el control dinámico de un sonido a menudo puede generar sonidos radicalmente diferentes a partir de la misma forma de onda.


Dos cosas deberían ser evidentes inmediatamente acerca de este comando: no hay ningún parámetro de duración y solo puede hacer referencia a un único canal a la vez. Tener un parámetro de duración requeriría que el procesador se detuviera y contara la duración del sonido, que es lo que estamos intentando eliminar. El parámetro de selección de canal es una ventaja definitiva; con él podremos tener cuatro sonidos completamente diferentes al mismo tiempo, convirtiendo al Atari en un auténtico instrumento polifónico.


Llenando El Vacío. La tarea del Programa 1 es inicializar la popularidad de tres candidatos en una simulación de elección presidencial y proporcionar una subrutina que "sondeará" los estados y proporcionará la popularidad promedio de cada candidato. Esta subrutina se llama cada vez que el candidato hace un cambio significativo en su plataforma o en su estrategia de campaña.


El sonido producido durante la inicialización no es crítico; se proporcionó para confirmar que el programa está activo y funcionando. La subrutina, sin embargo, se utiliza constantemente durante el transcurso de la simulación. Cualquier sonido proporcionado debe encajar en la textura del programa. Debe tener una razón obvia para su existencia: mejorar la atmósfera del juego y tal vez crear un poco de suspenso durante la pausa inevitable que requiere la tabulación de los datos.


Para el sonido de inicialización, se pusieron en servicio las variables de bucle Scan e Idate para la información de tono. Multiplicar los dos produce una serie de tonos descendentes. El usuario no tarda mucho en familiarizarse con la secuencia y anticipar cuánto durarán los cálculos. En este ejemplo, por supuesto, la base de datos es pequeña y las manipulaciones mínimas. Pero cuando el bucle se vuelve complejo, resulta vital darle al usuario una forma de calcular aproximadamente el tiempo que tendrá que esperar. Esto podría compararse con un largo viaje hasta la casa de un amigo. La mayoría de las personas descubren que a medida que se familiarizan con la ruta, el viaje parece mucho más corto que la primera vez que lo hicieron. El conocimiento y anticipación del tiempo requerido acorta enormemente el tiempo subjetivo del viajero.


En la subrutina de cálculo del promedio, los datos se muestran como resultados entrantes de una encuesta a nivel nacional. Mientras los candidatos se apiñan alrededor del monitor, el hiperactivo código Morse canta y pita, escupiendo ocasionalmente un porcentaje ante los aplausos y gemidos de los participantes. La tediosa pausa se ha transformado en la emoción de la noche electoral, y la participación en la simulación se ha intensificado mediante tres líneas de código: una línea para activar el tono al azar, una línea para apagarlo al azar y otra para detener el sonido al final de la rutina.


Se podría argumentar que el tiempo dedicado a seleccionar sonidos podría aprovecharse mejor para la tabulación, acelerando así todo el proceso. Esto es cierto cuando la rutina es tan pequeña que un gran porcentaje de la codificación se dedica a la generación de sonido, como en nuestro ejemplo. Sin embargo, ninguna simulación real será tan simple y, a medida que aumente la duración del programa, la contribución de la parte de audio al tiempo de ejecución se minimizará y la recompensa por llenar el vacío será mayor.


El Denominador Menos Común. La mayoría de los programas que se comercializan actualmente están escritos para varias marcas importantes de microcomputadoras. Aunque esta situación es mejor que la exclusividad extrema de tiempos pasados, lo triste es que hay muy pocos programadores que van más allá del mínimo indispensable de traducir la sintaxis de su programa a la versión del lenguaje de cada fabricante. El resultado final es un programa con el mínimo común denominador: puede ejecutarse en todas las máquinas disponibles pero no utiliza las características especiales de ninguna de ellas.


Esto es especialmente lamentable para el consumidor, quien, al invertir una cantidad de dinero considerable en su computadora, evidentemente ha mostrado interés por las particularidades de dicha marca. Si el consumidor compra un programa que no utiliza ninguna de estas características, se sentirá decepcionado, si no enojado.


Nada de esto es necesario y es una influencia inhibidora del mercado. Cuando el propietario de un Atari compra un juego de Doohickey Software y descubre que desarrollaron sus programas originales en un TRS-80, probablemente dejará de comprarles programas. Quiere un programa que utilice los 128 colores y sorprendentes efectos de sonido polifónicos; Depende del programador el proporcionarlos.


La próxima vez, veremos la música en el Atari y examinaremos un ingenioso programa que simultáneamente dibuja bonitos gráficos y reproduce música de cuatro voces generada aleatoriamente. Hasta entonces, ¡siga experimentando!


10 REM **********************************
20 REM *       SAMPLE PROGRAM ONE       *
30 REM **********************************
40 REM   
50 DIM STATES(50,3),CAND(3)
60 REM
70 REM **INITIALIZE POPULARITY**
80 REM
90 FOR IDATE=1 TO 3
100 CAND(IDATE)=0
110 FOR SCAN=1 TO 50
120 STATES(SCAN,IDATE)=INT(RND(0)*30)
130 REM "MAKE A NOISE**
140 SOUND 0,SCAN*IDATE,10,6
150 NEXT SCAN
160 NEXT IDATE
170 REM **SHUT OFF SOUND**
180 SOUND 0,0,0,0
190 REM "CALL AVERAGING ROUTINE**
200 GOSUB 1000
210 REM **INITIALIZATION COMPLETE**
220 REM MAIN BODY OF PROGRAM WOULD
230 REM FOLLOW....
240 END
250 REM
260 REM
1000 REM *** AVERAGING ROUTINE ***
1010 REM
1020 GRAPHICS 0
1030 PRINT "Only 3 weeks 'til the election!"
1040 PRINT
1050 PRINT "Latest UPI Poll Results follow...."
1060 PRINT:PRINT
1070 FOR IDATE=1 TO 3
1080 PRINT "Candidate ";IDATE;": %";
1090 FOR SCAN=1 TO 50
1100 CAND(IDATE)=CAND(IDATE)+STATES(SCAN,IDATE)
1110 REM
1120 REM *** SOUND PORTION ***
1130 REM RANDOM ON/OFF OF PURE TONE
1140 REM
1150 REM IF TRUE THEN SOUND ON
1160 IF RND(0)<0.4 THEN SOUND 0,20,10,8
1170 REM IF TRUE THEN SOUND OFF
1180 IF RND(0)<0.5 THEN SOUND 0,0,0,0
1190 NEXT SCAN
1200 REM **OUTPUT AVERAGE**
1210 CAND(IDATE)=INT(CAND(IDATE)/5)/10
1220 PRINT CAND(IDATE)
1230 NEXT IDATE
1240 REM **SHUT OFF SOUND**
1250 SOUND 0,0,0,0
1260 RETURN

2. Composición aleatoria e indirección musical

La música juega un papel importante en nuestras vidas. Lo encontramos en el trabajo y en el juego, viajando por la carretera; incluso el botón de espera del teléfono nos reproduce música. No debería sorprender, entonces, que cuando se les presenta una computadora equipada con generadores de sonido, la primera pregunta que la mayoría de la gente hace es: "¿Puede reproducir música?".


Notas sobre el Atari. Crear notas musicales con un Atari es bastante fácil. En el manual del Basic de Atari y en las Notas Técnicas del Usuario se proporciona una tabla de valores de tono para las notas. Para reproducir el Do central, lo buscamos en la tabla y encontramos el valor 121. Al colocarlo en el registro de frecuencia de audio de un canal determinado se reproducirá un Do central, siempre que estemos usando el parámetro de distorsión de "tono puro" 10. (El por qué esto no funciona con otros tonos está fuera del alcance de esta discusión; lo abordaremos más adelante). Si estás en BASIC, el comando:


SOUND 0,121,10,8


hará el trabajo.


Tocar acordes es una simple cuestión de buscar cada nota del acorde y poner el valor correspondiente en un canal de sonido diferente. Con cuatro canales, puede formar muchos acordes complejos (Do menor novena, Fa sostenido séptima mayor, etc.) o usar una línea melódica con voces más simples. Para darle estructura rítmica, la música preprogramada se puede almacenar en una tabla de datos con valores de duración; el programa, a su vez, utiliza el valor de duración como contador en un bucle de retardo antes de tocar la siguiente nota.


Composición Aleatoria. Producir música aleatoria es un poco más difícil. Hay una gran cantidad de valores de tono que no encajan en la escala temperada; de hecho, si simplemente cargamos datos aleatorios en los registros de frecuencia, las posibilidades de que obtengamos uno de estos tonos intermedios son aproximadamente 7 entre 8. El ruido resultante enloquecerá a cualquier persona en cuestión de minutos. El problema se ve agravado por la ausencia de una correlación obvia entre los intervalos musicales y sus equivalentes numéricos. Un medio paso en la parte inferior del rango del Atari es 13; un medio paso en la parte superior son 2. Ninguna fórmula viable se traducirá de intervalos musicales a valores de tono.


La solución obvia es almacenar sólo los valores "correctos" en una tabla y luego seleccionar aleatoriamente un puntero para recuperar uno de esos valores. Esto funciona, pero como compositor independiente tiene toda la validez musical de un gorila atacando a un Wurlitzer. Las notas pertenecen a la escala templada, sí, pero sólo los más vanguardistas lo llamarían música. La música se distingue del ruido aleatorio por tener un formato, algo que la mente puede captar e interpretar. La música escrita dentro de un formato se convierte en una mezcla de expectativa y sorpresa, y el oficio del compositor reside en la forma creativa en que se combinan estos dos elementos.


Un formato común que se encuentra en la música es el de la "clave". Aunque hay doce notas entre octavas, muchas combinaciones de estas notas resultarán no sonoras o ásperas para el oído. Las no sonoridades se pueden utilizar con eficacia, pero hay que tener cuidado de utilizarlas con habilidad, de la misma manera que un buen cocinero utiliza sus especias más picantes: con moderación y siempre con una razón. Al limitar las posibilidades de combinaciones de notas que tenemos ante nosotros, las escalas nos ayudan a organizar los tonos en secuencias más sonoras y, por lo tanto, aumentan nuestro control sobre las no sonoridades que elegimos incluir.


Una de las escalas más utilizadas en la música occidental es la Escala Mayor. Sigue el siguiente patrón (dado en términos de "pasos"; por ejemplo, de Do a Do Sostenido es un medio tono, de Do a Re es un paso completo):


1,1,1/2,1,1,1,1/2


Tenga en cuenta que el patrón no es simétrico, lo que le dará al compositor ajustes aleatorios de la misma manera que lo hicieron los valores de tono impredecibles. La situación se vuelve realmente confusa si queremos que el compositor haga algo elaborado como cambiar de tecla o formar y resolver correctamente acordes comunes dentro de la escala. Se necesita algún tipo de dispositivo software para mantener todos estos números organizados.


Indirección. Los propietarios de computadoras Atari deberían estar familiarizados con el concepto de indirección: Los registros de color son un método para especificar el color indirectamente. En lugar de trazar el número de un color, Atari traza el nombre de un registro de color que lo especifica. Por ejemplo, poner un 2 en el área de visualización de la pantalla no significa mostrar el color 2, significa mostrar cualquier color que esté en el registro de color 2. Si el registro de color 2 contiene 15, entonces se mostrará el color 15. Esta es una herramienta gráfica extremadamente poderosa, ya que grandes cantidades de puntos previamente trazados pueden cambiar de color instantáneamente con el movimiento de un solo byte.


La indirección es un concepto importante porque nos permite operar con datos en términos de categorías. Manipulamos clases de datos agrupados por sus similitudes y, al hacerlo, ignoramos las características individuales triviales que se encuentran dentro de una clase. Para volver al ejemplo del registro de color, operamos con datos en términos de su similitud (todos están especificados por el registro 2) e ignoramos sus características individuales, las coordenadas X e Y de cada punto.


¿Qué tiene esto que ver con la composición musical aleatoria? La indirección, al permitirnos operar sobre la similitud, nos permite concentrarnos en la función. Las notas de una escala, los acordes formados en la serie, tienen funciones específicas dependiendo de su posición en la clave. Para que el compositor aleatorio resuelva los tonos correctamente, debe reconocer y manipular las notas de acuerdo con sus funciones en la escala.


Por ejemplo, un tono crucial en la escala es el tono principal, la última nota de la escala. El tono principal tiene una fuerte compulsión de conducir a la nota superior, la tónica (la primera nota de la escala). Cuando seguimos el tono principal con la tónica, decimos que hemos resuelto el tono: le hemos proporcionado una conclusión satisfactoria.


En la tonalidad de Do, el tono principal es Si, lo que significa que tendrá una fuerte compulsión a resolverse en Do. Sin embargo, en la tonalidad de Mi, Si no es el tono principal: es el quinto tono en la escala de Mi mayor, no el séptimo, y no tiene ninguna intención de moverse. Tenemos entonces dos funciones completamente diferentes para la misma nota, dependiendo de su relación con la clave.


Si un compositor aleatorio va a producir música con algo de inteligencia, debe reconocer la nota no según el tono, que en realidad es una característica trivial, sino según su función en la tonalidad. Esto significa que tenemos que tratar indirectamente con la nota; especifíquelo de acuerdo con su relación y deje que algún otro mecanismo de software se encargue de las especificaciones de tono triviales.


Tónico Para El Tono. En nuestro programa de muestra, las notas se especifican por su relación con la clave actual. Este número irá del cero al seis, siendo el siete el que tiene la tónica una octava más alta. Luego, el número se convierte en un desplazamiento leyendo los datos de la tabla entre las líneas 1100–1107. Estas declaraciones de datos convierten las especificaciones de notas secuenciales en el patrón de intervalo correcto para una escala mayor.


Antes de tocar una nota, el programa añade el desplazamiento así obtenido a tonic, que especifica la clave. Este número es el número real de la nota que se tocará y se convierte en un valor de tono leyendo desde el arreglo pitch y luego se envía a los generadores de sonido. Todo este juego de pies nos permite especificar notas en cualquier tono con los números del 0 al 6. En nuestro ejemplo de tono principal, comprobaríamos el tono 6; si es verdadero, la nota se resolvería en 7 o, alternativamente, en 0 (la misma nota una octava más baja). Esta decisión se toma independientemente de la tecla que se toque.


La misma idea se aplica al acompañamiento de acordes. Los acordes se especifican por la relación de sus raíces con la clave, y el siguiente acorde está dictado por las reglas de resolución configuradas en el programa. Por ejemplo, cada vez que se toca un acorde IV, hay un 50 por ciento de posibilidades de que se cambie la clave (líneas 1490 a 1510). La nueva clave seleccionada siempre será una quinta por debajo del acorde (línea 1510; agregamos diez pasos cromáticos).


El Listado. La inicialización se encuentra al final del programa, en las líneas 1400 a 2020. Esto se hizo para acelerar la ejecución del bucle principal del programa en las líneas 100 a 890.


La tabla de traducción de tonos se carga en el arreglo pitch en la línea 1420. Esta línea se omite cuando se vuelve a ingresar la inicialización.


Las líneas 1450 a 1540 escriben un patrón de acordes para la pieza basado en la función del acorde en el compás anterior. Añada aquí sus propias reglas para resolver acordes.


Las líneas 1550 a 1590 crean un patrón de ocho notas que se utiliza para dar estructura a la melodía. Las líneas 1600 a 2020 configuran la rutina de gráficos. Esta rutina dibuja una línea desde un punto en movimiento hasta un punto central estacionario en el cuarto superior izquierdo de la pantalla y luego refleja el patrón en los cuartos restantes de la pantalla usando el popular algoritmo de caleidoscopio. No se pretende aquí ser original, pero el patrón producido es agradable y es una buena demostración de la capacidad de Atari para mantener gráficos y sonido al mismo tiempo.


Las líneas 110 a 160 reproducen el patrón de acordes. Tenga en cuenta que las primeras tres entradas de la tabla de datos en las líneas 1100 a la 1107 se toman prestadas para obtener los desplazamientos de la raíz necesarios para formar el acorde (la raíz, una tercera arriba y una quinta arriba). Este doble uso de la tabla se produce porque casualmente los números son los mismos.


Las líneas 170 a 380 reproducen la melodía. Esta melodía, a diferencia del patrón de acordes, no se determina de antemano. Las reglas para la resolución de tonos se encuentran en las líneas 190 a 340. Si las reglas no se aplican, se selecciona una nota aleatoria de la escala, con un 50 por ciento de probabilidad de ser realmente una nota en el acorde (línea 300).


Las líneas 400 a 480 se encargan de mover los puntos gráficos y dibujar líneas. Debido a que el tiempo de ejecución de esta rutina es función de la longitud de las líneas que se dibujan, también es un control de tempo interesante para la música: cuando la pantalla se llena, la música se reproduce más rápido.


Cuando se han tocado treinta y dos compases, la pantalla se borra, se escribe un nuevo patrón de acordes y el programa se repite. Mientras se escribe el patrón de acordes, la línea 1450 reproduce la escala de la última clave reproducida para mantener la transición fluida.


Este programa es realmente un punto de partida para jugar con la teoría musical. Las reglas definidas son lo mínimo necesario para hacer música sonora. Cuantas más reglas agregue, más inteligentemente compondrá el programa y menos aleatorios parecerán los resultados. Más importante aún, el esfuerzo creativo en la definición de un sistema musical dará como resultado una mayor comprensión de la música, la textura y el estilo.


En el próximo artículo, veremos los detalles internos de los generadores de sonido del Atari y descubriremos por qué las tablas de tono solo funcionan con el parámetro de distorsión 10.


10 REM ********************************************
20 REM *        AN AUDIO-VISUAL MEDITATION        *
30 REM *             by Bill Williams             * 
40 REM ********************************************
50 DIM PITCH(36),PATTRN(31),TONIC(31),MOTIF(7)
60 FETCH =800
70 REM CALL INITIALIZATION
80 GOSUB 1420
90 REM "MAIN PROGRAM LOOP"
100 FOR BAR=0 TO 31
110 REM UPDATE CHORD
120 FOR VOICE=0 TO 2
130 RESTORE 1100+VOICE:READ OFFST
140 OFFST=OFFST+PATTRN(BAR)
150 GOSUB FETCH:SOUND VOICE,PITCH(TONIC(BAR)+FOUND),10,4
160 NEXT VOICE
170 REM NOW FOR THE MELODY
180 FOR BEAT=0 TO 3
190 IF MELODY<>11 THEN 220
200 IF RND(0)<0.7 THEN MELODY=12:GOTO 370
210 MELODY=0:GOTO 370
220 REM PLAY MOTIF?
230 IF THEMEF=0 OR RND(0)< 0.1 THEN 270
240 THEMEP=THEMEP+1:IF THEMEP > 7 THEN THEMEP=0:THEMEF=0
250 OFFST=PATTRN(BAR)+MOTIF(THEMEP)
260 GOSUB FETCH:MELODY=FOUND:GOTO 370
270 REM START MOTIF?
280 IF RND(0)<0.15 THEN THEMEF=1
290 IF MELODY=5 THEN MELODY=4:GOTO 370
300 IF RND(0)<0.5 THEN 350
310 REM PICK NOTE IN CHORD
320 RESTORE 1100+INT(RND(0)*3)
330 READ OFFST:OFFST=OFFST+PATTRN(BAR)
340 GOSUB FETCH:MELODY=FOUND:GOTO 370
350 REM ANY RANDOM NOTE
360 RESTORE 1100+INT(RND(0)*6):READ MELODY
370 REM **PLAY MELODY**
380 SOUND 3,PITCH(TONIC(BAR)+MELODY+12),10,6
390 REM ** GRAPHICS ROUTINE **
400 X=X+DX:IF X<0 THEN X=0:DX=-DX
410 IF X>159 THEN X=159:DX=-DX
420 Y=Y+DY:IF Y<0 THEN Y=95
430 IF Y>95 THEN Y=0
440 PLOT CX,CY:DRAWTO X,Y
450 PLOT 319-CX,CY:DRAWTO 319-X,Y
460 PLOT CX,191-CY:DRAWTO X,191-Y
470 PLOT 319-CX,191-CY:DRAWTO 319-X,191-Y
480 IF RND(0)<0.1 THEN CX=INT(RND(0)*160):CY=INT(RND(0)*96)
490 NEXT BEAT:NEXT BAR
500 GOSUB 1440:GOTO 100
800 REM FETCH SUBROUTINE
810 IF OFFST>6 THEN OFFST=OFFST-7
820 RESTORE 1100+OFFST:READ FOUND
830 RETURN
1000 REM PITCH VALUE TABLE
1010 DATA 243,230,217,204,193,182,173,162,153,144,136,128,121,114,108,102,96,91,85,81,76,72
1020 DATA 68,64,60,57,53,50,47,45,42,40,37,35,33,31,29
1100 DATA 0
1101 DATA 2
1102 DATA 4
1103 DATA 5
1104 DATA 7
1105 DATA 9
1106 DATA 11
1107 DATA 12
1400 REM ** INITIALIZATION **
1410 REM 1420 IS "COLDSTART"
1420 TONIC(31)=0:PATTRN(0)=0:FOR L=0 TO 36:READ A:PITCH(L)=A:NEXT L
1430 REM 1440 IS "WARMSTART"
1440 TONIC(0)=TONIC(31)
1450 FOR BAR=1 TO 31:RESTORE 1100+INT(BAR/4):READ A:SOUND 3,PITCH(A+TONIC(0)),10,4
1460 TONIC(BAR)=TONIC(BAR-1)
1470 IF PATTRN(BAR-1)=6 THEN PATTRN(BAR)=0:GOTO 1520
1480 IF PATTRN(BAR-1)=1 AND RND(0)<0.8 THEN PATTRN(BAR)=4:GOTO 1520
1490 IF PATTRN(BAR-1)<>3 OR RND(0)<0.5 THEN PATTRN(BAR)=INT(RND(0)*7):GOTO 1520
1500 REM CHANGE KEY
1510 PATTRN(BAR)=0:TONIC(BAR)=TONIC(BAR)+10:IF TONIC(BAR)>11 THEN TONIC(BAR)=TONIC(BAR)-12
1520 SOUND 3,PITCH(TONIC(0)+12),10,4
1530 IF PATTRN(BAR)>6 THEN PATTRN(BAR)=PATTRN(BAR)-7
1540 NEXT BAR	

3. Tal como un reloj

El Atari tiene muy poca similitud con los monstruos análogos en los que normalmente pensamos cuando hablamos de generación de sonido electrónico. En lo que respecta al sonido, Atari hizo lo que se podría esperar que hicieran un grupo de ingenieros informáticos: lo hicieron digitalmente.


¿Dividir Por Quién? Si pensamos en el sonido como una serie de pulsos provenientes del altavoz del televisor, la frecuencia o tono de ese sonido dependerá de qué tan rápido lleguen esos pulsos: la frecuencia de su aparición. Por esta razón, medimos el tono en términos de Hercios (abreviado como Hz). Un Hercio equivale a un pulso cada segundo. El "pop-pop-pop" de 1 Hz es un sonido de muy baja frecuencia y normalmente tratamos con tonos mucho más altos. La media, por ejemplo (en Basic, sound 0,144,10,4), es 440 Hz. Cuando los pulsos llegan tan rápido, ya no los escuchamos individualmente, sino mezclados en una "nota" que podemos tararear. Para trabajar con frecuencias más altas, a menudo se utilizan los términos Kilohercios (1.000 pulsos por segundo) y Megahercios (1 millón de pulsos por segundo). Kilohercios y Megahercios se abrevian como kHz y MHz, respectivamente.


Para controlar la frecuencia de un sonido, se necesita un contador o reloj para cronometrar estos pulsos. Cada uno de los cuatro canales del Atari se puede vincular a una de tres velocidades de reloj diferentes: 1,79 MHz, 64 kHz y 15 kHz. Estos relojes maestros se utilizan para cronometrar los pulsos al altavoz del televisor y, al hacerlo, controlan el rango de frecuencias que se pueden obtener de ese canal.


Un generador de sonido con tres tonos diferentes para elegir no supone un gran avance para la humanidad. Evidentemente, se necesita algo más para extraer una mayor riqueza de frecuencias de estos tres relojes maestros. Este algo extra se llama circuito "dividir por N" y, aunque su concepto es simple, es un truco realmente genial. Este circuito espera hasta que hayan entrado N pulsos y luego emite un solo pulso. Si, por ejemplo, N es 7, entonces el circuito generará un pulso por cada siete pulsos que reciba.


Cuando el circuito dividir por N se coloca en línea con uno de los relojes seleccionables, el circuito enmascarará algunos de los pulsos provenientes del puerto maestro. Cambiar el tamaño de N cambiará el número de pulsos que se eliminan y, por tanto, cambiará la salida de tono del altavoz del televisor. ¡Voilá! Una fuente de tonos.


A partir de esto se puede ver que hay dos variables que afectan el tono final enviado al mundo real: la frecuencia de sincronización maestra que se ingresa al circuito dividir por N y el divisor N. El comando sound del Basic usa solo la frecuencia de sincronización "normal" de 64 kHz, lo que limita al usuario a manipular sólo el tamaño de N.


Hay otro factor que afecta el rango de frecuencias disponibles en Pokey: el tamaño de N. Si N es un número de un solo byte, la nota más baja posible será el reloj maestro dividido por 255. Si N es un número de dos bytes, la nota más baja posible será el reloj maestro dividido por 65.535. El tamaño de N se denomina "resolución de frecuencia" del canal. En funcionamiento normal, cada uno de los cuatro canales tiene una resolución de frecuencia de 8 bits. El Atari, sin embargo, permite unir dos canales para obtener una resolución de frecuencia de 16 bits.


Algunos Pokablos. Para jugar con los modos de sonido ocultos tenemos que hacer una incursión en el interior del Atari. El comando SOUND no nos llevará mucho más lejos, por lo que tendremos que hurgar en los registros del hardware nosotros mismos. Las siguientes ubicaciones de memoria son pertinentes para la generación de sonido:


AUDF1=53760 AUDC3=53765


AUDC1=53761 AUDF4=53766


AUDF2=53762 AUDC4=53767


AUDC2=53763


AUDF3=53764 AUDCTL= 53768


AUDF1 a AUDF4 son los registros de frecuencia para cada canal. Aquí es donde se obtiene el divisor N. Para encontrar la frecuencia del sonido seleccionado por AUDF1–4, use la siguiente fórmula (FIN es la frecuencia de entrada al circuito dividir por N y corresponde a la frecuencia del reloj maestro. FOUT es la frecuencia de salida al mundo real):


FOUT=FIN/(2*(AUDF+M))


M tendrá un valor de 1, a menos que FIN = 1,79. En este caso, M será igual a 4 si AUDF es un contador de ocho bits, o 7 si AUDF es un contador de dieciséis bits.


Los valores reales para cada una de las tres frecuencias del reloj maestro (FIN) son:


1,79 MHz = 1.789–789 Hz


64 kHz = 63.921 Hz


15 kHz = 15.699 Hz


AUDC1 a AUDC4 son los registros de control de audio para cada canal. Estos registros contienen los parámetros de volumen y distorsión para cada canal empaquetados en un byte. El nibble alto contiene el parámetro de distorsión y el nibble más bajo contiene el volumen.


AUDCTL es el registro de control del modo de audio y es el "interruptor maestro" de cómo se comportarán todos los canales de sonido. Cada bit en AUDCTL tiene su propio significado, por lo que AUDCTL es en realidad una colección de ocho conmutadores. Las acciones asociadas con cada bit se detallan a continuación. Para usar esta tabla con BASIC, simplemente seleccione las opciones que desee y sume los números POKE: correspondientes, luego introduzca el resultado en AUDCTL usando el comando POKE.


POKE: 128. Cambia el contador aleatorio polinómico de 17 bits a un contador de 9 bits.


POKE: 64. Registra el canal 1 con 1,79 MHz, en lugar de 64 kHz.


POKE: 32. Registra el canal 3 con 1,79 MHz, en lugar de 64 kHz.


POKE: 16. Registra el canal 2 con el canal 1, en lugar del reloj maestro. Esto une los canales 1 y 2 para formar un registro AUDF de 16 bits. AUDF1 se convierte en el byte de orden inferior y AUDF2 se convierte en el byte de orden superior. El sonido controlado por estos canales "sale" por el canal 2.


POKE: 8. Sincroniza el canal 4 con el canal 3, en lugar del reloj maestro. Esto une los canales 3 y 4 en un registro AUDF de dieciséis bits de la misma manera que POKE:16 une los canales 1 y 2.


POKE: 4. Inserta un "filtro paso alto" en la salida del canal l. La frecuencia de corte del filtro está controlada por AUDF3.


POKE: 2. Inserta un "filtro paso alto" en la salida del canal 2. La frecuencia de corte del filtro está controlada por AUDF4.


POKE: 1. Cambia la frecuencia de reloj normal de 64 kHz a un reloj de 15 kHz.


¿Cómo Uso Todo Esto? Primero, inicialice Pokey. Pokey también maneja todas las cosas de E/S, por lo que debe configurarse para la generación de sonido después de cada operación de E/S (como, por ejemplo, leer su programa). Ejecutar sound 0,0,0,0 al comienzo de su programa se encargará de esto.


A continuación, seleccione las opciones de modo de sonido que desee e introduzca el valor correspondiente en AUDCTL. Después de eso, simplemente introduzca los parámetros de distorsión y volumen en AUDC1–4, y sus parámetros de frecuencia en AUDF1–4. Sin embargo, si vuelve a utilizar el comando SOUND, borrará cualquier modo AUDCTL especial que haya seleccionado, por lo que tendrá que volver a escribir su valor después de cada comando SOUND de BASIC.


Para comenzar, el programa de muestra 1 muestra el procedimiento para configurar 2 canales de sonido con resolución de alta frecuencia sincronizados desde 1,79 MHz. Luego, el programa reproduce una breve secuencia musical a partir de las declaraciones de datos al final.


Para tocar notas musicales en estos modos de sonido especiales, por supuesto, necesita una tabla que muestre los valores correctos para cada nota igualmente templada en la escala occidental. El cuadro de notas incluido en el manual BASIC ya no funcionará: es para una resolución de frecuencia de ocho bits y una frecuencia de reloj de 64 kHz. Ahí es donde entra en juego el programa de muestra 2.


El programa de muestra 2, cuando se ejecute, imprimirá una lista de notas musicales y sus valores de 2 bytes correspondientes para cada una de las 3 frecuencias de sincronización. Simplemente busque la nota que desea e introduzca el primer número en el byte AUDF de orden superior y el segundo número en el byte AUDF de orden inferior.


El rango de las tablas va más allá de lo práctico: el Do más bajo es de 8,17 Hz, lo que apenas suena musical, y el Do más alto es de 134 kHz, lo que supera considerablemente la respuesta de frecuencia de su estéreo. También notará que a medida que cada gráfico se acerca al tope de su rango, comienzan a suceder cosas extrañas: el valor de 0,1 aparece para cada nota entre Sol# y Re#. Esto se debe a que no se permiten números decimales en los registros AUDF.


Con esto concluye nuestra discusión sobre el cronometraje. Los más perspicaces pueden haber notado que los modos de distorsión fueron pasados por alto; debido a su naturaleza, es importante comprender primero el sistema de cronometraje de Pokey. La próxima vez profundizaremos en los temas de ruido y cancelación de formas de onda.


20 AUDCTL=53768
30 AUDF1=53760:AUDC1=53761
40 AUDF2=AUDF1+2
50 AUDC2=AUDC1+2:AUDC3=AUDC2+2:AUDC4=AUDC3+2
60 REM Initialize everything
70 SOUND 0,0,0,0
80 REM Poke POKEY
90 POKE AUDCTL,120
100 REM Shut off unused outputs
110 POKE AUDC1,0:POKE AUDC3,0
120 REM Turn on channels 2 and 4
130 POKE AUDC2,168:POKE AUDC4,168
140 RESTORE
150     FOR TONE=1 TO 12
160         FOR VOICE=0 TO 4 STEP 4
170             READ HIBYTE,LOBYTE
180             POKE AUDF1+VOICE,LOBYTE
190             POKE AUDF2+VOICE,HIBYTE
200         NEXT VOICE
210         FOR WAIT=1 TO 50
220         NEXT WAIT
230     NEXT TONE
240 GOTO 140
400 DATA 0,88,0,120,0,78,0,106,0,94,0,128,0,88,0,120,15,221
420 DATA 47,150,14,33,42,100,13,85,40,3,14,33,42,100,11,224
440 DATA 28,73,10,148,31,192,10,148,127,22,10,148,63,136	
Programa de Muestra 1.
90 DIM CLOCK(2),M(2),NAME$(5)
110 CLOCK(0)=15699
120 CLOCK(1)=63921
130 CLOCK(2)=1789789
150 M(0)=1
160 M(1)=1
170 M(2)=7
190 FOR FREQ=0 TO 2
200 LPRINT: LPRINT
210 LPRINT "CLOCK: ";CLOCK(FREQ)
220 LPRINT
240 FOUT=8.1759375
260 SKEY=0
280     AUDF=INT((CLOCK(FREQ)/FOUT)-M(FREQ)+0.5)
290     REM Break into high and low
300     HIBYTE=INT(AUDF/256)
310     LOBYTE=AUDF-(HIBYTE*256)
330     IF HIBYTE>255 THEN 370
340         RESTORE 500+SKEY
350         READ NAME$
360         LPRINT NAME$;"= ", HIBYTE;".";LOBYTE, INT(FOUT*100)/100;"  Hz."
370         SKEY=SKEY+1
380         IF SKEY=12 THEN SKEY=0
390         FOUT= FOUT*1.059463094
410     IF HIBYTE>0 OR LOBYTE>0 THEN 280
420 NEXT FREQ
430 END
500 DATA C
501 DATA C#/Db
502 DATA D
503 DATA D#/Eb
504 DATA E
505 DATA F
506 DATA F#/Gb
507 DATA G
508 DATA G#/Ab
509 DATA A
510 DATA A#/Bb
511 DATA B
Programa de Muestra 2.

4. Un tono de ruido diferente

Analicemos algunos términos que surgen en el trabajo de síntesis de audio y veamos cómo se relacionan con el parámetro de distorsión del Atari. Estos términos son muy útiles para describir sonidos. Es posible que reciba miradas extrañas cuando le sugiera a un compañero hacker "usar un pequeño ruido marrón", pero es más digno que simplemente hacer gorgoteos.


Boom–Chicka–Boom. La palabra ruido tiene una connotación despectiva para la mayoría de las personas. Evoca imágenes de martillos neumáticos, tapas de botes de basura o el gusto musical de su compañero de cuarto. ¡Deshágase de esa noción! Los relajantes sonidos del viento y las olas, el repiqueteo de las gotas de lluvia en una ventana y el susurro de las hojas con la brisa del verano encajan en la categoría de ruido, al igual que las explosiones, los platillos y las palmas.


¿Qué significa entonces el ruido? Bueno, si consideramos las similitudes en los ejemplos que acabamos de citar, podríamos concluir que ruido significa un sonido sin tono. Al fin y al cabo, ninguno de los sonidos que hemos mencionado hasta ahora produce una nota que podamos silbar. Como definición, esto no está muy lejos, pero es engañoso.


Intentemos un experimento.


El Programa de Ejemplo de Ruido. Esta entrega es un examen rápido y exhaustivo de los componentes del ruido. Lo que revela este programa puede sorprenderle. Está escrito en Ensamblador porque BASIC es demasiado lento para transmitir el mensaje. El Listado 1 es el código reubicable en Ensamblador. El Listado 2 es un programa BASIC que introducirá el código en la memoria y lo ejecutará. Para probar esto, necesitará un juego de paddles conectadas al puerto 1.


Escriba el programa BASIC y guárdelo. La mayor alegría de la vida de un programa en lenguaje de máquina es comerse a sí mismo, y un solo error probablemente bloqueará su sistema. Si es así y ha guardado una copia, puede simplemente apagar la computadora, recargar el programa y encontrar el error. Si no ha guardado su programa, puede volver a escribirlo (y correr el riesgo de cometer otro error).


Una vez que haya guardado el programa, gire el paddle 1 en el sentido de las agujas del reloj y ejecute el programa. Debería escuchar una serie de notas aleatorias; al registro de control de audio se le ha hecho un POKE con $A8, que es el equivalente BASIC del registro de distorsión 10 (tono puro), volumen 8. Ahora gire la paleta en sentido antihorario y la velocidad a la que se producen las notas aleatorias se acelerará.


Hasta ahora, todo bien. Pero a medida que se acerca al límite antihorario de rotación del paddle, algo extraño le sucede al tono. Sí, ¡este sigue siendo el parámetro de distorsión 10!


Algunas Generalizaciones. De este pequeño experimento podemos concluir que el ruido no es un sonido sin tono, sino un montón de frecuencias aleatorias que se escuchan en rápida sucesión. Demasiadas notas clamando por nuestra atención, por así decirlo. Nuestros oídos levantan las manos (¿lóbulos?) y ya no pueden distinguir las notas individuales; de ahí nuestra primera conclusión de que el sonido no tenía ningún "tono".


Entonces, ¿qué diferencia un tipo de ruido de otro? El sonido del viento es obviamente diferente del sonido del trueno y, sin embargo, acabamos de decidir que ambos están hechos de la misma materia: notas aleatorias.


La respuesta está en cómo se distribuyen esas notas. A esto lo llamamos ancho de banda. Digamos, por ejemplo, que existe la misma probabilidad de que se produzcan frecuencias bajas que de frecuencias altas. Diríamos que este ruido tiene un amplio ancho de banda. Si se grafica en un continuo de frecuencias, el sonido se dispersaría en una amplia gama de tonos. Los ingenieros de audio establecen una analogía entre este espectro de frecuencia y el espectro de luz. Este tipo de sonido se llama ruido blanco. El ruido blanco es lo que escuchas cuando apagas tu Atari antes de bajar el volumen del televisor.


                  ;NOISDEMO.ASM
                  ;(Relocatable code)
                  ;
                  ;Hardware equates
= 0270            PADDL0     =    $270
= 027C            PTRIG0     =    $27C
= D20A            RANDOM     =    $D20A
= D200            AUDF       =    $D200
= D201            AUDC       =    $D201
                  ;
                  ;
0000 68           INIT    PLA                ;pop the stack
0001 A9A8                 LDA    #$A8        ;start pure tone
0003 8D01D2               STA    AUDC
0006 AD0AD2       LOOP    LDA    RANDOM      ;get random freq
0009 8D00D2               STA    AUDF        ;store it
000C AE7002               LDX    PADDL0      ;get delay value
000F F0F5^0006            BEQ    LOOP        ;no delay
0011 88           DLAY    DEY                ;twiddle thumbs
0012 D0FD^0011            BNE    DLAY
0014 CA                   DEX
0015 D0FA^0011            BNE    DLAY
0017 AD7C02               LDA    PTRIG0      ;trigger up?
001A D0EA^0006            BNE LOOP           ;do it again?
001C 8D01D2               STA AUDC           ;shut up!
001F 60                   RTS                ;back to basics	


Listado 1.


10 REM Basic routine to install
20 REM and execute noise demo
30 DIM DEMO$(1),DEMO(6)
40 FOR LOC=ADR(DEMO$)+1 TO ADR(DEMO$)+32
50 READ BYTE
60 POKE LOC,BYTE
70 NEXT LOC
80 REM Call Demo-
90 REM Press paddle trigger to
100 REM return to Basic
110 Q=USR(ADR(DEMO$)+1)
120 END
2000 DATA 104,169,168,141,1,210,173,10,210,141,0,210,174,112,2
2010 DATA 240,245,136,208,253,202,208,250,173,124,2,208,234,141,1,210,96	

Listado 2.


Los sonidos producidos por el viento y el oleaje generalmente se denominan ruidos rosa: el extremo superior de su ancho de banda está vacío y solo tienen tonos entre las frecuencias bajas y las frecuencias medias. Otros términos utilizados frecuentemente son ruido rojo, que se refiere a explosiones profundas y similares; y ruido marrón, que es el tipo de sonido que desprende el papel de los conos de altavoces de 200 dólares. Todos estos términos simplemente describen el contenido de frecuencia del ruido. Cuanto más claro es el color, más frecuencias altas tiene el ruido.


¿Hay un Ruido Negro? Bueno, si seguimos la analogía de la luz, el Ruido Negro sería silencio. ¡Intente hablar de eso y obtendrá miradas extrañas de cualquiera!


Aleatoriedad. Hasta ahora hemos analizado sólo una característica del ruido: la amplitud con la que se distribuyen las frecuencias aleatorias. Otro factor que afecta el carácter del ruido es el grado de aleatoriedad involucrado en la selección de estas frecuencias. Si esta distinción le parece trivial, considere los siguientes patrones:


[A] 40 2 40 2 40 2
[B] 7 6 6 5 7 5 6


El patrón A tiene un ancho de banda amplio (38) y, sin embargo, podemos discernir fácilmente el patrón, el patrón B tiene un ancho de banda muy estrecho (3) y, sin embargo, no hay un patrón obvio. El patrón B es más aleatorio.


Subjetivamente, cuanto más aleatorio es el ruido, más "natural" suena. Los patrones repetitivos tienden a parecer mecánicos. El generador de números aleatorios utilizado en el programa de demostración es bastante aleatorio, por lo que el patrón del sonido no es del todo obvio para nuestros oídos.


Para ver cómo se aplica esto al Atari, tenemos que reformular un poco nuestra pregunta. En lugar de preguntar "¿Qué tan aleatorias son las frecuencias?", preguntémonos: "¿Cuánto tiempo pasa antes de que se repita el patrón?". Como ve, las computadoras son notoriamente no aleatorias. Las mismas leyes que nos aseguran la misma respuesta a una pregunta idéntica (A = 2 + 2) también nos impiden obtener un número verdaderamente aleatorio. Entonces, si siempre va a haber un patrón, la pregunta clave es: "¿Cuánto dura el patrón?" Un patrón que tarda un año en repetirse nos parecerá aleatorio a la mayoría de nosotros, los humanos; El patrón A (usado anteriormente) no parece aleatorio en absoluto.


Bajando de la Torre de Marfil. Ahora tenemos definidas dos características del ruido: ancho de banda y aleatoriedad. Armados con estas nuevas distinciones, volvamos al tema de los relojes de nuestra última entrega y veamos si podemos encontrarle algún sentido a todo esto.


En una entrega anterior, detallamos cómo se deriva un tono a partir de los relojes maestros; en el modo de tono puro, estos pulsos pasan directamente al televisor. Con los otros modos de distorsión, esta forma de onda derivada se pasa a otros circuitos llamados comparadores. Estos circuitos omiten pulsos aleatorios de la forma de onda y, por lo tanto, la distorsionan de modo que un solo tono se convierte en una serie de tonos que varían aleatoriamente. Así es como obtenemos ruido.


¿Recuerda el circuito de dividir por N? El principio es el mismo. Varios pulsos ingresan al comparador y algunos de ellos quedan enmascarados. Sin embargo, con este circuito los pulsos que se eliminan son aleatorios e impredecibles; por tanto, la forma de onda también se vuelve impredecible.


La clave a tener en cuenta es que los comparadores no suman pulsos, solo los restan. Por lo tanto, el tono nunca puede ser más alto que el tono derivado originalmente. Cuanto más bajo sea el tono original, más baja será la frecuencia máxima de ruido. Así, al bajar la frecuencia (poniendo números mayores en AUDF), restringimos el ancho de banda del ruido.


Esto se llama filtro paso bajo porque, no importa cuál sea la frecuencia de "esquina" (el tono original seleccionado por AUDF), las frecuencias bajas siempre podrán pasar. Sólo se cortan las frecuencias por encima de la frecuencia de "esquina". Por lo tanto, a medida que esta se, pasamos del Ruido Blanco al Ruido Rosa, al Ruido Rojo y así sucesivamente. De esta manera, el parámetro de tono en estos modos es similar al control de tono de su estéreo.


El parámetro de distorsión, por otro lado, controla la aleatoriedad de los comparadores. Los comparadores hacen lo que su nombre implica: comparan la forma de onda de entrada con otra forma de onda de entrada y generan solo las similitudes. Si esta otra forma de onda (llámela entrada B) es aleatoria, entonces los pulsos que se permitirán pasar serán aleatorios. Al controlar la aleatoriedad de la entrada B, controlamos la aleatoriedad de la forma de onda de salida.


¿Cómo cambiamos la aleatoriedad de la forma de onda de entrada B? El Atari genera esta forma de onda usando algo llamado contador polinómico. Básicamente, un contador polinómico es un registro de desplazamiento que deriva su primer bit del estado de los demás bits (la derivación se realiza mediante puertas lógicas booleanas). Luego, este bit se introduce en el registro de desplazamiento, todos los demás bits se mueven hacia abajo para dejarle espacio y, ¡listo!, tenemos un nuevo número aleatorio. A partir de este número se deriva el siguiente bit nuevo.


El resultado de esto es que cuantos más bits haya en el contador polinómico, más tiempo tardará en repetirse el patrón. El Atari tiene tres contadores polinómicos diferentes, todos de diferentes longitudes de bits: 4, 5 y 17. El contador polinómico de 17 bits tarda mucho en repetirse, mientras que el contador de 4 bits es el que se repite más. El parámetro de distorsión simplemente desvía diferentes combinaciones de estos contadores polinómicos hacia los comparadores, produciendo diferentes grados de aleatoriedad en el patrón de distorsión. De nuestra discusión anterior, podemos predecir que el contador polinómico de 17 bits sonará mucho más natural que los demás.


El siguiente cuadro muestra cómo se combinan los contadores polinómicos para los diferentes parámetros de distorsión:


AUDC/contadores polinómicos usados 0 = 5 y 17 bits
2,6 = 5 bits
4 = 5 y 4 bits
8 = 17 bits
10,14 = ninguno
12 = 4 bits


Por cierto, todos los valores de distorsión (incluso el tono puro) reducen el tono resultante una octava antes de que llegue a su televisor.


Ahora debería poder ver por qué las tablas de notas tonales sólo funcionan con el parámetro de distorsión 10. El enmascaramiento aleatorio de los pulsos producidos por los otros modos de distorsión hace que sea muy difícil predecir cuál será la nota subjetiva. Pueden suceder todo tipo de cosas raras con la cancelación de formas de onda, pero eso es para otra entrega.


Finalmente, un pequeño sermón. El verdadero objetivo de esta columna es hacer que los pitidos y graznidos que obtenga sean predecibles, de modo que dedique menos tiempo a obtener el sonido perfecto para su programa. Ahora tiene una cantidad considerable de información a su haber. ¡Úsela! Cuando quiera un sonido en particular, busque una muestra y analícela. ¿Cuál es su ancho de banda? ¿Qué tan repetitivo es el patrón de distorsión? Idealmente, debería poder predecir la "calidad" de un sonido incluso antes de sentarse al teclado. ¿Suena imposible? Practique: ¡Puede que se sorprenda!


Errata editorial. La gente gritó "¡Espacio! ¡Espacio!" Pero no había espacio. Así que dividimos las últimas 12 líneas del programa de muestra 2 de noviembre en 2 líneas, y nos dimos cuenta demasiado tarde de nuestro trágico error. Para que esas notas de alta frecuencia con buen temperamento se impriman como deberían, reemplace las dos últimas líneas con estas:


500 DATA C
501 DATA C#/Db
502 DATA D
503 DATA D#/Eb
504 DATA E
505 DATA F
506 DATA F#/Gb
507 DATA G
508 DATA G#/Ab
509 DATA A
510 DATA A#/Bb
511 DATA B	

5. Aleluya – Coros en el Atari

Mire cualquier anuncio de algún sintetizador moderno y encontrará el uso recurrente de una palabra improbable: gordo. "¡Buen sonido gordo!", ellos gritan. "Múltiples osciladores para ese sonido moderno y gordo." Los guitarristas también se han visto afectados por una avalancha de dispositivos de efectos que prometen "sonidos gordos". Los miembros de la comunidad musical no han perdido colectivamente la cabeza; solo intentan describir los efectos subjetivos de tres técnicas diferentes: phasing (fase), flanqueo (flanger) y coro. Nuestra discusión sobre la cancelación de formas de onda concluirá con consejos sobre cómo hacer que su Atari gane peso.


Coros naturales. No sorprende que un buen ejemplo de coro natural sea un coro. La "grandeza" del sonido coral no puede atribuirse sólo al volumen adicional o a la variedad de cualidades vocales encontradas entre sus miembros individuales. Consideremos una sección de cuerdas formada por violines idénticos; todavía hay algo que le da al sonido una "grandeza" o "gordura" que un solo violín no tiene. El secreto de este efecto de coro es sorprendentemente simple: no importa cuán buenos sean los vocalistas o cuán precisos sean los violinistas, siempre estarán ligeramente desafinados entre sí. Esta ligera desviación de frecuencias, en lugar de ser desagradable, crea una forma de onda cambiante que es inherentemente más interesante que el tono estático producido por un solo intérprete.


¿Como Funciona? Para entender por qué sucede esto, debe entender que no importa cuántos sonidos haya en el aire al mismo tiempo, solo hay una forma de onda presente. Si tocamos Mozart en una calle concurrida de una ciudad y ejecutamos la mezcla de sonidos resultante en un osciloscopio, veremos una forma de onda muy compleja. Esto parece ir en contra del sentido común, pero consideremos un cono de altavoz o un tímpano. Ambas son membranas diseñadas para interactuar con el aire y, como la mayoría de los objetos, sólo pueden estar en un lugar a la vez. Sin embargo, un altavoz puede reproducir el sonido de tres personas hablando a la vez y el oído puede oírlo. Esto a pesar del hecho de que tanto el cono del altavoz como el tímpano sólo pueden moverse hacia adelante y hacia atrás siguiendo un único patrón en un momento dado.


Obviamente, múltiples sonidos deben combinarse para formar un patrón único que pueda reproducirse mediante una sola membrana. Además, la combinación de estas formas de onda debe seguir una regla racional y predecible; de lo contrario, nuestros oídos no podrían decodificar esta forma de onda en sus múltiples partes. Es posible que ya haya encontrado esta regla en trigonometría; se llama suma gráfica. La idea es bastante simple. Grafique dos formas de onda en un gráfico. El eje X representará el tiempo y el eje Y representará la amplitud (el volumen de la onda de presión). Ahora viaje a lo largo del eje X y en cada punto a lo largo de esa línea sume los valores Y (amplitudes) de las formas de onda. Trace el resultado y verá aparecer una tercera forma de onda. Esta forma de onda es la suma de las dos ondas originales, y si ambas se escucharan simultáneamente, una gráfica del sonido resultante se parecería a la tercera onda, la onda que obtuvimos mediante la suma gráfica.


Por ejemplo, supongamos que, a los 2 segundos, la forma de onda A tiene una amplitud de 4 y la forma de onda B tiene una amplitud de 3. La forma de onda resultante tendrá una amplitud de 7 en ese punto. Debido a que las ondas sonoras a menudo se representan gráficamente de forma simétricamente equilibrada con respecto al eje X, también obtendremos números negativos. El método de suma gráfica es el mismo. Si, a los 7 segundos, la forma de onda A tiene una amplitud de 6 y la forma de onda B tiene una amplitud de —4, la amplitud resultante será 2.


El punto clave a tener en cuenta en todo esto es que las ondas sonoras se afectan entre sí. Tendemos a visualizar ondas sonoras separadas flotando felizmente en el aire, totalmente despreocupadas unas de otras. Nada más lejos de la realidad. Cuando una flauta toca a dúo con un flautín, los sonidos que emiten los instrumentos interfieren entre sí y producen una forma de onda completamente diferente.


Las ramificaciones de esto son importantes. Tomemos, por ejemplo, dos ondas sinusoidales de la misma frecuencia que están desfasadas 180 grados; esto significa que sus formas son idénticas, pero que, cuando la onda A está en su parte superior, la onda B está en su parte inferior. En cualquier punto que elijas a lo largo del eje X, la suma de las dos ondas será la misma: si están simétricamente equilibradas con respecto al eje X, su suma será una línea recta en cero. Como el oído sólo responde al cambio, no oiremos nada.


Sin embargo, el efecto que más nos interesa se ilustra en el programa de ejemplo 1. Escríbalo y ejecútelo. Verás dos ondas sinusoidales dibujadas en la parte superior. Tienen una frecuencia muy cercana pero no idénticas. La onda inferior es la suma de estas dos y muestra un pulso interesante en la amplitud de la forma de onda. Cuando se completen los gráficos, el programa iniciará dos tonos muy juntos para que pueda escuchar el equivalente en audio de este efecto.


Aplicaciones Prácticas. Un uso de este efecto es espesar las líneas musicales. Es una triste realidad que los osciladores individuales tiendan a sonar débiles, por lo que muchos programas comerciales ahora utilizan este efecto para su tema musical. Muchos usuarios se preguntan qué "truco" especial se utilizó para mejorar el sonido. La respuesta es decepcionantemente simple.


10 REM Demo 1: Waveform Cancellation
20 GRAPHICS 8:SETCOLOR 2,0,0:SETCOLOR 1,0,12:COLOR 1
30 DIM A$(1)
40 FOR Tni0 TO 31 STEP 0.1
50 A=10*SIN(2•T)
60 B=10*SIN(2.2*T)
70 PLOT T*10,A +20
80 PLOT T*10,B+ 50
90 PLOT T*10,A+B+100
100 NEXT T
110 SOUND 0,200,10,8:SOUND 1,201,10,8
120 PRINT "Press return to stop";
130 INPUT AS	

Programa de Ejemplo 1.


El programa de ejemplo 2 demuestra esta técnica con una melodía que todos conocemos y amamos. Si selecciona la opción sin coros, la melodía se reproduce con un solo generador. Seleccionar “coro”, hace que la especificación de frecuencia se incremente en uno y se envíe a un segundo generador. Por supuesto, una desventaja de este método es que limita la cantidad de voces independientes que pueda usar.


Sin embargo, no se deje atrapar y pensar que esto es sólo un dispositivo musical. Pruebe esta técnica con otras especificaciones de distorsión y diferentes rangos de frecuencia. De esta manera se pueden obtener muchos efectos, desde explosiones huecas hasta gruñidos parecidos a los que se escuchan en Defender. ¡Engordar su Atari puede añadir mucho impacto a su programa! En el próximo número, le presentaremos al Ensamblador.


5 REM Demo 2: Chorused Melody 
10 DIM WORD$(11) 
20 ? "Do you want chorusing (0=no,1=yes)" 
30 INPUT CHORUS 
40 GRAPHICS 2:POSITION 0,4 
50 RESTORE 
60 FOR L=1 TO 8 
70 READ FREQ,WORD$ 
80 ? t16;WORD$; 
90 FOR V=14 TO 0 STEP —2 
100 SOUND 0, FREQ,10,V 
110 IF CHORUS THEN SOUND 1,FREQ+1,10,V:GOTO 140 
120 REM This just equalizes the tempo 
130 FOR D=1 TO 3:NEXT D 
140 NEXT V 
150 NEXT L 
160 GOTO 20 
170 DATA 96,HAVE ,96,YOU ,96 
180 REM Note 6 spaces after "played" 
190 DATA PLAYED ,96,A,81,TAR 
200 DATA 85,1 ,96,1-0,108,DAY?	

Programa de Ejemplo 2.

6. Un cambio de tono

El título de esta columna tiene un doble significado. El significado original tiene que ver con el tema que nos ocupa: formas de onda personalizadas en el Atari. Últimamente ha habido mucho interés en el tema, y con razón. La configuración de ondas personalizada puede producir efectos de sonido realistas, buenas imitaciones instrumentales y probablemente una de las modas más populares en la industria en este momento, la síntesis de voz. Es un tema maduro para una serie sobre generación de sonido.


Todas estas técnicas, sin embargo, dependen en gran medida del lenguaje Ensamblador. Los lenguajes de alto nivel no son lo suficientemente eficientes para llevar al altavoz a las frecuencias necesarias. Además, a medida que sigamos ampliando la gama de sonidos disponibles para nosotros, seguiremos encontrándonos con esta situación. De ahí el segundo significado del título: El tono de esta columna va a inclinarse seriamente hacia el lenguaje Ensamblador.


Esto no debería ser motivo de pánico entre los programadores BASIC. Se hará todo lo posible para proporcionar un código híbrido que le resulte útil o interesante. Este mes es un buen ejemplo: el listado BASIC proporciona un método conveniente para producir ondas sinusoidales que podrían adaptarse fácilmente para diferentes propósitos. Si está familiarizado con el Ensamblador, por supuesto, podrá experimentar más plenamente con formas de onda personalizadas.


Así termina la editorial. Hablemos de sonido.


De nuestra discusión anterior sobre la cancelación de formas de onda, debería tener una idea de lo que es una onda sonora: una serie rítmica de pulsos en el aire. La forma de cada pulso es lo que llamamos forma de onda y determina el carácter del sonido. Para hacer una generalización amplia, las formas de onda con bordes afilados suenan más zumbantes o más ásperas que las formas de onda con bordes redondeados. Cualquiera interesado en obtener una gama más amplia de tonos del Atari, entonces, debería interesarse por la generación de formas de onda personalizadas, ya que ofrece un mayor control sobre el carácter de un sonido.


Si tiene problemas para visualizar lo que hace una forma de onda, repasemos con un ejemplo que todos conocemos y amamos, el parámetro 10 de distorsión de tono puro del Atari. La salida de forma de onda normal con esta configuración es una onda cuadrada, una forma simple formada por arriba/abajos o encendido/apagados que se adapta perfectamente a la electrónica digital. Cuando se traduce al altavoz, una onda cuadrada implica (1) comenzar completamente retraída; (2) instantáneamente extenderse por completo; (3) instantáneamente volver a retraerse por completo.


Lo cual, dicho sea de paso, es la razón por la que realmente no se escucha una verdadera onda cuadrada. La transmisión de materia aún no se ha inventado y el altavoz no puede realmente saltar de un punto del espacio a otro. Sin embargo, se mueve allí lo más rápido que puede y el sonido es más o menos el mismo.


El punto clave a tener en cuenta es que una forma de onda es un conjunto de "instrucciones" para el cono del altavoz que tienen que ver con su patrón de movimiento. Cuando enviamos una forma de onda al altavoz, éste baila hacia adelante y hacia atrás al compás de la forma de onda que le damos. Este cono de papel vibrante empuja el aire, el que a su vez empuja una membrana en nuestro oído. El objetivo de la generación de sonido, entonces, es ofrecer un patrón interesante de movimiento hacia adelante y hacia atrás a nuestro tímpano. Afortunadamente, un altavoz puede ocupar más de dos posiciones, por lo que puede reproducir millones de patrones además de encendido/apagado/encendido/apagado. Digamos que nuestro altavoz tiene dieciséis posiciones diferentes, siendo 0 completamente retraído y 15 completamente extendido. Producir diferentes formas de onda implicaría enviar una serie repetitiva de números entre 0 y 15. El patrón de números determinaría la forma de onda y, por tanto, el carácter del tono. El tono o frecuencia del sonido dependería de la rapidez con la que se repitiera la secuencia. Las secuencias más rápidas ocurren en una frecuencia más alta y por lo tanto tienen un tono más alto.


Como ya podrá adivinar, Atari nos proporciona una manera de comunicarnos directamente con el cono del altavoz y entregar nuestro patrón de "instrucciones" o nuestra propia forma de onda. Al configurar el bit 4 en AUDCX, habilitamos un modo especial llamado salida forzada, el que pasa el parámetro de volumen directamente al altavoz en forma de un voltaje fijo. De esta manera podemos hacer que el altavoz baile en cualquier patrón que queramos (dentro de unos límites muy definidos).


Si no recuerda AUDCX, es el registro de control de audio para cada voz, denominado AUDC1 (dirección $D201), AUDC2 ($0203), etc. El nibble superior de AUDCX contiene el parámetro de distorsión para el canal, y el nibble inferior contiene la información del volumen. Un tono puro de volumen 8, por ejemplo, es $A8, el $A (10 decimal) para tono puro, el 8 para volumen. La implementación de salida forzada simplemente implica almacenar un valor entre $10 y $1F en el registro AUDC de un canal. Un valor de $10 retrae el altavoz, $1F lo extiende completamente y los valores entre estos extremos colocan al altavoz en una posición media correspondiente.


Por ejemplo, almacenar sucesivamente los valores $10, $1F, $10, $1F... en AUDC1 producirá la onda cuadrada normalmente producida por el ajuste de tono puro en el canal 1. (Excepto que será el resultado de su propio trabajo, y ¡por lo tanto sonará mucho mejor!) Una secuencia más interesante podría ser:


$10, $14, $18, $1C, $1F, $10, $14, $18, $1F...


lo que produciría una onda de rampa (o de sierra).


Sin embargo, existen algunas desventajas de la salida forzada que debe tener en cuenta. La primera es importante: el tiempo del procesador. Para producir una alta frecuencia, el 6502 debe dedicar todos sus recursos a la producción de la forma de onda. Esto significa que su programa se detendrá cada vez que desee cantar. No hay cambios gráficos, nada. Sin embargo, esa no es toda la historia. Para producir una forma de onda sin distorsión, todas las interrupciones deben estar desactivadas, al igual que todo DMA.


Si no está familiarizado con estos términos, se refieren a eventos que interrumpen momentáneamente la ejecución del programa por parte del 6502. Cada vez que se presiona una tecla, se produce una interrupción del teclado y el 6502 guarda toda la información con la que estaba trabajando, salta a un programa para manejar la interrupción y luego regresa a su programa después de recuperar la información que guardó. Es como leer un libro durante una conversación: siempre estás poniendo un dedo en la página para poder responder una pregunta. No se puede leer mucho de esta manera y el 6502 no se puede realizar mucha computación. DMA significa Direct Memory Access (Acceso Directo a Memoria) y ocurre cada vez que se genera la visualización en pantalla. ANTIC (el chip que maneja la pantalla) detiene el 6502 para que pueda tomar prestadas las líneas de dirección y datos. Estas constantes interrupciones crean "interrupciones" de zumbidos en la forma de onda que deben detenerse.


¿Qué significa esto para el usuario? Bueno, fealdad. Cada vez que se ingresa a la rutina de sonido, la pantalla parpadea y es reemplazada por el color de fondo. Si el color de fondo es negro, se parece mucho a un fallo del sistema. Además, si se entra y sale de la rutina repetidamente, como en la demostración, el parpadeo distraerá a todos menos a un ciego.


¿Qué se puede hacer al respecto? Muy poco. Es útil almacenar el color normal de la pantalla (en los gráficos 0, $94) en el registro de fondo. Esto significa que sólo el texto parpadea. Sí, sigue siendo feo.


También existen otras limitaciones. El uso de la memoria es uno de ellos. La síntesis de voz se mencionó anteriormente como un derivado del modo de salida forzada; La idea de la síntesis de voz sin tener que comprar ningún periférico adicional es realmente apasionante, y actualmente existen algunos programas en el mercado que hacen exactamente eso. Desafortunadamente, los requisitos de almacenamiento de datos, incluso para una simple oración, son asombrosos, y el programador serio debe considerar cuidadosamente qué opciones renunciará para tener una mayor fidelidad de la salida de voz. Una gran cantidad de código de máquina puede caber en el espacio reservado para "¡Te tenemos, basura terrestre!" Esto no quiere decir que la síntesis de voz no tenga cabida en el Atari, sólo que se debe realizar un examen de conciencia serio antes de tomar la decisión.


El Programa de Ejemplo. La demostración muestra un uso más modesto de la salida forzada: proporciona una manera de reproducir formas de onda personalizadas desde BASIC. Tal como está escrito, la demostración reproduce ondas sinusoidales, que son considerablemente más relajantes que las ondas cuadradas y deberían ser un cambio de tono bienvenido para aquellos que no son programadores en su casa.


El listado en Ensamblador está completamente comentado y debería ser fácil de seguir. Acepta una tabla de longitud de onda, duración de nota y frecuencia desde el BASIC. La tabla de ondas se fija en la página 6 y puede tener una longitud máxima de 256 pasos. La duración y la frecuencia también pueden ser cualquier número entre 0 y 255.


;***********************
;*  Forced Output Demo *
;*  Assembler Listing  *
;***********************

NMIEN   = $D40E
IRQEN   = $D20E
DMACTL  = $D400
AUDC    = $D201
AUDCTL  = $D208
SKCTL   = $D20F
COLBK   = $D01A
DUMMY   = $D01E
;
WVTAB   = $600
;
      ORG $CB
WVTL  DS  1
DURA  DS  2
WVPNT DS  1
WFREQ DS  1
WCNT  DS  1
;
      ORG $4000 ;(relocatable)
;
PLA;number of args
PLA;wave table length hi
PLA;wave table length In
STA WVTL
PLA;duration hi
PLA;duration lo
STA DURA+1
PLA;freq hi
PLA;freq lo
STA WFREQ;store freq
STA WCNT
LDA #0;init pointer
STA WVPNT
;no interrupts, no DMA
STA NMIEN
STA IRQEN
STA DMACTL
;init audio
STA AUDCTL
STA DURA
LDA #3
STA SKCTL
;color the screen
LDA #$94
STA COLBK
;
PLAYIT DEC WCNT;dec freq cnt
BEQ DOVOX
;waste some machine cycles
    LDX    #5
WASTE DEX
BNE WASTE
STA DUMMY,X
BEQ UPDUR
DOVOX LDA WFREQ ;recharge cnt
STA WCNT
;get index into wave table
LDY WVPNT
;get volume and set force output
LDA WVTAB,Y
ORA #$10
;punch the speaker
STA AUDC
;move the wave pointer
INY
;check for table end
CPY WVTL
BCC NOWRAP
;wrap pointer
LDY #0
NOWRAP STY    WVPNT
;update duration counter
UPDUR DEC DURA
BNE PLAYIT
DEC DURA+1
BNE PLAYIT
;time's up, return to Basic
LDA #$FF
STA NMIEN
STA    IRQEN
RTS
;
END	

No se utilizan trucos reales en el código, pero observe la ubicación del mecanismo de retardo: es importante colocar el retardo entre cada paso de la forma de onda, para que se conserve la forma de onda. También tenga en cuenta que si aún no se ha alcanzado el tiempo para generar otro paso de onda, se ingresa un pequeño retraso para ecualizar el tiempo de ejecución del bucle.


Hay dos factores de distorsión: cada 256 iteraciones, se utilizan 7 ciclos de máquina adicionales para actualizar el byte alto del recuento de duración, y cada vez que se repite la forma de onda, se utilizan 2 ciclos adicionales. Los puristas pueden estremecerse, pero cuando el 6502 funciona a 1,79 MHz, los ciclos perdidos son mínimos.


Tenga en cuenta también que la parte del Ensamblador fuerza el bit 4 de AUDCX a 1, de modo que BASIC pueda pasar los bits de volumen a través de la tabla de formas de onda. Configurar un bit específico es mucho más fácil en Ensamblador que en BASIC.


La rutina BASIC primero coloca el Código de Máquina en su lugar (líneas 29 a 50) y luego compila la tabla de formas de onda. La fórmula utilizada formará una onda sinusoidal compuesta de LNG pasos. El valor de LNG se puede cambiar (línea 9) a cualquier valor hasta 255. Cuanto mayor sea la tabla de formas de onda, mayor será la resolución de la onda sinusoidal y, por lo tanto, representará con mayor precisión una onda sinusoidal verdadera (en términos más subjetivos). Sin embargo, a medida que la forma de onda se alarga, la frecuencia más alta disminuye, porque hay más pasos que cubrir antes de que la forma se repita. Las ondas sinusoidales suenan mejor en frecuencias más altas, por lo que las longitudes de onda largas no son muy útiles con esta fórmula. Otras formas de onda funcionan mucho mejor en las frecuencias más bajas; pruebe la forma de onda de rampa discutida anteriormente.


Después de introducir la forma de onda, BASIC lee una frecuencia de la tabla de datos en las líneas 26 y 27 y llama al controlador del altavoz en Lenguaje de Máquina.


La demostración se puede modificar fácilmente para diferentes formas de onda; simplemente introduzca la secuencia deseada desde las ubicaciones 1536 a 1791. Las formas de onda se pueden leer desde una tabla de datos o ingresar con cualquier esquema de edición extraño que se le ocurra. Aquí hay mucho potencial para la experimentación, incluso sin profundizar en la parte del Ensamblador.


Oh sí; Recuerde siempre guardar el código híbrido antes de ejecutarlo. Un solo error en las declaraciones de datos del Código de Máquina probablemente devorará el programa, el sistema operativo, DOS, su gato...


0001 REM *************************
0002 REM *  FORCED OUTPUT DEMO   *
0003 REM *   by Bill Williams    *
0004 REM *     Basic Listing     *
0005 REM *************************
0006
0007 GOSUB 0029
0008 SETCOLOR 4,9,4
0009 LNG=10:TEMPO=10
0010 ? "Waveshape:"
0011 FOR L=0 TO LNG-1
0012    N=INT(SIN(L*6.2831853/LNG)*7)+7
0013    POKE 1536+L,N
0014    ? N;" ";
0015 NEXT L
0016 FOR REPEAT=1 TO 4
0017    RESTORE 0025
0018    FOR MELODY=1 TO 14
0019    READ N
0020    USR(ADR(QQQ$)+1,LNG,TEMPO,N)
0021    NEXT MELODY
0022 NEXT REPEAT
0023 END
0024
0025 REM Melody notes
0026    DATA 2,16,8,4,2,4,2,16
0027    DATA 20,16,20,18,16,15
0028
0029 REM Machine code (double-check!)
0030    DATA 104,104,104,133,203,104,104
0031    DATA 133,205,104,104,133,207,133
0032    DATA 208,169,0,133,206,141,14,212
0033    DATA 141,14,210,141,0,212,141,8
0034    DATA 210,133,204,169,3,141,15,210
0035    DATA 169,148,141,26,208,198,208
0036    DATA 240,10,162,5,202,208,253,157
0037    DATA 30,208,240,23,165,207,133,208
0038    DATA 164,206,185,0,6,9,16,141,1
0039    DATA 210,200,196,203,144,2,160,0
0040    DATA 132,206,198,204,208,215,198
0041    DATA 205,208,211,169,255,141,14
0042    DATA 212,141,14,210,96
0043
0044    DIM QQQ$(1),QQQ(17)
0045    RESTORE 0029
0046    FOR QQL=ADR(QQQ$)+1 TO ADR(QQQ$)+97
0047    READ QQB
0048    POKE QQL,QQB
0049    NEXT QQL
0050    RETURN
0051

7. Forma de onda personalizada y revisada

Con el fin de acortar la discusión sobre las formas de onda, la última vez se hicieron algunas generalizaciones. Volvamos atrás y maticemos esos comentarios.


Esta es la verdad: las formas de onda personalizadas se pueden usar simultáneamente con otras rutinas. Es más, incluso pueden convivir con rutinas BASIC. ¿Sorprendido? Repasemos los argumentos en contra del uso simultáneo de formas de onda personalizadas.


(1) "BASIC es demasiado lento para cambiar los bits del control de volumen a una frecuencia utilizable". Bien. Se puede hacer muy poco con respecto a la velocidad a la que se ejecuta BASIC.


(2) "La única forma en que BASIC puede ejecutarse simultáneamente con una rutina de lenguaje de máquina es mediante el uso de interrupciones". Hasta donde sabemos, eso también es correcto.


(3) "Las VBI (Vertical Blank Interrupt – Interrupcion de supresión vertical) funcionan a una frecuencia demasiado baja (a intervalos de un sexagésimo de segundo). La frecuencia más alta que se puede obtener a través de VBI sería una onda cuadrada de 30 Hz". También es cierto.


Teniendo en cuenta esto, sería fácil concluir que no hay manera de producir una forma de onda personalizada utilizable que continúe reproduciéndose durante la ejecución de un programa BASIC. Sin embargo, donde esta línea de pensamiento se desvía es en el supuesto de que la única interrupción que tenemos a nuestra disposición es el espacio en "vertical blank – supresión vertical". Es un error comprensible: las rutinas VB1 se han vuelto tan populares que los programadores ahora cuelgan la mitad de sus códigos en ellas, y una vez que descubrimos un enfoque útil para un problema tendemos a pensar en todos los problemas similares de la misma manera. Sin embargo, hay muchas otras interrupciones disponibles, y una en particular que es tremendamente más flexible que una VBI.


Los Temporizadores De Interrupción Del Pokey. El mismo hardware utilizado para generar efectos de sonido estándar del Atari tiene un truco interesante: cuando los temporizadores Pokey 1, 2 y 4 cuentan regresivamente hasta cero, generan una solicitud de interrupción, lo que hace que el 6502 salte a través de los vectores RAM globales VTIMR1, VTIMR2 o VTIMR4. Las direcciones de estos vectores son $210, $212 y $214 respectivamente. Por lo general, estos vectores simplemente apuntan a una secuencia PLA-RTI, por lo que no sucede nada como resultado de la interrupción.


Sin embargo, cuando uno de estos vectores se cambia para que apunte a una rutina de interrupción, nos proporciona una ingeniosa interrupción controlable por frecuencia para rutinas en las que el tiempo es crítico. Las viejas fórmulas para determinar la frecuencia aún se aplican, por lo que todo lo que tenemos que hacer es determinar con qué frecuencia queremos que se ejecute la rutina, calcular el valor AUDF correspondiente e introducirlo en los registros de frecuencia para ese canal en particular. Ni siquiera tendremos que preocuparnos por recargar el temporizador, Pokey lo recarga automáticamente con el último valor introducido en AUDF.


A veces, la capacidad incorporada en esta máquina simplemente te deja sin aliento.


Generar formas de onda personalizadas simultáneas se vuelve terriblemente fácil cuando se ve desde esta perspectiva. Necesitamos tres rutinas: una para configurar la interrupción (iniciar el sonido), otra para borrar la interrupción (hacer que se calle) y la interrupción en sí, que simplemente seleccionará los valores sucesivos de una tabla de formas de onda y los introducirá en AUDC. Una vez que se ha ejecutado la rutina de configuración, BASIC selecciona la frecuencia de la nota y la introduce en AUDF de la misma manera que hemos estado seleccionando frecuencias todo el tiempo. BASIC puede elegir una nota, irse y hacer algo interesante (El programa de ejemplo dibuja algunos cuadros bonitos) y el sonido no se detendrá hasta que ejecutemos la rutina de silencio.


¿Las limitaciones? Bueno, hay tres. Cuanto mayor sea la frecuencia, más notable será la distorsión causada por DMA, etc. Aún más alto, las interrupciones ocurren tan rápido que la máquina no puede prestar atención a nada más y bloquea el programa en el valor AUDF 2. (Puede recuperarlo presionando la tecla SYSTEM RESET y su programa seguirá intacto).


La tercera limitación es que cuanto mayor sea su frecuencia, menos tiempo de máquina estará disponible para el resto del programa. Por ejemplo, usar el editor de pantalla mientras la interrupción avanza con un valor AUDF 3 es extraño: el sistema operativo se vuelve tan lento que puede ver cada línea individual desplazarse hacia arriba en la pantalla. Evidentemente, este efecto sería bastante inaceptable en un programa.


El Programa De Ejemplo. La demostración BASIC instala las tres rutinas que acabamos de describir y luego las utiliza para reproducir un motivo aplastado de la "Sonata Claro de Luna" de Beethoven mientras dibuja cuadros en la pantalla. Las variaciones de tempo son estrictamente el resultado de los distintos períodos de tiempo necesarios para dibujar los cuadros de diferentes tamaños; ciertamente es posible lograr un tempo constante alterando la rutina de visualización a una de una duración más predecible.


La forma de onda que se reproduce es una onda de rampa (o sierra), que produce un tono nasal bastante diferente al de la onda cuadrada estándar y, sin embargo, lo suficientemente agradable como para usarlo como efecto musical. Las frecuencias utilizadas son bastante bajas, para evitar los problemas descritos anteriormente, pero se pueden utilizar frecuencias más altas que éstas.


Ambos listados deberían explicarse por sí solos, pero dos elementos merecen un comentario. Tenga en cuenta que la rutina BASIC tiene cuidado de introducir un valor de frecuencia en AUDF antes de llamar a la rutina de configuración. Esto es para garantizar que AUDF contenga un valor lo suficientemente bajo como para evitar el bloqueo del programa.


También tenga en consideración que la tabla de formas de onda (en el listado Ensamblador) tiene ocho pasos. Acortar o alargar esta tabla aumentará o disminuirá las frecuencias obtenidas por AUDF. Acortar la tabla a cuatro pasos, por ejemplo, produciría sonidos una octava más altos que los creados por la versión actual. Esta es una forma de conseguir tonos más altos sin que el resto del programa se vuelva lento, a expensas de la resolución de la forma de onda. Si cambia la longitud de la tabla, recuerde cambiar la parte del código que envuelve el puntero WINDX de la forma de onda.


Ingrese este programa, use las rutinas en su propio trabajo y esperamos que disfrute tanto usándolas como lo hizo su corresponsal al escribirlas. Esta técnica, y las posibilidades que abre, es uno de esos descubrimientos fortuitos sobre Atari que pueden dejarte emocionado durante días.


Demostración BASIC De Sonido Usando Una Interrupción


0001 REM Interrupt Sound Demo
0002 REM (with apologies to Ludwig)
0003 REM Routine addresses and freq register equate
0004 SET=1536:CLEAR=1558:AUDF=53760
0005 REM Poke in machine language
0006 FOR L=1536 TO 1600
0007    READ BYTE
0008    POKE L,BYTE
0009 NEXT L
0010 REM Data bug check
011 READ BYTE
0012 IF BYTE <>-1 THEN ?"Data error.":STOP
0013 REM Set up graphics
0014 GRAPHICS 23
0015 REM Initialize freq first!
0016 POKE AUDF,127
0017 REM Start interrupt
0018 Q=USR(SET)
0019 REM Our first beat
0020 FOR D=1 TO 100:NEXT D
0021 REM Play melody
0022 BYTE=95
0023 POKE AUDF,BYTE
0024 C=C+1:IF C>3 THEN C=0
0025 X=X+7:IF X >80 THEN X=X-80
0026 Y=Y+1:IF Y>48 THEN Y=0
0027 COLOR C
0028 PLOT X,Y:DRAWTO 159—X,Y
0029 DRAWTO 159—X,95—Y:DRAWTO X,95-Y
0030 DRAWTO X,Y
0031 READ BYTE
0032 IF BYTE<> —1 THEN 0023
0033 REM Turn of interrupt
0034 Q=USR(CLEAR)
0035 REM Wait for key press
0036 IF PEEK(764)=255 THEN 0036
0037 END
0038 REM Machine language routines
0039 DATA 120,169,35,141,16,2,169,6
0040 DATA 141,17,2,165,16,9,1,133,16
0041 DATA 141,14,210,208,10,120,165
0042 DATA 16,41,254,133,16,141,14
0043 DATA 210,88,104,96,138,72,174
0044 DATA 57,6,189,58,6,141,1,210
0045 DATA 232,138,41,7,141,57,6,104
0046 DATA 170,104,64,16,18,20,24,31
0047 DATA 16,16,16,-1
0046 REM
0049 REM Melody data
0050 DATA 80,127,95,80,120,95
0051 DATA 80,120,95,80,120,90,71,120
0052 DATA 90,71,127,101,71,127,95
0053 DATA 80,127,95,85,143,101,85
0054 DATA 161,127,95,161,127,161
0055 DATA 192,192,192,-1	


Rutina En Lenguaje Ensamblador De Sonido Usando Una Interrupción


TITLE 'Interrupt Snd Demo'
;OS equates
IRQEN = $D20E ;IRQ enable
POKMSK = $10 ;shadow for IRQEN
AUDF = $D200 ;freq register
AUDC = $D201 ;volume register
VTIMR1 = $210 ;timer vector
;
ORG $600
;Set and Clear are to turn
;the sound routine on and off
SET    SEI
LDA #LOW TIMRI ;point to
STA VTIMR1 ;sound
LDA #HIGH TIMR1 ;routine
STA VTIMR1+1
LDA POKMSK ;enable it
ORA #1
STA POKMSK
STA IRQEN
BNE SET2
CLEAR SEI
LDA POKMSK ;kill rout
AND #$FE
STA POKMSK
STA IRQEN
SET2    CLI
PLA ;remove args
RTS ;back to Basic
;
;here's the interrupt sound routine
TIMRI    TXA ;save X
PHA
LDX WINDX ;get wave X
LDA WAVE,X ;get audc value
STA AUDC ;& place it
INX ;next step
TXA ;do wraparound
AND #7
STA WINDX
PLA ;restore X
TAX
PLA
RTI
;
WINDX    DS 1    ;wave index
;waveform table
WAVE    DB $10,$12,$14,$18
DB    $1F,$10,$10,$0
END	

8. Modulación por ancho de pulso

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í:


10000000


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:


11110000


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.


0005 REM This program will print
0010 REM all the equally tempered
0015 REM notes available from the
0020 REM PWM assembler demo
0025 DIM N$(25)
0030 ? "Turn on printer, press RETURN"
0035 INPUT N$
0040 LPRINT "Note","Value", "Frequency"
0045 LPRINT
0050 REM Note placement of spaces
0055 N$ = "C C#D D#E F F#G G#A A#B"
0060 FOUT = 8.1759375:SKEY = 1
0065 CLOCK = 63921
0070 AUDF = INT((CLOCK/(FOUT*12))- 0.5)
0075 IF AUDF > 255 THEN 0090
0080 IF AUDF < 23 THEN 0110
0085 LPRINT N$(SKEY,SKEY + 1),AUDF,INT(FOUT*100)/100
0090 SKEY = SKEY + 2
0095 IF SKEY = 25 THEN SKEY = 1
0100 FOUT = FOUT * 1.059463094
0105 GOTO 0070
0110 END	

Listado 2.


0005 REM ********************
0010 REM *     PWM Demo     *
0015 REM *                  *
0020 REM ********************
0025 REM
0030 GOSUB 0235
0035 REM Start up interrupt
0040 Q = USR(ION)
0045 RESTORE
0050 A = 243:COL = 15
0055 POKE PWM,255
0060 POKE AUDF,ABS(A):POKE VOL,15
0065 IF A< 0 THEN POKE PWM,20:COL=15
0070 SETCOLOR 2,COL,O;IF COL>0 THEN COL = COL - 3
0075 X=ABS(A)/1.5:Y=Y+8-COL/2
0080 IF Y > 191 THEN Y = 0
0085 PLOT 0,0:DRAWTO X,Y:DRAWTO 160,96:DRAWTO 319 - X,Y:DRAWTO 319,0
0090 PLOT 0,191:DRAWTO X,191 Y:DRAWTO 160,96:DRAWTO 319 - X,191 - Y:DRAWTO 319,191
0095 READ A
0100 IF A <> 0 THEN 0060
0105 REM Note table
0110 DATA 204,172, - 144,102,85,72
0115 DATA 60, - 45,42,- 35,37,45,47
0120 DATA -33,35,42,45,26,31, - 37,45
0125 DATA - 53,31,37,45,57,57, - 42,67
0130 DATA 57,67, 85,136, - 114,136
0135 DATA 172,172,172,172,172,172
0140 DATA 172172,0
0145 REM Turn off interrupt
0150 Q=USR(IOFF)
0155 GOTO 0155
0160 REM Machine code follows
0165 DATA 120,162,255,142,0,210,232
0170 DATA 134,203,232,134,207,134
0175 DATA 206,169,48,141,16,2,169,6
0180 DATA 141,17,2,165,16,9,1,133
0185 DATA 16,141,14,210,208,10,120
0190 DATA 165,16,41,254,133,16,141
0195 DATA 14,210,88,104,96,165,207
0200 DATA 74,144,2,169,8,133,207,37
0205 DATA 206,37,203,9,16,141,1,210
0210 DATA 198,205,208,18,165,204,133
0215 DATA 205,48,2,230,204,24,165
0220 DATA 206,41,4,208,1,56,38,206
0225 DATA 104,64
0230 DATA -1
0235 REM Poke in machine code
0240 GRAPHICS 24:COLOR 1
0245 SETCOLOR 1,0,12
0250 RESTORE 0160
0255 FOR L=1536 TO 1625
0260 READ A
0265 POKE L,A
0270 S=S + A
0275 SETCOLOR 2,0,14 - INT(S/736)
0280 NEXT L
0285 READ A
0290 IF A <> -1 OR S <> 10309 THEN ? "Error in machine code": STOP
0295 REM Machine code entry points
0300 ION = 1536;IOFF = 1571
0305 REM Special register equates
0310 VOL = 203:PWM = 204:AUDF = 53760
0315 RETURN

Listado 3.



9. Sin título

A medida que avanzaba esta columna, analizamos algunas técnicas de sonido simples en BASIC y algunos trucos avanzados de formas de onda en lenguaje ensamblador. En el camino, nos hemos encontrado con el eterno equilibrio entre la facilidad de programación en BASIC y la velocidad de la máquina en Ensamblador. Este mes vamos a concluir nuestra discusión con una mirada a un lenguaje de nivel medio perfectamente adecuado para trabajar con sonido: C.


Si tiene una copia de C, esta columna debería brindarle un buen punto de partida para tocar música con ruido rosa y aclarar al menos una sorpresa que pueda encontrar en el sonido C. Si no tiene una copia de C, este artículo demostrará parte del poder del lenguaje y tal vez lo anime a obtener una copia. El programa de muestra fue desarrollado con el compilador Deep Blue C por John H. Palevich, vendido por Atari Program Exchange (número de catálogo APX-20166).


Ruido 1/f y Richard Voss. La aplicación que vamos a ver implica un enfoque diferente a la música aleatoria, llamado ruido 1/f. Este algoritmo es un intento de resolver uno de los problemas más complicados inherentes a la composición aleatoria: la falta de orden. La naturaleza misma de un generador de números aleatorios es caótica; la mayoría de la música no lo es. Si escribimos un programa para seleccionar aleatoriamente un montón de notas candidatas y tocarlas, el resultado es muy difícil de escuchar. La probabilidad de que se toque cualquier nota es la misma: este es el equivalente musical del ruido blanco.


Al matemático Richard Voss se le ocurrió un algoritmo muy popular para "filtrar" números aleatorios y darles una tasa de distribución más natural. Este algoritmo simple pero elegante produce un flujo de valores que reflejan los procesos naturales (viento y lluvia, copos de nieve, etc.) en su tendencia a combinar pequeños cambios con grandes saltos ocasionales en el valor. Esto es útil en muchas más situaciones además de la música: digamos que estás escribiendo un videojuego que tiene una araña que se mueve más o menos aleatoriamente. La sustitución del algoritmo de Voss por un generador de números aleatorios convencional produce una diferencia notable en la calidad del movimiento de la araña; parece un poco más inteligente.


Para imaginar cómo funcionan estos "números Voss", imagine una fila de cuatro dados, todos con el 1 boca arriba. Los leemos en orden decimal para producir un total de 1111. Ahora tiramos el dado en la posición de las "unidades". Esto nos dará seis valores posibles, que van desde 1.111 a 1.116. Pero si tiramos el dado en la posición de las "decenas", obtenemos seis valores posibles que van desde 1.111 a 1.161. En lugar de un posible cambio de valor de 5, obtenemos un rango de 50.


Una vez más: lancemos ambos dados en la posición de "decenas" y de "unidades". Ahora podemos obtener treinta y seis valores posibles, que van desde 1.111 a 1.166, un posible cambio de 55. Finalmente, cuando avanzamos hasta tirar los cuatro dados, obtenemos 1.296 valores posibles (6 elevado a la cuarta potencia) con un rango de valores de 5.555.


Ahora, para mostrar el patrón de tiradas de dados de forma más explícita: En este gráfico, un "1" significa tirar el dado, un "0" significa dejarlo en pie.


0001 0010 0011 0100 0101 0110 1111 1000


¡Que es, por supuesto, el familiar patrón de conteo binario!


Entonces, para utilizar este sistema de tirada de dados en un programa, necesitamos dos variables para cada parámetro. Necesitamos una variable para realizar un seguimiento del "estado" actual de todos los dados: en esta variable, cada bit representará un único dado de dos caras (De acuerdo, tal vez sea una moneda). Luego necesitamos una segunda variable a la que llamaremos nuestro "filtro". Esta variable nos dirá qué dados podemos "tirar", es decir, cuál de los bits de la variable de dados podemos cambiar. Para lanzar un dado en particular, usaremos el generador de números aleatorios para decidir si ese bit es un 1 o un 0. Finalmente, cada vez que obtengamos un nuevo número tenemos que incrementar la variable de filtro para actualizar nuestro patrón de lanzamiento de dados.


¿Suena complicado? Si no tiene claro el concepto de manipulaciones de bits, lo es. Pero en C, se vuelve terriblemente fácil. Observe:


Almacenaremos los dados y las variables de filtro en un arreglo; recuerde que queremos realizar un seguimiento de los dados y los filtros para cada parámetro que usará la función (volumen, tempo, tono, etc.). Cada una de estas variables tendrá una longitud de un byte. En C, eso se llama carácter y tenemos que declararlo como tipo de datos.


char d[12]; char f[12];


Esto nos da 13 parámetros para jugar (C usa el elemento 0). Tenga en cuenta que los arreglos se forman con corchetes, no con paréntesis.


Ahora escribamos la función.


+ + f[p]


Esta frase incrementa automáticamente el elemento de filtro al que apunta "p". En BASIC, diríamos "F(P) = F(P) + 1". La frase:


peek(RANDOM)& + + f[p]


incrementa simultáneamente f[p] y luego realiza un AND lógico (&) del resultado con el generador de números aleatorios. (Esto supone que RANDOM se definió previamente como hexadecimal D20A, que es la posición de memoria del registro de desplazamiento aleatorio). El resultado de esta operación es que siempre que un bit sea 0 en el filtro, obtendremos un 0 en la salida. Pero siempre que un bit sea 1 en el filtro, obtendremos un 1 o un 0 determinado aleatoriamente en la salida: algunos bits del número aleatorio se fuerzan a 0. Esto nos da nuestra tirada de dado aleatoria.


Ahora queremos injertar el nuevo estado de los dados con el antiguo estado de los dados. En C, el carácter "^" hace un OR exclusivo (EOR en ensamblador). Si el bit de nuestro nuevo dado es 0, entonces el bit del dado anterior no se modifica. Si el bit del nuevo dado es un 1, el bit del dado antiguo se invierte: un 1 se convierte en un 0 y viceversa. Esto tiene el efecto de "tirar" sólo los dados que corresponden a unos en la variable de filtro.


d[p] = d[p] ^ (peek(RANDOM)& + + f[p]);


Pero tenemos otro atajo en C. Los programadores siempre dicen cosas como "A = A + 10" o "ZOT = ZOT*25". Al colocar el operador antes del signo igual, podemos omitir la repetición del nombre de la variable: así obtenemos "a + = 10" y "zot* = 25". Si esto le parece un toque frívolo, intente escribir el equivalente BASIC a "r[3.14*g[x + = 10/y]]* = 22;".


d[p] ^ = (peek(ALEATORIO)& + + f[p]); hace lo mismo que la orden anterior de tirar el dado; simplemente está abreviado. Esto hace todo lo que le pedimos a nuestra función "Voss". Para el programa, agregaremos un detalle más: un divisor ® para reducir el resultado final. Si r es 2, el valor más alto posible será 127 en lugar de 255. Finalmente, agruparemos toda la expresión dentro de la orden "return", que finaliza la función y devuelve el valor de la expresión. La forma final de nuestra función se puede ver en la parte inferior del listado del programa de ejemplo.


Son expresiones como esta en donde C realmente brilla. Intente duplicar esta función de una línea en BASIC; Le sorprenderá la expansión que se produce.


La concisión del lenguaje se mejora de otras maneras. Por ejemplo, cada expresión tiene un valor: el valor asignado al lado izquierdo de la expresión. Esto hace que declaraciones como "a = b = c = d = 0" sean legales, ya que la expresión "d = 0" tiene un valor de 0, lo que forma la expresión implícita "c = 0", y así sucesivamente. Si mira cerca de la parte inferior de la función "main", verás la siguiente orden:


plot(x = voss(10,2),y = voss(11,3));


que llama a la función "Voss" y asigna el valor devuelto a x, llama a "Voss" nuevamente y asigna el valor a y, luego llama a la función "plot" y le pasa los nuevos valores de x e y. Este tipo de compresión es increíblemente poderosa, una vez que se acostumbra.


Un Recorrido Por El Programa De Ejemplo. La primera parte del programa es un comentario largo que describe el archivo de enlace necesario para compilar el programa. En C, los comentarios se marcan con un solo "/*", y el comentario no termina hasta que se cierra con un "*/".


Las declaraciones "#define" son comandos simples de reemplazo de texto. Dondequiera que se encuentre la cadena "Parms", la declaración se procesará como si se hubiera escrito "12". Tenga en cuenta que esta es una definición de etiqueta y no tiene nada que ver con variables: no se desperdicia nada de su memoria de tiempo de ejecución.


Luego vienen las declaraciones de los arreglos. Estos se crean encima de la primera definición de función para que sean accesibles, o "globales", para todas las funciones siguientes. C tiene un conjunto completo de reglas para proteger el "alcance" y la "privacidad" de las variables. Las variables que no son de tipo arreglo, por ejemplo, generalmente se pasan por valor, por lo que una función llamada obtiene su propia "copia privada" de la variable que puede modificar sin estropear la copia de la función que llama.


La función que se ejecutará cuando se carga el programa debe denominarse "main". A esto le sigue una lista de parámetros vacía (los paréntesis) para indicar que no se pasan parámetros a esta función. Las cadenas "$(" y "$)" son adaptaciones al Atari, el que no cuenta con los símbolos de llaves. Las declaraciones lógicas se agrupan con llaves: cuando se cierra la última llave, el programa finaliza.


En la inicialización, primero corregimos un error menor en C y le damos a POKEY los valores que nos permitirán usar el canal 4 sin interferencias. Si no lo hace, se quedará con un Atari de tres voces. Luego le damos a nuestros dados y filtros algunos valores iniciales aleatorios, copiamos la cadena de notas candidatas en el arreglo "fn" (la especificación de frecuencia de nota a nota se proporciona en forma de caracteres ATASCII) y configuramos los gráficos para un brillo azul espantosamente brillante.


Después de eso, lo único que queda es entrar en un bucle "while" infinito, tocar las voces y dibujar algunas líneas en el azul horriblemente brillante. C tiene tres tipos de bucles. El familiar for-next se ha convertido en el bucle "for". Las siguientes declaraciones son equivalentes:


for(voice = 0;voice < 4; + + voice)


100 FOR VOICE = 0 to 3 STEP 1


C también tiene un bucle "while" que continúa mientras la expresión sea verdadera, y un bucle "do while" que hace lo mismo pero coloca la prueba en la parte inferior en lugar de en la parte superior. Ah, sí: a diferencia del BASIC, el bucle "for" de C realiza su prueba en la parte superior, donde debería estar. La declaración dentro de este bucle. . .


for(x 5;x<0; + + x)


nunca será ejecutado.


Rastros felices. Y así salimos del alcance del Circle C y concluimos nuestra descripción general de las posibilidades de programación de sonido de la computadora Atari. Con suerte, utilizando las técnicas presentadas aquí durante el último año y medio, haya podido obtener algunos efectos tonales intrigantes de su máquina y haya desarrollado algunas técnicas propias. Me alegro de que podamos ayudar.


Que todos tus programas de juego, presentes y conjeturados, no sean ni sonoramente mansos ni audiblemente irritantes.


/*Música en C

por Bill Williams

Si el nombre de este archivo es CMUS.C,

su archivo de enlace (CMUS.LNK)

debería verse así:

cmus

aio

graphics

dbc.obj

*/
#define PARMS 12
#define RAND 0xD20A
*define TRUE 1
#define SKCTL 0xD20F
#define SSKCTL 0x232
#define AUDCTL 0x0208
char d[PARMS]; /* dice */
char f[PARMS]; /* pinking filters */
char fn[10]; /* freq/note table */
char    vol[4]; /* volume counts */
char note[4]; /* voice’s note */
main()
$(
int    i,voice,tempo,x,y;
/*Initialize Pokey for sound */
poke(SSKCTL,3);
poke(SKCTL,3);
poke(AUDCTL,0);
/* Initialize pink numbers */
for(i = 0; i <= PARMS; + + i)$(
d[i] = peek(RAND);
f[i] = peek(RAND);
$)
/* Set up note table.
Current candidate notes
make up a two-octave pentatonic
scale.
*/
strcpy(fn,"!%*29DLUfr");
for(voice = 0;voice < 4;+ + voice)
  note[voice] = fn[rnd(10)];
/* Set up graphics */
graphics(24);
color(1);
setcolor(1,8,0);
setcolor(2,8,12):
/* Play Music */
while(TRUE)$(
for(voice = 0;voice < 4; + + voice)
play(voice);
for(tempo = voss(9,6);tempo > 0;- - tempo);
if(!rnd(8))$(
plot(x = voss(10,2),y = voss(11,3));
drawto(191,peek(RAND)&0xC0);
drawto(319—(x/2),y):
$)
$)
$)
play(v)
char    v; /* voice index */
$(
/* if volume is not 0, play voice
and decrement volume. Otherwise,
pick a new starting volume and a new note.
*/
if(vol[v])
  sound(v,note[v], 10,vol[v]--);
else $(
vol[v] = 3 + voss(v,31);
note[v] = fn[voss(v + 4.28)];
$)
$)
/* Voss' 1/f noise algorithm */
voss(p,r)
char p; /* parameter number */
char r; /* range (divisor) */
$(
return((d[p]^ = (peek(RAND)& + + f[p]))/r);
$)