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}
137
138#[derive(Clone, Debug, PartialEq, Eq)]
143pub struct PerformanceSourceState {
144 pub held_notes: BTreeMap<PerformanceNoteKey, HeldPerformanceNote>,
146 pub sustain_pedal: bool,
148 pub octave_shift: i8,
150 pub transpose: i8,
152 pub scale_lock: Option<ScaleLock>,
154 pub channel: Channel,
156}
157
158impl PerformanceSourceState {
159 pub fn new(channel: Channel) -> Self {
161 Self {
162 held_notes: BTreeMap::new(),
163 sustain_pedal: false,
164 octave_shift: 0,
165 transpose: 0,
166 scale_lock: None,
167 channel,
168 }
169 }
170
171 pub fn held_note_count(&self) -> usize {
173 self.held_notes.len()
174 }
175
176 fn transform_pitch(&self, pitch: Pitch) -> Pitch {
177 let transposed =
178 pitch.transpose(i32::from(self.transpose) + i32::from(self.octave_shift) * 12);
179 self.scale_lock
180 .as_ref()
181 .map(|lock| lock.apply(transposed))
182 .unwrap_or(transposed)
183 }
184
185 fn observe_event(&mut self, event: &PerformanceEvent) {
186 match &event.intent {
187 PerformanceIntent::NoteOn {
188 pitch,
189 velocity,
190 channel,
191 } => {
192 self.held_notes.insert(
193 PerformanceNoteKey::new(*channel, *pitch),
194 HeldPerformanceNote {
195 pitch: *pitch,
196 velocity: *velocity,
197 channel: *channel,
198 started_at: event.time,
199 released_while_sustained: false,
200 },
201 );
202 }
203 PerformanceIntent::NoteOff { pitch, channel, .. } => {
204 let key = PerformanceNoteKey::new(*channel, *pitch);
205 if self.sustain_pedal {
206 if let Some(note) = self.held_notes.get_mut(&key) {
207 note.released_while_sustained = true;
208 }
209 } else {
210 self.held_notes.remove(&key);
211 }
212 }
213 PerformanceIntent::Sustain { down, .. } => {
214 self.sustain_pedal = *down;
215 if !down {
216 self.held_notes
217 .retain(|_, note| !note.released_while_sustained);
218 }
219 }
220 PerformanceIntent::Panic => {
221 self.held_notes.clear();
222 self.sustain_pedal = false;
223 }
224 PerformanceIntent::Aftertouch { .. }
225 | PerformanceIntent::PitchBend { .. }
226 | PerformanceIntent::Parameter { .. } => {}
227 }
228 }
229}
230
231pub trait PerformanceSource {
237 fn bind_input(&mut self, binding: PerformanceInputBinding) -> Result<()>;
239 fn poll_events(&mut self, inputs: Vec<PerformanceInput>) -> Result<Vec<PerformanceEvent>>;
241 fn panic(&mut self, input_time: Tick) -> Result<Vec<PerformanceEvent>>;
243 fn capture_start(&mut self, take_id: Symbol) -> Result<()>;
245 fn capture_stop(&mut self) -> Result<PerformanceTake>;
247 fn as_clip(&self, take: &PerformanceTake) -> Result<Music>;
249}
250
251#[derive(Clone, Debug, PartialEq, Eq)]
253pub struct MemoryPerformanceSource {
254 source_id: Symbol,
255 binding: Option<PerformanceInputBinding>,
256 state: PerformanceSourceState,
257 capture: Option<PerformanceCapture>,
258}
259
260impl MemoryPerformanceSource {
261 pub fn new(source_id: Symbol, channel: Channel) -> Self {
263 Self {
264 source_id,
265 binding: None,
266 state: PerformanceSourceState::new(channel),
267 capture: None,
268 }
269 }
270
271 pub fn source_id(&self) -> &Symbol {
273 &self.source_id
274 }
275
276 pub fn state(&self) -> &PerformanceSourceState {
278 &self.state
279 }
280
281 pub fn state_mut(&mut self) -> &mut PerformanceSourceState {
283 &mut self.state
284 }
285
286 pub fn set_octave_shift(&mut self, octave_shift: i8) {
288 self.state.octave_shift = octave_shift;
289 }
290
291 pub fn set_transpose(&mut self, transpose: i8) {
293 self.state.transpose = transpose;
294 }
295
296 pub fn set_scale_lock(&mut self, scale_lock: Option<ScaleLock>) {
298 self.state.scale_lock = scale_lock;
299 }
300
301 fn binding(&self) -> Result<&PerformanceInputBinding> {
302 self.binding
303 .as_ref()
304 .ok_or_else(|| Error::Eval("performance source input is not bound".to_owned()))
305 }
306
307 fn push_capture(&mut self, events: &[PerformanceEvent]) {
308 if let Some(capture) = &mut self.capture {
309 capture.events.extend_from_slice(events);
310 }
311 }
312}
313
314impl PerformanceSource for MemoryPerformanceSource {
315 fn bind_input(&mut self, binding: PerformanceInputBinding) -> Result<()> {
316 self.state.channel = binding.channel;
317 self.binding = Some(binding);
318 Ok(())
319 }
320
321 fn poll_events(&mut self, inputs: Vec<PerformanceInput>) -> Result<Vec<PerformanceEvent>> {
322 let binding = self.binding()?.clone();
323 let mut events = Vec::new();
324 for input in inputs {
325 let event = PerformanceEvent {
326 lane_id: binding.lane_id.clone(),
327 source_id: self.source_id.clone(),
328 input_time: input.input_time,
329 time: input.input_time,
330 intent: transform_intent(input.intent, &self.state),
331 };
332 self.state.observe_event(&event);
333 events.push(event);
334 }
335 self.push_capture(&events);
336 Ok(events)
337 }
338
339 fn panic(&mut self, input_time: Tick) -> Result<Vec<PerformanceEvent>> {
340 let binding = self.binding()?.clone();
341 let mut events = self
342 .state
343 .held_notes
344 .values()
345 .map(|note| PerformanceEvent {
346 lane_id: binding.lane_id.clone(),
347 source_id: self.source_id.clone(),
348 input_time,
349 time: input_time,
350 intent: PerformanceIntent::NoteOff {
351 pitch: note.pitch,
352 velocity: 0,
353 channel: note.channel,
354 },
355 })
356 .collect::<Vec<_>>();
357 events.push(PerformanceEvent {
358 lane_id: binding.lane_id,
359 source_id: self.source_id.clone(),
360 input_time,
361 time: input_time,
362 intent: PerformanceIntent::Panic,
363 });
364 for event in &events {
365 self.state.observe_event(event);
366 }
367 self.push_capture(&events);
368 Ok(events)
369 }
370
371 fn capture_start(&mut self, take_id: Symbol) -> Result<()> {
372 if self.capture.is_some() {
373 return Err(Error::Eval(
374 "performance capture is already active".to_owned(),
375 ));
376 }
377 self.capture = Some(PerformanceCapture {
378 take_id,
379 events: Vec::new(),
380 });
381 Ok(())
382 }
383
384 fn capture_stop(&mut self) -> Result<PerformanceTake> {
385 let capture = self
386 .capture
387 .take()
388 .ok_or_else(|| Error::Eval("performance capture is not active".to_owned()))?;
389 PerformanceTake::new(self.source_id.clone(), capture.take_id, capture.events)
390 }
391
392 fn as_clip(&self, take: &PerformanceTake) -> Result<Music> {
393 take.as_clip()
394 }
395}
396
397#[derive(Clone, Debug, PartialEq, Eq)]
398struct PerformanceCapture {
399 take_id: Symbol,
400 events: Vec<PerformanceEvent>,
401}
402
403fn transform_intent(
404 intent: PerformanceIntent,
405 state: &PerformanceSourceState,
406) -> PerformanceIntent {
407 match intent {
408 PerformanceIntent::NoteOn {
409 pitch,
410 velocity,
411 channel,
412 } => PerformanceIntent::NoteOn {
413 pitch: state.transform_pitch(pitch),
414 velocity,
415 channel,
416 },
417 PerformanceIntent::NoteOff {
418 pitch,
419 velocity,
420 channel,
421 } => PerformanceIntent::NoteOff {
422 pitch: state.transform_pitch(pitch),
423 velocity,
424 channel,
425 },
426 PerformanceIntent::Aftertouch {
427 pitch,
428 pressure,
429 channel,
430 } => PerformanceIntent::Aftertouch {
431 pitch: state.transform_pitch(pitch),
432 pressure,
433 channel,
434 },
435 other => other,
436 }
437}