# 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