194 lines
6.2 KiB
Python
194 lines
6.2 KiB
Python
# SPDX-FileCopyrightText: 2020 Anne Barela for Adafruit Industries
|
|
#
|
|
# SPDX-License-Identifier: MIT
|
|
#
|
|
# Talking A, B, Cs Soundboards: Animal ABCs and "E is for Electronics" ABCs
|
|
|
|
import time
|
|
import board
|
|
import audioio
|
|
import audiocore
|
|
import adafruit_fancyled.adafruit_fancyled as fancy
|
|
import adafruit_trellism4
|
|
|
|
# Custom colors for keys
|
|
RED = 0xFF0000
|
|
MAROON = 0xFF0044
|
|
ORANGE = 0xFF6600
|
|
YELLOW = 0xFFFF00
|
|
BROWN = 0x8B4513
|
|
GREEN = 0x008000
|
|
AQUA = 0x33ff33
|
|
TEAL = 0x66ffff
|
|
BLUE = 0x0000FF
|
|
NAVY = 0x24248f
|
|
PURPLE = 0x660066
|
|
PINK = 0xFF66B3
|
|
WHITE = 0xFFFFFF
|
|
EXTRA = 0x888888
|
|
|
|
# Select the folder for the ABC files, only define one,
|
|
# the other line should have a # to comment it out
|
|
#SAMPLE_FOLDER = "/animals/"
|
|
SAMPLE_FOLDER = "/electronics/"
|
|
|
|
# This soundboard can select up to *32* sound clips! each one has a filename
|
|
# which will be inside the SAMPLE_FOLDER above, and a *color* in a tuple ()
|
|
SAMPLES = [("A.wav", RED),
|
|
("B.wav", MAROON),
|
|
("C.wav", ORANGE),
|
|
("D.wav", YELLOW),
|
|
("E.wav", BROWN),
|
|
("F.wav", GREEN),
|
|
("G.wav", AQUA),
|
|
("H.wav", TEAL),
|
|
("I.wav", BLUE),
|
|
("J.wav", NAVY),
|
|
("K.wav", PURPLE),
|
|
("L.wav", PINK),
|
|
("M.wav", RED),
|
|
("N.wav", MAROON),
|
|
("O.wav", ORANGE),
|
|
("P.wav", YELLOW),
|
|
("Q.wav", BROWN),
|
|
("R.wav", GREEN),
|
|
("S.wav", AQUA),
|
|
("T.wav", TEAL),
|
|
("U.wav", BLUE),
|
|
("V.wav", NAVY),
|
|
("W.wav", PURPLE),
|
|
("X.wav", PINK),
|
|
("Y.wav", RED),
|
|
("Z.wav", MAROON),
|
|
("01.wav", EXTRA), # Keys beyond the 26 alphabetic keys
|
|
("02.wav", EXTRA),
|
|
("03.wav", EXTRA),
|
|
("04.wav", EXTRA),
|
|
("05.wav", EXTRA),
|
|
("06.wav", EXTRA)]
|
|
|
|
# For the intro, pick any number of colors to make a fancy gradient!
|
|
INTRO_SWIRL = [RED, GREEN, BLUE]
|
|
# The color for the pressed key
|
|
SELECTED_COLOR = 0x333300
|
|
|
|
PLAY_SAMPLES_ON_START = False # Will not play all the sounds on start
|
|
|
|
# Our keypad + NeoPixel driver
|
|
trellis = adafruit_trellism4.TrellisM4Express(rotation=0)
|
|
|
|
# Play the welcome wav (if its there)
|
|
with audioio.AudioOut(board.A1, right_channel=board.A0) as audio:
|
|
try:
|
|
f = open(SAMPLE_FOLDER+SAMPLES[27][0], "rb") # Use 02.wav as welcome
|
|
wave = audiocore.WaveFile(f)
|
|
audio.play(wave)
|
|
swirl = 0 # we'll swirl through the colors in the gradient
|
|
while audio.playing:
|
|
for i in range(32):
|
|
palette_index = ((swirl+i) % 32) / 32
|
|
color = fancy.palette_lookup(INTRO_SWIRL, palette_index)
|
|
# display it!
|
|
trellis.pixels[(i%8, i//8)] = color.pack()
|
|
swirl += 1
|
|
time.sleep(0.005)
|
|
f.close()
|
|
# just hold a moment
|
|
time.sleep(0.5)
|
|
except OSError:
|
|
# no biggie, they could have deleted it
|
|
pass
|
|
|
|
# Parse the first file to figure out what format it's in
|
|
channel_count = None
|
|
bits_per_sample = None
|
|
sample_rate = None
|
|
with open(SAMPLE_FOLDER+SAMPLES[0][0], "rb") as f:
|
|
wav = audiocore.WaveFile(f)
|
|
print("%d channels, %d bits per sample, %d Hz sample rate " %
|
|
(wav.channel_count, wav.bits_per_sample, wav.sample_rate))
|
|
|
|
# Audio playback object - we'll go with either mono or stereo depending on
|
|
# what we see in the first file
|
|
if wav.channel_count == 1:
|
|
audio = audioio.AudioOut(board.A1)
|
|
elif wav.channel_count == 2:
|
|
audio = audioio.AudioOut(board.A1, right_channel=board.A0)
|
|
else:
|
|
raise RuntimeError("Must be mono or stereo waves!")
|
|
|
|
# Turn on, maybe play all of the buttons
|
|
for i, v in enumerate(SAMPLES):
|
|
filename = SAMPLE_FOLDER+v[0]
|
|
try:
|
|
with open(filename, "rb") as f:
|
|
wav = audiocore.WaveFile(f)
|
|
print(filename,
|
|
"%d channels, %d bits per sample, %d Hz sample rate " %
|
|
(wav.channel_count, wav.bits_per_sample, wav.sample_rate))
|
|
if wav.channel_count != channel_count:
|
|
pass
|
|
if wav.bits_per_sample != bits_per_sample:
|
|
pass
|
|
if wav.sample_rate != sample_rate:
|
|
pass
|
|
trellis.pixels[(i%8, i//8)] = v[1]
|
|
if PLAY_SAMPLES_ON_START:
|
|
audio.play(wav)
|
|
while audio.playing:
|
|
pass
|
|
except OSError:
|
|
# File not found! skip to next
|
|
pass
|
|
|
|
def stop_playing_sample(playback_details):
|
|
print("playing: ", playback_details)
|
|
audio.stop()
|
|
trellis.pixels[playback_details['neopixel_location']] = playback_details['neopixel_color']
|
|
playback_details['file'].close()
|
|
playback_details['voice'] = None
|
|
|
|
current_press = set()
|
|
currently_playing = {'voice' : None}
|
|
last_samplenum = None
|
|
while True:
|
|
pressed = set(trellis.pressed_keys)
|
|
# if pressed:
|
|
# print("Pressed:", pressed)
|
|
|
|
just_pressed = pressed - current_press
|
|
just_released = current_press - pressed
|
|
|
|
# if just_pressed:
|
|
# print("Just pressed", just_pressed)
|
|
for down in just_pressed:
|
|
sample_num = down[1]*8 + down[0]
|
|
print(sample_num)
|
|
try:
|
|
filename = SAMPLE_FOLDER+SAMPLES[sample_num][0]
|
|
f = open(filename, "rb")
|
|
wav = audiocore.WaveFile(f)
|
|
|
|
# is something else playing? interrupt it!
|
|
if currently_playing['voice'] != None:
|
|
print("Interrupt")
|
|
stop_playing_sample(currently_playing)
|
|
|
|
trellis.pixels[down] = SELECTED_COLOR
|
|
audio.play(wav)
|
|
# voice, neopixel tuple, color, and sample, file handle
|
|
currently_playing = {
|
|
'voice': 0,
|
|
'neopixel_location': down,
|
|
'neopixel_color': SAMPLES[sample_num][1],
|
|
'sample_num': sample_num,
|
|
'file': f}
|
|
except OSError:
|
|
pass # File not found! skip to next
|
|
|
|
# check if any samples are done
|
|
if not audio.playing and currently_playing['voice'] != None:
|
|
stop_playing_sample(currently_playing)
|
|
|
|
time.sleep(0.01) # a little delay here helps avoid debounce annoyances
|
|
current_press = pressed
|