diff --git a/Disc_Step_Sequencer/Disc_Step_Sequencer.py b/Disc_Step_Sequencer/Disc_Step_Sequencer.py new file mode 100644 index 00000000..51229f6f --- /dev/null +++ b/Disc_Step_Sequencer/Disc_Step_Sequencer.py @@ -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) diff --git a/Disc_Step_Sequencer/ch_01.wav b/Disc_Step_Sequencer/ch_01.wav new file mode 100644 index 00000000..dc16722f Binary files /dev/null and b/Disc_Step_Sequencer/ch_01.wav differ diff --git a/Disc_Step_Sequencer/clap_01.wav b/Disc_Step_Sequencer/clap_01.wav new file mode 100644 index 00000000..59ae55fb Binary files /dev/null and b/Disc_Step_Sequencer/clap_01.wav differ diff --git a/Disc_Step_Sequencer/debouncer.py b/Disc_Step_Sequencer/debouncer.py new file mode 100644 index 00000000..b65083df --- /dev/null +++ b/Disc_Step_Sequencer/debouncer.py @@ -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) diff --git a/Disc_Step_Sequencer/fB_bd_tek.wav b/Disc_Step_Sequencer/fB_bd_tek.wav new file mode 100644 index 00000000..89cea991 Binary files /dev/null and b/Disc_Step_Sequencer/fB_bd_tek.wav differ diff --git a/Disc_Step_Sequencer/fB_elec_hi_snare.wav b/Disc_Step_Sequencer/fB_elec_hi_snare.wav new file mode 100644 index 00000000..9dcb7450 Binary files /dev/null and b/Disc_Step_Sequencer/fB_elec_hi_snare.wav differ