sequencer code, debouncer, and samples first commit
This commit is contained in:
parent
7af22472fc
commit
4ed8f3483d
6 changed files with 241 additions and 0 deletions
145
Disc_Step_Sequencer/Disc_Step_Sequencer.py
Normal file
145
Disc_Step_Sequencer/Disc_Step_Sequencer.py
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
"""
|
||||
Opto Mechanical Disc Step Sequencer from John Park's Workshop
|
||||
Crickit Feather M4 Express, Crickit FeatherWing, continuous servo,
|
||||
four reflection sensors, speaker
|
||||
|
||||
Adafruit invests time and resources providing this open source code.
|
||||
Please support Adafruit and open source hardware by purchasing
|
||||
products from Adafruit!
|
||||
|
||||
Written by Dave Astels for Adafruit Industries
|
||||
Copyright (c) 2018 Adafruit Industries
|
||||
Licensed under the MIT license.
|
||||
|
||||
All text above must be included in any redistribution.
|
||||
"""
|
||||
|
||||
import audioio
|
||||
import board
|
||||
from digitalio import DigitalInOut, Direction
|
||||
from adafruit_crickit import crickit
|
||||
from debouncer import Debouncer
|
||||
|
||||
# You get 4 samples, they must all have the same sample rate and must
|
||||
# all be mono or stereo (no mix-n-match!)
|
||||
# mixer info https://circuitpython.readthedocs.io/en/latest/shared-bindings/audioio/Mixer.html
|
||||
|
||||
VOICES = ["bd_tek.wav", "elec_hi_snare.wav", "ch_01.wav", "clap_01.wav"]
|
||||
# Parse the first file to figure out what format its in
|
||||
with open(VOICES[0], "rb") as f:
|
||||
wav = audioio.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.A0)
|
||||
elif wav.channel_count == 2:
|
||||
# audio = audioio.AudioOut(board.A0, right_channel=board.A0)
|
||||
audio = audioio.AudioOut(board.A0)
|
||||
else:
|
||||
raise RuntimeError("Must be mono or stereo waves!")
|
||||
mixer = audioio.Mixer(voice_count=4,
|
||||
sample_rate=wav.sample_rate,
|
||||
channel_count=wav.channel_count,
|
||||
bits_per_sample=wav.bits_per_sample,
|
||||
samples_signed=True)
|
||||
audio.play(mixer)
|
||||
|
||||
samples = []
|
||||
# Read the 4 wave files, convert to stereo samples, and store
|
||||
# (show load status on neopixels and play audio once loaded too!)
|
||||
for v in VOICES:
|
||||
wave_file = open(v, "rb")
|
||||
print(v)
|
||||
# OK we managed to open the wave OK
|
||||
sample = audioio.WaveFile(wave_file)
|
||||
# debug play back on load!
|
||||
mixer.play(sample, voice=0)
|
||||
while mixer.playing:
|
||||
pass
|
||||
samples.append(sample)
|
||||
|
||||
|
||||
led = DigitalInOut(board.D13)
|
||||
led.direction = Direction.OUTPUT
|
||||
|
||||
# For signal control, we'll chat directly with seesaw, use 'ss' to shorten typing!
|
||||
ss = crickit.seesaw
|
||||
|
||||
# define and set up inputs to use the debouncer
|
||||
def make_criket_signal_debouncer(pin): # create pin signal objects
|
||||
ss.pin_mode(pin, ss.INPUT_PULLUP)
|
||||
return Debouncer(lambda : ss.digital_read(pin))
|
||||
|
||||
# The IR sensors on are pullups, connect to ground to activate
|
||||
clock_pin = make_criket_signal_debouncer(crickit.SIGNAL1)
|
||||
voice_1_pin = make_criket_signal_debouncer(crickit.SIGNAL2)
|
||||
voice_2_pin = make_criket_signal_debouncer(crickit.SIGNAL3)
|
||||
voice_3_pin = make_criket_signal_debouncer(crickit.SIGNAL4)
|
||||
voice_4_pin = make_criket_signal_debouncer(crickit.SIGNAL5)
|
||||
# Crickit capacitive touch pads
|
||||
touch_1_pad = Debouncer(lambda: crickit.touch_1.value)
|
||||
touch_4_pad = Debouncer(lambda: crickit.touch_4.value)
|
||||
touch_2_3_pad = Debouncer(lambda: crickit.touch_2.value and crickit.touch_3.value)
|
||||
|
||||
crickit.continuous_servo_1.set_pulse_width_range(min_pulse=500, max_pulse=2500)
|
||||
speed = -0.04 #this is clockwise/forward at a moderate tempo
|
||||
|
||||
|
||||
def play_voice(vo):
|
||||
mixer.stop_voice(vo)
|
||||
mixer.play(samples[vo], voice=vo, loop=False)
|
||||
|
||||
while True:
|
||||
clock_pin.update() #debouncer at work
|
||||
voice_1_pin.update()
|
||||
voice_2_pin.update()
|
||||
voice_3_pin.update()
|
||||
voice_4_pin.update()
|
||||
touch_1_pad.update()
|
||||
touch_4_pad.update()
|
||||
touch_2_3_pad.update()
|
||||
|
||||
crickit.continuous_servo_1.throttle = speed # spin the disc at speed defined by touch pads
|
||||
|
||||
if clock_pin.fell: # sensor noticed change from white (reflection) to black (no reflection)
|
||||
# this means a clock tick has begun, time to check if any steps will play
|
||||
led.value = 0
|
||||
|
||||
if voice_1_pin.value: # a black step (no reflection) mark during clock tick, play a sound!
|
||||
led.value = 1 # light up LED when step is read
|
||||
# print('| .kick. | | | |')
|
||||
play_voice(0)
|
||||
|
||||
if voice_2_pin.value:
|
||||
led.value = 1
|
||||
# print('| | .snare. | | |')
|
||||
play_voice(1)
|
||||
|
||||
if voice_3_pin.value:
|
||||
led.value = 1
|
||||
# print('| | | .closed hat. | |')
|
||||
play_voice(2)
|
||||
|
||||
if voice_4_pin.value:
|
||||
led.value = 1
|
||||
# print('| | | | .clap. |')
|
||||
play_voice(3)
|
||||
|
||||
if touch_4_pad.rose: # speed it up
|
||||
speed -= 0.001
|
||||
# print("speed: %s" % speed)
|
||||
|
||||
if touch_1_pad.rose: # slow it down
|
||||
speed += 0.001
|
||||
# you can comment out the next two lines if you want to go backwards
|
||||
# however, the clock ticks may not register with the default template spacing
|
||||
if speed >= 0: # to prevent backwards
|
||||
speed = 0
|
||||
# print("speed: %s" % speed)
|
||||
|
||||
if touch_2_3_pad.rose: # stop the disc
|
||||
speed = 0
|
||||
# print("speed: %s" % speed)
|
||||
BIN
Disc_Step_Sequencer/ch_01.wav
Normal file
BIN
Disc_Step_Sequencer/ch_01.wav
Normal file
Binary file not shown.
BIN
Disc_Step_Sequencer/clap_01.wav
Normal file
BIN
Disc_Step_Sequencer/clap_01.wav
Normal file
Binary file not shown.
96
Disc_Step_Sequencer/debouncer.py
Normal file
96
Disc_Step_Sequencer/debouncer.py
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
"""
|
||||
GPIO Pin Debouncer
|
||||
|
||||
Adafruit invests time and resources providing this open source code.
|
||||
Please support Adafruit and open source hardware by purchasing
|
||||
products from Adafruit!
|
||||
|
||||
Written by Dave Astels for Adafruit Industries
|
||||
Copyright (c) 2018 Adafruit Industries
|
||||
Licensed under the MIT license.
|
||||
|
||||
All text above must be included in any redistribution.
|
||||
"""
|
||||
|
||||
import time
|
||||
import digitalio
|
||||
|
||||
class Debouncer(object):
|
||||
"""Debounce an input pin"""
|
||||
|
||||
DEBOUNCED_STATE = 0x01
|
||||
UNSTABLE_STATE = 0x02
|
||||
CHANGED_STATE = 0x04
|
||||
|
||||
|
||||
def __init__(self, pin_or_predicate, mode=digitalio.Pull.UP, interval=0.010):
|
||||
"""Make am instance.
|
||||
:param int/function pin_or_predicate: the pin (from board) to debounce
|
||||
:param int mode: digitalio.Pull.UP or .DOWN (default is no pull up/down)
|
||||
:param int interval: bounce threshold in seconds (default is 0.010, i.e. 10 milliseconds)
|
||||
"""
|
||||
self.state = 0x00
|
||||
if isinstance(pin_or_predicate, int):
|
||||
p = digitalio.DigitalInOut(pin_or_predicate)
|
||||
p.direction = digitalio.Direction.INPUT
|
||||
p.pull = mode
|
||||
self.f = lambda : p.value
|
||||
else:
|
||||
self.f = pin_or_predicate
|
||||
if self.f():
|
||||
self.__set_state(Debouncer.DEBOUNCED_STATE | Debouncer.UNSTABLE_STATE)
|
||||
self.previous_time = 0
|
||||
if interval is None:
|
||||
self.interval = 0.010
|
||||
else:
|
||||
self.interval = interval
|
||||
|
||||
|
||||
def __set_state(self, bits):
|
||||
self.state |= bits
|
||||
|
||||
|
||||
def __unset_state(self, bits):
|
||||
self.state &= ~bits
|
||||
|
||||
|
||||
def __toggle_state(self, bits):
|
||||
self.state ^= bits
|
||||
|
||||
|
||||
def __get_state(self, bits):
|
||||
return (self.state & bits) != 0
|
||||
|
||||
|
||||
def update(self):
|
||||
"""Update the debouncer state. Must be called before using any of the properties below"""
|
||||
now = time.monotonic()
|
||||
self.__unset_state(Debouncer.CHANGED_STATE)
|
||||
current_state = self.f()
|
||||
if current_state != self.__get_state(Debouncer.UNSTABLE_STATE):
|
||||
self.previous_time = now
|
||||
self.__toggle_state(Debouncer.UNSTABLE_STATE)
|
||||
else:
|
||||
if now - self.previous_time >= self.interval:
|
||||
if current_state != self.__get_state(Debouncer.DEBOUNCED_STATE):
|
||||
self.previous_time = now
|
||||
self.__toggle_state(Debouncer.DEBOUNCED_STATE)
|
||||
self.__set_state(Debouncer.CHANGED_STATE)
|
||||
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
"""Return the current debounced value of the input."""
|
||||
return self.__get_state(Debouncer.DEBOUNCED_STATE)
|
||||
|
||||
|
||||
@property
|
||||
def rose(self):
|
||||
"""Return whether the debounced input went from low to high at the most recent update."""
|
||||
return self.__get_state(self.DEBOUNCED_STATE) and self.__get_state(self.CHANGED_STATE)
|
||||
|
||||
|
||||
@property
|
||||
def fell(self):
|
||||
"""Return whether the debounced input went from high to low at the most recent update."""
|
||||
return (not self.__get_state(self.DEBOUNCED_STATE)) and self.__get_state(self.CHANGED_STATE)
|
||||
BIN
Disc_Step_Sequencer/fB_bd_tek.wav
Normal file
BIN
Disc_Step_Sequencer/fB_bd_tek.wav
Normal file
Binary file not shown.
BIN
Disc_Step_Sequencer/fB_elec_hi_snare.wav
Normal file
BIN
Disc_Step_Sequencer/fB_elec_hi_snare.wav
Normal file
Binary file not shown.
Loading…
Reference in a new issue