use std::collections::HashMap;
#[derive(Debug, Clone, Copy)]
pub struct MpeConfig {
pub lower_zone_size: u8,
pub upper_zone_size: u8,
pub pitch_bend_range: f32,
}
impl Default for MpeConfig {
fn default() -> Self {
Self {
lower_zone_size: 15, upper_zone_size: 0, pitch_bend_range: 48.0,
}
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct NoteExpression {
pub note: u8,
pub channel: u8,
pub velocity: u8,
pub pitch_bend: f32,
pub pressure: f32,
pub timbre: f32,
}
pub struct MpeEngine {
config: MpeConfig,
active_notes: HashMap<(u8, u8), NoteExpression>,
note_to_channel: HashMap<u8, u8>,
next_channel: u8,
}
impl MpeEngine {
pub fn new() -> Self {
Self::with_config(MpeConfig::default())
}
pub fn with_config(config: MpeConfig) -> Self {
Self {
config,
active_notes: HashMap::new(),
note_to_channel: HashMap::new(),
next_channel: 2, }
}
pub fn config(&self) -> &MpeConfig {
&self.config
}
pub fn set_config(&mut self, config: MpeConfig) {
self.config = config;
}
pub fn note_on(&mut self, note: u8, velocity: u8) -> NoteExpression {
let channel = self.allocate_channel();
self.note_to_channel.insert(note, channel);
let expr = NoteExpression {
note,
channel,
velocity,
pitch_bend: 0.0,
pressure: 0.0,
timbre: 0.0,
};
self.active_notes.insert((channel, note), expr);
expr
}
pub fn note_off(&mut self, note: u8) -> Option<NoteExpression> {
if let Some(&channel) = self.note_to_channel.get(¬e) {
self.note_to_channel.remove(¬e);
self.active_notes.remove(&(channel, note))
} else {
None
}
}
pub fn pitch_bend(&mut self, channel: u8, value: u16) {
let normalized = (value as f32 - 8192.0) / 8192.0; let semitones = normalized * self.config.pitch_bend_range;
for ((ch, _note), expr) in self.active_notes.iter_mut() {
if *ch == channel {
expr.pitch_bend = semitones;
}
}
}
pub fn channel_pressure(&mut self, channel: u8, value: u8) {
let normalized = value as f32 / 127.0;
for ((ch, _note), expr) in self.active_notes.iter_mut() {
if *ch == channel {
expr.pressure = normalized;
}
}
}
pub fn timbre(&mut self, channel: u8, value: u8) {
let normalized = value as f32 / 127.0;
for ((ch, _note), expr) in self.active_notes.iter_mut() {
if *ch == channel {
expr.timbre = normalized;
}
}
}
pub fn get_note_expression(&self, note: u8) -> Option<&NoteExpression> {
if let Some(&channel) = self.note_to_channel.get(¬e) {
self.active_notes.get(&(channel, note))
} else {
None
}
}
pub fn active_notes(&self) -> impl Iterator<Item = &NoteExpression> {
self.active_notes.values()
}
pub fn active_note_count(&self) -> usize {
self.active_notes.len()
}
pub fn clear_all_notes(&mut self) {
self.active_notes.clear();
self.note_to_channel.clear();
self.next_channel = 2;
}
fn allocate_channel(&mut self) -> u8 {
let max_channel = if self.config.lower_zone_size > 0 {
1 + self.config.lower_zone_size
} else {
16
};
let channel = self.next_channel;
self.next_channel += 1;
if self.next_channel > max_channel {
self.next_channel = 2; }
channel
}
}
impl Default for MpeEngine {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_mpe_note_on_off() {
let mut mpe = MpeEngine::new();
let expr = mpe.note_on(60, 100);
assert_eq!(expr.note, 60);
assert_eq!(expr.velocity, 100);
assert_eq!(expr.channel, 2); assert_eq!(mpe.active_note_count(), 1);
let removed = mpe.note_off(60);
assert!(removed.is_some());
assert_eq!(mpe.active_note_count(), 0);
}
#[test]
fn test_mpe_pitch_bend() {
let mut mpe = MpeEngine::new();
let expr = mpe.note_on(60, 100);
let channel = expr.channel;
mpe.pitch_bend(channel, 8192 + 170);
let updated = mpe.get_note_expression(60).unwrap();
assert!(updated.pitch_bend > 0.9 && updated.pitch_bend < 1.1);
}
#[test]
fn test_mpe_pressure() {
let mut mpe = MpeEngine::new();
let expr = mpe.note_on(60, 100);
let channel = expr.channel;
mpe.channel_pressure(channel, 64);
let updated = mpe.get_note_expression(60).unwrap();
assert!((updated.pressure - 0.5).abs() < 0.01);
}
#[test]
fn test_mpe_timbre() {
let mut mpe = MpeEngine::new();
let expr = mpe.note_on(60, 100);
let channel = expr.channel;
mpe.timbre(channel, 127);
let updated = mpe.get_note_expression(60).unwrap();
assert_eq!(updated.timbre, 1.0);
}
#[test]
fn test_mpe_channel_allocation() {
let mut mpe = MpeEngine::new();
let expr1 = mpe.note_on(60, 100);
let expr2 = mpe.note_on(62, 100);
let expr3 = mpe.note_on(64, 100);
assert_eq!(expr1.channel, 2);
assert_eq!(expr2.channel, 3);
assert_eq!(expr3.channel, 4);
}
#[test]
fn test_mpe_clear_all() {
let mut mpe = MpeEngine::new();
mpe.note_on(60, 100);
mpe.note_on(62, 100);
mpe.note_on(64, 100);
assert_eq!(mpe.active_note_count(), 3);
mpe.clear_all_notes();
assert_eq!(mpe.active_note_count(), 0);
}
}