Merge 211de9a20d
into b42d06ecf7
This commit is contained in:
commit
3998209f5c
23 changed files with 1608 additions and 0 deletions
|
@ -37,6 +37,7 @@ target_sources(app PRIVATE src/events/activity_state_changed.c)
|
|||
target_sources(app PRIVATE src/events/position_state_changed.c)
|
||||
target_sources(app PRIVATE src/events/sensor_event.c)
|
||||
target_sources(app PRIVATE src/events/mouse_button_state_changed.c)
|
||||
target_sources(app PRIVATE src/events/midi_key_state_changed.c)
|
||||
target_sources_ifdef(CONFIG_ZMK_WPM app PRIVATE src/events/wpm_state_changed.c)
|
||||
target_sources_ifdef(CONFIG_USB_DEVICE_STACK app PRIVATE src/events/usb_conn_state_changed.c)
|
||||
target_sources(app PRIVATE src/behaviors/behavior_reset.c)
|
||||
|
@ -76,6 +77,14 @@ if ((NOT CONFIG_ZMK_SPLIT) OR CONFIG_ZMK_SPLIT_ROLE_CENTRAL)
|
|||
target_sources(app PRIVATE src/events/keycode_state_changed.c)
|
||||
target_sources_ifdef(CONFIG_ZMK_HID_INDICATORS app PRIVATE src/hid_indicators.c)
|
||||
|
||||
if (CONFIG_ZMK_MIDI)
|
||||
target_sources(app PRIVATE src/midi.c)
|
||||
target_sources(app PRIVATE src/midi_listener.c)
|
||||
target_sources_ifdef(CONFIG_ZMK_BEHAVIOR_MIDI_KEY_PRESS app PRIVATE src/behaviors/behavior_midi_key_press.c)
|
||||
target_sources_ifdef(CONFIG_ZMK_USB app PRIVATE src/usb_midi.c)
|
||||
target_sources_ifdef(CONFIG_ZMK_USB app PRIVATE src/usb_midi_packet.c)
|
||||
endif()
|
||||
|
||||
if (CONFIG_ZMK_BLE)
|
||||
target_sources(app PRIVATE src/events/ble_active_profile_changed.c)
|
||||
target_sources(app PRIVATE src/behaviors/behavior_bt.c)
|
||||
|
|
|
@ -381,6 +381,14 @@ config ZMK_MOUSE
|
|||
#Mouse Options
|
||||
endmenu
|
||||
|
||||
menu "USB MIDI Options"
|
||||
|
||||
config ZMK_MIDI
|
||||
bool "Enable ZMK midi emulation"
|
||||
|
||||
#MIDI Options
|
||||
endmenu
|
||||
|
||||
menu "Power Management"
|
||||
|
||||
config ZMK_BATTERY_REPORTING
|
||||
|
|
|
@ -93,6 +93,12 @@ config ZMK_BEHAVIOR_SOFT_OFF
|
|||
default y
|
||||
depends on DT_HAS_ZMK_BEHAVIOR_SOFT_OFF_ENABLED && ZMK_PM_SOFT_OFF
|
||||
|
||||
config ZMK_BEHAVIOR_MIDI_KEY_PRESS
|
||||
bool
|
||||
default y
|
||||
depends on DT_HAS_ZMK_BEHAVIOR_MIDI_KEY_PRESS_ENABLED
|
||||
imply ZMK_MIDI
|
||||
|
||||
config ZMK_BEHAVIOR_SENSOR_ROTATE_COMMON
|
||||
bool
|
||||
|
||||
|
|
|
@ -21,3 +21,5 @@
|
|||
#include <behaviors/macros.dtsi>
|
||||
#include <behaviors/mouse_key_press.dtsi>
|
||||
#include <behaviors/soft_off.dtsi>
|
||||
#include <behaviors/midi_key_press.dtsi>
|
||||
|
||||
|
|
14
app/dts/behaviors/midi_key_press.dtsi
Normal file
14
app/dts/behaviors/midi_key_press.dtsi
Normal file
|
@ -0,0 +1,14 @@
|
|||
/*
|
||||
* Copyright (c) 2024 The ZMK Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
/ {
|
||||
behaviors {
|
||||
/omit-if-no-ref/ midi: midi_key_press {
|
||||
compatible = "zmk,behavior-midi-key-press";
|
||||
#binding-cells = <1>;
|
||||
};
|
||||
};
|
||||
};
|
|
@ -0,0 +1,8 @@
|
|||
# Copyright (c) 2024 The ZMK Contributors
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
description: Midi key press/release behavior
|
||||
|
||||
compatible: "zmk,behavior-midi-key-press"
|
||||
|
||||
include: one_param.yaml
|
223
app/include/dt-bindings/zmk/midi.h
Normal file
223
app/include/dt-bindings/zmk/midi.h
Normal file
|
@ -0,0 +1,223 @@
|
|||
/*
|
||||
* Copyright (c) 2024 The ZMK Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <zephyr/dt-bindings/dt-util.h>
|
||||
|
||||
// NOTE MIDI octave index is +2 vs "normal" octave index
|
||||
// so standard C3 is MIDI C5 here
|
||||
|
||||
// All NOTE_* have the identical keycode and midi code
|
||||
#define NOTE_C 0x0
|
||||
#define NOTE_Cs 0x1
|
||||
#define NOTE_Db NOTE_Cs
|
||||
#define NOTE_D 0x2
|
||||
#define NOTE_Ds 0x3
|
||||
#define NOTE_Eb NOTE_Ds
|
||||
#define NOTE_E 0x4
|
||||
#define NOTE_F 0x5
|
||||
#define NOTE_Fs 0x6
|
||||
#define NOTE_Gb NOTE_Fs
|
||||
#define NOTE_G 0x7
|
||||
#define NOTE_Gs 0x8
|
||||
#define NOTE_Ab NOTE_Gs
|
||||
#define NOTE_A 0x9
|
||||
#define NOTE_As 0xa
|
||||
#define NOTE_Bb NOTE_As
|
||||
#define NOTE_B 0xb
|
||||
|
||||
#define NOTE_C_1 0xc
|
||||
#define NOTE_Cs_1 0xd
|
||||
#define NOTE_Db_1 NOTE_Cs_1
|
||||
#define NOTE_D_1 0xe
|
||||
#define NOTE_Ds_1 0xf
|
||||
#define NOTE_Eb_1 NOTE_Ds_1
|
||||
#define NOTE_E_1 0x10
|
||||
#define NOTE_F_1 0x11
|
||||
#define NOTE_Fs_1 0x12
|
||||
#define NOTE_Gb_1 NOTE_Fs_1
|
||||
#define NOTE_G_1 0x13
|
||||
#define NOTE_Gs_1 0x14
|
||||
#define NOTE_Ab_1 NOTE_Gs_1
|
||||
#define NOTE_A_1 0x15
|
||||
#define NOTE_As_1 0x16
|
||||
#define NOTE_Bb_1 NOTE_As_1
|
||||
#define NOTE_B_1 0x17
|
||||
|
||||
#define NOTE_C_2 0x18
|
||||
#define NOTE_Cs_2 0x19
|
||||
#define NOTE_Db_2 NOTE_Cs_2
|
||||
#define NOTE_D_2 0x1a
|
||||
#define NOTE_Ds_2 0x1b
|
||||
#define NOTE_Eb_2 NOTE_Ds_2
|
||||
#define NOTE_E_2 0x1c
|
||||
#define NOTE_F_2 0x1d
|
||||
#define NOTE_Fs_2 0x1e
|
||||
#define NOTE_Gb_2 NOTE_Fs_2
|
||||
#define NOTE_G_2 0x1f
|
||||
#define NOTE_Gs_2 0x20
|
||||
#define NOTE_Ab_2 NOTE_Gs_2
|
||||
#define NOTE_A_2 0x21
|
||||
#define NOTE_As_2 0x22
|
||||
#define NOTE_Bb_2 NOTE_As_2
|
||||
#define NOTE_B_2 0x23
|
||||
|
||||
#define NOTE_C_3 0x24
|
||||
#define NOTE_Cs_3 0x25
|
||||
#define NOTE_Db_3 NOTE_Cs_3
|
||||
#define NOTE_D_3 0x26
|
||||
#define NOTE_Ds_3 0x27
|
||||
#define NOTE_Eb_3 NOTE_Ds_3
|
||||
#define NOTE_E_3 0x28
|
||||
#define NOTE_F_3 0x29
|
||||
#define NOTE_Fs_3 0x2a
|
||||
#define NOTE_Gb_3 NOTE_Fs_3
|
||||
#define NOTE_G_3 0x2b
|
||||
#define NOTE_Gs_3 0x2c
|
||||
#define NOTE_Ab_3 NOTE_Gs_3
|
||||
#define NOTE_A_3 0x2d
|
||||
#define NOTE_As_3 0x2e
|
||||
#define NOTE_Bb_3 NOTE_As_3
|
||||
#define NOTE_B_3 0x2f
|
||||
|
||||
#define NOTE_C_4 0x30
|
||||
#define NOTE_Cs_4 0x31
|
||||
#define NOTE_Db_4 NOTE_Cs_4
|
||||
#define NOTE_D_4 0x32
|
||||
#define NOTE_Ds_4 0x33
|
||||
#define NOTE_Eb_4 NOTE_Ds_4
|
||||
#define NOTE_E_4 0x34
|
||||
#define NOTE_F_4 0x35
|
||||
#define NOTE_Fs_4 0x36
|
||||
#define NOTE_Gb_4 NOTE_Fs_4
|
||||
#define NOTE_G_4 0x37
|
||||
#define NOTE_Gs_4 0x38
|
||||
#define NOTE_Ab_4 NOTE_Gs_4
|
||||
#define NOTE_A_4 0x39
|
||||
#define NOTE_As_4 0x3a
|
||||
#define NOTE_Bb_4 NOTE_As_4
|
||||
#define NOTE_B_4 0x3b
|
||||
|
||||
#define NOTE_C_5 0x3c
|
||||
#define NOTE_Cs_5 0x3d
|
||||
#define NOTE_Db_5 NOTE_Cs_5
|
||||
#define NOTE_D_5 0x3e
|
||||
#define NOTE_Ds_5 0x3f
|
||||
#define NOTE_Eb_5 NOTE_Ds_5
|
||||
#define NOTE_E_5 0x40
|
||||
#define NOTE_F_5 0x41
|
||||
#define NOTE_Fs_5 0x42
|
||||
#define NOTE_Gb_5 NOTE_Fs_5
|
||||
#define NOTE_G_5 0x43
|
||||
#define NOTE_Gs_5 0x44
|
||||
#define NOTE_Ab_5 NOTE_Gs_5
|
||||
#define NOTE_A_5 0x45
|
||||
#define NOTE_As_5 0x46
|
||||
#define NOTE_Bb_5 NOTE_As_5
|
||||
#define NOTE_B_5 0x47
|
||||
|
||||
#define NOTE_C_6 0x48
|
||||
#define NOTE_Cs_6 0x49
|
||||
#define NOTE_Db_6 NOTE_Cs_6
|
||||
#define NOTE_D_6 0x4a
|
||||
#define NOTE_Ds_6 0x4b
|
||||
#define NOTE_Eb_6 NOTE_Ds_6
|
||||
#define NOTE_E_6 0x4c
|
||||
#define NOTE_F_6 0x4d
|
||||
#define NOTE_Fs_6 0x4e
|
||||
#define NOTE_Gb_6 NOTE_Fs_6
|
||||
#define NOTE_G_6 0x4f
|
||||
#define NOTE_Gs_6 0x50
|
||||
#define NOTE_Ab_6 NOTE_Gs_6
|
||||
#define NOTE_A_6 0x51
|
||||
#define NOTE_As_6 0x52
|
||||
#define NOTE_Bb_6 NOTE_As_6
|
||||
#define NOTE_B_6 0x53
|
||||
|
||||
#define NOTE_C_7 0x54
|
||||
#define NOTE_Cs_7 0x55
|
||||
#define NOTE_Db_7 NOTE_Cs_7
|
||||
#define NOTE_D_7 0x56
|
||||
#define NOTE_Ds_7 0x57
|
||||
#define NOTE_Eb_7 NOTE_Ds_7
|
||||
#define NOTE_E_7 0x58
|
||||
#define NOTE_F_7 0x59
|
||||
#define NOTE_Fs_7 0x5a
|
||||
#define NOTE_Gb_7 NOTE_Fs_7
|
||||
#define NOTE_G_7 0x5b
|
||||
#define NOTE_Gs_7 0x5c
|
||||
#define NOTE_Ab_7 NOTE_Gs_7
|
||||
#define NOTE_A_7 0x5d
|
||||
#define NOTE_As_7 0x5e
|
||||
#define NOTE_Bb_7 NOTE_As_7
|
||||
#define NOTE_B_7 0x5f
|
||||
|
||||
#define NOTE_C_8 0x60
|
||||
#define NOTE_Cs_8 0x61
|
||||
#define NOTE_Db_8 NOTE_Cs_8
|
||||
#define NOTE_D_8 0x62
|
||||
#define NOTE_Ds_8 0x63
|
||||
#define NOTE_Eb_8 NOTE_Ds_8
|
||||
#define NOTE_E_8 0x64
|
||||
#define NOTE_F_8 0x65
|
||||
#define NOTE_Fs_8 0x66
|
||||
#define NOTE_Gb_8 NOTE_Fs_8
|
||||
#define NOTE_G_8 0x67
|
||||
#define NOTE_Gs_8 0x68
|
||||
#define NOTE_Ab_8 NOTE_Gs_8
|
||||
#define NOTE_A_8 0x69
|
||||
#define NOTE_As_8 0x6a
|
||||
#define NOTE_Bb_8 NOTE_As_8
|
||||
#define NOTE_B_8 0x6b
|
||||
|
||||
#define NOTE_C_9 0x6c
|
||||
#define NOTE_Cs_9 0x6d
|
||||
#define NOTE_Db_9 NOTE_Cs_9
|
||||
#define NOTE_D_9 0x6e
|
||||
#define NOTE_Ds_9 0x6f
|
||||
#define NOTE_Eb_9 NOTE_Ds_9
|
||||
#define NOTE_E_9 0x70
|
||||
#define NOTE_F_9 0x71
|
||||
#define NOTE_Fs_9 0x72
|
||||
#define NOTE_Gb_9 NOTE_Fs_9
|
||||
#define NOTE_G_9 0x73
|
||||
#define NOTE_Gs_9 0x74
|
||||
#define NOTE_Ab_9 NOTE_Gs_9
|
||||
#define NOTE_A_9 0x75
|
||||
#define NOTE_As_9 0x76
|
||||
#define NOTE_Bb_9 NOTE_As_9
|
||||
#define NOTE_B_9 0x77
|
||||
|
||||
#define NOTE_C_10 0x78
|
||||
#define NOTE_Cs_10 0x79
|
||||
#define NOTE_Db_10 NOTE_Cs_10
|
||||
#define NOTE_D_10 0x7a
|
||||
#define NOTE_Ds_10 0x7b
|
||||
#define NOTE_Eb_10 NOTE_Ds_10
|
||||
#define NOTE_E_10 0x7c
|
||||
#define NOTE_F_10 0x7d
|
||||
#define NOTE_Fs_10 0x7e
|
||||
#define NOTE_Gb_10 NOTE_Fs_10
|
||||
#define NOTE_G_10 0x7f
|
||||
// 0x7f aka 127 is the max value
|
||||
|
||||
// NOTE sentinals
|
||||
#define MIDI_MIN_NOTE NOTE_C
|
||||
#define MIDI_MAX_NOTE NOTE_G_10
|
||||
#define MIDI_INVALID 0xFF
|
||||
|
||||
// Midi control change keycodes
|
||||
// appended with 0xB0
|
||||
#define SUSTAIN 0xB040
|
||||
#define PORTAMENTO 0xB041
|
||||
#define SOSTENUTO 0xB042
|
||||
#define OCT_UP 0xB081
|
||||
#define OCT_DOWN 0xB082
|
||||
|
||||
// midi control sentinals
|
||||
#define MIDI_MIN_CONTROL SUSTAIN
|
||||
#define MIDI_MAX_CONTROL OCT_DOWN
|
|
@ -75,3 +75,7 @@ int zmk_endpoints_send_mouse_report();
|
|||
#endif // IS_ENABLE(CONFIG_ZMK_MOUSE)
|
||||
|
||||
void zmk_endpoints_clear_current(void);
|
||||
|
||||
#if IS_ENABLED(CONFIG_ZMK_MIDI)
|
||||
int zmk_endpoints_send_midi_report();
|
||||
#endif // IS_ENABLE(CONFIG_ZMK_MIDI)
|
||||
|
|
34
app/include/zmk/events/midi_key_state_changed.h
Normal file
34
app/include/zmk/events/midi_key_state_changed.h
Normal file
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Copyright (c) 2024 The ZMK Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zmk/event_manager.h>
|
||||
#include <zmk/midi.h>
|
||||
|
||||
struct zmk_midi_key_state_changed {
|
||||
zmk_midi_key_t key;
|
||||
bool state;
|
||||
int64_t timestamp;
|
||||
};
|
||||
|
||||
ZMK_EVENT_DECLARE(zmk_midi_key_state_changed);
|
||||
|
||||
static inline struct zmk_midi_key_state_changed
|
||||
zmk_midi_key_state_changed_from_encoded(uint32_t encoded, bool pressed, int64_t timestamp) {
|
||||
|
||||
// no decoding necessary
|
||||
zmk_midi_key_t id = encoded;
|
||||
|
||||
return (struct zmk_midi_key_state_changed){.key = id, .state = pressed, .timestamp = timestamp};
|
||||
}
|
||||
|
||||
static inline int raise_zmk_midi_key_state_changed_from_encoded(uint32_t encoded, bool pressed,
|
||||
int64_t timestamp) {
|
||||
return raise_zmk_midi_key_state_changed(
|
||||
zmk_midi_key_state_changed_from_encoded(encoded, pressed, timestamp));
|
||||
}
|
48
app/include/zmk/midi.h
Normal file
48
app/include/zmk/midi.h
Normal file
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Copyright (c) 2024 The ZMK Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <zmk/midi_keys.h>
|
||||
|
||||
// in hid.c
|
||||
#define ZMK_MIDI_NUM_KEYS 0x100
|
||||
|
||||
// should come after the last ZMK_HID_REPORT_ID in hid.h
|
||||
#define ZMK_REPORT_ID_MIDI 0x04
|
||||
|
||||
#define ZMK_MIDI_CIN_NOTE_ON 0x90
|
||||
#define ZMK_MIDI_CIN_NOTE_OFF 0x80
|
||||
#define ZMK_MIDI_CIN_CONTROL_CHANGE 0xB0
|
||||
#define ZMK_MIDI_CIN_PITCH_BEND_CHANGE 0xE0
|
||||
|
||||
#define ZMK_MIDI_MAX_VELOCITY 0x7F
|
||||
#define ZMK_MIDI_ON_VELOCITY 0x3F
|
||||
#define ZMK_MIDI_OFF_VELOCITY 0x64
|
||||
|
||||
#define ZMK_MIDI_TOGGLE_ON 0x7F
|
||||
#define ZMK_MIDI_TOGGLE_OFF 0x0
|
||||
|
||||
// Analogous to zmk_hid_mouse_report_body in hid.h
|
||||
struct zmk_midi_key_report_body {
|
||||
zmk_midi_cin_t cin;
|
||||
zmk_midi_key_t key;
|
||||
zmk_midi_value_t key_value;
|
||||
} __packed;
|
||||
|
||||
// Analogous to zmk_hid_mouse_report in hid.h
|
||||
struct zmk_midi_report {
|
||||
uint8_t report_id;
|
||||
struct zmk_midi_key_report_body body;
|
||||
} __packed;
|
||||
|
||||
// Analogous to zmk_hid_mouse* in hid.h
|
||||
int zmk_midi_key_press(zmk_midi_key_t key);
|
||||
int zmk_midi_key_release(zmk_midi_key_t key);
|
||||
void zmk_midi_clear(void);
|
||||
|
||||
// Analogous to zmk_hid_get_mouse_report in hid.h
|
||||
struct zmk_midi_report *zmk_get_midi_report();
|
17
app/include/zmk/midi_keys.h
Normal file
17
app/include/zmk/midi_keys.h
Normal file
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* Copyright (c) 2024 The ZMK Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <zephyr/kernel.h>
|
||||
#include <dt-bindings/zmk/midi.h>
|
||||
|
||||
typedef uint8_t zmk_midi_cin_t;
|
||||
typedef uint16_t zmk_midi_key_t;
|
||||
typedef uint8_t zmk_midi_value_t;
|
||||
|
||||
// used for bitmaps
|
||||
typedef uint64_t zmk_midi_keys_t;
|
|
@ -13,4 +13,7 @@ int zmk_usb_hid_send_consumer_report(void);
|
|||
#if IS_ENABLED(CONFIG_ZMK_MOUSE)
|
||||
int zmk_usb_hid_send_mouse_report(void);
|
||||
#endif // IS_ENABLED(CONFIG_ZMK_MOUSE)
|
||||
#if IS_ENABLED(CONFIG_ZMK_MIDI)
|
||||
int zmk_usb_hid_send_midi_report(void);
|
||||
#endif // IS_ENABLED(CONFIG_ZMK_MIDI)
|
||||
void zmk_usb_hid_set_protocol(uint8_t protocol);
|
||||
|
|
347
app/include/zmk/usb_midi.h
Normal file
347
app/include/zmk/usb_midi.h
Normal file
|
@ -0,0 +1,347 @@
|
|||
/*
|
||||
* Copyright (c) 2024 The ZMK Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <zephyr/usb/usb_device.h>
|
||||
#include <zmk/midi.h>
|
||||
#include <usb_descriptor.h>
|
||||
|
||||
// TODO can add this back as a config option, but for now hardcode it
|
||||
#define USB_MIDI_NUM_INPUTS 1
|
||||
#define USB_MIDI_NUM_OUTPUTS 1
|
||||
|
||||
#define USB_MIDI_DEFAULT_CABLE_NUM 0
|
||||
#define USB_MIDI_MAX_NUM_BYTES 3
|
||||
|
||||
// TODO: these are hardcoded here for usb_write, but bEndpointAddress actually get assigned
|
||||
// automatically in the usb configs hard coding them in the usb configs doesn't seem to help, so we
|
||||
// don't have a good way of ensuring that the endpoint addresses defined here actually match what
|
||||
// zephyr gives our endpoints you can see what the endpoint addresses are by doing "cat
|
||||
// /sys/kernel/debug/usb/devices" when the device is plugged in eventually we should find a way to
|
||||
// get this information back out of the usb device configuration
|
||||
#define ZMK_USB_MIDI_EP_IN 0x81
|
||||
#define ZMK_USB_MIDI_EP_OUT 0x01
|
||||
|
||||
/* Require at least one jack */
|
||||
BUILD_ASSERT((USB_MIDI_NUM_INPUTS + USB_MIDI_NUM_OUTPUTS > 0),
|
||||
"USB MIDI device must have more than 0 jacks");
|
||||
|
||||
/**
|
||||
* MS (MIDI streaming) Class-Specific Interface Descriptor Subtypes.
|
||||
* See table A.1 in the spec.
|
||||
*/
|
||||
enum usb_midi_if_desc_subtype {
|
||||
USB_MIDI_IF_DESC_UNDEFINED = 0x00,
|
||||
USB_MIDI_IF_DESC_MS_HEADER = 0x01,
|
||||
USB_MIDI_IF_DESC_MIDI_IN_JACK = 0x02,
|
||||
USB_MIDI_IF_DESC_MIDI_OUT_JACK = 0x03,
|
||||
USB_MIDI_IF_DESC_ELEMENT = 0x04
|
||||
};
|
||||
|
||||
/**
|
||||
* MS Class-Specific Endpoint Descriptor Subtypes.
|
||||
* See table A.2 in the spec.
|
||||
*/
|
||||
enum usb_midi_ep_desc_subtype {
|
||||
USB_MIDI_EP_DESC_UNDEFINED = 0x00,
|
||||
USB_MIDI_EP_DESC_MS_GENERAL = 0x01
|
||||
};
|
||||
|
||||
/**
|
||||
* MS MIDI IN and OUT Jack types.
|
||||
* See table A.3 in the spec.
|
||||
*/
|
||||
enum usb_midi_jack_type {
|
||||
USB_MIDI_JACK_TYPE_UNDEFINED = 0x00,
|
||||
USB_MIDI_JACK_TYPE_EMBEDDED = 0x01,
|
||||
USB_MIDI_JACK_TYPE_EXTERNAL = 0x02
|
||||
};
|
||||
|
||||
#define USB_MIDI_AUDIO_INTERFACE_CLASS 0x01
|
||||
#define USB_MIDI_MIDISTREAMING_INTERFACE_SUBCLASS 0x03
|
||||
#define USB_MIDI_AUDIOCONTROL_INTERFACE_SUBCLASS 0x01
|
||||
|
||||
/**
|
||||
* USB MIDI input pin.
|
||||
*/
|
||||
struct usb_midi_input_pin {
|
||||
uint8_t baSourceID;
|
||||
uint8_t baSourcePin;
|
||||
} __packed;
|
||||
|
||||
/**
|
||||
* Class-specific AC (audio control) Interface Descriptor.
|
||||
*/
|
||||
struct usb_midi_ac_if_descriptor {
|
||||
uint8_t bLength;
|
||||
uint8_t bDescriptorType;
|
||||
uint8_t bDescriptorSubtype;
|
||||
uint16_t bcdADC;
|
||||
uint16_t wTotalLength;
|
||||
uint8_t bInCollection;
|
||||
uint8_t baInterfaceNr;
|
||||
} __packed;
|
||||
|
||||
/**
|
||||
* Class-Specific MS Interface Header Descriptor.
|
||||
* See table 6.2 in the spec.
|
||||
*/
|
||||
struct usb_midi_ms_if_descriptor {
|
||||
/** Size of this descriptor, in bytes */
|
||||
uint8_t bLength;
|
||||
/** CS_INTERFACE descriptor type */
|
||||
uint8_t bDescriptorType;
|
||||
/** MS_HEADER descriptor subtype. */
|
||||
uint8_t bDescriptorSubtype;
|
||||
/**
|
||||
* MIDIStreaming SubClass Specification Release Number in
|
||||
* Binary-Coded Decimal. Currently 01.00.
|
||||
*/
|
||||
uint16_t BcdADC;
|
||||
/**
|
||||
* Total number of bytes returned for the class-specific
|
||||
* MIDIStreaming interface descriptor. Includes the combined
|
||||
* length of this descriptor header and all Jack and Element descriptors.
|
||||
*/
|
||||
uint16_t wTotalLength;
|
||||
} __packed;
|
||||
|
||||
/**
|
||||
* MIDI IN Jack Descriptor. See table 6.3 in the spec.
|
||||
*/
|
||||
struct usb_midi_in_jack_descriptor {
|
||||
/** Size of this descriptor, in bytes. */
|
||||
uint8_t bLength;
|
||||
/** CS_INTERFACE descriptor type. */
|
||||
uint8_t bDescriptorType;
|
||||
/** MIDI_IN_JACK descriptor subtype. */
|
||||
uint8_t bDescriptorSubtype;
|
||||
/** EMBEDDED or EXTERNAL */
|
||||
uint8_t bJackType;
|
||||
/**
|
||||
* Constant uniquely identifying the MIDI IN Jack within
|
||||
* the USB-MIDI function.
|
||||
*/
|
||||
uint8_t bJackID;
|
||||
/** Index of a string descriptor, describing the MIDI IN Jack. */
|
||||
uint8_t iJack;
|
||||
} __packed;
|
||||
|
||||
/**
|
||||
* MIDI OUT Jack Descriptor. See table 6.4 in the spec.
|
||||
*/
|
||||
struct usb_midi_out_jack_descriptor {
|
||||
/** Size of this descriptor, in bytes: */
|
||||
uint8_t bLength;
|
||||
/** CS_INTERFACE descriptor type. */
|
||||
uint8_t bDescriptorType;
|
||||
/** MIDI_OUT_JACK descriptor subtype. */
|
||||
uint8_t bDescriptorSubtype;
|
||||
/** EMBEDDED or EXTERNAL */
|
||||
uint8_t bJackType;
|
||||
/**
|
||||
* Constant uniquely identifying the MIDI OUT Jack
|
||||
* within the USB-MIDI function.
|
||||
*/
|
||||
uint8_t bJackID;
|
||||
/**
|
||||
* Number of Input Pins of this MIDI OUT Jack
|
||||
* (assumed to be 1 in this implementation).
|
||||
*/
|
||||
uint8_t bNrInputPins;
|
||||
/** ID and source pin of the entity to which this jack is connected. */
|
||||
struct usb_midi_input_pin input_pin;
|
||||
/** Index of a string descriptor, describing the MIDI OUT Jack. */
|
||||
uint8_t iJack;
|
||||
} __packed;
|
||||
|
||||
/**
|
||||
* The same as Zephyr's usb_ep_descriptor but with two additional fields
|
||||
* to match the USB MIDI spec.
|
||||
*/
|
||||
struct usb_ep_descriptor_padded {
|
||||
uint8_t bLength;
|
||||
uint8_t bDescriptorType;
|
||||
uint8_t bEndpointAddress;
|
||||
uint8_t bmAttributes;
|
||||
uint16_t wMaxPacketSize;
|
||||
uint8_t bInterval;
|
||||
/* The following two attributes were added to match the USB MIDI spec. */
|
||||
uint8_t bRefresh;
|
||||
uint8_t bSynchAddress;
|
||||
} __packed;
|
||||
|
||||
/**
|
||||
* Class-Specific MS Bulk Data Endpoint Descriptor
|
||||
* corresponding to a MIDI output. See table 6-7 in the spec.
|
||||
*/
|
||||
struct usb_midi_bulk_out_ep_descriptor {
|
||||
uint8_t bLength;
|
||||
uint8_t bDescriptorType;
|
||||
uint8_t bDescriptorSubtype;
|
||||
uint8_t bNumEmbMIDIJack;
|
||||
uint8_t BaAssocJackID[USB_MIDI_NUM_INPUTS];
|
||||
} __packed;
|
||||
|
||||
/**
|
||||
* Class-Specific MS Bulk Data Endpoint Descriptor
|
||||
* corresponding to a MIDI input. See table 6-7 in the spec.
|
||||
*/
|
||||
struct usb_midi_bulk_in_ep_descriptor {
|
||||
uint8_t bLength;
|
||||
uint8_t bDescriptorType;
|
||||
uint8_t bDescriptorSubtype;
|
||||
uint8_t bNumEmbMIDIJack;
|
||||
uint8_t BaAssocJackID[USB_MIDI_NUM_OUTPUTS];
|
||||
} __packed;
|
||||
|
||||
#define USB_MIDI_ELEMENT_CAPS_COUNT 1
|
||||
|
||||
/**
|
||||
* Element descriptor. See table 6-5 in the spec.
|
||||
*/
|
||||
struct usb_midi_element_descriptor {
|
||||
uint8_t bLength;
|
||||
uint8_t bDescriptorType;
|
||||
uint8_t bDescriptorSubtype;
|
||||
uint8_t bElementID;
|
||||
uint8_t bNrInputPins;
|
||||
|
||||
struct usb_midi_input_pin input_pins[USB_MIDI_NUM_INPUTS];
|
||||
uint8_t bNrOutputPins;
|
||||
uint8_t bInTerminalLink;
|
||||
uint8_t bOutTerminalLink;
|
||||
uint8_t bElCapsSize;
|
||||
uint8_t bmElementCaps[USB_MIDI_ELEMENT_CAPS_COUNT];
|
||||
uint8_t iElement;
|
||||
} __packed;
|
||||
|
||||
/**
|
||||
* A complete set of descriptors for a USB MIDI device without physical jacks.
|
||||
*/
|
||||
struct usb_midi_config {
|
||||
struct usb_if_descriptor ac_if;
|
||||
struct usb_midi_ac_if_descriptor ac_cs_if;
|
||||
struct usb_if_descriptor ms_if;
|
||||
struct usb_midi_ms_if_descriptor ms_cs_if;
|
||||
struct usb_midi_in_jack_descriptor in_jacks_emb[USB_MIDI_NUM_INPUTS];
|
||||
struct usb_midi_out_jack_descriptor out_jacks_emb[USB_MIDI_NUM_OUTPUTS];
|
||||
struct usb_midi_element_descriptor element;
|
||||
struct usb_ep_descriptor_padded out_ep;
|
||||
struct usb_midi_bulk_out_ep_descriptor out_cs_ep;
|
||||
struct usb_ep_descriptor_padded in_ep;
|
||||
struct usb_midi_bulk_in_ep_descriptor in_cs_ep;
|
||||
} __packed;
|
||||
|
||||
/* No jack string descriptors by default */
|
||||
#define INPUT_JACK_STRING_DESCR_IDX(jack_idx) 0
|
||||
#define OUTPUT_JACK_STRING_DESCR_IDX(jack_idx) 0
|
||||
|
||||
/* Audio control interface descriptor */
|
||||
#define INIT_AC_IF \
|
||||
{ \
|
||||
.bLength = sizeof(struct usb_if_descriptor), .bDescriptorType = USB_DESC_INTERFACE, \
|
||||
.bInterfaceNumber = 0, .bAlternateSetting = 0, .bNumEndpoints = 0, \
|
||||
.bInterfaceClass = USB_MIDI_AUDIO_INTERFACE_CLASS, \
|
||||
.bInterfaceSubClass = USB_MIDI_AUDIOCONTROL_INTERFACE_SUBCLASS, \
|
||||
.bInterfaceProtocol = 0x00, .iInterface = 0x00 \
|
||||
}
|
||||
|
||||
/* Class specific audio control interface descriptor */
|
||||
#define INIT_AC_CS_IF \
|
||||
{ \
|
||||
.bLength = sizeof(struct usb_midi_ac_if_descriptor), \
|
||||
.bDescriptorType = USB_DESC_CS_INTERFACE, .bDescriptorSubtype = 0x01, .bcdADC = 0x0100, \
|
||||
.wTotalLength = sizeof(struct usb_midi_ac_if_descriptor), .bInCollection = 0x01, \
|
||||
.baInterfaceNr = 0x01 \
|
||||
}
|
||||
|
||||
/* MIDI streaming interface descriptor */
|
||||
#define INIT_MS_IF \
|
||||
{ \
|
||||
.bLength = sizeof(struct usb_if_descriptor), .bDescriptorType = USB_DESC_INTERFACE, \
|
||||
.bInterfaceNumber = 0x01, .bAlternateSetting = 0x00, .bNumEndpoints = 2, \
|
||||
.bInterfaceClass = USB_MIDI_AUDIO_INTERFACE_CLASS, \
|
||||
.bInterfaceSubClass = USB_MIDI_MIDISTREAMING_INTERFACE_SUBCLASS, \
|
||||
.bInterfaceProtocol = 0x00, .iInterface = 0x00 \
|
||||
}
|
||||
|
||||
/* Class specific MIDI streaming interface descriptor */
|
||||
#define INIT_MS_CS_IF \
|
||||
{ \
|
||||
.bLength = sizeof(struct usb_midi_ms_if_descriptor), \
|
||||
.bDescriptorType = USB_DESC_CS_INTERFACE, .bDescriptorSubtype = 0x01, .BcdADC = 0x0100, \
|
||||
.wTotalLength = MIDI_MS_IF_DESC_TOTAL_SIZE \
|
||||
}
|
||||
|
||||
/* Embedded MIDI input jack */
|
||||
#define INIT_IN_JACK(idx, idx_offset) \
|
||||
{ \
|
||||
.bLength = sizeof(struct usb_midi_in_jack_descriptor), \
|
||||
.bDescriptorType = USB_DESC_CS_INTERFACE, \
|
||||
.bDescriptorSubtype = USB_MIDI_IF_DESC_MIDI_IN_JACK, \
|
||||
.bJackType = USB_MIDI_JACK_TYPE_EMBEDDED, .bJackID = 1 + idx + idx_offset, \
|
||||
.iJack = INPUT_JACK_STRING_DESCR_IDX(idx), \
|
||||
}
|
||||
|
||||
/* Embedded MIDI output jack */
|
||||
#define INIT_OUT_JACK(idx, jack_id_idx_offset) \
|
||||
{ \
|
||||
.bLength = sizeof(struct usb_midi_out_jack_descriptor), \
|
||||
.bDescriptorType = USB_DESC_CS_INTERFACE, \
|
||||
.bDescriptorSubtype = USB_MIDI_IF_DESC_MIDI_OUT_JACK, \
|
||||
.bJackType = USB_MIDI_JACK_TYPE_EMBEDDED, .bJackID = 1 + idx + jack_id_idx_offset, \
|
||||
.bNrInputPins = 0x01, \
|
||||
.input_pin = \
|
||||
{ \
|
||||
.baSourceID = ELEMENT_ID, \
|
||||
.baSourcePin = 1 + idx, \
|
||||
}, \
|
||||
.iJack = OUTPUT_JACK_STRING_DESCR_IDX(idx) \
|
||||
}
|
||||
|
||||
/* Out endpoint */
|
||||
#define INIT_OUT_EP \
|
||||
{ \
|
||||
.bLength = sizeof(struct usb_ep_descriptor_padded), .bDescriptorType = USB_DESC_ENDPOINT, \
|
||||
.bEndpointAddress = ZMK_USB_MIDI_EP_OUT, .bmAttributes = 0x02, .wMaxPacketSize = 0x0040, \
|
||||
.bInterval = 0x00, .bRefresh = 0x00, .bSynchAddress = 0x00, \
|
||||
}
|
||||
|
||||
/* In endpoint */
|
||||
#define INIT_IN_EP \
|
||||
{ \
|
||||
.bLength = sizeof(struct usb_ep_descriptor_padded), .bDescriptorType = USB_DESC_ENDPOINT, \
|
||||
.bEndpointAddress = ZMK_USB_MIDI_EP_IN, .bmAttributes = 0x02, .wMaxPacketSize = 0x0040, \
|
||||
.bInterval = 0x00, .bRefresh = 0x00, .bSynchAddress = 0x00, \
|
||||
}
|
||||
|
||||
#define ELEMENT_ID 0xf0
|
||||
#define IDX_WITH_OFFSET(index, offset) (index + offset)
|
||||
#define INIT_INPUT_PIN(index, offset) \
|
||||
{ .baSourceID = (index + offset), .baSourcePin = 1 }
|
||||
|
||||
#define INIT_ELEMENT \
|
||||
{ \
|
||||
.bLength = sizeof(struct usb_midi_element_descriptor), \
|
||||
.bDescriptorType = USB_DESC_CS_INTERFACE, .bDescriptorSubtype = USB_MIDI_IF_DESC_ELEMENT, \
|
||||
.bElementID = ELEMENT_ID, .bNrInputPins = USB_MIDI_NUM_INPUTS, \
|
||||
.input_pins = {LISTIFY(USB_MIDI_NUM_INPUTS, INIT_INPUT_PIN, (, ), 1)}, \
|
||||
.bNrOutputPins = USB_MIDI_NUM_OUTPUTS, .bInTerminalLink = 0, .bOutTerminalLink = 0, \
|
||||
.bElCapsSize = 1, .bmElementCaps = 1, .iElement = 0 \
|
||||
}
|
||||
|
||||
/* Value for the wTotalLength field of the class-specific MS Interface Descriptor,
|
||||
i.e the total number of bytes following that descriptor. */
|
||||
#define MIDI_MS_IF_DESC_TOTAL_SIZE \
|
||||
(sizeof(struct usb_midi_in_jack_descriptor) * USB_MIDI_NUM_INPUTS + \
|
||||
sizeof(struct usb_midi_out_jack_descriptor) * USB_MIDI_NUM_OUTPUTS + \
|
||||
sizeof(struct usb_midi_element_descriptor) + sizeof(struct usb_ep_descriptor_padded) + \
|
||||
sizeof(struct usb_midi_bulk_out_ep_descriptor) + sizeof(struct usb_ep_descriptor_padded) + \
|
||||
sizeof(struct usb_midi_bulk_in_ep_descriptor))
|
||||
|
||||
int zmk_usb_send_midi_report(struct zmk_midi_key_report_body *body);
|
80
app/include/zmk/usb_midi_packet.h
Normal file
80
app/include/zmk/usb_midi_packet.h
Normal file
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
* Copyright (c) 2024 The ZMK Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
enum usb_midi_error_t {
|
||||
USB_MIDI_SUCCESS = 0,
|
||||
USB_MIDI_ERROR_INVALID_CIN = -1,
|
||||
USB_MIDI_ERROR_INVALID_CABLE_NUM = -2,
|
||||
USB_MIDI_ERROR_INVALID_MIDI_MSG = -3
|
||||
};
|
||||
|
||||
/* Code Index Numbers. See table 4-1 in the spec. */
|
||||
enum usb_midi_cin_t {
|
||||
/* Miscellaneous function codes. Reserved for future extensions. */
|
||||
USB_MIDI_CIN_MISC = 0x0,
|
||||
/* Cable events. Reserved for future expansion. */
|
||||
USB_MIDI_CIN_CABLE_EVENT = 0x1,
|
||||
/* Two-byte System Common messages like MTC, SongSelect, etc. */
|
||||
USB_MIDI_CIN_SYSCOM_2BYTE = 0x2,
|
||||
/* Three-byte System Common messages like SPP, etc. */
|
||||
USB_MIDI_CIN_SYSCOM_3BYTE = 0x3,
|
||||
/* SysEx starts or continues */
|
||||
USB_MIDI_CIN_SYSEX_START_OR_CONTINUE = 0x4,
|
||||
/* Single-byte System Common Message or SysEx ends with following single byte. */
|
||||
USB_MIDI_CIN_SYS_COMMON_OR_SYSEX_END_1BYTE = 0x5,
|
||||
/* SysEx ends with following two bytes. */
|
||||
USB_MIDI_CIN_SYSEX_END_2BYTE = 0x6,
|
||||
/* SysEx ends with following three bytes. */
|
||||
USB_MIDI_CIN_SYSEX_END_3BYTE = 0x7,
|
||||
/* Note-off */
|
||||
USB_MIDI_CIN_NOTE_ON = 0x8,
|
||||
/* Note-on */
|
||||
USB_MIDI_CIN_NOTE_OFF = 0x9,
|
||||
/* Poly-KeyPress */
|
||||
USB_MIDI_CIN_POLY_KEYPRESS = 0xA,
|
||||
/* Control Change */
|
||||
USB_MIDI_CIN_CONTROL_CHANGE = 0xB,
|
||||
/* Program Change */
|
||||
USB_MIDI_CIN_PROGRAM_CHANGE = 0xC,
|
||||
/* Channel Pressure */
|
||||
USB_MIDI_CIN_CHANNEL_PRESSURE = 0xD,
|
||||
/* PitchBend Change */
|
||||
USB_MIDI_CIN_PITCH_BEND_CHANGE = 0xE,
|
||||
/* Single Byte */
|
||||
USB_MIDI_CIN_1BYTE_DATA = 0xF
|
||||
};
|
||||
|
||||
/** Called when a non-sysex message has been parsed */
|
||||
typedef void (*usb_midi_message_cb_t)(uint8_t *bytes, uint8_t num_bytes, uint8_t cable_num);
|
||||
/** Called when a sysex message starts */
|
||||
typedef void (*usb_midi_sysex_start_cb_t)(uint8_t cable_num);
|
||||
/** Called when sysex data bytes have been received */
|
||||
typedef void (*usb_midi_sysex_data_cb_t)(uint8_t *data_bytes, uint8_t num_data_bytes,
|
||||
uint8_t cable_num);
|
||||
/** Called when a sysex message ends */
|
||||
typedef void (*usb_midi_sysex_end_cb_t)(uint8_t cable_num);
|
||||
|
||||
struct usb_midi_parse_cb_t {
|
||||
usb_midi_message_cb_t message_cb;
|
||||
usb_midi_sysex_start_cb_t sysex_start_cb;
|
||||
usb_midi_sysex_data_cb_t sysex_data_cb;
|
||||
usb_midi_sysex_end_cb_t sysex_end_cb;
|
||||
};
|
||||
|
||||
/* A USB MIDI event packet. See chapter 4 in the spec. */
|
||||
struct usb_midi_packet_t {
|
||||
uint8_t cable_num;
|
||||
uint8_t cin;
|
||||
uint8_t bytes[4];
|
||||
uint8_t num_midi_bytes;
|
||||
};
|
||||
|
||||
enum usb_midi_error_t usb_midi_packet_from_midi_bytes(uint8_t *midi_bytes, uint8_t cable_num,
|
||||
struct usb_midi_packet_t *packet);
|
46
app/src/behaviors/behavior_midi_key_press.c
Normal file
46
app/src/behaviors/behavior_midi_key_press.c
Normal file
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Copyright (c) 2024 The ZMK Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#define DT_DRV_COMPAT zmk_behavior_midi_key_press
|
||||
|
||||
#include <zephyr/device.h>
|
||||
#include <drivers/behavior.h>
|
||||
#include <zephyr/logging/log.h>
|
||||
|
||||
#include <zmk/behavior.h>
|
||||
#include <zmk/event_manager.h>
|
||||
#include <zmk/events/midi_key_state_changed.h>
|
||||
|
||||
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
|
||||
|
||||
#if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT)
|
||||
|
||||
static int behavior_midi_key_press_init(const struct device *dev) { return 0; };
|
||||
|
||||
static int on_keymap_binding_pressed(struct zmk_behavior_binding *binding,
|
||||
struct zmk_behavior_binding_event event) {
|
||||
LOG_DBG("position %d keycode 0x%02X", event.position, binding->param1);
|
||||
|
||||
return raise_zmk_midi_key_state_changed_from_encoded(binding->param1, true, event.timestamp);
|
||||
}
|
||||
|
||||
static int on_keymap_binding_released(struct zmk_behavior_binding *binding,
|
||||
struct zmk_behavior_binding_event event) {
|
||||
LOG_DBG("position %d keycode 0x%02X", event.position, binding->param1);
|
||||
return raise_zmk_midi_key_state_changed_from_encoded(binding->param1, false, event.timestamp);
|
||||
}
|
||||
|
||||
static const struct behavior_driver_api behavior_midi_key_press_driver_api = {
|
||||
.binding_pressed = on_keymap_binding_pressed, .binding_released = on_keymap_binding_released};
|
||||
|
||||
#define MIDI_INST(n) \
|
||||
BEHAVIOR_DT_INST_DEFINE(n, behavior_midi_key_press_init, NULL, NULL, NULL, POST_KERNEL, \
|
||||
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, \
|
||||
&behavior_midi_key_press_driver_api);
|
||||
|
||||
DT_INST_FOREACH_STATUS_OKAY(MIDI_INST)
|
||||
|
||||
#endif /* DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) */
|
|
@ -14,6 +14,8 @@
|
|||
#include <zmk/hid.h>
|
||||
#include <dt-bindings/zmk/hid_usage_pages.h>
|
||||
#include <zmk/usb_hid.h>
|
||||
#include <zmk/usb_midi.h>
|
||||
#include <zmk/midi.h>
|
||||
#include <zmk/hog.h>
|
||||
#include <zmk/event_manager.h>
|
||||
#include <zmk/events/ble_active_profile_changed.h>
|
||||
|
@ -239,6 +241,40 @@ int zmk_endpoints_send_mouse_report() {
|
|||
}
|
||||
#endif // IS_ENABLED(CONFIG_ZMK_MOUSE)
|
||||
|
||||
#if IS_ENABLED(CONFIG_ZMK_MIDI)
|
||||
int zmk_endpoints_send_midi_report() {
|
||||
|
||||
struct zmk_midi_report *midi_report = zmk_get_midi_report();
|
||||
switch (current_instance.transport) {
|
||||
case ZMK_TRANSPORT_USB: {
|
||||
#if IS_ENABLED(CONFIG_ZMK_USB)
|
||||
int err = zmk_usb_send_midi_report(&midi_report->body);
|
||||
if (err) {
|
||||
LOG_ERR("FAILED TO SEND OVER USB: %d", err);
|
||||
}
|
||||
return err;
|
||||
#else
|
||||
LOG_ERR("USB endpoint is not supported");
|
||||
return -ENOTSUP;
|
||||
#endif /* IS_ENABLED(CONFIG_ZMK_USB) */
|
||||
}
|
||||
|
||||
case ZMK_TRANSPORT_BLE: {
|
||||
#if IS_ENABLED(CONFIG_ZMK_BLE)
|
||||
LOG_ERR("BLE midi endpoint is not supported");
|
||||
return -ENOTSUP;
|
||||
#else
|
||||
LOG_ERR("BLE midi endpoint is not supported");
|
||||
return -ENOTSUP;
|
||||
#endif /* IS_ENABLED(CONFIG_ZMK_BLE) */
|
||||
}
|
||||
}
|
||||
|
||||
LOG_ERR("Unhandled endpoint transport %d", current_instance.transport);
|
||||
return -ENOTSUP;
|
||||
}
|
||||
#endif // IS_ENABLED(CONFIG_ZMK_MIDI)
|
||||
|
||||
#if IS_ENABLED(CONFIG_SETTINGS)
|
||||
|
||||
static int endpoints_handle_set(const char *name, size_t len, settings_read_cb read_cb,
|
||||
|
|
9
app/src/events/midi_key_state_changed.c
Normal file
9
app/src/events/midi_key_state_changed.c
Normal file
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* Copyright (c) 2024 The ZMK Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <zmk/events/midi_key_state_changed.h>
|
||||
|
||||
ZMK_EVENT_IMPL(zmk_midi_key_state_changed);
|
156
app/src/midi.c
Normal file
156
app/src/midi.c
Normal file
|
@ -0,0 +1,156 @@
|
|||
/*
|
||||
* Copyright (c) 2024 The ZMK Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "zmk/midi.h"
|
||||
#include <zephyr/logging/log.h>
|
||||
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
|
||||
#include <dt-bindings/zmk/modifiers.h>
|
||||
|
||||
static struct zmk_midi_report midi_report = {
|
||||
.report_id = ZMK_REPORT_ID_MIDI,
|
||||
.body = {.cin = MIDI_INVALID, .key = MIDI_INVALID, .key_value = MIDI_INVALID}};
|
||||
|
||||
static bool sustain_toggle_on = false;
|
||||
static bool sostenuto_toggle_on = false;
|
||||
|
||||
void set_bitmap(uint64_t map, uint32_t bit_num, bool value) {
|
||||
// do this in a function as WRITE_BIT
|
||||
// dirties the value in bitnum
|
||||
WRITE_BIT(map, bit_num, value);
|
||||
}
|
||||
|
||||
bool bit_is_set(uint64_t map, uint32_t bit_num) {
|
||||
// The BIT macro modifies the value, so using it outside of a function
|
||||
// can dirty the bit_num variable
|
||||
return (map & BIT(bit_num));
|
||||
}
|
||||
|
||||
void zmk_midi_report_clear() {
|
||||
midi_report.body.cin = MIDI_INVALID;
|
||||
midi_report.body.key = MIDI_INVALID;
|
||||
midi_report.body.key_value = MIDI_INVALID;
|
||||
}
|
||||
|
||||
int zmk_midi_key_press(const zmk_midi_key_t key) {
|
||||
LOG_INF("zmk_midi_key_press received: 0x%04x aka %d", key, key);
|
||||
|
||||
switch (key) {
|
||||
case MIDI_MIN_NOTE ... MIDI_MAX_NOTE:
|
||||
// and write and updated report
|
||||
zmk_midi_report_clear();
|
||||
midi_report.body.cin = ZMK_MIDI_CIN_NOTE_ON;
|
||||
midi_report.body.key = key;
|
||||
midi_report.body.key_value = ZMK_MIDI_ON_VELOCITY;
|
||||
break;
|
||||
case MIDI_MIN_CONTROL ... MIDI_MAX_CONTROL:
|
||||
zmk_midi_key_t control_key_transformed = (uint8_t)key;
|
||||
if (SUSTAIN == key) {
|
||||
if (!sustain_toggle_on) {
|
||||
// we set the toggle on in the release
|
||||
// since there will be 2 releases before we want
|
||||
// to turn off the toggle
|
||||
// dont set the toggle on here!
|
||||
zmk_midi_report_clear();
|
||||
midi_report.body.cin = ZMK_MIDI_CIN_CONTROL_CHANGE;
|
||||
midi_report.body.key = control_key_transformed;
|
||||
midi_report.body.key_value = ZMK_MIDI_TOGGLE_ON;
|
||||
} else {
|
||||
zmk_midi_report_clear();
|
||||
return -EINPROGRESS;
|
||||
}
|
||||
} else if (SOSTENUTO == key) {
|
||||
if (!sostenuto_toggle_on) {
|
||||
// we set the toggle on in the release
|
||||
// since there will be 2 releases before we want
|
||||
// to turn off the toggle
|
||||
// dont set the toggle on here!
|
||||
zmk_midi_report_clear();
|
||||
midi_report.body.cin = ZMK_MIDI_CIN_CONTROL_CHANGE;
|
||||
midi_report.body.key = control_key_transformed;
|
||||
midi_report.body.key_value = ZMK_MIDI_TOGGLE_ON;
|
||||
} else {
|
||||
zmk_midi_report_clear();
|
||||
return -EINPROGRESS;
|
||||
}
|
||||
} else {
|
||||
// not implemented
|
||||
zmk_midi_report_clear();
|
||||
LOG_INF("midi control handling not implemented");
|
||||
}
|
||||
return 0;
|
||||
break;
|
||||
default:
|
||||
LOG_ERR("Unsupported midi key %d", key);
|
||||
return -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int zmk_midi_key_release(const zmk_midi_key_t key) {
|
||||
LOG_INF("zmk_midi_key_release received: 0x%04x aka %d", key, key);
|
||||
|
||||
switch (key) {
|
||||
case MIDI_MIN_NOTE ... MIDI_MAX_NOTE:
|
||||
// write an updated report
|
||||
zmk_midi_report_clear();
|
||||
midi_report.body.cin = ZMK_MIDI_CIN_NOTE_OFF;
|
||||
midi_report.body.key = key;
|
||||
midi_report.body.key_value = ZMK_MIDI_OFF_VELOCITY;
|
||||
return 0;
|
||||
break;
|
||||
case MIDI_MIN_CONTROL ... MIDI_MAX_CONTROL:
|
||||
zmk_midi_key_t control_key_transformed = (uint8_t)key;
|
||||
if (SUSTAIN == key) {
|
||||
if (!sustain_toggle_on) {
|
||||
// the first release we see of a toggle we should ignore
|
||||
// otherwise it doesn't behave as a toggle!
|
||||
// just set the toggle variable
|
||||
sustain_toggle_on = true;
|
||||
zmk_midi_report_clear();
|
||||
return -EINPROGRESS;
|
||||
} else if (sustain_toggle_on) {
|
||||
sustain_toggle_on = false;
|
||||
zmk_midi_report_clear();
|
||||
midi_report.body.cin = ZMK_MIDI_CIN_CONTROL_CHANGE;
|
||||
midi_report.body.key = control_key_transformed;
|
||||
midi_report.body.key_value = ZMK_MIDI_TOGGLE_OFF;
|
||||
}
|
||||
} else if (SOSTENUTO == key) {
|
||||
if (!sostenuto_toggle_on) {
|
||||
// the first release we see of a toggle we should ignore
|
||||
// otherwise it doesn't behave as a toggle!
|
||||
// just set the toggle variable
|
||||
sostenuto_toggle_on = true;
|
||||
zmk_midi_report_clear();
|
||||
return -EINPROGRESS;
|
||||
} else if (sostenuto_toggle_on) {
|
||||
sostenuto_toggle_on = false;
|
||||
zmk_midi_report_clear();
|
||||
midi_report.body.cin = ZMK_MIDI_CIN_CONTROL_CHANGE;
|
||||
midi_report.body.key = control_key_transformed;
|
||||
midi_report.body.key_value = ZMK_MIDI_TOGGLE_OFF;
|
||||
}
|
||||
} else {
|
||||
// not implemented
|
||||
zmk_midi_report_clear();
|
||||
LOG_INF("midi control handling not implemented");
|
||||
}
|
||||
return 0;
|
||||
break;
|
||||
default:
|
||||
LOG_ERR("Unsupported midi key %d", key);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
void zmk_midi_clear(void) { memset(&midi_report.body, 0, sizeof(midi_report.body)); }
|
||||
|
||||
struct zmk_midi_report *zmk_get_midi_report(void) {
|
||||
return &midi_report;
|
||||
}
|
50
app/src/midi_listener.c
Normal file
50
app/src/midi_listener.c
Normal file
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* Copyright (c) 2024 The ZMK Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <drivers/behavior.h>
|
||||
#include <zephyr/logging/log.h>
|
||||
|
||||
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
|
||||
|
||||
#include <zmk/events/midi_key_state_changed.h>
|
||||
#include <zmk/endpoints.h>
|
||||
#include <zmk/midi.h>
|
||||
|
||||
static void listener_midi_key_pressed(const struct zmk_midi_key_state_changed *ev) {
|
||||
LOG_DBG("midi key: 0x%04X", ev->key);
|
||||
int ret = zmk_midi_key_press(ev->key);
|
||||
if (ret < 0) {
|
||||
LOG_DBG("listener_midi_key_pressed received error, ignoring");
|
||||
return;
|
||||
}
|
||||
zmk_endpoints_send_midi_report();
|
||||
}
|
||||
|
||||
static void listener_midi_key_released(const struct zmk_midi_key_state_changed *ev) {
|
||||
LOG_DBG("midi key: 0x%04X", ev->key);
|
||||
int ret = zmk_midi_key_release(ev->key);
|
||||
if (ret < 0) {
|
||||
LOG_DBG("listener_midi_key_released received error, ignoring");
|
||||
return;
|
||||
}
|
||||
zmk_endpoints_send_midi_report();
|
||||
}
|
||||
|
||||
int midi_listener(const zmk_event_t *eh) {
|
||||
const struct zmk_midi_key_state_changed *midi_key_ev = as_zmk_midi_key_state_changed(eh);
|
||||
if (midi_key_ev) {
|
||||
if (midi_key_ev->state) {
|
||||
listener_midi_key_pressed(midi_key_ev);
|
||||
} else {
|
||||
listener_midi_key_released(midi_key_ev);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
ZMK_LISTENER(midi_listener, midi_listener);
|
||||
ZMK_SUBSCRIPTION(midi_listener, zmk_midi_key_state_changed);
|
206
app/src/usb_midi.c
Normal file
206
app/src/usb_midi.c
Normal file
|
@ -0,0 +1,206 @@
|
|||
/*
|
||||
* Copyright (c) 2024 The ZMK Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <zephyr/usb/usb_device.h>
|
||||
#include <zmk/usb_midi.h>
|
||||
#include <zmk/usb_midi_packet.h>
|
||||
#include <zmk/usb.h>
|
||||
|
||||
#include <zephyr/logging/log.h>
|
||||
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
|
||||
|
||||
static K_SEM_DEFINE(midi_sem, 1, 1);
|
||||
|
||||
static int usb_midi_is_available = false;
|
||||
|
||||
// This macros should be used to place the USB descriptors
|
||||
// in predetermined order in the RAM.
|
||||
USBD_CLASS_DESCR_DEFINE(primary, 0)
|
||||
struct usb_midi_config usb_midi_config_data = {
|
||||
.ac_if = INIT_AC_IF,
|
||||
.ac_cs_if = INIT_AC_CS_IF,
|
||||
.ms_if = INIT_MS_IF,
|
||||
.ms_cs_if = INIT_MS_CS_IF,
|
||||
.out_jacks_emb = {LISTIFY(USB_MIDI_NUM_OUTPUTS, INIT_OUT_JACK, (, ), 0)},
|
||||
.in_jacks_emb = {LISTIFY(USB_MIDI_NUM_INPUTS, INIT_IN_JACK, (, ), USB_MIDI_NUM_OUTPUTS)},
|
||||
.element = INIT_ELEMENT,
|
||||
.in_ep = INIT_IN_EP,
|
||||
.in_cs_ep = {.bLength = sizeof(struct usb_midi_bulk_in_ep_descriptor),
|
||||
.bDescriptorType = USB_DESC_CS_ENDPOINT,
|
||||
.bDescriptorSubtype = 0x01,
|
||||
.bNumEmbMIDIJack = USB_MIDI_NUM_OUTPUTS,
|
||||
.BaAssocJackID = {LISTIFY(USB_MIDI_NUM_OUTPUTS, IDX_WITH_OFFSET, (, ), 1)}},
|
||||
.out_ep = INIT_OUT_EP,
|
||||
.out_cs_ep = {.bLength = sizeof(struct usb_midi_bulk_out_ep_descriptor),
|
||||
.bDescriptorType = USB_DESC_CS_ENDPOINT,
|
||||
.bDescriptorSubtype = 0x01,
|
||||
.bNumEmbMIDIJack = USB_MIDI_NUM_INPUTS,
|
||||
.BaAssocJackID = {LISTIFY(USB_MIDI_NUM_INPUTS, IDX_WITH_OFFSET, (, ),
|
||||
1 + USB_MIDI_NUM_OUTPUTS)}}};
|
||||
|
||||
void usb_status_callback(struct usb_cfg_data *cfg, enum usb_dc_status_code cb_status,
|
||||
const uint8_t *param) {
|
||||
switch (cb_status) {
|
||||
/** USB error reported by the controller */
|
||||
case USB_DC_ERROR:
|
||||
LOG_DBG("USB_DC_ERROR");
|
||||
break;
|
||||
/** USB reset */
|
||||
case USB_DC_RESET:
|
||||
LOG_DBG("USB_DC_RESET");
|
||||
break;
|
||||
/** USB connection established, hardware enumeration is completed */
|
||||
case USB_DC_CONNECTED:
|
||||
LOG_DBG("USB_DC_CONNECTED");
|
||||
break;
|
||||
/** USB configuration done */
|
||||
case USB_DC_CONFIGURED:
|
||||
LOG_DBG("USB_DC_CONFIGURED");
|
||||
LOG_INF("USB MIDI device is available");
|
||||
usb_midi_is_available = true;
|
||||
break;
|
||||
/** USB connection lost */
|
||||
case USB_DC_DISCONNECTED:
|
||||
LOG_DBG("USB_DC_DISCONNECTED");
|
||||
break;
|
||||
/** USB connection suspended by the HOST */
|
||||
case USB_DC_SUSPEND:
|
||||
LOG_DBG("USB_DC_SUSPEND");
|
||||
LOG_INF("USB MIDI device is unavailable");
|
||||
usb_midi_is_available = false;
|
||||
break;
|
||||
/** USB connection resumed by the HOST */
|
||||
case USB_DC_RESUME:
|
||||
LOG_DBG("USB_DC_RESUME");
|
||||
break;
|
||||
/** USB interface selected */
|
||||
case USB_DC_INTERFACE:
|
||||
LOG_DBG("USB_DC_INTERFACE");
|
||||
break;
|
||||
/** Set Feature ENDPOINT_HALT received */
|
||||
case USB_DC_SET_HALT:
|
||||
LOG_DBG("USB_DC_SET_HALT");
|
||||
break;
|
||||
/** Clear Feature ENDPOINT_HALT received */
|
||||
case USB_DC_CLEAR_HALT:
|
||||
LOG_DBG("USB_DC_CLEAR_HALT");
|
||||
break;
|
||||
/** Start of Frame received */
|
||||
case USB_DC_SOF:
|
||||
LOG_DBG("USB_DC_SOF");
|
||||
break;
|
||||
/** Initial USB connection status */
|
||||
case USB_DC_UNKNOWN:
|
||||
LOG_DBG("USB_DC_UNKNOWN");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void midi_out_ep_cb(uint8_t ep, enum usb_dc_ep_cb_status_code ep_status) {
|
||||
LOG_DBG("midi_out_ep_cb is not implemented");
|
||||
}
|
||||
|
||||
static void midi_in_ep_cb(uint8_t ep, enum usb_dc_ep_cb_status_code ep_status) {
|
||||
LOG_DBG("midi_in_ep_cb is not implemented");
|
||||
}
|
||||
|
||||
static struct usb_ep_cfg_data midi_ep_cfg[] = {{
|
||||
.ep_cb = midi_in_ep_cb,
|
||||
.ep_addr = ZMK_USB_MIDI_EP_IN,
|
||||
},
|
||||
{
|
||||
.ep_cb = midi_out_ep_cb,
|
||||
.ep_addr = ZMK_USB_MIDI_EP_OUT,
|
||||
}};
|
||||
|
||||
static void midi_interface_config(struct usb_desc_header *head, uint8_t bInterfaceNumber) {
|
||||
struct usb_if_descriptor *if_desc = (struct usb_if_descriptor *)head;
|
||||
struct usb_midi_config *desc = CONTAINER_OF(if_desc, struct usb_midi_config, ac_if);
|
||||
|
||||
desc->ac_if.bInterfaceNumber = bInterfaceNumber;
|
||||
desc->ms_if.bInterfaceNumber = bInterfaceNumber + 1;
|
||||
}
|
||||
|
||||
// this is the macro that sets up the usb device for midi
|
||||
USBD_DEFINE_CFG_DATA(usb_midi_config) = {
|
||||
.usb_device_description = NULL,
|
||||
.interface_config = midi_interface_config,
|
||||
.interface_descriptor = &usb_midi_config_data.ac_if,
|
||||
.cb_usb_status = usb_status_callback,
|
||||
.interface =
|
||||
{
|
||||
.class_handler = NULL,
|
||||
.custom_handler = NULL,
|
||||
.vendor_handler = NULL,
|
||||
},
|
||||
.num_endpoints = ARRAY_SIZE(midi_ep_cfg),
|
||||
.endpoint = midi_ep_cfg,
|
||||
};
|
||||
|
||||
static int zmk_usb_midi_send(uint8_t cable_number, uint8_t *midi_bytes, size_t len) {
|
||||
|
||||
LOG_INF("Sending midi bytes %02x %02x %02x", midi_bytes[0], midi_bytes[1], midi_bytes[2]);
|
||||
// prepare the packet
|
||||
struct usb_midi_packet_t packet;
|
||||
enum usb_midi_error_t error =
|
||||
usb_midi_packet_from_midi_bytes(midi_bytes, cable_number, &packet);
|
||||
if (error != USB_MIDI_SUCCESS) {
|
||||
LOG_ERR("Building packet from MIDI bytes %02x %02x %02x failed with error %d",
|
||||
midi_bytes[0], midi_bytes[1], midi_bytes[2], error);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
LOG_INF("Sending midi packet %02x %02x %02x %02x to endpoint %02x", packet.bytes[0],
|
||||
packet.bytes[1], packet.bytes[2], packet.bytes[3], ZMK_USB_MIDI_EP_IN);
|
||||
|
||||
// ensure usb is ready
|
||||
switch (zmk_usb_get_status()) {
|
||||
case USB_DC_SUSPEND:
|
||||
return usb_wakeup_request();
|
||||
case USB_DC_ERROR:
|
||||
case USB_DC_RESET:
|
||||
case USB_DC_DISCONNECTED:
|
||||
case USB_DC_UNKNOWN:
|
||||
return -ENODEV;
|
||||
default:
|
||||
k_sem_take(&midi_sem, K_MSEC(30));
|
||||
LOG_INF("doing midi usb_write");
|
||||
uint32_t num_written_bytes = 0;
|
||||
int ret = usb_write(ZMK_USB_MIDI_EP_IN, packet.bytes, 4, &num_written_bytes);
|
||||
if (ret < 0) {
|
||||
LOG_INF("usb_midi usb write error %d", ret);
|
||||
}
|
||||
LOG_INF("completed midi usb write %d", ret);
|
||||
|
||||
// TODO error if num_written_bytes != 4, make sure to release sem on error like usb_hid.c
|
||||
|
||||
// TODO usb_hid.c holds the sem until its in_ready_cb is hit. do we have something like
|
||||
// this? usb status seems to be different, perhaps that is using hid status? anyway, for now
|
||||
// just release the sem right after we transmit
|
||||
|
||||
k_sem_give(&midi_sem);
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int zmk_usb_send_midi_report(struct zmk_midi_key_report_body *body) {
|
||||
uint8_t midi_bytes[USB_MIDI_MAX_NUM_BYTES];
|
||||
|
||||
LOG_INF("body cin = %d, key = %d, key_value = %d", body->cin, body->key, body->key_value);
|
||||
|
||||
if (body->key > 0 && body->key < MIDI_INVALID && body->key_value < MIDI_INVALID) {
|
||||
|
||||
midi_bytes[0] = body->cin; // note on, note off, control change, etc
|
||||
midi_bytes[1] = body->key; // the note, control change code, etc
|
||||
midi_bytes[2] = body->key_value; // the velocity, or control change value, etc
|
||||
} else {
|
||||
LOG_ERR("No valid midi key!");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return zmk_usb_midi_send(USB_MIDI_DEFAULT_CABLE_NUM, midi_bytes, USB_MIDI_MAX_NUM_BYTES);
|
||||
}
|
214
app/src/usb_midi_packet.c
Normal file
214
app/src/usb_midi_packet.c
Normal file
|
@ -0,0 +1,214 @@
|
|||
/*
|
||||
* Copyright (c) 2024 The ZMK Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <zmk/usb_midi_packet.h>
|
||||
|
||||
#define SYSEX_START_BYTE 0xF0
|
||||
#define SYSEX_END_BYTE 0xF7
|
||||
|
||||
static enum usb_midi_error_t channel_msg_cin(uint8_t first_byte, uint8_t *cin) {
|
||||
uint8_t high_nibble = first_byte >> 4;
|
||||
|
||||
switch (high_nibble) {
|
||||
case 0x8: /* Note off */
|
||||
case 0x9: /* Note on */
|
||||
case 0xa: /* Poly KeyPress */
|
||||
case 0xb: /* Control Change */
|
||||
case 0xe: /* PitchBend Change */
|
||||
/* Three byte channel Voice Message */
|
||||
*cin = high_nibble;
|
||||
break;
|
||||
case 0xc: /* Program Change */
|
||||
case 0xd: /* Channel Pressure */
|
||||
/* Two byte channel Voice Message */
|
||||
*cin = high_nibble;
|
||||
break;
|
||||
default:
|
||||
/* Invalid status byte */
|
||||
return USB_MIDI_ERROR_INVALID_MIDI_MSG;
|
||||
}
|
||||
|
||||
/* Valid status byte */
|
||||
return USB_MIDI_SUCCESS;
|
||||
}
|
||||
|
||||
static enum usb_midi_error_t non_sysex_system_msg_cin(uint8_t first_byte, uint8_t *cin) {
|
||||
switch (first_byte) {
|
||||
case 0xf1: /* MIDI Time Code Quarter Frame */
|
||||
case 0xf3: /* Song Select */
|
||||
/* 2 byte System Common message */
|
||||
*cin = USB_MIDI_CIN_SYSCOM_2BYTE;
|
||||
break;
|
||||
case 0xf2: /* Song Position Pointer */
|
||||
/* 3 byte System Common message */
|
||||
*cin = USB_MIDI_CIN_SYSCOM_3BYTE;
|
||||
break;
|
||||
case 0xf6: /* Tune request */
|
||||
/* Single-byte System Common Message */
|
||||
*cin = USB_MIDI_CIN_SYS_COMMON_OR_SYSEX_END_1BYTE;
|
||||
break;
|
||||
case 0xf8: /* Timing Clock */
|
||||
case 0xfa: /* Start */
|
||||
case 0xfb: /* Continue */
|
||||
case 0xfc: /* Stop */
|
||||
case 0xfe: /* Active Sensing */
|
||||
case 0xff: /* System Reset */
|
||||
/* 1 byte system real time */
|
||||
*cin = USB_MIDI_CIN_1BYTE_DATA;
|
||||
break;
|
||||
default:
|
||||
/* Invalid status byte */
|
||||
return USB_MIDI_ERROR_INVALID_MIDI_MSG;
|
||||
}
|
||||
|
||||
/* Valid status byte */
|
||||
return USB_MIDI_SUCCESS;
|
||||
}
|
||||
|
||||
static enum usb_midi_error_t sysex_msg_cin(uint8_t *midi_bytes, uint8_t *cin) {
|
||||
int is_data_byte[3] = {midi_bytes[0] < 0x80, midi_bytes[1] < 0x80, midi_bytes[2] < 0x80};
|
||||
|
||||
if (midi_bytes[0] == SYSEX_START_BYTE) {
|
||||
if (midi_bytes[1] == SYSEX_END_BYTE) {
|
||||
/* Sysex case 1: F0 F7 */
|
||||
*cin = USB_MIDI_CIN_SYSEX_END_2BYTE;
|
||||
} else if (is_data_byte[1]) {
|
||||
if (midi_bytes[2] == SYSEX_END_BYTE) {
|
||||
/* Sysex case 2: F0 d F7 */
|
||||
*cin = USB_MIDI_CIN_SYSEX_END_3BYTE;
|
||||
} else if (is_data_byte[2]) {
|
||||
/* Sysex case 3: F0 d d */
|
||||
*cin = USB_MIDI_CIN_SYSEX_START_OR_CONTINUE;
|
||||
}
|
||||
}
|
||||
} else if (is_data_byte[0]) {
|
||||
if (is_data_byte[1]) {
|
||||
if (is_data_byte[2]) {
|
||||
/* Sysex case 4: d d d */
|
||||
*cin = USB_MIDI_CIN_SYSEX_START_OR_CONTINUE;
|
||||
} else if (midi_bytes[2] == SYSEX_END_BYTE) {
|
||||
/* Sysex case 5: d d F7 */
|
||||
*cin = USB_MIDI_CIN_SYSEX_END_3BYTE;
|
||||
}
|
||||
} else if (midi_bytes[1] == SYSEX_END_BYTE) {
|
||||
/* Sysex case 6: d F7 */
|
||||
*cin = USB_MIDI_CIN_SYSEX_END_2BYTE;
|
||||
}
|
||||
} else if (midi_bytes[0] == SYSEX_END_BYTE) {
|
||||
/* Sysex case 7: F7 */
|
||||
*cin = USB_MIDI_CIN_SYS_COMMON_OR_SYSEX_END_1BYTE;
|
||||
} else {
|
||||
/* Invalid sysex sequence */
|
||||
return USB_MIDI_ERROR_INVALID_MIDI_MSG;
|
||||
}
|
||||
|
||||
/* Valid sysex sequence */
|
||||
return USB_MIDI_SUCCESS;
|
||||
}
|
||||
|
||||
static uint8_t num_midi_bytes_for_cin(uint8_t cin) {
|
||||
switch (cin) {
|
||||
case USB_MIDI_CIN_MISC:
|
||||
case USB_MIDI_CIN_CABLE_EVENT:
|
||||
/* Reserved for future expansion. Ignore. */
|
||||
return 0;
|
||||
case USB_MIDI_CIN_SYS_COMMON_OR_SYSEX_END_1BYTE:
|
||||
case USB_MIDI_CIN_1BYTE_DATA:
|
||||
return 1;
|
||||
case USB_MIDI_CIN_SYSCOM_2BYTE:
|
||||
case USB_MIDI_CIN_SYSEX_END_2BYTE:
|
||||
case USB_MIDI_CIN_PROGRAM_CHANGE:
|
||||
case USB_MIDI_CIN_CHANNEL_PRESSURE:
|
||||
return 2;
|
||||
default:
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
|
||||
enum usb_midi_error_t usb_midi_packet_from_midi_bytes(uint8_t *midi_bytes, uint8_t cable_num,
|
||||
struct usb_midi_packet_t *packet) {
|
||||
/* Building a USB MIDI packet from a MIDI message amounts to determining the code
|
||||
* index number (CIN) corresponding to the message. This in turn determines the
|
||||
* size of the MIDI message.
|
||||
*
|
||||
* The MIDI message is assumed to not contain interleaved system real time bytes.
|
||||
*
|
||||
* A MIDI message contained in a USB MIDI packet is 1, 2 or 3 bytes long. It either
|
||||
*
|
||||
* 1. is a channel message starting with one of the follwing status bytes
|
||||
* followed by data bytes: (n is the MIDI channel)
|
||||
*
|
||||
* 8n - note off, cin 0x8
|
||||
* 9n - note on, cin 0x9
|
||||
* An - Polyphonic aftertouch, cin 0xa
|
||||
* Bn - Control change, cin 0xb
|
||||
* Cn - Program change, cin 0xc
|
||||
* Dn - Channel aftertouch, cin 0xd
|
||||
* En - Pitch bend change, cin 0xe
|
||||
*
|
||||
* 2. is a non-sysex system message starting with one of the following
|
||||
* status bytes followed by zero or more data bytes
|
||||
* (F4, F5, F9 and FD are undefined. F0, F7 are sysex)
|
||||
*
|
||||
* F1 - MIDI Time Code Qtr. Frame, cin 0x2
|
||||
* F2 - Song Position Pointer, cin 0x3
|
||||
* F3 - Song Select, cin 0x2
|
||||
* F6 - Tune request, cin 0x5
|
||||
* F8 - Timing clock, cin 0xf
|
||||
* FA - Start, cin 0xf
|
||||
* FB - Continue, cin 0xf
|
||||
* FC - Stop, cin 0xf
|
||||
* FE - Active Sensing, cin 0xf
|
||||
* FF - System reset, cin 0xf
|
||||
*
|
||||
* 3. is a (partial) sysex message, taking one of the following forms (d is a data byte)
|
||||
* F0, F7 - sysex case 1, cin 0x6 (SysEx ends with following two bytes)
|
||||
* F0, d, F7 - sysex case 2, cin 0x7 (SysEx ends with following three bytes)
|
||||
* F0, d, d - sysex case 3, cin 0x4 (SysEx starts or continues)
|
||||
* d, d, d - sysex case 4, cin 0x4 (SysEx starts or continues)
|
||||
* d, d, F7 - sysex case 5, cin 0x7 (SysEx ends with following three bytes)
|
||||
* d, F7 - sysex case 6, cin 0x6 (SysEx ends with following two bytes)
|
||||
* F7 - sysex case 7, cin 0x5 (Single-byte System Common Message or
|
||||
* SysEx ends with following single byte.)
|
||||
*/
|
||||
|
||||
if (cable_num >= 16) {
|
||||
return USB_MIDI_ERROR_INVALID_CABLE_NUM;
|
||||
}
|
||||
|
||||
packet->cable_num = cable_num;
|
||||
packet->cin = 0;
|
||||
packet->num_midi_bytes = 0;
|
||||
|
||||
enum usb_midi_error_t cin_error = channel_msg_cin(midi_bytes[0], &packet->cin);
|
||||
if (cin_error != USB_MIDI_SUCCESS) {
|
||||
cin_error = non_sysex_system_msg_cin(midi_bytes[0], &packet->cin);
|
||||
}
|
||||
if (cin_error != USB_MIDI_SUCCESS) {
|
||||
cin_error = sysex_msg_cin(midi_bytes, &packet->cin);
|
||||
}
|
||||
|
||||
packet->num_midi_bytes = num_midi_bytes_for_cin(packet->cin);
|
||||
|
||||
if (cin_error != USB_MIDI_SUCCESS || packet->num_midi_bytes == 0) {
|
||||
/* Invalid MIDI message. */
|
||||
return USB_MIDI_ERROR_INVALID_MIDI_MSG;
|
||||
}
|
||||
|
||||
/* Put cable number and CIN in packet byte 0 */
|
||||
packet->bytes[0] = (packet->cable_num << 4) | packet->cin;
|
||||
|
||||
/* Fill packet bytes 1,2 and 3 with zero padded midi bytes. */
|
||||
packet->bytes[1] = 0;
|
||||
packet->bytes[2] = 0;
|
||||
packet->bytes[3] = 0;
|
||||
for (int i = 0; i < packet->num_midi_bytes; i++) {
|
||||
packet->bytes[i + 1] = midi_bytes[i];
|
||||
}
|
||||
|
||||
/* No errors */
|
||||
return USB_MIDI_SUCCESS;
|
||||
}
|
|
@ -72,6 +72,12 @@ Below is a summary of pre-defined behavior bindings and user-definable behaviors
|
|||
| `&ext_power` | [Power management](power.md#behavior-binding) | Allows enabling or disabling the VCC power output to save power |
|
||||
| `&soft_off` | [Soft off](soft-off.md#behavior-binding) | Turns the keyboard off. |
|
||||
|
||||
## MIDI Behaviors
|
||||
|
||||
| Binding | Behavior | Description |
|
||||
| ------- | ------------------------------------------ | ----------------------------------- |
|
||||
| `&midi` | [MIDI Key Press](midi.md#behavior-binding) | Sends MIDI messages to the USB host |
|
||||
|
||||
## User-Defined Behaviors
|
||||
|
||||
| Behavior | Description |
|
||||
|
|
82
docs/docs/behaviors/midi.md
Normal file
82
docs/docs/behaviors/midi.md
Normal file
|
@ -0,0 +1,82 @@
|
|||
---
|
||||
title: MIDI Behavior
|
||||
sidebar_label: MIDI
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
The MIDI feature allows a keyboard to send MIDI messages to host.
|
||||
|
||||
Unlike other behaviors, MIDI only works over usb. Bluetooth MIDI is not supported.
|
||||
|
||||
Currently, only sending MIDI messages is supported. Boards cannot receive MIDI messages.
|
||||
|
||||
## Enabling MIDI support
|
||||
|
||||
MIDI support has been tested on both the `bluemicro840_v1` and the `nice_nano_v2`
|
||||
|
||||
1. add the config option to the boards `.conf`
|
||||
|
||||
```ini
|
||||
CONFIG_ZMK_MIDI=y
|
||||
```
|
||||
|
||||
2. include the dt-binding header file at the top of the boards `.keymap`
|
||||
|
||||
```dts
|
||||
#include <dt-bindings/zmk/midi.h>
|
||||
```
|
||||
|
||||
enabling MIDI support adds two new USB endpoints to the board. On linux, these get picked up by the `snd-usb-audio` driver
|
||||
|
||||
```
|
||||
sudo cat /sys/kernel/debug/usb/devices
|
||||
...
|
||||
...
|
||||
T: Bus=03 Lev=01 Prnt=01 Port=00 Cnt=01 Dev#= 17 Spd=12 MxCh= 0
|
||||
D: Ver= 2.00 Cls=00(>ifc ) Sub=00 Prot=00 MxPS=64 #Cfgs= 1
|
||||
P: Vendor=1d50 ProdID=615e Rev= 3.05
|
||||
S: Manufacturer=ZMK Project
|
||||
S: Product=btrfld
|
||||
S: SerialNumber=DF4A1D9720CD8BD8
|
||||
C:* #Ifs= 3 Cfg#= 1 Atr=e0 MxPwr=100mA
|
||||
I:* If#= 0 Alt= 0 #EPs= 0 Cls=01(audio) Sub=01 Prot=00 Driver=snd-usb-audio
|
||||
I:* If#= 1 Alt= 0 #EPs= 2 Cls=01(audio) Sub=03 Prot=00 Driver=snd-usb-audio
|
||||
E: Ad=01(O) Atr=02(Bulk) MxPS= 64 Ivl=0ms
|
||||
E: Ad=81(I) Atr=02(Bulk) MxPS= 64 Ivl=0ms
|
||||
I:* If#= 2 Alt= 0 #EPs= 1 Cls=03(HID ) Sub=00 Prot=00 Driver=usbhid
|
||||
E: Ad=82(I) Atr=03(Int.) MxPS= 16 Ivl=1ms
|
||||
```
|
||||
|
||||
## MIDI keycodes
|
||||
|
||||
MIDI keycodes are defined in the header [`dt-bindings/zmk/midi.h`](https://github.com/zmkfirmware/zmk/blob/main/app/include/dt-bindings/zmk/midi.h)
|
||||
|
||||
The majority of the keycode defines are the Note On/Off messages, which are denoted by `NOTE_*`There is one for each note in a standard octave, with 10 octaves available.
|
||||
|
||||
There is also support for control change messages, with `SUSTAIN`, `PORTAMENTO`, and `SOSTENUTO` currently implemented.
|
||||
|
||||
The following documents can be used to learn about all of the possible MIDI messages:
|
||||
https://midi.org/summary-of-midi-1-0-messages
|
||||
https://www.cs.cmu.edu/~music/cmsip/readings/MIDI%20tutorial%20for%20programmers.html
|
||||
|
||||
## Behavior Binding
|
||||
|
||||
- Reference: `&midi`
|
||||
- Parameter #1: The midi keycode, e.g. `NOTE_C_5` or `SUSTAIN`
|
||||
|
||||
### Examples
|
||||
|
||||
1. while pressed, sends the E9 Note
|
||||
|
||||
```dts
|
||||
&midi NOTE_E_9
|
||||
```
|
||||
|
||||
2. while pressed, presses the sustain pedal
|
||||
|
||||
```dts
|
||||
&midi SUSTAIN
|
||||
```
|
||||
|
||||
MIDI keycodes can be combined with the other zmk behaviors to create interesting instruments.
|
Loading…
Add table
Reference in a new issue