From cb33b7a0b7d69c56b675506b0bd5248db822a273 Mon Sep 17 00:00:00 2001 From: John Park Date: Mon, 19 Dec 2022 21:41:06 -0800 Subject: [PATCH] first commit drum trigger code --- Drum_Trigger_2040/code.py | 224 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 224 insertions(+) create mode 100644 Drum_Trigger_2040/code.py diff --git a/Drum_Trigger_2040/code.py b/Drum_Trigger_2040/code.py new file mode 100644 index 000000000..12f42c44b --- /dev/null +++ b/Drum_Trigger_2040/code.py @@ -0,0 +1,224 @@ +# SPDX-FileCopyrightText: 2022 John Park for Adafruit Industries +# +# SPDX-License-Identifier: MIT +# Drum Trigger Sequencer 2040 +# Based on code by Tod Kurt @todbot https://github.com/todbot/picostepseq + +# Uses General MIDI drum notes on channel 10 +# Range is note 35/B0 - 81/A4, but classic 808 set is defined here + +import time +from adafruit_ticks import ticks_ms, ticks_diff, ticks_add +import board +from digitalio import DigitalInOut, Pull +import keypad +import adafruit_aw9523 +import usb_midi +from adafruit_seesaw import seesaw, rotaryio, digitalio +from adafruit_debouncer import Debouncer +from adafruit_ht16k33 import segments + + +# define I2C +i2c = board.STEMMA_I2C() + +num_steps = 16 # number of steps/switches +num_drums = 11 # primary 808 drums used here, but you can use however many you like +# Beat timing assumes 4/4 time signature, e.g. 4 beats per measure, 1/4 note gets the beat +bpm = 120 # default BPM +beat_time = 60/bpm # time length of a single beat +beat_millis = beat_time * 1000 # time length of single beat in milliseconds +steps_per_beat = 4 # subdivide beats down to to 16th notes +steps_millis = beat_millis / steps_per_beat # time length of a beat subdivision, e.g. 1/16th note + +step_counter = 0 # goes from 0 to length of sequence - 1 +sequence_length = 16 # how many notes stored in a sequence +curr_drum = 0 +playing = False + +# Setup button +start_button_in = DigitalInOut(board.A2) +start_button_in.pull = Pull.UP +start_button = Debouncer(start_button_in) + + +# Setup switches +switch_pins = ( + board.TX, board.RX, board.D2, board.D3, + board.D4, board.D5, board.D6, board.D7, + board.D8, board.D9, board.D10, board.MOSI, + board.MISO, board.SCK, board.A0, board.A1 +) +switches = keypad.Keys(switch_pins, value_when_pressed=False, pull=True) + +# Setup LEDs +leds = adafruit_aw9523.AW9523(i2c, address=0x5B) # both jumperes soldered on board +for led in range(num_steps): # turn them off + leds.set_constant_current(led, 0) +leds.LED_modes = 0xFFFF # constant current mode +leds.directions = 0xFFFF # output + +# Values for LED brightness 0-255 +offled = 0 +dimled = 2 +midled = 20 +highled = 150 + +for led in range(num_steps): # dramatic boot up light sequence + leds.set_constant_current(led, dimled) + time.sleep(0.05) +time.sleep(0.5) +# +# STEMMA QT Rotary encoder setup +rotary_seesaw = seesaw.Seesaw(i2c, addr=0x36) # default address is 0x36 +encoder = rotaryio.IncrementalEncoder(rotary_seesaw) +last_encoder_pos = 0 +rotary_seesaw.pin_mode(24, rotary_seesaw.INPUT_PULLUP) # setup the button pin +knobbutton_in = digitalio.DigitalIO(rotary_seesaw, 24) # use seesaw digitalio +knobbutton = Debouncer(knobbutton_in) # create debouncer object for button +encoder_pos = -encoder.position + +# MIDI setup +midi = usb_midi.ports[1] + +drum_names = [ + "Bass", "Snar", "LTom", "MTom", "HTom", + "Clav", "Clap", "Cowb", "Cymb", "OHat", "CHat" +] +drum_notes = [36, 38, 41, 43, 45, 37, 39, 56, 49, 46, 42] # general midi drum notes matched to 808 + +# default starting sequence needs to match number of drums in num_drums +sequence = [ + [ 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0 ], # bass drum + [ 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0 ], # snare + [ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0 ], # low tom + [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0 ], # mid tom + [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 ], # high tom + [ 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], # rimshot/claves + [ 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0 ], # handclap/maracas + [ 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0 ], # cowbell + [ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], # cymbal + [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0 ], # hihat open + [ 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0 ] # hihat closed +] + +def play_drum(note): + midi_msg_on = bytearray([0x99, note, 120]) # 0x90 is noteon ch 1, 0x99 is noteon ch 10 + midi_msg_off = bytearray([0x89, note, 0]) + midi.write(midi_msg_on) + midi.write(midi_msg_off) + +def light_steps(step, state): + if state: + leds.set_constant_current(step, midled) + else: + leds.set_constant_current(step, offled) + +def light_beat(step): + leds.set_constant_current(step, highled) + +def edit_mode_toggle(): + # pylint: disable=global-statement + global edit_mode + # pylint: disable=used-before-assignment + edit_mode = (edit_mode + 1) % num_modes + display.fill(0) + if edit_mode == 0: + display.print(bpm) + elif edit_mode == 1: + display.print(drum_names[curr_drum]) + +def print_sequence(): + print("sequence = [ ") + for k in range(num_drums): + print(" [" + ",".join('1' if e else '0' for e in sequence[k]) + "], #", drum_names[k]) + print("]") + +# set the leds +for j in range(sequence_length): + light_steps(j, sequence[curr_drum][j]) + +display = segments.Seg14x4(i2c, address=(0x71)) +display.brightness = 0.3 +display.fill(0) +display.show() +display.print(bpm) +display.show() + +edit_mode = 0 # 0=bpm, 1=voices +num_modes = 2 + +print("Drum Trigger 2040") + + +display.fill(0) +display.show() +display.marquee("Drum", 0.05, loop=False) +time.sleep(0.5) +display.marquee("Trigger", 0.075, loop=False) +time.sleep(0.5) +display.marquee("2040", 0.05, loop=False) +time.sleep(1) +display.marquee("BPM", 0.05, loop=False) +time.sleep(0.75) +display.marquee(str(bpm), 0.1, loop=False) + + +while True: + start_button.update() + if start_button.fell: # pushed encoder button plays/stops transport + if playing is True: + print_sequence() + playing = not playing + step_counter = 0 + last_step = int(ticks_add(ticks_ms(), -steps_millis)) + print("*** Play:", playing) + + if playing: + now = ticks_ms() + diff = ticks_diff(now, last_step) + if diff >= steps_millis: + late_time = ticks_diff(int(diff), int(steps_millis)) + last_step = ticks_add(now, - late_time//2) + + light_beat(step_counter) # brighten current step + for i in range(num_drums): + if sequence[i][step_counter]: # if there's a 1 at the step for the seq, play it + play_drum(drum_notes[i]) + light_steps(step_counter, sequence[curr_drum][step_counter]) # return led to step value + step_counter = (step_counter + 1) % sequence_length + encoder_pos = -encoder.position # only check encoder while playing between steps + knobbutton.update() + if knobbutton.fell: + edit_mode_toggle() + else: # check the encoder all the time when not playing + encoder_pos = -encoder.position + knobbutton.update() + if knobbutton.fell: # change edit mode, refresh display + edit_mode_toggle() + + # switches add or remove steps + switch = switches.events.get() + if switch: + if switch.pressed: + i = switch.key_number + sequence[curr_drum][i] = not sequence[curr_drum][i] # toggle step + light_steps(i, sequence[curr_drum][i]) # toggle light + + if encoder_pos != last_encoder_pos: + encoder_delta = encoder_pos - last_encoder_pos + if edit_mode == 0: + bpm = bpm + encoder_delta # or (encoder_delta * 5) + bpm = min(max(bpm, 10), 400) + beat_time = 60/bpm # time length of a single beat + beat_millis = beat_time * 1000 + steps_millis = beat_millis / steps_per_beat + display.fill(0) + display.print(bpm) + if edit_mode == 1: + curr_drum = (curr_drum + encoder_delta) % num_drums + # quickly set the step leds + for i in range(sequence_length): + light_steps(i, sequence[curr_drum][i]) + display.print(drum_names[curr_drum]) + last_encoder_pos = encoder_pos