Skip to content

Commit b397a62

Browse files
Add code & sounds for Star Trek Kzinti props
1 parent 66869a0 commit b397a62

File tree

13 files changed

+135
-0
lines changed

13 files changed

+135
-0
lines changed

Trek_Kzinti_Prop/code.py

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
# SPDX-FileCopyrightText: 2021 Phil Burgess for Adafruit Industries
2+
#
3+
# SPDX-License-Identifier: MIT
4+
5+
"""
6+
KZINTI COSPLAY PROPS for Feather M4 Express. Same code can be used for
7+
the "talking computer" prop or the simpler "total conversion beam."
8+
It's essentially just a sound board and relies on a bit of acting flair
9+
from its operator: understand that the "mode selector" slider really
10+
just makes clicky noises (doesn't select actual modes), the keypad (if
11+
building the talking computer) plays or selects one of nine sounds, and
12+
the trigger either plays a zap-gun noise or the last-selected sound.
13+
14+
'pew.wav' derived from freesound.org/people/newlocknew/sounds/520056
15+
CC BY 3.0 creativecommons.org/licenses/by/3.0
16+
Other sounds via Adafruit, MIT license.
17+
"""
18+
19+
import board # For pin names
20+
import keypad # For talking computer buttons
21+
import pwmio # For LED flicker
22+
from analogio import AnalogIn # For slider potentiometer
23+
from audiocore import WaveFile # For WAV file handling
24+
25+
# audioio is present on boards w/DAC out. If not available, fall back on
26+
# audiopwmio. If neither is supported, code stops w/ImportError exception.
27+
try:
28+
from audioio import AudioOut
29+
except ImportError:
30+
from audiopwmio import PWMAudioOut as AudioOut
31+
32+
33+
# CONFIGURABLES -----
34+
35+
# If building the talking computer: setting this True makes the keypad
36+
# buttons play sounds when pressed. If False, keypad buttons select but
37+
# do not play sounds -- that's done with the trigger button.
38+
buttons_play = True
39+
40+
sound_folder = "/sounds" # Location of WAV files
41+
num_modes = 5 # Number of clicks from slider, plus one
42+
pin_to_wave = ( # This table maps input pins to corresponding WAV files:
43+
(board.A1, "pew.wav"), # Trigger on handle
44+
(board.D4, "1.wav"), # 9 buttons on top (if building
45+
(board.D12, "2.wav"), # talking computer, else ignored)
46+
(board.D11, "3.wav"),
47+
(board.D10, "4.wav"),
48+
(board.D9, "5.wav"),
49+
(board.D6, "6.wav"),
50+
(board.D5, "7.wav"),
51+
(board.SCL, "8.wav"),
52+
(board.SDA, "9.wav"),
53+
) # Tip: avoid pin D13 for keypad input; LED sometimes interferes.
54+
55+
# HARDWARE SETUP ---- mode selector, LED, speaker, keypad ----
56+
57+
analog_in = AnalogIn(board.A2) # Slider for "mode selector"
58+
mode = (analog_in.value * (num_modes - 1) + 32768) // 65536 # Initial mode
59+
bounds = ( # Lower, upper limit to detect change from current mode
60+
(mode * 65535 - 32768) // (num_modes - 1) - 512,
61+
(mode * 65535 + 32768) // (num_modes - 1) + 512,
62+
)
63+
64+
led = pwmio.PWMOut(board.A3)
65+
led.duty_cycle = 0 # Start w/LED off
66+
led_sync = 0 # LED behavior for different sounds, see comments later
67+
68+
# AudioOut MUST be invoked AFTER PWMOut, for correct WAV playback timing.
69+
# Maybe sharing a timer or IRQ. Unsure if bug or just overlooked docs.
70+
audio = AudioOut(board.A0) # A0 is DAC pin on M0/M4 boards
71+
72+
# To simplify the build, each key is wired to a separate input pin rather
73+
# than making an X/Y matrix. CircuitPython's keypad module is still used
74+
# (treating the buttons as a 1x10 matrix) as this gives us niceties such
75+
# as background processing, debouncing and an event queue!
76+
keys = keypad.Keys([x[0] for x in pin_to_wave], value_when_pressed=False, pull=True)
77+
event = keypad.Event() # Single key event for re-use
78+
keys.events.clear()
79+
80+
# Load all the WAV files from the pin_to_wave list, and one more for the
81+
# mode selector, sharing a common buffer since only one is used at a time.
82+
# Also, play a startup sound.
83+
audio_buf = bytearray(1024)
84+
waves = [
85+
WaveFile(open(sound_folder + "/" + x[1], "rb"), audio_buf) for x in pin_to_wave
86+
]
87+
active_sound = 0 # Index of waves[] to play when trigger is pressed
88+
selector_wave = WaveFile(open(sound_folder + "/" + "click.wav", "rb"), audio_buf)
89+
audio.play(WaveFile(open(sound_folder + "/" + "startup.wav", "rb"), audio_buf))
90+
91+
# MAIN LOOP --------- repeat forever ----
92+
93+
while True:
94+
95+
# Process the mode selector slider, check if moved into a new position.
96+
# This is currently just used to make click noises, it doesn't actually
97+
# change any "mode" in the operation of the prop, but it could if we
98+
# really wanted, with additional code (e.g. different sound sets).
99+
selector_pos = analog_in.value
100+
if not bounds[0] < selector_pos < bounds[1]: # Moved out of mode range?
101+
# New mode, new bounds. +/-512 adds a little hysteresis to selection.
102+
mode = (selector_pos * (num_modes - 1) + 32768) // 65536
103+
bounds = (
104+
(mode * 65535 - 32768) // (num_modes - 1) - 512,
105+
(mode * 65535 + 32768) // (num_modes - 1) + 512,
106+
)
107+
led_sync = 0 # LED stays off for selector sound
108+
audio.play(selector_wave) # Make click sound
109+
110+
# Process keypad input. If building the "total conversion beam,"
111+
# only the trigger button is wired up, the rest simply ignored.
112+
if keys.events.get_into(event) and event.pressed:
113+
if event.key_number == 0: # Trigger button
114+
# LED is steady for zap gun (index 0), flickers for other sounds
115+
led_sync = 1 if active_sound else 2
116+
audio.play(waves[active_sound])
117+
elif buttons_play: # Other buttons, play immediately
118+
led_sync = 1
119+
audio.play(waves[event.key_number])
120+
else: # Other buttons, select but don't play
121+
# Once another sound is selected, no going back to the zap.
122+
active_sound = event.key_number
123+
led_sync = 0 # Don't blink during selector sound
124+
audio.play(selector_wave)
125+
126+
# LED is continually updated. If sound playing, and led_sync set above...
127+
if audio.playing and led_sync > 0:
128+
# Trigger button sound is steady on. For others, peek inside the
129+
# WAV audio buffer, this provides a passable voice-to-LED flicker.
130+
if led_sync == 2:
131+
led.duty_cycle = 65535
132+
else:
133+
led.duty_cycle = 65535 - abs(audio_buf[1] - 128) * 65535 // 128
134+
else: # No sound, or is just selector clicks (no LED)
135+
led.duty_cycle = 0

Trek_Kzinti_Prop/sounds/1.wav

101 KB
Binary file not shown.

Trek_Kzinti_Prop/sounds/2.wav

112 KB
Binary file not shown.

Trek_Kzinti_Prop/sounds/3.wav

180 KB
Binary file not shown.

Trek_Kzinti_Prop/sounds/4.wav

160 KB
Binary file not shown.

Trek_Kzinti_Prop/sounds/5.wav

103 KB
Binary file not shown.

Trek_Kzinti_Prop/sounds/6.wav

132 KB
Binary file not shown.

Trek_Kzinti_Prop/sounds/7.wav

22.5 KB
Binary file not shown.

Trek_Kzinti_Prop/sounds/8.wav

17.1 KB
Binary file not shown.

Trek_Kzinti_Prop/sounds/9.wav

55 KB
Binary file not shown.

Trek_Kzinti_Prop/sounds/click.wav

500 Bytes
Binary file not shown.

Trek_Kzinti_Prop/sounds/pew.wav

17.5 KB
Binary file not shown.

Trek_Kzinti_Prop/sounds/startup.wav

126 KB
Binary file not shown.

0 commit comments

Comments
 (0)