1use std::collections::HashMap;
21
22#[derive(Debug, Clone, Copy)]
24pub struct MpeConfig {
25 pub lower_zone_size: u8,
28
29 pub upper_zone_size: u8,
32
33 pub pitch_bend_range: f32,
35}
36
37impl Default for MpeConfig {
38 fn default() -> Self {
39 Self {
40 lower_zone_size: 15, upper_zone_size: 0, pitch_bend_range: 48.0,
43 }
44 }
45}
46
47#[derive(Debug, Clone, Copy, Default)]
49pub struct NoteExpression {
50 pub note: u8,
52
53 pub channel: u8,
55
56 pub velocity: u8,
58
59 pub pitch_bend: f32,
61
62 pub pressure: f32,
64
65 pub timbre: f32,
67}
68
69pub struct MpeEngine {
71 config: MpeConfig,
72
73 active_notes: HashMap<(u8, u8), NoteExpression>,
75
76 note_to_channel: HashMap<u8, u8>,
79
80 next_channel: u8,
82}
83
84impl MpeEngine {
85 pub fn new() -> Self {
87 Self::with_config(MpeConfig::default())
88 }
89
90 pub fn with_config(config: MpeConfig) -> Self {
92 Self {
93 config,
94 active_notes: HashMap::new(),
95 note_to_channel: HashMap::new(),
96 next_channel: 2, }
98 }
99
100 pub fn config(&self) -> &MpeConfig {
102 &self.config
103 }
104
105 pub fn set_config(&mut self, config: MpeConfig) {
107 self.config = config;
108 }
109
110 pub fn note_on(&mut self, note: u8, velocity: u8) -> NoteExpression {
123 let channel = self.allocate_channel();
125 self.note_to_channel.insert(note, channel);
126
127 let expr = NoteExpression {
128 note,
129 channel,
130 velocity,
131 pitch_bend: 0.0,
132 pressure: 0.0,
133 timbre: 0.0,
134 };
135
136 self.active_notes.insert((channel, note), expr);
137 expr
138 }
139
140 pub fn note_off(&mut self, note: u8) -> Option<NoteExpression> {
150 if let Some(&channel) = self.note_to_channel.get(¬e) {
151 self.note_to_channel.remove(¬e);
152 self.active_notes.remove(&(channel, note))
153 } else {
154 None
155 }
156 }
157
158 pub fn pitch_bend(&mut self, channel: u8, value: u16) {
165 let normalized = (value as f32 - 8192.0) / 8192.0; let semitones = normalized * self.config.pitch_bend_range;
168
169 for ((ch, _note), expr) in self.active_notes.iter_mut() {
171 if *ch == channel {
172 expr.pitch_bend = semitones;
173 }
174 }
175 }
176
177 pub fn channel_pressure(&mut self, channel: u8, value: u8) {
184 let normalized = value as f32 / 127.0;
185
186 for ((ch, _note), expr) in self.active_notes.iter_mut() {
188 if *ch == channel {
189 expr.pressure = normalized;
190 }
191 }
192 }
193
194 pub fn timbre(&mut self, channel: u8, value: u8) {
201 let normalized = value as f32 / 127.0;
202
203 for ((ch, _note), expr) in self.active_notes.iter_mut() {
205 if *ch == channel {
206 expr.timbre = normalized;
207 }
208 }
209 }
210
211 pub fn get_note_expression(&self, note: u8) -> Option<&NoteExpression> {
221 if let Some(&channel) = self.note_to_channel.get(¬e) {
222 self.active_notes.get(&(channel, note))
223 } else {
224 None
225 }
226 }
227
228 pub fn active_notes(&self) -> impl Iterator<Item = &NoteExpression> {
230 self.active_notes.values()
231 }
232
233 pub fn active_note_count(&self) -> usize {
235 self.active_notes.len()
236 }
237
238 pub fn clear_all_notes(&mut self) {
240 self.active_notes.clear();
241 self.note_to_channel.clear();
242 self.next_channel = 2;
243 }
244
245 fn allocate_channel(&mut self) -> u8 {
247 let max_channel = if self.config.lower_zone_size > 0 {
248 1 + self.config.lower_zone_size
249 } else {
250 16
251 };
252
253 let channel = self.next_channel;
254 self.next_channel += 1;
255 if self.next_channel > max_channel {
256 self.next_channel = 2; }
258
259 channel
260 }
261}
262
263impl Default for MpeEngine {
264 fn default() -> Self {
265 Self::new()
266 }
267}
268
269#[cfg(test)]
270mod tests {
271 use super::*;
272
273 #[test]
274 fn test_mpe_note_on_off() {
275 let mut mpe = MpeEngine::new();
276
277 let expr = mpe.note_on(60, 100);
279 assert_eq!(expr.note, 60);
280 assert_eq!(expr.velocity, 100);
281 assert_eq!(expr.channel, 2); assert_eq!(mpe.active_note_count(), 1);
283
284 let removed = mpe.note_off(60);
286 assert!(removed.is_some());
287 assert_eq!(mpe.active_note_count(), 0);
288 }
289
290 #[test]
291 fn test_mpe_pitch_bend() {
292 let mut mpe = MpeEngine::new();
293
294 let expr = mpe.note_on(60, 100);
295 let channel = expr.channel;
296
297 mpe.pitch_bend(channel, 8192 + 170); let updated = mpe.get_note_expression(60).unwrap();
301 assert!(updated.pitch_bend > 0.9 && updated.pitch_bend < 1.1);
302 }
303
304 #[test]
305 fn test_mpe_pressure() {
306 let mut mpe = MpeEngine::new();
307
308 let expr = mpe.note_on(60, 100);
309 let channel = expr.channel;
310
311 mpe.channel_pressure(channel, 64); let updated = mpe.get_note_expression(60).unwrap();
315 assert!((updated.pressure - 0.5).abs() < 0.01);
316 }
317
318 #[test]
319 fn test_mpe_timbre() {
320 let mut mpe = MpeEngine::new();
321
322 let expr = mpe.note_on(60, 100);
323 let channel = expr.channel;
324
325 mpe.timbre(channel, 127); let updated = mpe.get_note_expression(60).unwrap();
329 assert_eq!(updated.timbre, 1.0);
330 }
331
332 #[test]
333 fn test_mpe_channel_allocation() {
334 let mut mpe = MpeEngine::new();
335
336 let expr1 = mpe.note_on(60, 100);
338 let expr2 = mpe.note_on(62, 100);
339 let expr3 = mpe.note_on(64, 100);
340
341 assert_eq!(expr1.channel, 2);
343 assert_eq!(expr2.channel, 3);
344 assert_eq!(expr3.channel, 4);
345 }
346
347 #[test]
348 fn test_mpe_clear_all() {
349 let mut mpe = MpeEngine::new();
350
351 mpe.note_on(60, 100);
352 mpe.note_on(62, 100);
353 mpe.note_on(64, 100);
354 assert_eq!(mpe.active_note_count(), 3);
355
356 mpe.clear_all_notes();
357 assert_eq!(mpe.active_note_count(), 0);
358 }
359}