midi_control/
lib.rs

1//
2// (c) 2020 Hubert Figuière
3//
4// License: LGPL-3.0-or-later
5
6//! MIDI control is a crate to allow creating MIDI message to control
7//! MIDI messages.
8//!
9//! ## Example
10//!
11//! To simply prepare a Note On message for note 60 (Middle C) at
12//! maximum velocity:
13//!
14//! ```rust
15//! use midi_control::*;
16//! let message = midi_control::note_on(Channel::Ch1, 60, 127);
17//! let buffer: Vec<u8> = message.into();
18//! /* send the buffer to your MIDI device */
19//! ```
20//!
21//! If you have the `transport` feature enabled, you can use midir directly
22//! to send the same message.
23//!
24//! ```no_run
25//! use midi_control::*;
26//! use midi_control::transport::MidiMessageSend;  // to use the trait
27//! # use midir;
28//! # let out = midir::MidiOutput::new("MIDITest").unwrap();
29//! # let port = &out.ports()[0];
30//! # let mut midi_out: midir::MidiOutputConnection = out.connect(port, "MIDI device").unwrap();
31//! let message = midi_control::note_on(Channel::Ch1, 60, 127);
32//! // midi_out is a midir::MidiOutputConnection
33//! midi_out.send_message(message);
34//! ```
35
36pub mod consts;
37pub mod message;
38pub mod note;
39pub mod sysex;
40#[cfg(feature = "transport")]
41pub mod transport;
42pub mod vendor;
43
44pub use crate::message::{Channel, ControlEvent, KeyEvent, MidiMessage, SysExEvent};
45pub use crate::note::MidiNote;
46#[cfg(feature = "transport")]
47pub use crate::transport::MidiMessageSend;
48
49/// Create a Note On message
50pub fn note_on(channel: Channel, note: MidiNote, velocity: u8) -> MidiMessage {
51    MidiMessage::NoteOn(
52        channel,
53        KeyEvent {
54            key: note,
55            value: velocity,
56        },
57    )
58}
59
60/// Create a Note Off message
61pub fn note_off(channel: Channel, note: MidiNote, velocity: u8) -> MidiMessage {
62    MidiMessage::NoteOff(
63        channel,
64        KeyEvent {
65            key: note,
66            value: velocity,
67        },
68    )
69}
70
71/// Create a Poly Key Pressure message
72pub fn poly_key_pressure(channel: Channel, note: MidiNote, pressure: u8) -> MidiMessage {
73    MidiMessage::PolyKeyPressure(
74        channel,
75        KeyEvent {
76            key: note,
77            value: pressure,
78        },
79    )
80}
81
82/// Create a Control Change message
83pub fn control_change(channel: Channel, control: u8, value: u8) -> MidiMessage {
84    MidiMessage::ControlChange(channel, ControlEvent { control, value })
85}
86
87/// Create a Program Change message
88pub fn program_change(channel: Channel, program: u8) -> MidiMessage {
89    MidiMessage::ProgramChange(channel, program)
90}
91
92/// Create a Channel Pressure message
93pub fn channel_pressure(channel: Channel, pressure: u8) -> MidiMessage {
94    MidiMessage::ProgramChange(channel, pressure)
95}
96
97/// Create a Pitch Bend message
98///
99/// # Error
100/// Will return an `MidiMessage::Invalid` message if `change` is more than 14bits
101pub fn pitch_bend(channel: Channel, change: u16) -> MidiMessage {
102    if change > 0x3fff {
103        return MidiMessage::Invalid;
104    }
105    let lsb: u8 = (change & 0b0111_1111) as u8;
106    let msb: u8 = ((change >> 7) & 0b0111_1111) as u8;
107    MidiMessage::PitchBend(channel, lsb, msb)
108}
109
110/// Create a non Realtime Universal SysEx message.
111pub fn non_rt_usysex(device: u8, subid: [u8; 2], data: &[u8]) -> MidiMessage {
112    MidiMessage::SysEx(SysExEvent::new_non_realtime(device, subid, data))
113}
114
115#[cfg(test)]
116mod tests {
117    use super::*;
118
119    #[test]
120    pub fn test_simple_api() {
121        let msg = MidiMessage::NoteOn(Channel::Ch1, KeyEvent { key: 59, value: 88 });
122        assert_eq!(note_on(Channel::Ch1, 59, 88), msg);
123
124        let msg = MidiMessage::NoteOff(Channel::Ch1, KeyEvent { key: 60, value: 0 });
125        assert_eq!(note_off(Channel::Ch1, 60, 0), msg);
126
127        let msg = MidiMessage::ControlChange(
128            Channel::Ch1,
129            ControlEvent {
130                control: 114,
131                value: 65,
132            },
133        );
134        assert_eq!(control_change(Channel::Ch1, 114, 65), msg);
135
136        let msg = MidiMessage::PitchBend(Channel::Ch1, 0, 76);
137        assert_eq!(pitch_bend(Channel::Ch1, 0x2600), msg);
138
139        // pitch bend value is too large
140        assert_eq!(pitch_bend(Channel::Ch1, 0x8000), MidiMessage::Invalid);
141
142        let msg = MidiMessage::SysEx(SysExEvent::new_non_realtime(
143            consts::usysex::ALL_CALL,
144            [0x06, 0x01],
145            &[0xf7],
146        ));
147        assert_eq!(
148            non_rt_usysex(consts::usysex::ALL_CALL, [0x06, 0x01], &[0xf7]),
149            msg
150        );
151    }
152}