Salida de Audio con CircuitPython

CircuitPython 3.0 y posteriores, vienen con una versión actualizada de audioio, la cual provee capacidades de salida de audio. Puedes tocar tonos generados. También puedes tocar, pausar y quitar la pausa de archivos tipo wave. Puedes tener 3V de pico-a-pico de salida analógica o una salida digital I2S. En esta página te vamos a mostrar como usar salida analógica.

Esto es excelente para todo tipo de proyectos que requieren sonido, ¡como un piano de tonos o cualquier cosa a la que desees agregar efectos de sonido!

¡Las ESP8266, las Trinket M0 y las Gemma M0 no tienen capacidades de audioio! Debes usar una tarjeta M0 Express o M4 Express para esto.

El primer ejemplo que vamos a mostrarte es como generar un tono y que suene al usar un botón. El segundo ejemplo te va a mostrar como tocar, poner en pausa y quitar la pausa de un archivo tipo wave, utilizando un botón para quitar la pausa. Ambos van a tocar el sonido por medio de un conector de audio. El volumen por omisión de ambos ejemplo va a ser dolorosamente alto para tocar con audífonos. Así que hemos agregado un potenciómetro  e incluido código en el ejemplo de generación de tono, para controlar el volumen.

En nuestro código, vamos a utilizar el pin A0 para la salida de audio, dado que es el único pin con DAC disponible en todas las tarjetas Express. Las tarjetas M0 Express tienen la salida de audio en A0. Las tarjetas M4 Express tienen dos pines para salida de audio, A0 y A1, sin embargo solo vamos a utilizar A0 en esta guía.

Tocar un Tono

Copie y pegue el siguiente código dentro de code.py utilizando tu editor favorito, y salva el archivo.

import time
import array
import math
import board
import digitalio

try:
    from audiocore import RawSample
except ImportError:
    from audioio import RawSample

try:
    from audioio import AudioOut
except ImportError:
    try:
        from audiopwmio import PWMAudioOut as AudioOut
    except ImportError:
        pass  # not always supported by every board!

button = digitalio.DigitalInOut(board.A1)
button.switch_to_input(pull=digitalio.Pull.UP)

tone_volume = 0.1  # Increase this to increase the volume of the tone.
frequency = 440  # Set this to the Hz of the tone you want to generate.
length = 8000 // frequency
sine_wave = array.array("H", [0] * length)
for i in range(length):
    sine_wave[i] = int((1 + math.sin(math.pi * 2 * i / length)) * tone_volume * (2 ** 15 - 1))

audio = AudioOut(board.A0)
sine_wave_sample = RawSample(sine_wave)

while True:
    if not button.value:
        audio.play(sine_wave_sample, loop=True)
        time.sleep(1)
        audio.stop()

Primero, creamos un objeto botón y lo asignamos al pin A1, y lo configuramos como entrada y con un pull-up. Aunque el switch de botón involucra digitalio,estamos utilizando un pin-A por lo que el código va a funcionar para todas las tarjetas.

Ya que el volumen por omisión es muy alto, hemos incluido una variable llamada tone_volume en el código de onda sinusoide. Usted puede utilizar el código para controlar el volumen aumentando o reduciendo este número para aumentar o reducir el volumen. También puede controlar el volumen, rotando la perilla del potenciómetro.

Para configurar la frecuencia del tono generado, cambie el número asignado en la variable frequency para definir la frecuencia en Hz del tono que deseas generar.

Luego, generamos un período de la onda sinusoide con la función math.sin, y se lo asignamos a sine_wave.

Luego, creamos el objeto de audio, y lo asignamos al pin A0.

Creamos un sample de la onda sinusoide utilizando RawSample y dándole el sine_wave que creamos.

Dentro de nuestro bucle, revisamos si el botón ha sido presionado. El botón tiene dos estados de  True y False(verdadero y falso). Encontramos el valor del botón en button.value el cual por omisión retorna el estado True cuando no está presionado. Así que para revisar si ha sido presionado, revisamos si está presente el estado de False. Entonces, el chequeo que hacemos para ver  if not button.value es un equivalente de not True, o False.

Una vez que el botón es presionado, hacemos play (o "tocar") con el sample que hemos creado y hacemos un bucle. El comando time.sleep(1) le dice que se cicle por un segundo para tocar el sonido. Cuando el segundo se ha cumplido, nos detenemos con stop. Puedes aumentar o reducir la duración del tiempo que se toca el sonido, aumentando o bajando la cantidad de segundos que se le piden que haga pausa a time.sleep(). Trata de cambiarlo de 1 a 0.5. Ahora trata de cambiarlo a 2. ¡Lo puedes cambiar a tu conveniencia!

¡Eso es todo!

Tocar un Archivo Wave

Puede utilizar cualquier archivo tipo wave que esté soportado. CircuitPython trabaja con formato WAV mono o estéreo, a una taza de sample de 22 KHz (o menor) y 16-bits. Las tarjetas M0 SOLO trabajan en MONO. ¡La razón sobre mono es que solo hay una salida analógica en estas tarjetas! Las tarjetas M4 soportan estéreo porque tienen dos salidas. Lo de 22 KHz o menor es porque CircuitPython no puede manejar más datos que eso (y además no va a sonar mucho mejor) y la salida del DAC es de 10-bits por lo cual cualquier cosa por arriba de 16-bits simplemente va a tomar mayor espacio sin mejor calidad.

Dado que el archivo WAV debe caber en el sistema de archivos de CircuitPython, debe ser menor a 2MB.

CircuitPython no trabaja con OGG o MP3. ¡Solo WAV!

Hemos creado una guía detallada de como generar archivos WAV aquí.

Hemos incluido la que usamos aquí. La puedes descargar y copiar a tu tarjeta.

Vamos a tocar el archivo wave por 6 segundos, ponerlo en pausa, esperar a que un botón sea presionado, y luego quitar la pausa al archivo y dejarlo que toque hasta el final. ¡Luego comienza de nuevo desde el principio! Vamos a mirar.

Copia y pega el siguiente código en code.py utilizando tu editor favorito, y salva el archivo.

import time
import board
import digitalio

try:
    from audiocore import WaveFile
except ImportError:
    from audioio import WaveFile

try:
    from audioio import AudioOut
except ImportError:
    try:
        from audiopwmio import PWMAudioOut as AudioOut
    except ImportError:
        pass  # not always supported by every board!

button = digitalio.DigitalInOut(board.A1)
button.switch_to_input(pull=digitalio.Pull.UP)

wave_file = open("StreetChicken.wav", "rb")
wave = WaveFile(wave_file)
audio = AudioOut(board.A0)

while True:
    audio.play(wave)

    # This allows you to do other things while the audio plays!
    t = time.monotonic()
    while time.monotonic() - t < 6:
        pass

    audio.pause()
    print("Waiting for button press to continue!")
    while button.value:
        pass
    audio.resume()
    while audio.playing:
        pass
    print("Done!")

Primero creamos el objeto botón, y lo asignamos al pin A1,y lo configuramos para entrada con un pull-up.

Luego abrimos el archivo, StreetChicken.wav como un binario para lectura (readable binary) y guardamos el objeto archivo en wave_file lo cual es lo que usamos para leer el audio de: wave_file = open("StreetChicken.wav", "rb").

Ahora, le pedimos al sistema de audio que cargue los datos del wave del archivo wave = audioio.WaveFile(wave_file) y finalmente solicitamos que el audio sea tocar por medio del pin de salida analógica A0 audio = audioio.AudioOut(board.A0).

¡El archivo de audio ya está listo, y se puede tocar en cualquier momento con audio.play(wave)!

Dentro del ciclo, comenzamos por tocar el archivo.

Luego tenemos el bloque que le dice al código que se espere 6 segundo antes de poner el archivo en pausa. Decidimos utilizar time.monotonic() dado que es non-blocking que significa que puedes realizar otras cosas mientras el archivo está siendo tocado, ¡como controlar servos o NeoPixeles! En un momento dado, time.monotonic() va a ser igual a la cantidad de segundos desde que prendiste tu tarjeta. (El reiniciado-suave que ocurre con el auto cargado cuando salvas archivos en CircuitPython, or al entrar y salir del REPL, no comienza desde cero). Cuando es llamado, retorna un número con un decimal. Cuando le asignas time.monotonic() a la variable, la variable es igual al número de segundos a la cantidad de segundos que tuvo time.monotonic() al momento de asignar la variable. Lo puedes llamar una y otra vez y restarle la variable a time.monotonic() para saber cuanto tiempo ha transcurrido. Para más detalles, puedes ver este ejemplo.

Así que asignamos t = time.monotonic() para tener un punto de inicio. Luego decimospass, o "no haga nada", hasta que la diferencia entre  t y time.monotonic() sea mayor a 6 segundos. Dicho de otra forma, continuamos tocando hasta que hayan pasado 6 segundos. Recueda, puedes agregar otro código aquí para hacer otras cosas mientras cosas audio por 6 segundos.

Luego ponemos el audio en pausa (pause) e imprimimos (print) a la consola serial, "Waiting for button press to continue!" (o "¡Esperando a que un botón sea presionado para continuar!")

Ahora, vamos a esperar a que un botón presione de la misma forma que lo hicimos cuando tocamos un tono generado. Estamos diciendo while button.value, o mientras el botón retorne True, pass. Una vez que el botón es presionado, el retorna False, y esto le dice al código que continúe.

Una vez que el botón es presionado, quitamos la pausa (resume) y seguimos tocando el archivo. Le decimos que termine de tocar el archivo con while audio.playing: pass.

Finalmente, imprimimos (print) a la consola serial, "Done!" (o "¡Listo!)

Puedes realizar esto con cualquier archivo wave soportado, y puedes agregarle todo tipo de cosas al código de tu proyecto, mientras el archivo está tocando. ¡Pruébalo!

Cableándolo

Además de tu placa microncontroladora, vamos a requerir:

Breadboard-Friendly 3.5mm Stereo Headphone Jack

PRODUCT ID: 1699
Pipe audio in or out of your project with this very handy breadboard-friendly audio jack. It's a stereo jack with disconnect-switches on Left and Right channels as well as a center...
$0.95
IN STOCK

Tactile Switch Buttons (12mm square, 6mm tall) x 10 pack

PRODUCT ID: 1119
Medium-sized clicky momentary switches are standard input "buttons" on electronic projects. These work best in a PCB but
$2.50
IN STOCK

Panel Mount 10K potentiometer (Breadboard Friendly)

PRODUCT ID: 562
This potentiometer is a two-in-one, good in a breadboard or with a panel. It's a fairly standard linear taper 10K ohm potentiometer, with a grippy shaft. It's smooth and easy...
$0.95
IN STOCK

100uF 16V Electrolytic Capacitors - Pack of 10

PRODUCT ID: 2193
We like capacitors so much we made a kids show about them. ...
OUT OF STOCK

Full sized breadboard

PRODUCT ID: 239
This is a 'full-size' breadboard, 830 tie points. Good for small and medium projects. It's 2.2" x 7" (5.5 cm x 17 cm) with a standard double-strip in the middle...
$5.95
IN STOCK

Premium Male/Male Jumper Wires - 20 x 6" (150mm)

PRODUCT ID: 1957
These Male/Male Jumper Wires are handy for making wire harnesses or jumpering between headers on PCB's. These premium jumper wires are 6" (150mm) long and come in a 'strip' of 20...
OUT OF STOCK

Y para que sea más sencillo el cablearlo con la CircuitPlayground Express:

Small Alligator Clip to Male Jumper Wire Bundle - 6 Pieces

PRODUCT ID: 3448
When working with unusual non-header-friendly surfaces, these handy cables will be your best friends! No longer will you have long, cumbersome strands of alligator clips. These...
$3.95
IN STOCK

Cuando los interruptores de botón tienen cuatro pines, realmente son solo dos pines. Cuando se cablea un interruptor de botón con cuatro pines, lo más sencillo es verificar que estás cableando los pines correctos, es conectando esquinas opuestas del interruptor de botón. Así no hay chance de que accidentalmente conectes el cable al mismo pin dos veces.

Estos son los pasos que hay que seguir para cablear estos componentes:

  • Conecte el pin de tierra de su tarjeta a el riel de tierra en la breadboard porque vamos a conectar los tres componentes a tierra.
  • Conect un pin del interruptor de botón al pin A1 de tu tarjeta, y el pin opuesto del interruptor de botón al riel de tierra de la breadboard.
  • Conecte los pines izquiero y derecho de la entrada de audio entre ellos.
  • Conecte el pin central de la entrada de audio al riel de tierra de la breadboard.
  • Conecte el pin izquierdo al lado negativo del capacitor de 100mF.
  • Conecte el lado positivo del capacitor al pin central del potenciómetro.
  • Conecte el lado derecho del potenciómetro al pin A0 de tu tarjeta.
  • Conecte el pin izquierdo del potenciómetro al riel de tierra de la breadboard.

La siguiente lista muestra diagramas de cableado para ayudar a encontrar los pines correctos y el cableado con diferentes componentes. Los cables para tierra son negros. El cable para el interruptor de botón es amarillo. Los cables involucrados con audio de color azul.

El cableado es el mismo para las tarjetas versión M4 que para las versión M0. Siga la imagen para ambas.

 

¡Utilice un breadboard para que el cableado quede limpio y ordenado!

La Circuit Playground Express está cableada eléctricamente igual que el ejemplo arriba para una tarjeta tipo ItsyBitsy/Feather/Metro, pero utilizamos cables con clip de lagarto como cables de conexión en lugar de cables tipo jumper.

This guide was first published on Jun 24, 2020. It was last updated on Jun 24, 2020.
This page (Salida de Audio con CircuitPython) was last updated on Jul 02, 2020.