From e52af4f58a831eaf1e4fc6443802ff951f613c0e Mon Sep 17 00:00:00 2001 From: DonutCables Date: Wed, 2 Mar 2022 23:22:58 -0500 Subject: [PATCH] MIDI module and docs --- docs/midi.md | 18 ++++++++ docs/modules.md | 19 +++++--- kmk/modules/midi.py | 108 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 139 insertions(+), 6 deletions(-) create mode 100644 docs/midi.md create mode 100644 kmk/modules/midi.py diff --git a/docs/midi.md b/docs/midi.md new file mode 100644 index 0000000..926df6d --- /dev/null +++ b/docs/midi.md @@ -0,0 +1,18 @@ +# MIDI +The MIDI module adds keymap entries for sending MIDI data streams. It will require adding the `adafruit_midi` library from the [Adafruit CircuitPython Bundle](https://circuitpython.org/libraries) to your device's folder. +Add it to your keyboard's modules list with: + +```python +from kmk.modules.midi import MidiKeys +keyboard.modules.append(MidiKeys()) +``` +## Keycodes + +|Key |Description | +|-----------------|------------------------------------------------------------------------| +|`KC.MIDI_CC()` |Sends a ControlChange message; accepts two integer arguments of `0`-`15`(controller number) then `0`-`127`(control value) | +|`KC.MIDI_NOTE()` |Sends a Note message with both 'On' and 'Off' segments; accepts two integer arguments of `0`-`127`(note number) and `0`-`127`(velocity) | +|`KC.MIDI_PB()` |Sends a Pitch Wheel message; accepts a single integer argument of `0`-`16383`, centered on `8192` | +|`KC.MIDI_PC()` |Sends a Program Change message; accepts a single integer argument of `0`-`127`(program number) | +|`KC.MIDI_START()` |Sends a Start message; accepts no arguments | +|`KC.MIDI_STOP()` |Sends a Stop message; accepts no arguments | \ No newline at end of file diff --git a/docs/modules.md b/docs/modules.md index 04bca4b..52719f1 100644 --- a/docs/modules.md +++ b/docs/modules.md @@ -4,20 +4,27 @@ the ability to alter the core code in any way. Unlike extensions, these are not sandbox, and can make massive changes to normal operation. ## Core Modules -These modules are proveded in all builds and can be enabled. Currently offered +These modules are provided in all builds and can be enabled. Currently offered modules are - [Layers](layers.md): Adds layer support (Fn key) to allow many more keys to be -put on your keyboard +put on your keyboard. - [ModTap](modtap.md): Adds support for augmented modifier keys to act as one key when tapped, and modifier when held. -- [Mouse keys](mouse_keys.md): Adds mouse keycodes +- [Mouse keys](mouse_keys.md): Adds mouse keycodes. - [OneShot](oneshot.md): Adds support for oneshot/sticky keys. - [Power](power.md): Power saving features. This is mostly useful when on battery power. - [Split](split_keyboards.md): Keyboards split in two. Seems ergonomic! - [TapDance](tapdance.md): Different key actions depending on how often it is pressed. +### Require Libraries +These modules can be used without specific hardware, but require additional libraries such as the `Adafruit CircuitPython Bundle`. + + - [MIDI](midi.md): Adds sending MIDI data in the form of keymap entries. + + ### Peripherals -- [ADNS9800](adns9800.md): Controlling ADNS9800 optical sensor -- [Encoder](encoder.md): Handling rotary encoders -- [Pimoroni trackball](pimoroni_trackball.md): Handling a small I2C trackball made by Pimoroni +These modules are for specific hardware and may require additional libraries to function. +- [ADNS9800](adns9800.md): Controlling ADNS9800 optical sensor. +- [Encoder](encoder.md): Handling rotary encoders. +- [Pimoroni trackball](pimoroni_trackball.md): Handling a small I2C trackball made by Pimoroni. diff --git a/kmk/modules/midi.py b/kmk/modules/midi.py new file mode 100644 index 0000000..ad8194b --- /dev/null +++ b/kmk/modules/midi.py @@ -0,0 +1,108 @@ +# Originally put together by xs5871 on the KMK Firmware Discord/Matrix channel + +import usb_midi + +from kmk.modules import Module +from kmk.keys import make_argumented_key + +import adafruit_midi +from adafruit_midi.control_change import ControlChange +from adafruit_midi.note_off import NoteOff +from adafruit_midi.note_on import NoteOn +from adafruit_midi.pitch_bend import PitchBend +from adafruit_midi.program_change import ProgramChange +from adafruit_midi.start import Start +from adafruit_midi.stop import Stop + + +class midiNoteValidator: + def __init__(self, note=69, velocity=64, channel=None): + self.note = note + self.velocity = velocity + self.channel = channel + + +class MidiKeys(Module): + def __init__(self): + make_argumented_key( + names=('MIDI_CC',), + validator=ControlChange, + on_press=self.on_press, + ) + + make_argumented_key( + names=('MIDI_NOTE',), + validator=midiNoteValidator, + on_press=self.note_on, + on_release=self.note_off, + ) + + make_argumented_key( + names=('MIDI_PB',), + validator=PitchBend, + on_press=self.on_press, + ) + + make_argumented_key( + names=('MIDI_PC',), + validator=ProgramChange, + on_press=self.on_press, + ) + + make_argumented_key( + names=('MIDI_START',), + validator=Start, + on_press=self.on_press, + ) + + make_argumented_key( + names=('MIDI_STOP',), + validator=Stop, + on_press=self.on_press, + ) + + try: + self.midi = adafruit_midi.MIDI( + midi_out=usb_midi.ports[1], out_channel=0 + ) + except IndexError: + self.midi = None + # if debug_enabled: + print('No midi device found.') + + def during_bootup(self, keyboard): + return None + + def before_matrix_scan(self, keyboard): + return None + + def after_matrix_scan(self, keyboard): + return None + + def process_key(self, keyboard, key, is_pressed, int_coord): + return key + + def before_hid_send(self, keyboard): + return None + + def after_hid_send(self, keyboard): + return None + + def on_powersave_enable(self, keyboard): + return None + + def on_powersave_disable(self, keyboard): + return None + + def send(self, message): + if self.midi: + self.midi.send(message) + + def on_press(self, key, keyboard, *args, **kwargs): + self.send(key.meta) + + def note_on(self, key, keyboard, *args, **kwargs): + self.send(NoteOn(key.meta.note, key.meta.velocity, channel=key.meta.channel)) + + def note_off(self, key, keyboard, *args, **kwargs): + self.send(NoteOff(key.meta.note, key.meta.velocity, channel=key.meta.channel))