Text Editor
Adafruit recommends using the Mu editor for editing your CircuitPython code. You can get more info in this guide.
Alternatively, you can use any text editor that saves simple text files.
Download the Project Bundle
Your project will use a specific set of CircuitPython libraries, and the code.py file. To get everything you need, click on the Download Project Bundle link below, and uncompress the .zip file.
Connect your computer to the M7 board via a known good USB power+data cable. A new flash drive should show up as CIRCUITPY.
Drag the contents of the uncompressed bundle directory onto your board's CIRCUITPY drive, replacing any existing files or directories with the same names, and adding any new ones that are necessary.
# SPDX-FileCopyrightText: 2023 John Park # # SPDX-License-Identifier: MIT # Ambient Machine inspired by Yuri Suzuki https://www.yurisuzuki.com/projects/the-ambient-machine import os import gc import board import busio import audiocore import audiobusio import audiomixer from digitalio import Pull from adafruit_debouncer import Debouncer from adafruit_mcp230xx.mcp23017 import MCP23017 i2c = busio.I2C(board.SCL, board.SDA) mcp_a = MCP23017(i2c, address=0x20) # default address mcp_b = MCP23017(i2c, address=0x21) # MCP23017 w/ address 0 jumper set switches = [] # set up all the switch pins on two MCP23017 boards as debouncer switches for p in (8,9,10,11,12,4,3,2,1,0): pin = mcp_a.get_pin(p) pin.switch_to_input(pull=Pull.UP) switches.append(Debouncer(pin)) for p in (8,9,10,11,12,4,3,2,1,0): pin = mcp_b.get_pin(p) pin.switch_to_input(pull=Pull.UP) switches.append(Debouncer(pin)) wav_files = [] for filename in os.listdir('/samples/'): # on board flash if filename.lower().endswith('.wav') and not filename.startswith('.'): wav_files.append("/samples/"+filename) print(filename) wav_files.sort() # put in alphabetical/numberical order gc.collect() # Metro M7 pins for the I2S amp: lck_pin, bck_pin, dat_pin = board.D9, board.D10, board.D12 audio = audiobusio.I2SOut(bit_clock=bck_pin, word_select=lck_pin, data=dat_pin) mixer = audiomixer.Mixer(voice_count=len(wav_files), sample_rate=22050, channel_count=1, bits_per_sample=16, samples_signed=True, buffer_size=2048) audio.play(mixer) for i in range(10): # start playing all wavs on loop w levels down wave = audiocore.WaveFile(wav_files[i], bytearray(1024)) mixer.voice[i].play(wave, loop=True) mixer.voice[i].level = 0.0 LOW_VOL = 0.2 HIGH_VOL = 0.5 while True: for i in range(len(switches)): switches[i].update() switch_row = i // 5 if switch_row == 0: # first row plays five samples if switches[i].fell: if switches[i+5].value is False: # check vol switch (pull-down, so 'False' is 'on') mixer.voice[i].level = HIGH_VOL # if up else: mixer.voice[i].level = LOW_VOL # if down if switches[i].rose: mixer.voice[i].level = 0.0 if switch_row == 1: # second row adjusts volume of first row if switches[i].fell: if switches[i-5].value is False: # raise volume if it is on mixer.voice[i-5].level = HIGH_VOL if switches[i].rose: if switches[i-5].value is False: # lower volume if it is on mixer.voice[i-5].level = LOW_VOL if switch_row == 2: # third row plays five different samples if switches[i].fell: if switches[i+5].value is False: mixer.voice[i-5].level = HIGH_VOL else: mixer.voice[i-5].level = LOW_VOL if switches[i].rose: mixer.voice[i-5].level = 0.0 if switch_row == 3: # fourth row adjust volumes of third row if switches[i].fell: if switches[i-5].value is False: mixer.voice[i-10].level = HIGH_VOL if switches[i].rose: if switches[i-5].value is False: mixer.voice[i-10].level = LOW_VOL
How It Works
The code has two key functions:
- Play ten wav file audio samples and use the audio mixer to mute/unmute them and adjust their volume levels.
- Watch for the toggle switches to be flipped in order to adjust the audio mixer levels.
Import Libraries
First, you'll import libraries:
-
os
: Operating system functions for file and directory operations -
gc
: garbage collection module for managing memory -
board
: Provide pin mappings for the board -
busio
: Handle communication on I2C bus -
audiocore
,audiobusio
: handle audio playback -
audiomixer
: for mixing audio channels -
digitalio
: handle digital GPIO pins -
adafruit_debouncer
: handle switch debouncing/switch events -
adafruit_mcp230xx.mcp23017
: interact with the MCP23017 GPIO expander
import os import gc import board import busio import audiocore import audiobusio import audiomixer from digitalio import Pull from adafruit_debouncer import Debouncer from adafruit_mcp230xx.mcp23017 import MCP23017
GPIO and I2C Setup
Next, you'll set up the I2C object and two instances of the MCP23017 GPIO expanders. You'll set up the two lists of pins used per board.
i2c = busio.I2C(board.SCL, board.SDA) mcp_a = MCP23017(i2c, address=0x20) # default address mcp_b = MCP23017(i2c, address=0x21) # MCP23017 w/ address 0 jumper set switches = [] # set up all the switch pins on two MCP23017 boards as debouncer switches for p in (8,9,10,11,12,4,3,2,1,0): pin = mcp_a.get_pin(p) pin.switch_to_input(pull=Pull.UP) switches.append(Debouncer(pin)) for p in (8,9,10,11,12,4,3,2,1,0): pin = mcp_b.get_pin(p) pin.switch_to_input(pull=Pull.UP) switches.append(Debouncer(pin))
Audio File Handling
Here you'll create a list of wav files by getting a filtered list of files from on-board flash in the /samples
directory. These are filtered to remove any possible operating system hidden .
dot files and to only include files ending in .wav
The wav_files[]
list is then re-sorted alphabetically.
wav_files = [] for filename in os.listdir('/samples/'): # on board flash if filename.lower().endswith('.wav') and not filename.startswith('.'): wav_files.append("/samples/"+filename) print(filename) wav_files.sort() # put in alphabetical/numerical order
I2S Audio Playback Setup
The D9, D10, and D12 pins on the Metro M7 are assigned to lck_pin
, bck_pin
, and dat_pin
, respectively, for the I2S digital audio amplifier board.
lck_pin, bck_pin, dat_pin = board.D9, board.D10, board.D12 audio = audiobusio.I2SOut(bit_clock=bck_pin, word_select=lck_pin, data=dat_pin)
Audio Mixer
You'll create an instance of audiomixer.Mixer
with the correct number of voices (10), sample rate, channel count, bits per sample, and buffer size of 2048.
Then, all ten samples are started playing and looping, with their mixer levels initially set to 0 so no sound will play until you toggle sample switches.
Two volume constants are created to set the mixer levels low and high when the volume toggles are flipped, per sample.
mixer = audiomixer.Mixer(voice_count=len(wav_files), sample_rate=22050, channel_count=1, bits_per_sample=16, samples_signed=True, buffer_size=2048) audio.play(mixer) for i in range(10): # start playing all wavs on loop w levels down wave = audiocore.WaveFile(wav_files[i], bytearray(1024)) mixer.voice[i].play(wave, loop=True) mixer.voice[i].level = 0.0 LOW_VOL = 0.2 HIGH_VOL = 0.5
Main Loop
In the main loop, the switches are checked for events. If a switch is flipped the code checks to see which row it is in.
The first row plays samples 0-4. The second row sets the volume low or high for the sample switch directly above it. The third row plays samples 5-9, and the fourth row sets the volume of the samples above them.
while True: for i in range(len(switches)): switches[i].update() switch_row = i // 5 if switch_row == 0: # first row plays five samples if switches[i].fell: if switches[i+5].value is False: # check vol switch (pull-down, so 'False' is 'on') mixer.voice[i].level = HIGH_VOL # if up else: mixer.voice[i].level = LOW_VOL # if down if switches[i].rose: mixer.voice[i].level = 0.0 if switch_row == 1: # second row adjusts volume of first row if switches[i].fell: if switches[i-5].value is False: # raise volume if it is on mixer.voice[i-5].level = HIGH_VOL if switches[i].rose: if switches[i-5].value is False: # lower volume if it is on mixer.voice[i-5].level = LOW_VOL if switch_row == 2: # third row plays five different samples if switches[i].fell: if switches[i+5].value is False: mixer.voice[i-5].level = HIGH_VOL else: mixer.voice[i-5].level = LOW_VOL if switches[i].rose: mixer.voice[i-5].level = 0.0 if switch_row == 3: # fourth row adjust volumes of third row if switches[i].fell: if switches[i-5].value is False: mixer.voice[i-10].level = HIGH_VOL if switches[i].rose: if switches[i-5].value is False: mixer.voice[i-10].level = LOW_VOL
Text editor powered by tinymce.