1use std::collections::BTreeMap;
2
3use sim_kernel::{Error, Result, Symbol};
4
5use crate::{
6 Channel, LaneId, Music, PerformanceEvent, PerformanceInput, PerformanceIntent, PerformanceTake,
7 Pitch, Tick,
8};
9
10#[derive(Clone, Debug, PartialEq, Eq)]
15pub struct PerformanceInputBinding {
16 pub input_id: Symbol,
18 pub lane_id: LaneId,
20 pub channel: Channel,
22}
23
24impl PerformanceInputBinding {
25 pub fn new(input_id: Symbol, lane_id: LaneId, channel: Channel) -> Self {
27 Self {
28 input_id,
29 lane_id,
30 channel,
31 }
32 }
33}
34
35#[derive(Clone, Debug, PartialEq, Eq)]
52pub struct ScaleLock {
53 pub allowed_classes: Vec<u8>,
55}
56
57impl ScaleLock {
58 pub fn new(mut allowed_classes: Vec<u8>) -> Result<Self> {
63 if allowed_classes.is_empty() || allowed_classes.iter().any(|class| *class >= 12) {
64 return Err(Error::Eval(
65 "scale lock pitch classes must be in 0..12".to_owned(),
66 ));
67 }
68 allowed_classes.sort_unstable();
69 allowed_classes.dedup();
70 Ok(Self { allowed_classes })
71 }
72
73 pub fn major() -> Self {
75 Self::new(vec![0, 2, 4, 5, 7, 9, 11]).expect("major scale lock is valid")
76 }
77
78 pub fn apply(&self, pitch: Pitch) -> Pitch {
83 let semitone = pitch.semitone();
84 let class = semitone.rem_euclid(12) as u8;
85 if self.allowed_classes.binary_search(&class).is_ok() {
86 return pitch;
87 }
88 let delta = (-6..=6)
89 .filter(|delta| {
90 let candidate = (class as i32 + delta).rem_euclid(12) as u8;
91 self.allowed_classes.binary_search(&candidate).is_ok()
92 })
93 .min_by_key(|delta| (delta.abs(), (*delta > 0) as u8))
94 .expect("scale lock has at least one class");
95 Pitch::from_semitone(semitone + delta)
96 }
97}
98
99#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
103pub struct PerformanceNoteKey {
104 pub channel: u8,
106 pub semitone: i32,
108}
109
110impl PerformanceNoteKey {
111 pub fn new(channel: Channel, pitch: Pitch) -> Self {
113 Self {
114 channel: channel.0,
115 semitone: pitch.semitone(),
116 }
117 }
118}
119
120#[derive(Clone, Debug, PartialEq, Eq)]
125pub struct HeldPerformanceNote {
126 pub pitch: Pitch,
128 pub velocity: u8,
130 pub channel: Channel,
132 pub started_at: Tick,
134 pub released_while_sustained: bool,
136 pub key_down: bool,
138 pub sostenuto_captured: bool,
140}
141
142#[derive(Clone, Debug, PartialEq, Eq)]
147pub struct PerformanceSourceState {
148 pub held_notes: BTreeMap<PerformanceNoteKey, HeldPerformanceNote>,
150 pub sustain_pedal: bool,
152 pub sostenuto_pedal: bool,
154 pub octave_shift: i8,
156 pub transpose: i8,
158 pub scale_lock: Option<ScaleLock>,
160 pub channel: Channel,
162}
163
164impl PerformanceSourceState {
165 pub fn new(channel: Channel) -> Self {
167 Self {
168 held_notes: BTreeMap::new(),
169 sustain_pedal: false,
170 sostenuto_pedal: false,
171 octave_shift: 0,
172 transpose: 0,
173 scale_lock: None,
174 channel,
175 }
176 }
177
178 pub fn held_note_count(&self) -> usize {
180 self.held_notes.len()
181 }
182
183 fn transform_pitch(&self, pitch: Pitch) -> Pitch {
184 let transposed =
185 pitch.transpose(i32::from(self.transpose) + i32::from(self.octave_shift) * 12);
186 self.scale_lock
187 .as_ref()
188 .map(|lock| lock.apply(transposed))
189 .unwrap_or(transposed)
190 }
191
192 fn observe_event(&mut self, event: &PerformanceEvent) {
193 match &event.intent {
194 PerformanceIntent::NoteOn {
195 pitch,
196 velocity,
197 channel,
198 } => {
199 self.held_notes.insert(
200 PerformanceNoteKey::new(*channel, *pitch),
201 HeldPerformanceNote {
202 pitch: *pitch,
203 velocity: *velocity,
204 channel: *channel,
205 started_at: event.time,
206 released_while_sustained: false,
207 key_down: true,
208 sostenuto_captured: false,
209 },
210 );
211 }
212 PerformanceIntent::NoteOff { pitch, channel, .. } => {
213 let key = PerformanceNoteKey::new(*channel, *pitch);
214 let held = if let Some(note) = self.held_notes.get_mut(&key) {
215 note.key_down = false;
216 note.released_while_sustained = self.sustain_pedal;
217 self.sustain_pedal || (self.sostenuto_pedal && note.sostenuto_captured)
218 } else {
219 false
220 };
221 if !held {
222 self.held_notes.remove(&key);
223 }
224 }
225 PerformanceIntent::Sustain { down, .. } => {
226 self.sustain_pedal = *down;
227 if !down {
228 self.held_notes.retain(|_, note| {
229 note.key_down || (self.sostenuto_pedal && note.sostenuto_captured)
230 });
231 }
232 }
233 PerformanceIntent::Sostenuto { down, .. } => {
234 if *down && !self.sostenuto_pedal {
235 for note in self.held_notes.values_mut() {
236 note.sostenuto_captured = true;
237 }
238 }
239 self.sostenuto_pedal = *down;
240 if !down {
241 self.held_notes
242 .retain(|_, note| note.key_down || self.sustain_pedal);
243 for note in self.held_notes.values_mut() {
244 note.sostenuto_captured = false;
245 }
246 }
247 }
248 PerformanceIntent::AllNotesOff { channel } => {
249 for note in self
250 .held_notes
251 .values_mut()
252 .filter(|note| note.channel == *channel)
253 {
254 note.key_down = false;
255 note.released_while_sustained = self.sustain_pedal;
256 }
257 self.held_notes.retain(|_, note| {
258 note.channel != *channel
259 || self.sustain_pedal
260 || (self.sostenuto_pedal && note.sostenuto_captured)
261 });
262 }
263 PerformanceIntent::AllSoundOff { channel } => {
264 self.held_notes.retain(|_, note| note.channel != *channel);
265 }
266 PerformanceIntent::ResetControllers { channel } => {
267 self.sustain_pedal = false;
268 self.sostenuto_pedal = false;
269 self.held_notes
270 .retain(|_, note| note.channel != *channel || note.key_down);
271 for note in self.held_notes.values_mut() {
272 if note.channel == *channel {
273 note.sostenuto_captured = false;
274 note.released_while_sustained = false;
275 }
276 }
277 }
278 PerformanceIntent::Panic => {
279 self.held_notes.clear();
280 self.sustain_pedal = false;
281 self.sostenuto_pedal = false;
282 }
283 PerformanceIntent::Aftertouch { .. }
284 | PerformanceIntent::PitchBend { .. }
285 | PerformanceIntent::Parameter { .. } => {}
286 }
287 }
288}
289
290pub trait PerformanceSource {
296 fn bind_input(&mut self, binding: PerformanceInputBinding) -> Result<()>;
298 fn poll_events(&mut self, inputs: Vec<PerformanceInput>) -> Result<Vec<PerformanceEvent>>;
300 fn panic(&mut self, input_time: Tick) -> Result<Vec<PerformanceEvent>>;
302 fn capture_start(&mut self, take_id: Symbol) -> Result<()>;
304 fn capture_stop(&mut self) -> Result<PerformanceTake>;
306 fn as_clip(&self, take: &PerformanceTake) -> Result<Music>;
308}
309
310#[derive(Clone, Debug, PartialEq, Eq)]
312pub struct MemoryPerformanceSource {
313 source_id: Symbol,
314 binding: Option<PerformanceInputBinding>,
315 state: PerformanceSourceState,
316 capture: Option<PerformanceCapture>,
317}
318
319impl MemoryPerformanceSource {
320 pub fn new(source_id: Symbol, channel: Channel) -> Self {
322 Self {
323 source_id,
324 binding: None,
325 state: PerformanceSourceState::new(channel),
326 capture: None,
327 }
328 }
329
330 pub fn source_id(&self) -> &Symbol {
332 &self.source_id
333 }
334
335 pub fn state(&self) -> &PerformanceSourceState {
337 &self.state
338 }
339
340 pub fn state_mut(&mut self) -> &mut PerformanceSourceState {
342 &mut self.state
343 }
344
345 pub fn set_octave_shift(&mut self, octave_shift: i8) {
347 self.state.octave_shift = octave_shift;
348 }
349
350 pub fn set_transpose(&mut self, transpose: i8) {
352 self.state.transpose = transpose;
353 }
354
355 pub fn set_scale_lock(&mut self, scale_lock: Option<ScaleLock>) {
357 self.state.scale_lock = scale_lock;
358 }
359
360 fn binding(&self) -> Result<&PerformanceInputBinding> {
361 self.binding
362 .as_ref()
363 .ok_or_else(|| Error::Eval("performance source input is not bound".to_owned()))
364 }
365
366 fn push_capture(&mut self, events: &[PerformanceEvent]) {
367 if let Some(capture) = &mut self.capture {
368 capture.events.extend_from_slice(events);
369 }
370 }
371}
372
373impl PerformanceSource for MemoryPerformanceSource {
374 fn bind_input(&mut self, binding: PerformanceInputBinding) -> Result<()> {
375 self.state.channel = binding.channel;
376 self.binding = Some(binding);
377 Ok(())
378 }
379
380 fn poll_events(&mut self, inputs: Vec<PerformanceInput>) -> Result<Vec<PerformanceEvent>> {
381 let binding = self.binding()?.clone();
382 let mut events = Vec::new();
383 for input in inputs {
384 let event = PerformanceEvent {
385 lane_id: binding.lane_id.clone(),
386 source_id: self.source_id.clone(),
387 input_time: input.input_time,
388 time: input.input_time,
389 intent: transform_intent(input.intent, &self.state),
390 };
391 self.state.observe_event(&event);
392 events.push(event);
393 }
394 self.push_capture(&events);
395 Ok(events)
396 }
397
398 fn panic(&mut self, input_time: Tick) -> Result<Vec<PerformanceEvent>> {
399 let binding = self.binding()?.clone();
400 let mut events = self
401 .state
402 .held_notes
403 .values()
404 .map(|note| PerformanceEvent {
405 lane_id: binding.lane_id.clone(),
406 source_id: self.source_id.clone(),
407 input_time,
408 time: input_time,
409 intent: PerformanceIntent::NoteOff {
410 pitch: note.pitch,
411 velocity: 0,
412 channel: note.channel,
413 },
414 })
415 .collect::<Vec<_>>();
416 events.push(PerformanceEvent {
417 lane_id: binding.lane_id,
418 source_id: self.source_id.clone(),
419 input_time,
420 time: input_time,
421 intent: PerformanceIntent::Panic,
422 });
423 for event in &events {
424 self.state.observe_event(event);
425 }
426 self.push_capture(&events);
427 Ok(events)
428 }
429
430 fn capture_start(&mut self, take_id: Symbol) -> Result<()> {
431 if self.capture.is_some() {
432 return Err(Error::Eval(
433 "performance capture is already active".to_owned(),
434 ));
435 }
436 self.capture = Some(PerformanceCapture {
437 take_id,
438 events: Vec::new(),
439 });
440 Ok(())
441 }
442
443 fn capture_stop(&mut self) -> Result<PerformanceTake> {
444 let capture = self
445 .capture
446 .take()
447 .ok_or_else(|| Error::Eval("performance capture is not active".to_owned()))?;
448 PerformanceTake::new(self.source_id.clone(), capture.take_id, capture.events)
449 }
450
451 fn as_clip(&self, take: &PerformanceTake) -> Result<Music> {
452 take.as_clip()
453 }
454}
455
456#[derive(Clone, Debug, PartialEq, Eq)]
457struct PerformanceCapture {
458 take_id: Symbol,
459 events: Vec<PerformanceEvent>,
460}
461
462fn transform_intent(
463 intent: PerformanceIntent,
464 state: &PerformanceSourceState,
465) -> PerformanceIntent {
466 match intent {
467 PerformanceIntent::NoteOn {
468 pitch,
469 velocity,
470 channel,
471 } => PerformanceIntent::NoteOn {
472 pitch: state.transform_pitch(pitch),
473 velocity,
474 channel,
475 },
476 PerformanceIntent::NoteOff {
477 pitch,
478 velocity,
479 channel,
480 } => PerformanceIntent::NoteOff {
481 pitch: state.transform_pitch(pitch),
482 velocity,
483 channel,
484 },
485 PerformanceIntent::Aftertouch {
486 pitch,
487 pressure,
488 channel,
489 } => PerformanceIntent::Aftertouch {
490 pitch: state.transform_pitch(pitch),
491 pressure,
492 channel,
493 },
494 other => other,
495 }
496}