use std::collections::BTreeMap;
use sim_kernel::{Error, Result, Symbol};
use crate::{
Channel, LaneId, Music, PerformanceEvent, PerformanceInput, PerformanceIntent, PerformanceTake,
Pitch, Tick,
};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PerformanceInputBinding {
pub input_id: Symbol,
pub lane_id: LaneId,
pub channel: Channel,
}
impl PerformanceInputBinding {
pub fn new(input_id: Symbol, lane_id: LaneId, channel: Channel) -> Self {
Self {
input_id,
lane_id,
channel,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ScaleLock {
pub allowed_classes: Vec<u8>,
}
impl ScaleLock {
pub fn new(mut allowed_classes: Vec<u8>) -> Result<Self> {
if allowed_classes.is_empty() || allowed_classes.iter().any(|class| *class >= 12) {
return Err(Error::Eval(
"scale lock pitch classes must be in 0..12".to_owned(),
));
}
allowed_classes.sort_unstable();
allowed_classes.dedup();
Ok(Self { allowed_classes })
}
pub fn major() -> Self {
Self::new(vec![0, 2, 4, 5, 7, 9, 11]).expect("major scale lock is valid")
}
pub fn apply(&self, pitch: Pitch) -> Pitch {
let semitone = pitch.semitone();
let class = semitone.rem_euclid(12) as u8;
if self.allowed_classes.binary_search(&class).is_ok() {
return pitch;
}
let delta = (-6..=6)
.filter(|delta| {
let candidate = (class as i32 + delta).rem_euclid(12) as u8;
self.allowed_classes.binary_search(&candidate).is_ok()
})
.min_by_key(|delta| (delta.abs(), (*delta > 0) as u8))
.expect("scale lock has at least one class");
Pitch::from_semitone(semitone + delta)
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct PerformanceNoteKey {
pub channel: u8,
pub semitone: i32,
}
impl PerformanceNoteKey {
pub fn new(channel: Channel, pitch: Pitch) -> Self {
Self {
channel: channel.0,
semitone: pitch.semitone(),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct HeldPerformanceNote {
pub pitch: Pitch,
pub velocity: u8,
pub channel: Channel,
pub started_at: Tick,
pub released_while_sustained: bool,
pub key_down: bool,
pub sostenuto_captured: bool,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PerformanceSourceState {
pub held_notes: BTreeMap<PerformanceNoteKey, HeldPerformanceNote>,
pub sustain_pedal: bool,
pub sostenuto_pedal: bool,
pub octave_shift: i8,
pub transpose: i8,
pub scale_lock: Option<ScaleLock>,
pub channel: Channel,
}
impl PerformanceSourceState {
pub fn new(channel: Channel) -> Self {
Self {
held_notes: BTreeMap::new(),
sustain_pedal: false,
sostenuto_pedal: false,
octave_shift: 0,
transpose: 0,
scale_lock: None,
channel,
}
}
pub fn held_note_count(&self) -> usize {
self.held_notes.len()
}
fn transform_pitch(&self, pitch: Pitch) -> Pitch {
let transposed =
pitch.transpose(i32::from(self.transpose) + i32::from(self.octave_shift) * 12);
self.scale_lock
.as_ref()
.map(|lock| lock.apply(transposed))
.unwrap_or(transposed)
}
fn observe_event(&mut self, event: &PerformanceEvent) {
match &event.intent {
PerformanceIntent::NoteOn {
pitch,
velocity,
channel,
} => {
self.held_notes.insert(
PerformanceNoteKey::new(*channel, *pitch),
HeldPerformanceNote {
pitch: *pitch,
velocity: *velocity,
channel: *channel,
started_at: event.time,
released_while_sustained: false,
key_down: true,
sostenuto_captured: false,
},
);
}
PerformanceIntent::NoteOff { pitch, channel, .. } => {
let key = PerformanceNoteKey::new(*channel, *pitch);
let held = if let Some(note) = self.held_notes.get_mut(&key) {
note.key_down = false;
note.released_while_sustained = self.sustain_pedal;
self.sustain_pedal || (self.sostenuto_pedal && note.sostenuto_captured)
} else {
false
};
if !held {
self.held_notes.remove(&key);
}
}
PerformanceIntent::Sustain { down, .. } => {
self.sustain_pedal = *down;
if !down {
self.held_notes.retain(|_, note| {
note.key_down || (self.sostenuto_pedal && note.sostenuto_captured)
});
}
}
PerformanceIntent::Sostenuto { down, .. } => {
if *down && !self.sostenuto_pedal {
for note in self.held_notes.values_mut() {
note.sostenuto_captured = true;
}
}
self.sostenuto_pedal = *down;
if !down {
self.held_notes
.retain(|_, note| note.key_down || self.sustain_pedal);
for note in self.held_notes.values_mut() {
note.sostenuto_captured = false;
}
}
}
PerformanceIntent::AllNotesOff { channel } => {
for note in self
.held_notes
.values_mut()
.filter(|note| note.channel == *channel)
{
note.key_down = false;
note.released_while_sustained = self.sustain_pedal;
}
self.held_notes.retain(|_, note| {
note.channel != *channel
|| self.sustain_pedal
|| (self.sostenuto_pedal && note.sostenuto_captured)
});
}
PerformanceIntent::AllSoundOff { channel } => {
self.held_notes.retain(|_, note| note.channel != *channel);
}
PerformanceIntent::ResetControllers { channel } => {
self.sustain_pedal = false;
self.sostenuto_pedal = false;
self.held_notes
.retain(|_, note| note.channel != *channel || note.key_down);
for note in self.held_notes.values_mut() {
if note.channel == *channel {
note.sostenuto_captured = false;
note.released_while_sustained = false;
}
}
}
PerformanceIntent::Panic => {
self.held_notes.clear();
self.sustain_pedal = false;
self.sostenuto_pedal = false;
}
PerformanceIntent::Aftertouch { .. }
| PerformanceIntent::PitchBend { .. }
| PerformanceIntent::Parameter { .. } => {}
}
}
}
pub trait PerformanceSource {
fn bind_input(&mut self, binding: PerformanceInputBinding) -> Result<()>;
fn poll_events(&mut self, inputs: Vec<PerformanceInput>) -> Result<Vec<PerformanceEvent>>;
fn panic(&mut self, input_time: Tick) -> Result<Vec<PerformanceEvent>>;
fn capture_start(&mut self, take_id: Symbol) -> Result<()>;
fn capture_stop(&mut self) -> Result<PerformanceTake>;
fn as_clip(&self, take: &PerformanceTake) -> Result<Music>;
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct MemoryPerformanceSource {
source_id: Symbol,
binding: Option<PerformanceInputBinding>,
state: PerformanceSourceState,
capture: Option<PerformanceCapture>,
}
impl MemoryPerformanceSource {
pub fn new(source_id: Symbol, channel: Channel) -> Self {
Self {
source_id,
binding: None,
state: PerformanceSourceState::new(channel),
capture: None,
}
}
pub fn source_id(&self) -> &Symbol {
&self.source_id
}
pub fn state(&self) -> &PerformanceSourceState {
&self.state
}
pub fn state_mut(&mut self) -> &mut PerformanceSourceState {
&mut self.state
}
pub fn set_octave_shift(&mut self, octave_shift: i8) {
self.state.octave_shift = octave_shift;
}
pub fn set_transpose(&mut self, transpose: i8) {
self.state.transpose = transpose;
}
pub fn set_scale_lock(&mut self, scale_lock: Option<ScaleLock>) {
self.state.scale_lock = scale_lock;
}
fn binding(&self) -> Result<&PerformanceInputBinding> {
self.binding
.as_ref()
.ok_or_else(|| Error::Eval("performance source input is not bound".to_owned()))
}
fn push_capture(&mut self, events: &[PerformanceEvent]) {
if let Some(capture) = &mut self.capture {
capture.events.extend_from_slice(events);
}
}
}
impl PerformanceSource for MemoryPerformanceSource {
fn bind_input(&mut self, binding: PerformanceInputBinding) -> Result<()> {
self.state.channel = binding.channel;
self.binding = Some(binding);
Ok(())
}
fn poll_events(&mut self, inputs: Vec<PerformanceInput>) -> Result<Vec<PerformanceEvent>> {
let binding = self.binding()?.clone();
let mut events = Vec::new();
for input in inputs {
let event = PerformanceEvent {
lane_id: binding.lane_id.clone(),
source_id: self.source_id.clone(),
input_time: input.input_time,
time: input.input_time,
intent: transform_intent(input.intent, &self.state),
};
self.state.observe_event(&event);
events.push(event);
}
self.push_capture(&events);
Ok(events)
}
fn panic(&mut self, input_time: Tick) -> Result<Vec<PerformanceEvent>> {
let binding = self.binding()?.clone();
let mut events = self
.state
.held_notes
.values()
.map(|note| PerformanceEvent {
lane_id: binding.lane_id.clone(),
source_id: self.source_id.clone(),
input_time,
time: input_time,
intent: PerformanceIntent::NoteOff {
pitch: note.pitch,
velocity: 0,
channel: note.channel,
},
})
.collect::<Vec<_>>();
events.push(PerformanceEvent {
lane_id: binding.lane_id,
source_id: self.source_id.clone(),
input_time,
time: input_time,
intent: PerformanceIntent::Panic,
});
for event in &events {
self.state.observe_event(event);
}
self.push_capture(&events);
Ok(events)
}
fn capture_start(&mut self, take_id: Symbol) -> Result<()> {
if self.capture.is_some() {
return Err(Error::Eval(
"performance capture is already active".to_owned(),
));
}
self.capture = Some(PerformanceCapture {
take_id,
events: Vec::new(),
});
Ok(())
}
fn capture_stop(&mut self) -> Result<PerformanceTake> {
let capture = self
.capture
.take()
.ok_or_else(|| Error::Eval("performance capture is not active".to_owned()))?;
PerformanceTake::new(self.source_id.clone(), capture.take_id, capture.events)
}
fn as_clip(&self, take: &PerformanceTake) -> Result<Music> {
take.as_clip()
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
struct PerformanceCapture {
take_id: Symbol,
events: Vec<PerformanceEvent>,
}
fn transform_intent(
intent: PerformanceIntent,
state: &PerformanceSourceState,
) -> PerformanceIntent {
match intent {
PerformanceIntent::NoteOn {
pitch,
velocity,
channel,
} => PerformanceIntent::NoteOn {
pitch: state.transform_pitch(pitch),
velocity,
channel,
},
PerformanceIntent::NoteOff {
pitch,
velocity,
channel,
} => PerformanceIntent::NoteOff {
pitch: state.transform_pitch(pitch),
velocity,
channel,
},
PerformanceIntent::Aftertouch {
pitch,
pressure,
channel,
} => PerformanceIntent::Aftertouch {
pitch: state.transform_pitch(pitch),
pressure,
channel,
},
other => other,
}
}