Add conversions from note events to MIDI
This commit is contained in:
parent
66f5a0e7de
commit
0d2330d773
1 changed files with 175 additions and 0 deletions
175
src/midi.rs
175
src/midi.rs
|
@ -232,6 +232,85 @@ impl NoteEvent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a MIDI message from this note event. Return `None` if this even does not have a
|
||||||
|
/// direct MIDI equivalent. `PolyPressure` will be converted to polyphonic key pressure, but the
|
||||||
|
/// other polyphonic note expression types will not be converted to MIDI CC messages.
|
||||||
|
pub fn as_midi(self) -> Option<[u8; 3]> {
|
||||||
|
match self {
|
||||||
|
NoteEvent::NoteOn {
|
||||||
|
timing: _,
|
||||||
|
channel,
|
||||||
|
note,
|
||||||
|
velocity,
|
||||||
|
} => Some([
|
||||||
|
midi::NOTE_ON | channel,
|
||||||
|
note,
|
||||||
|
(velocity * 127.0).round().clamp(0.0, 127.0) as u8,
|
||||||
|
]),
|
||||||
|
NoteEvent::NoteOff {
|
||||||
|
timing: _,
|
||||||
|
channel,
|
||||||
|
note,
|
||||||
|
velocity,
|
||||||
|
} => Some([
|
||||||
|
midi::NOTE_OFF | channel,
|
||||||
|
note,
|
||||||
|
(velocity * 127.0).round().clamp(0.0, 127.0) as u8,
|
||||||
|
]),
|
||||||
|
NoteEvent::PolyPressure {
|
||||||
|
timing: _,
|
||||||
|
channel,
|
||||||
|
note,
|
||||||
|
pressure,
|
||||||
|
} => Some([
|
||||||
|
midi::POLYPHONIC_KEY_PRESSURE | channel,
|
||||||
|
note,
|
||||||
|
(pressure * 127.0).round().clamp(0.0, 127.0) as u8,
|
||||||
|
]),
|
||||||
|
NoteEvent::MidiChannelPressure {
|
||||||
|
timing: _,
|
||||||
|
channel,
|
||||||
|
pressure,
|
||||||
|
} => Some([
|
||||||
|
midi::CHANNEL_KEY_PRESSURE | channel,
|
||||||
|
(pressure * 127.0).round().clamp(0.0, 127.0) as u8,
|
||||||
|
0,
|
||||||
|
]),
|
||||||
|
NoteEvent::MidiPitchBend {
|
||||||
|
timing: _,
|
||||||
|
channel,
|
||||||
|
value,
|
||||||
|
} => {
|
||||||
|
const PITCH_BEND_RANGE: f32 = ((1 << 14) - 1) as f32;
|
||||||
|
let midi_value = (value * PITCH_BEND_RANGE)
|
||||||
|
.round()
|
||||||
|
.clamp(0.0, PITCH_BEND_RANGE) as u16;
|
||||||
|
|
||||||
|
Some([
|
||||||
|
midi::PITCH_BEND_CHANGE | channel,
|
||||||
|
(midi_value & ((1 << 7) - 1)) as u8,
|
||||||
|
(midi_value >> 7) as u8,
|
||||||
|
])
|
||||||
|
}
|
||||||
|
NoteEvent::MidiCC {
|
||||||
|
timing: _,
|
||||||
|
channel,
|
||||||
|
cc,
|
||||||
|
value,
|
||||||
|
} => Some([
|
||||||
|
midi::CONTROL_CHANGE | channel,
|
||||||
|
cc,
|
||||||
|
(value * 127.0).round().clamp(0.0, 127.0) as u8,
|
||||||
|
]),
|
||||||
|
NoteEvent::PolyVolume { .. }
|
||||||
|
| NoteEvent::PolyPan { .. }
|
||||||
|
| NoteEvent::PolyTuning { .. }
|
||||||
|
| NoteEvent::PolyVibrato { .. }
|
||||||
|
| NoteEvent::PolyExpression { .. }
|
||||||
|
| NoteEvent::PolyBrightness { .. } => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Subtract a sample offset from this event's timing, needed to compensate for the block
|
/// Subtract a sample offset from this event's timing, needed to compensate for the block
|
||||||
/// splitting in the VST3 wrapper implementation because all events have to be read upfront.
|
/// splitting in the VST3 wrapper implementation because all events have to be read upfront.
|
||||||
pub(crate) fn subtract_timing(&mut self, samples: u32) {
|
pub(crate) fn subtract_timing(&mut self, samples: u32) {
|
||||||
|
@ -251,3 +330,99 @@ impl NoteEvent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
const TIMING: u32 = 5;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_note_on_midi_conversion() {
|
||||||
|
let event = NoteEvent::NoteOn {
|
||||||
|
timing: TIMING,
|
||||||
|
channel: 1,
|
||||||
|
note: 2,
|
||||||
|
// The value will be rounded in the conversion to MIDI, hence this overly specific value
|
||||||
|
velocity: 0.6929134,
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
NoteEvent::from_midi(TIMING, event.as_midi().unwrap()).unwrap(),
|
||||||
|
event
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_note_off_midi_conversion() {
|
||||||
|
let event = NoteEvent::NoteOff {
|
||||||
|
timing: TIMING,
|
||||||
|
channel: 1,
|
||||||
|
note: 2,
|
||||||
|
velocity: 0.6929134,
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
NoteEvent::from_midi(TIMING, event.as_midi().unwrap()).unwrap(),
|
||||||
|
event
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_poly_pressure_midi_conversion() {
|
||||||
|
let event = NoteEvent::PolyPressure {
|
||||||
|
timing: TIMING,
|
||||||
|
channel: 1,
|
||||||
|
note: 2,
|
||||||
|
pressure: 0.6929134,
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
NoteEvent::from_midi(TIMING, event.as_midi().unwrap()).unwrap(),
|
||||||
|
event
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_channel_pressure_midi_conversion() {
|
||||||
|
let event = NoteEvent::MidiChannelPressure {
|
||||||
|
timing: TIMING,
|
||||||
|
channel: 1,
|
||||||
|
pressure: 0.6929134,
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
NoteEvent::from_midi(TIMING, event.as_midi().unwrap()).unwrap(),
|
||||||
|
event
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_pitch_bend_midi_conversion() {
|
||||||
|
let event = NoteEvent::MidiPitchBend {
|
||||||
|
timing: TIMING,
|
||||||
|
channel: 1,
|
||||||
|
value: 0.6929134,
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
NoteEvent::from_midi(TIMING, event.as_midi().unwrap()).unwrap(),
|
||||||
|
event
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_cc_midi_conversion() {
|
||||||
|
let event = NoteEvent::MidiCC {
|
||||||
|
timing: TIMING,
|
||||||
|
channel: 1,
|
||||||
|
cc: 2,
|
||||||
|
value: 0.6929134,
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
NoteEvent::from_midi(TIMING, event.as_midi().unwrap()).unwrap(),
|
||||||
|
event
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue