1use sim_kernel::{Error, Expr, Result, Symbol};
2use sim_value::access;
3
4use crate::{Channel, Pitch, Tick};
5
6#[derive(Clone, Debug, PartialEq, Eq)]
11pub struct PerformanceInput {
12 pub input_time: Tick,
14 pub intent: PerformanceIntent,
16}
17
18impl PerformanceInput {
19 pub fn new(input_time: Tick, intent: PerformanceIntent) -> Self {
21 Self { input_time, intent }
22 }
23
24 pub fn note_on(input_time: Tick, channel: Channel, midi: u8, velocity: u8) -> Self {
26 Self::new(
27 input_time,
28 PerformanceIntent::NoteOn {
29 pitch: Pitch::from_midi(midi),
30 velocity,
31 channel,
32 },
33 )
34 }
35
36 pub fn note_off(input_time: Tick, channel: Channel, midi: u8, velocity: u8) -> Self {
38 Self::new(
39 input_time,
40 PerformanceIntent::NoteOff {
41 pitch: Pitch::from_midi(midi),
42 velocity,
43 channel,
44 },
45 )
46 }
47
48 pub fn sustain(input_time: Tick, channel: Channel, down: bool) -> Self {
50 Self::new(input_time, PerformanceIntent::Sustain { down, channel })
51 }
52
53 pub fn sostenuto(input_time: Tick, channel: Channel, down: bool) -> Self {
55 Self::new(input_time, PerformanceIntent::Sostenuto { down, channel })
56 }
57
58 pub fn all_notes_off(input_time: Tick, channel: Channel) -> Self {
60 Self::new(input_time, PerformanceIntent::AllNotesOff { channel })
61 }
62
63 pub fn all_sound_off(input_time: Tick, channel: Channel) -> Self {
65 Self::new(input_time, PerformanceIntent::AllSoundOff { channel })
66 }
67
68 pub fn reset_controllers(input_time: Tick, channel: Channel) -> Self {
70 Self::new(input_time, PerformanceIntent::ResetControllers { channel })
71 }
72}
73
74#[derive(Clone, Debug, PartialEq, Eq)]
80pub enum PerformanceIntent {
81 NoteOn {
83 pitch: Pitch,
85 velocity: u8,
87 channel: Channel,
89 },
90 NoteOff {
92 pitch: Pitch,
94 velocity: u8,
96 channel: Channel,
98 },
99 Aftertouch {
101 pitch: Pitch,
103 pressure: u8,
105 channel: Channel,
107 },
108 PitchBend {
110 value: u16,
112 channel: Channel,
114 },
115 Sustain {
117 down: bool,
119 channel: Channel,
121 },
122 Sostenuto {
124 down: bool,
126 channel: Channel,
128 },
129 AllNotesOff {
131 channel: Channel,
133 },
134 AllSoundOff {
136 channel: Channel,
138 },
139 ResetControllers {
141 channel: Channel,
143 },
144 Parameter {
146 target: Symbol,
148 value: i64,
150 },
151 Panic,
153}
154
155impl PerformanceIntent {
156 pub fn kind_symbol(&self) -> Symbol {
158 Symbol::qualified("music/performance-intent", self.kind_label())
159 }
160
161 pub fn kind_label(&self) -> &'static str {
163 match self {
164 Self::NoteOn { .. } => "note-on",
165 Self::NoteOff { .. } => "note-off",
166 Self::Aftertouch { .. } => "aftertouch",
167 Self::PitchBend { .. } => "pitch-bend",
168 Self::Sustain { .. } => "sustain",
169 Self::Sostenuto { .. } => "sostenuto",
170 Self::AllNotesOff { .. } => "all-notes-off",
171 Self::AllSoundOff { .. } => "all-sound-off",
172 Self::ResetControllers { .. } => "reset-controllers",
173 Self::Parameter { .. } => "parameter",
174 Self::Panic => "panic",
175 }
176 }
177
178 pub fn to_expr(&self) -> Expr {
180 let mut entries = vec![(
181 Expr::Symbol(Symbol::new("kind")),
182 Expr::Symbol(self.kind_symbol()),
183 )];
184 match self {
185 Self::NoteOn {
186 pitch,
187 velocity,
188 channel,
189 }
190 | Self::NoteOff {
191 pitch,
192 velocity,
193 channel,
194 } => {
195 entries.push((Expr::Symbol(Symbol::new("pitch")), pitch_expr(*pitch)));
196 entries.push((
197 Expr::Symbol(Symbol::new("velocity")),
198 Expr::String(velocity.to_string()),
199 ));
200 entries.push((Expr::Symbol(Symbol::new("channel")), channel_expr(*channel)));
201 }
202 Self::Aftertouch {
203 pitch,
204 pressure,
205 channel,
206 } => {
207 entries.push((Expr::Symbol(Symbol::new("pitch")), pitch_expr(*pitch)));
208 entries.push((
209 Expr::Symbol(Symbol::new("pressure")),
210 Expr::String(pressure.to_string()),
211 ));
212 entries.push((Expr::Symbol(Symbol::new("channel")), channel_expr(*channel)));
213 }
214 Self::PitchBend { value, channel } => {
215 entries.push((
216 Expr::Symbol(Symbol::new("value")),
217 Expr::String(value.to_string()),
218 ));
219 entries.push((Expr::Symbol(Symbol::new("channel")), channel_expr(*channel)));
220 }
221 Self::Sustain { down, channel } | Self::Sostenuto { down, channel } => {
222 entries.push((Expr::Symbol(Symbol::new("down")), Expr::Bool(*down)));
223 entries.push((Expr::Symbol(Symbol::new("channel")), channel_expr(*channel)));
224 }
225 Self::AllNotesOff { channel }
226 | Self::AllSoundOff { channel }
227 | Self::ResetControllers { channel } => {
228 entries.push((Expr::Symbol(Symbol::new("channel")), channel_expr(*channel)));
229 }
230 Self::Parameter { target, value } => {
231 entries.push((
232 Expr::Symbol(Symbol::new("target")),
233 Expr::Symbol(target.clone()),
234 ));
235 entries.push((
236 Expr::Symbol(Symbol::new("value")),
237 Expr::String(value.to_string()),
238 ));
239 }
240 Self::Panic => {}
241 }
242 Expr::Map(entries)
243 }
244
245 pub fn from_expr(expr: &Expr) -> Result<Self> {
250 let Expr::Map(entries) = expr else {
251 return Err(Error::Eval("performance intent must be a map".to_owned()));
252 };
253 match symbol_field(entries, "kind")?.as_qualified_str().as_str() {
254 "note-on" | "music/performance-intent/note-on" => Ok(Self::NoteOn {
255 pitch: pitch_field(entries, "pitch")?,
256 velocity: u8_field(entries, "velocity")?,
257 channel: channel_field(entries, "channel")?,
258 }),
259 "note-off" | "music/performance-intent/note-off" => Ok(Self::NoteOff {
260 pitch: pitch_field(entries, "pitch")?,
261 velocity: u8_field(entries, "velocity")?,
262 channel: channel_field(entries, "channel")?,
263 }),
264 "aftertouch" | "music/performance-intent/aftertouch" => Ok(Self::Aftertouch {
265 pitch: pitch_field(entries, "pitch")?,
266 pressure: u8_field(entries, "pressure")?,
267 channel: channel_field(entries, "channel")?,
268 }),
269 "pitch-bend" | "music/performance-intent/pitch-bend" => Ok(Self::PitchBend {
270 value: u16_field(entries, "value")?,
271 channel: channel_field(entries, "channel")?,
272 }),
273 "sustain" | "music/performance-intent/sustain" => Ok(Self::Sustain {
274 down: bool_field(entries, "down")?,
275 channel: channel_field(entries, "channel")?,
276 }),
277 "sostenuto" | "music/performance-intent/sostenuto" => Ok(Self::Sostenuto {
278 down: bool_field(entries, "down")?,
279 channel: channel_field(entries, "channel")?,
280 }),
281 "all-notes-off" | "music/performance-intent/all-notes-off" => Ok(Self::AllNotesOff {
282 channel: channel_field(entries, "channel")?,
283 }),
284 "all-sound-off" | "music/performance-intent/all-sound-off" => Ok(Self::AllSoundOff {
285 channel: channel_field(entries, "channel")?,
286 }),
287 "reset-controllers" | "music/performance-intent/reset-controllers" => {
288 Ok(Self::ResetControllers {
289 channel: channel_field(entries, "channel")?,
290 })
291 }
292 "parameter" | "music/performance-intent/parameter" => Ok(Self::Parameter {
293 target: symbol_field(entries, "target")?.clone(),
294 value: i64_field(entries, "value")?,
295 }),
296 "panic" | "music/performance-intent/panic" => Ok(Self::Panic),
297 other => Err(Error::Eval(format!(
298 "unknown performance intent kind {other}"
299 ))),
300 }
301 }
302}
303
304fn pitch_expr(pitch: Pitch) -> Expr {
305 Expr::String(pitch.semitone().to_string())
306}
307
308fn channel_expr(channel: Channel) -> Expr {
309 Expr::String(channel.0.to_string())
310}
311
312fn string_field<'a>(entries: &'a [(Expr, Expr)], name: &str) -> Result<&'a str> {
313 access::entry_required_str(entries, name, "string field")
314}
315
316fn symbol_field<'a>(entries: &'a [(Expr, Expr)], name: &str) -> Result<&'a Symbol> {
317 access::entry_required_sym(entries, name, "symbol field")
318}
319
320fn bool_field(entries: &[(Expr, Expr)], name: &str) -> Result<bool> {
321 access::entry_required_bool(entries, name, "boolean field")
322}
323
324fn i64_field(entries: &[(Expr, Expr)], name: &str) -> Result<i64> {
325 string_field(entries, name)?
326 .parse::<i64>()
327 .map_err(|err| Error::Eval(format!("invalid {name}: {err}")))
328}
329
330fn i32_field(entries: &[(Expr, Expr)], name: &str) -> Result<i32> {
331 string_field(entries, name)?
332 .parse::<i32>()
333 .map_err(|err| Error::Eval(format!("invalid {name}: {err}")))
334}
335
336fn u8_field(entries: &[(Expr, Expr)], name: &str) -> Result<u8> {
337 string_field(entries, name)?
338 .parse::<u8>()
339 .map_err(|err| Error::Eval(format!("invalid {name}: {err}")))
340}
341
342fn u16_field(entries: &[(Expr, Expr)], name: &str) -> Result<u16> {
343 string_field(entries, name)?
344 .parse::<u16>()
345 .map_err(|err| Error::Eval(format!("invalid {name}: {err}")))
346}
347
348fn pitch_field(entries: &[(Expr, Expr)], name: &str) -> Result<Pitch> {
349 Ok(Pitch::from_semitone(i32_field(entries, name)?))
350}
351
352fn channel_field(entries: &[(Expr, Expr)], name: &str) -> Result<Channel> {
353 Channel::new(u8_field(entries, name)?).map_err(|err| Error::Eval(err.to_string()))
354}