diff --git a/README.md b/README.md index 5413630..e27bcd9 100644 --- a/README.md +++ b/README.md @@ -54,13 +54,13 @@ changes, then it should load up and run the code! * [LED sleep](#led-sleep) * [Attaching functions to keys with decorators](#attaching-functions-to-keys-with-decorators) * [Key combos](#key-combos) -* [USB MIDI](#usb-midi) - * [Setup](#setup) - * [Sending MIDI notes](#sending-midi-notes) * [USB HID](#usb-hid) - * [Setup](#setup-1) + * [Setup](#setup) * [Sending key presses](#sending-key-presses) * [Sending strings of text](#sending-strings-of-text) +* [USB MIDI](#usb-midi) + * [Setup](#setup-1) + * [Sending MIDI notes](#sending-midi-notes) # Library functionality @@ -368,74 +368,6 @@ held and a third to be pressed, and so on... The [colour-picker.py example](examples/colour-picker.py) has an example of using a modifier key to change the hue of the keys. -# USB MIDI - -This covers basic MIDI note messages and how to link them to key presses. - -## Setup - -USB MIDI requires the `adafruit_midi` CircuitPython library. Download it from -the link below and then drop the `adafruit_midi` folder into the `lib` folder on -your `CIRCUITPY` drive. - -[Download the Adafruit MIDI CircuitPython library](https://github.com/adafruit/Adafruit_CircuitPython_MIDI) - -You'll need to connect your Keybow 2040 with a USB cable to a computer running a -software synth or DAW like Ableton Live, to a hardware synth that accepts USB -MIDI, or through a MIDI interface that will convert the USB MIDI messages to -regular serial MIDI through a DIN connector. - -Using USB MIDI, Keybow 2040 shows up as a device with the name -`Keybow 2040 (CircuitPython usb midi.ports[1])` - -In my testing, Keybow 2040 works with the Teenage Engineering OP-Z quite nicely. - -## Sending MIDI notes - -Here's a complete, minimal example of how to send a single MIDI note (middle C, -or MIDI note number 60) when key 0 is pressed, sending a note on message when -pressed and a note off message when released. - -``` -import board -from keybow2040 import Keybow2040 - -import usb_midi -import adafruit_midi -from adafruit_midi.note_off import NoteOff -from adafruit_midi.note_on import NoteOn - -i2c = board.I2C() -keybow = Keybow2040(i2c) -keys = keybow.keys - -midi = adafruit_midi.MIDI(midi_out=usb_midi.ports[1], out_channel=0) - -key = keys[0] -note = 60 -velocity = 127 - -was_pressed = False - -while True: - keybow.update() - - if key.pressed: - midi.send(NoteOn(note, velocity)) - was_pressed = True - elif not key.pressed and was_pressed: - midi.send(NoteOff(note, 0)) - was_pressed = False -``` - -There'a more complete example of how to set up all of Keybow's keys with -associated MIDI notes using decorators in the -[midi-keys.py example](examples/midi-keys.py). - -The example above, and the `midi-keys.py` example both send notes on MIDI -channel 0 (all channels), but you can set this to a specific channel, if you -like, by changing `out_channel=` when you instantiate your `midi` object. - # USB HID This covers setting up a USB HID keyboard and linking physical key presses to @@ -535,7 +467,7 @@ while True: ``` This code is available in the -hid-keys-simple.py example](examples/hid-keys-simple.py). +[hid-keys-simple.py example](examples/hid-keys-simple.py). As well as sending a single keypress, you can send multiple keypresses at once, simply by adding them as additional argumemnts to `keyboard.send()`, e.g. @@ -582,4 +514,72 @@ and then check against a `time_elapsed` variable created with Also, be aware that the Adafruit HID CircuitPython library only currently supports US Keyboard layouts, so you'll have to work around that and map any -keycodes that differ from their US counterpart to whatever your is. \ No newline at end of file +keycodes that differ from their US counterpart to whatever your is. + +# USB MIDI + +This covers basic MIDI note messages and how to link them to key presses. + +## Setup + +USB MIDI requires the `adafruit_midi` CircuitPython library. Download it from +the link below and then drop the `adafruit_midi` folder into the `lib` folder on +your `CIRCUITPY` drive. + +[Download the Adafruit MIDI CircuitPython library](https://github.com/adafruit/Adafruit_CircuitPython_MIDI) + +You'll need to connect your Keybow 2040 with a USB cable to a computer running a +software synth or DAW like Ableton Live, to a hardware synth that accepts USB +MIDI, or through a MIDI interface that will convert the USB MIDI messages to +regular serial MIDI through a DIN connector. + +Using USB MIDI, Keybow 2040 shows up as a device with the name +`Keybow 2040 (CircuitPython usb midi.ports[1])` + +In my testing, Keybow 2040 works with the Teenage Engineering OP-Z quite nicely. + +## Sending MIDI notes + +Here's a complete, minimal example of how to send a single MIDI note (middle C, +or MIDI note number 60) when key 0 is pressed, sending a note on message when +pressed and a note off message when released. + +``` +import board +from keybow2040 import Keybow2040 + +import usb_midi +import adafruit_midi +from adafruit_midi.note_off import NoteOff +from adafruit_midi.note_on import NoteOn + +i2c = board.I2C() +keybow = Keybow2040(i2c) +keys = keybow.keys + +midi = adafruit_midi.MIDI(midi_out=usb_midi.ports[1], out_channel=0) + +key = keys[0] +note = 60 +velocity = 127 + +was_pressed = False + +while True: + keybow.update() + + if key.pressed: + midi.send(NoteOn(note, velocity)) + was_pressed = True + elif not key.pressed and was_pressed: + midi.send(NoteOff(note, 0)) + was_pressed = False +``` + +There'a more complete example of how to set up all of Keybow's keys with +associated MIDI notes using decorators in the +[midi-keys.py example](examples/midi-keys.py). + +The example above, and the `midi-keys.py` example both send notes on MIDI +channel 0 (all channels), but you can set this to a specific channel, if you +like, by changing `out_channel=` when you instantiate your `midi` object. \ No newline at end of file diff --git a/examples/hid-keys-advanced.py b/examples/hid-keys-advanced.py new file mode 100644 index 0000000..e2fa2c6 --- /dev/null +++ b/examples/hid-keys-advanced.py @@ -0,0 +1,167 @@ +# SPDX-FileCopyrightText: 2021 Sandy Macdonald +# +# SPDX-License-Identifier: MIT + +# An advanced example of how to set up a HID keyboard. + +# There are three layers, selected by pressing and holding key 0 (bottom left), +# then tapping one of the coloured layer selector keys above it to switch layer. + +# The layer colours are as follows: + +# * layer 1: pink: numpad-style keys, 0-9, delete, and enter. +# * layer 2: blue: sends strings on each key press +# * layer 3: media controls, rev, play/pause, fwd on row one, vol. down, mute, +# vol. up on row two + +# You'll need to connect Keybow 2040 to a computer, as you would with a regular +# USB keyboard. + +# Drop the keybow2040.py file into your `lib` folder on your `CIRCUITPY` drive. + +# NOTE! Requires the adafruit_hid CircuitPython library also! + +import board +import time +from keybow2040 import Keybow2040 + +import usb_hid +from adafruit_hid.keyboard import Keyboard +from adafruit_hid.keyboard_layout_us import KeyboardLayoutUS +from adafruit_hid.keycode import Keycode + +from adafruit_hid.consumer_control import ConsumerControl +from adafruit_hid.consumer_control_code import ConsumerControlCode + +# Set up Keybow +i2c = board.I2C() +keybow = Keybow2040(i2c) +keys = keybow.keys + +# Set up the keyboard and layout +keyboard = Keyboard(usb_hid.devices) +layout = KeyboardLayoutUS(keyboard) + +# Set up consumer control (used to send media key presses) +consumer_control = ConsumerControl(usb_hid.devices) + +# Our layers. The key of item in the layer dictionary is the key number on +# Keybow to map to, and the value is the key press to send. + +# Note that keys 0-3 are reserved as the modifier and layer selector keys +# respectively. + +layer_1 = {4: Keycode.ZERO, + 5: Keycode.ONE, + 6: Keycode.FOUR, + 7: Keycode.SEVEN, + 8: Keycode.DELETE, + 9: Keycode.TWO, + 10: Keycode.FIVE, + 11: Keycode.EIGHT, + 12: Keycode.ENTER, + 13: Keycode.THREE, + 14: Keycode.SIX, + 15: Keycode.NINE} + +layer_2 = {7: "pack ", + 11: "my ", + 15: "box ", + 6: "with ", + 10: "five ", + 14: "dozen ", + 5: "liquor ", + 9: "jugs "} + +layer_3 = {6: ConsumerControlCode.VOLUME_DECREMENT, + 7: ConsumerControlCode.SCAN_PREVIOUS_TRACK, + 10: ConsumerControlCode.MUTE, + 11: ConsumerControlCode.PLAY_PAUSE, + 14: ConsumerControlCode.VOLUME_INCREMENT, + 15: ConsumerControlCode.SCAN_NEXT_TRACK} + +layers = {1: layer_1, + 2: layer_2, + 3: layer_3} + +# Define the modifier key and layer selector keys +modifier = keys[0] + +selectors = {1: keys[1], + 2: keys[2], + 3: keys[3]} + +# Start on layer 1 +current_layer = 1 + +# The colours for each layer +colours = {1: (255, 0, 255), + 2: (0, 255, 255), + 3: (255, 255, 0)} + +layer_keys = range(4, 16) + +# Set the LEDs for each key in the current layer +for k in layers[current_layer].keys(): + keys[k].set_led(*colours[current_layer]) + +# To prevent the strings (as opposed to single key presses) that are sent from +# refiring on a single key press, the debounce time for the strings has to be +# longer. +short_debounce = 0.025 +long_debounce = 0.25 +debounce = 0.025 +fired = False + +while True: + # Always remember to call keybow.update()! + keybow.update() + + # This handles the modifier and layer selector behaviour + if modifier.held: + # If the modifier key is held, light up the layer selector keys + for layer in layers.keys(): + keys[layer].set_led(*colours[layer]) + + # Change layer if layer key is pressed + if current_layer != layer: + if selectors[layer].pressed: + current_layer = layer + + # Set the key LEDs first to off, then to their layer colour + for k in layer_keys: + keys[k].set_led(0, 0, 0) + + for k in layers[layer].keys(): + keys[k].set_led(*colours[layer]) + + # Turn off the layer selector LEDs if the modifier isn't held + else: + for layer in layers.keys(): + keys[layer].led_off() + + # Loop through all of the keys in the layer and if they're pressed, get the + # key code from the layer's key map + for k in layers[current_layer].keys(): + if keys[k].pressed: + key_press = layers[current_layer][k] + + # If the key hasn't just fired (prevents refiring) + if not fired: + fired = True + + # Send the right sort of key press and set debounce for each + # layer accordingly (layer 2 needs a long debounce) + if current_layer == 1: + debounce = short_debounce + keyboard.send(key_press) + elif current_layer == 2: + debounce = long_debounce + layout.write(key_press) + elif current_layer == 3: + debounce = short_debounce + consumer_control.send(key_press) + + # If enough time has passed, reset the fired variable + if fired and time.monotonic() - keybow.time_of_last_press > debounce: + fired = False \ No newline at end of file diff --git a/examples/hid-keys-simple.py b/examples/hid-keys-simple.py index 67db221..201ada0 100644 --- a/examples/hid-keys-simple.py +++ b/examples/hid-keys-simple.py @@ -1,3 +1,16 @@ +# SPDX-FileCopyrightText: 2021 Sandy Macdonald +# +# SPDX-License-Identifier: MIT + +# A simple example of how to set up a keymap and HID keyboard on Keybow 2040. + +# You'll need to connect Keybow 2040 to a computer, as you would with a regular +# USB keyboard. + +# Drop the keybow2040.py file into your `lib` folder on your `CIRCUITPY` drive. + +# NOTE! Requires the adafruit_hid CircuitPython library also! + import board from keybow2040 import Keybow2040 @@ -6,13 +19,16 @@ from adafruit_hid.keyboard import Keyboard from adafruit_hid.keyboard_layout_us import KeyboardLayoutUS from adafruit_hid.keycode import Keycode +# Set up Keybow i2c = board.I2C() keybow = Keybow2040(i2c) keys = keybow.keys +# Set up the keyboard and layout keyboard = Keyboard(usb_hid.devices) layout = KeyboardLayoutUS(keyboard) +# A map of keycodes that will be mapped sequentially to each of the keys, 0-15 keymap = [Keycode.ZERO, Keycode.ONE, Keycode.TWO, @@ -30,11 +46,23 @@ keymap = [Keycode.ZERO, Keycode.E, Keycode.F] +# The colour to set the keys when pressed, yellow. +rgb = (255, 255, 0) + +# Attach handler functions to all of the keys for key in keys: + # A press handler that sends the keycode and turns on the LED @keybow.on_press(key) def press_handler(key): keycode = keymap[key.number] keyboard.send(keycode) + key.set_led(*rgb) + + # A release handler that turns off the LED + @keybow.on_release(key) + def release_handler(key): + key.led_off() while True: + # Always remember to call keybow.update()! keybow.update() diff --git a/examples/midi-arp.py b/examples/midi-arp.py index d75fa1c..0a39624 100644 --- a/examples/midi-arp.py +++ b/examples/midi-arp.py @@ -11,7 +11,7 @@ # Drop the keybow2040.py file into your `lib` folder on your `CIRCUITPY` drive. -# NOTE! Requires the adafruit_midi CircuitPython library! +# NOTE! Requires the adafruit_midi CircuitPython library also! import time import board diff --git a/examples/midi-keys.py b/examples/midi-keys.py index a05eaf6..62030b8 100644 --- a/examples/midi-keys.py +++ b/examples/midi-keys.py @@ -10,7 +10,7 @@ # Drop the keybow2040.py file into your `lib` folder on your `CIRCUITPY` drive. -# NOTE! Requires the adafruit_midi CircuitPython library! +# NOTE! Requires the adafruit_midi CircuitPython library also! import time import board