From f799a7ef31bc12e9ff8d7faeb58f699582a44e40 Mon Sep 17 00:00:00 2001 From: sandyjmacdonald Date: Thu, 11 Mar 2021 20:43:13 +0000 Subject: [PATCH] Improving MIDI arpeggiator. LEDs show current note now. Documentation. --- examples/colour-picker.py | 2 + examples/decorators.py | 2 + examples/midi-arp.py | 81 +++++++++++++++++++++++++++----------- examples/midi-keys.py | 2 + examples/rainbow.py | 2 + examples/reactive-press.py | 2 + keybow2040.py | 1 + 7 files changed, 70 insertions(+), 22 deletions(-) diff --git a/examples/colour-picker.py b/examples/colour-picker.py index 42d2b5e..5f1a826 100644 --- a/examples/colour-picker.py +++ b/examples/colour-picker.py @@ -5,6 +5,8 @@ # This example demonstrates the use of a modifier key to pick the colour of the # keys' LEDs, as well as the LED sleep functionality. +# Drop the keybow2040.py file into your `lib` folder on your `CIRCUITPY` drive. + import time import board import random diff --git a/examples/decorators.py b/examples/decorators.py index df2c916..091b32e 100644 --- a/examples/decorators.py +++ b/examples/decorators.py @@ -5,6 +5,8 @@ # This example demonstrates attaching functions to keys using decorators, and # the ability to turn the LEDs off with led_sleep_enabled and led_sleep_time. +# Drop the keybow2040.py file into your `lib` folder on your `CIRCUITPY` drive. + import time import board import random diff --git a/examples/midi-arp.py b/examples/midi-arp.py index 1fcec41..1d39594 100644 --- a/examples/midi-arp.py +++ b/examples/midi-arp.py @@ -2,12 +2,15 @@ # # SPDX-License-Identifier: MIT -# Demonstrates how to send MIDI notes by attaching handler functions to key -# presses with decorators. +# A MIDI arpeggiator, with three different styles: up, down, or up-down. BPM and +# note length are both configurable, and LEDs cycle with the currently-played +# key/note to give some visual feedback. # You'll need to connect Keybow 2040 to a computer running a DAW like Ableton, # or other software synth, or to a hardware synth that accepts USB MIDI. +# Drop the keybow2040.py file into your `lib` folder on your `CIRCUITPY` drive. + # NOTE! Requires the adafruit_midi CircuitPython library! import time @@ -28,90 +31,124 @@ keys = keybow.keys # Set USB MIDI up on channel 0. midi = adafruit_midi.MIDI(midi_out=usb_midi.ports[1], out_channel=5) -# The colour to set the keys when pressed. +# The colour to set the keys when pressed, orange-y. rgb = (255, 50, 0) # MIDI velocity. start_note = 60 -velocity = 127 +velocity = 100 + +# Beats per minute +bpm = 120 + +# Play 16th notes +note_length = 1/16 + +# Assumes BPM is calculated on quarter notes +note_time = 60 / bpm * (note_length * 4) # Arpeggio style: # 0 = up # 1 = down -# 2 = up down +# 2 = up-down arp_style = 2 +# Start the arp in a forwards direction (1) if the style is up or up-down, or +# or backwards (-1) if the style is down. if arp_style == 0 or arp_style == 2: direction = 1 elif arp_style == 1: direction = -1 +# Keep track of time of last note played and last keys pressed last_played = None last_pressed = [] -speed = 5 - -# Loop through keys and attach decorators. -for key in keys: - # If pressed, turn on LED. - @keybow.on_press(key) - def press_handler(key): - key.set_led(*rgb) - - # If released, turn off LED. - @keybow.on_release(key) - def release_handler(key): - key.set_led(0, 0, 0) while True: # Always remember to call keybow.update()! keybow.update() + # If any keys are pressed, go through shenanigans if keybow.any_pressed(): + # Fetch a list of pressed keys pressed = keybow.get_pressed() + # If the keys pressed have changed... if pressed != last_pressed: + # Keys that were pressed, but are no longer + missing = [k for k in last_pressed if k not in pressed] + + # Any keys that were pressed, but are no longer, turn LED off + # and send MIDI note off for the respective note. + for k in missing: + note = start_note +k + midi.send(NoteOff(note, 0)) + keys[k].set_led(0, 0, 0) + + # Calculate MIDI note numbers notes = [start_note + k for k in pressed] last_pressed = pressed + # If going forward (up or starting up-down), start at 0, + # otherwise start at the end of the list of notes. if arp_style == 0 or arp_style == 2: this_note = 0 elif arp_style == 1: this_note = len(notes) - 1 + # Send MIDI note on message for current note and turn LED on midi.send(NoteOn(notes[this_note], velocity)) - print(notes[this_note]) + keys[pressed[this_note]].set_led(*rgb) + # Update last_played time, set elapsed to 0, and update current and + # last note indices. last_played = time.monotonic() elapsed = 0 last_note = this_note this_note += direction + # If the currently pressed notes are the same as the last loop, then... else: if notes != []: + # Check time elapsed since last note played elapsed = time.monotonic() - last_played - if elapsed > 1 / speed: + # If the note time has elapsed, then... + if elapsed > note_time: + # Reset at the end or start of the notes list if this_note == len(notes) and direction == 1: this_note = 0 elif this_note < 0: this_note = len(notes) - 1 + # Send a MIDI note off for the last note, turn off LED midi.send(NoteOff(notes[last_note], 0)) - midi.send(NoteOn(notes[this_note], velocity)) - print(notes[this_note]) + keys[pressed[last_note]].set_led(0, 0, 0) + # Send a MIDI note on for the next note, turn on LED + midi.send(NoteOn(notes[this_note], velocity)) + keys[pressed[this_note]].set_led(*rgb) + + # Update time last_played, make this note last note last_played = time.monotonic() last_note = this_note + # For the up-down style, switch direction at either end if arp_style == 2 and this_note == len(notes) -1: direction = -1 elif arp_style == 2 and this_note == 0: direction = 1 + # Increment note this_note += direction + # If nothing is now pressed, but was last time, then send MIDI note off + # for every note, and turn all the LEDs off. elif len(last_pressed) and keybow.none_pressed(): for note in range(128): midi.send(NoteOff(note, 0)) + for key in keys: + key.set_led(0, 0, 0) + # Nothing is pressed, so reset last_pressed list last_pressed = [] \ No newline at end of file diff --git a/examples/midi-keys.py b/examples/midi-keys.py index e47e38a..44fc56e 100644 --- a/examples/midi-keys.py +++ b/examples/midi-keys.py @@ -8,6 +8,8 @@ # You'll need to connect Keybow 2040 to a computer running a DAW like Ableton, # or other software synth, or to a hardware synth that accepts USB MIDI. +# Drop the keybow2040.py file into your `lib` folder on your `CIRCUITPY` drive. + # NOTE! Requires the adafruit_midi CircuitPython library! import time diff --git a/examples/rainbow.py b/examples/rainbow.py index 461ab8d..b9fda21 100644 --- a/examples/rainbow.py +++ b/examples/rainbow.py @@ -4,6 +4,8 @@ # This example displays a rainbow animation on Keybow 2040's keys. +# Drop the keybow2040.py file into your `lib` folder on your `CIRCUITPY` drive. + import time import math import board diff --git a/examples/reactive-press.py b/examples/reactive-press.py index b1b15c6..26a0c1d 100644 --- a/examples/reactive-press.py +++ b/examples/reactive-press.py @@ -4,6 +4,8 @@ # This example demonstrates how to light keys when pressed. +# Drop the keybow2040.py file into your `lib` folder on your `CIRCUITPY` drive. + import board from keybow2040 import Keybow2040 diff --git a/keybow2040.py b/keybow2040.py index 877872f..b68264b 100755 --- a/keybow2040.py +++ b/keybow2040.py @@ -8,6 +8,7 @@ CircuitPython driver for the Pimoroni Keybow 2040. +Drop the keybow2040.py file into your `lib` folder on your `CIRCUITPY` drive. * Author: Sandy Macdonald