1use std::any::Any;
4use std::collections::BTreeMap;
5use std::fmt::Debug;
6
7use sim_lib_music_core::{Articulation, Time};
8use sim_lib_serial_core::{AggregateRule, AlphabetId, SeriesTransform};
9use thiserror::Error;
10
11use crate::{ParameterAlphabet, ParameterError, ParameterSeries, ParameterValue};
12
13#[derive(Copy, Clone, Debug, PartialEq, Eq)]
15pub enum Exhaustion {
16 Cycle,
18 Truncate,
20 OneShot,
22}
23
24#[derive(Clone, Debug, PartialEq, Eq)]
26pub struct ParameterStep<T: ParameterValue> {
27 pub plan_ordinal: usize,
29 pub parameter_ordinal: usize,
31 pub cycle: usize,
33 pub value: T,
35}
36
37#[derive(Clone, Debug, PartialEq, Eq)]
39pub struct ParameterProjection<T: ParameterValue> {
40 name: String,
41 alphabet_id: AlphabetId,
42 source_len: usize,
43 phase: usize,
44 exhaustion: Exhaustion,
45 plan_len: usize,
46 steps: Vec<ParameterStep<T>>,
47 omitted_plan_ordinals: Vec<usize>,
48}
49
50impl<T: ParameterValue> ParameterProjection<T> {
51 pub fn name(&self) -> &str {
53 &self.name
54 }
55
56 pub fn alphabet_id(&self) -> &AlphabetId {
58 &self.alphabet_id
59 }
60
61 pub fn source_len(&self) -> usize {
63 self.source_len
64 }
65
66 pub fn phase(&self) -> usize {
68 self.phase
69 }
70
71 pub const fn exhaustion(&self) -> Exhaustion {
73 self.exhaustion
74 }
75
76 pub fn plan_len(&self) -> usize {
78 self.plan_len
79 }
80
81 pub fn steps(&self) -> &[ParameterStep<T>] {
83 &self.steps
84 }
85
86 pub fn omitted_plan_ordinals(&self) -> &[usize] {
88 &self.omitted_plan_ordinals
89 }
90}
91
92#[derive(Clone, Debug, PartialEq, Eq)]
94pub struct ParameterTrack<T: ParameterValue> {
95 name: String,
96 series: ParameterSeries<T>,
97 phase: usize,
98 exhaustion: Exhaustion,
99}
100
101impl<T: ParameterValue> ParameterTrack<T> {
102 pub fn try_new(
104 name: impl Into<String>,
105 values: Vec<T>,
106 exhaustion: Exhaustion,
107 ) -> Result<Self, IntegralError> {
108 let name = validate_parameter_name(name.into())?;
109 Ok(Self {
110 series: ParameterSeries::try_new(parameter_alphabet_id(&name), values)?,
111 name,
112 phase: 0,
113 exhaustion,
114 })
115 }
116
117 pub fn try_new_with_rule(
119 name: impl Into<String>,
120 rule: AggregateRule,
121 values: Vec<T>,
122 exhaustion: Exhaustion,
123 ) -> Result<Self, IntegralError> {
124 let name = validate_parameter_name(name.into())?;
125 Ok(Self {
126 series: ParameterSeries::try_new_with_rule(parameter_alphabet_id(&name), rule, values)?,
127 name,
128 phase: 0,
129 exhaustion,
130 })
131 }
132
133 pub fn with_phase(mut self, phase: usize) -> Self {
135 self.phase = phase;
136 self
137 }
138
139 pub fn name(&self) -> &str {
141 &self.name
142 }
143
144 pub fn series(&self) -> &ParameterSeries<T> {
146 &self.series
147 }
148
149 pub fn phase(&self) -> usize {
151 self.phase
152 }
153
154 pub const fn exhaustion(&self) -> Exhaustion {
156 self.exhaustion
157 }
158
159 pub fn transformed(
161 &self,
162 transform: &SeriesTransform<ParameterAlphabet<T>>,
163 ) -> Result<Self, IntegralError> {
164 Ok(Self {
165 name: self.name.clone(),
166 series: self.series.apply(transform)?,
167 phase: self.phase,
168 exhaustion: self.exhaustion,
169 })
170 }
171
172 pub fn project(&self, plan_len: usize) -> ParameterProjection<T> {
174 let source = self.series.order();
175 let source_len = source.len();
176 let mut steps = Vec::new();
177 let mut omitted_plan_ordinals = Vec::new();
178
179 if source_len == 0 {
180 return ParameterProjection {
181 name: self.name.clone(),
182 alphabet_id: self.series.alphabet().id().clone(),
183 source_len,
184 phase: self.phase,
185 exhaustion: self.exhaustion,
186 plan_len,
187 steps,
188 omitted_plan_ordinals,
189 };
190 }
191
192 let base_phase = match self.exhaustion {
193 Exhaustion::Cycle => self.phase % source_len,
194 Exhaustion::Truncate | Exhaustion::OneShot => self.phase,
195 };
196 for plan_ordinal in 0..plan_len {
197 let absolute = base_phase + plan_ordinal;
198 match self.exhaustion {
199 Exhaustion::Cycle => {
200 let parameter_ordinal = absolute % source_len;
201 let cycle = absolute / source_len;
202 steps.push(ParameterStep {
203 plan_ordinal,
204 parameter_ordinal,
205 cycle,
206 value: source[parameter_ordinal].clone(),
207 });
208 }
209 Exhaustion::Truncate => {
210 if absolute >= source_len {
211 break;
212 }
213 steps.push(ParameterStep {
214 plan_ordinal,
215 parameter_ordinal: absolute,
216 cycle: 0,
217 value: source[absolute].clone(),
218 });
219 }
220 Exhaustion::OneShot => {
221 if absolute >= source_len {
222 omitted_plan_ordinals.push(plan_ordinal);
223 continue;
224 }
225 steps.push(ParameterStep {
226 plan_ordinal,
227 parameter_ordinal: absolute,
228 cycle: 0,
229 value: source[absolute].clone(),
230 });
231 }
232 }
233 }
234
235 ParameterProjection {
236 name: self.name.clone(),
237 alphabet_id: self.series.alphabet().id().clone(),
238 source_len,
239 phase: self.phase,
240 exhaustion: self.exhaustion,
241 plan_len,
242 steps,
243 omitted_plan_ordinals,
244 }
245 }
246}
247
248#[derive(Clone, Debug, PartialEq, Eq)]
250pub struct ParameterOrdinalLedgerEntry {
251 pub plan_ordinal: usize,
253 pub parameter_ordinal: usize,
255 pub cycle: usize,
257}
258
259#[derive(Clone, Debug, PartialEq, Eq)]
261pub struct BoundParameterTrack<T: ParameterValue> {
262 track: ParameterTrack<T>,
263 projection: ParameterProjection<T>,
264}
265
266impl<T: ParameterValue> BoundParameterTrack<T> {
267 pub fn track(&self) -> &ParameterTrack<T> {
269 &self.track
270 }
271
272 pub fn projection(&self) -> &ParameterProjection<T> {
274 &self.projection
275 }
276
277 pub fn ordinal_ledger(&self) -> Vec<ParameterOrdinalLedgerEntry> {
279 self.projection
280 .steps()
281 .iter()
282 .map(|step| ParameterOrdinalLedgerEntry {
283 plan_ordinal: step.plan_ordinal,
284 parameter_ordinal: step.parameter_ordinal,
285 cycle: step.cycle,
286 })
287 .collect()
288 }
289}
290
291pub trait ErasedParameterBinding: Debug + Send + Sync {
293 fn name(&self) -> &str;
295 fn alphabet_id(&self) -> &AlphabetId;
297 fn phase(&self) -> usize;
299 fn exhaustion(&self) -> Exhaustion;
301 fn source_len(&self) -> usize;
303 fn plan_len(&self) -> usize;
305 fn ordinal_ledger(&self) -> Vec<ParameterOrdinalLedgerEntry>;
307 fn debug_values(&self) -> Vec<String>;
309 fn omitted_plan_ordinals(&self) -> &[usize];
311 fn as_any(&self) -> &dyn Any;
313}
314
315impl<T: ParameterValue + Send + Sync> ErasedParameterBinding for BoundParameterTrack<T> {
316 fn name(&self) -> &str {
317 self.track.name()
318 }
319
320 fn alphabet_id(&self) -> &AlphabetId {
321 self.projection.alphabet_id()
322 }
323
324 fn phase(&self) -> usize {
325 self.track.phase()
326 }
327
328 fn exhaustion(&self) -> Exhaustion {
329 self.track.exhaustion()
330 }
331
332 fn source_len(&self) -> usize {
333 self.projection.source_len()
334 }
335
336 fn plan_len(&self) -> usize {
337 self.projection.plan_len()
338 }
339
340 fn ordinal_ledger(&self) -> Vec<ParameterOrdinalLedgerEntry> {
341 BoundParameterTrack::ordinal_ledger(self)
342 }
343
344 fn debug_values(&self) -> Vec<String> {
345 self.projection
346 .steps()
347 .iter()
348 .map(|step| format!("{:?}", step.value))
349 .collect()
350 }
351
352 fn omitted_plan_ordinals(&self) -> &[usize] {
353 self.projection.omitted_plan_ordinals()
354 }
355
356 fn as_any(&self) -> &dyn Any {
357 self
358 }
359}
360
361#[derive(Debug)]
363pub struct IntegralPlan {
364 length: usize,
365 parameters: BTreeMap<String, Box<dyn ErasedParameterBinding>>,
366}
367
368impl IntegralPlan {
369 pub fn new(length: usize) -> Self {
371 Self {
372 length,
373 parameters: BTreeMap::new(),
374 }
375 }
376
377 pub fn length(&self) -> usize {
379 self.length
380 }
381
382 pub fn bind_parameter<T: ParameterValue + Send + Sync + 'static>(
384 &mut self,
385 track: ParameterTrack<T>,
386 ) -> Result<(), IntegralError> {
387 if self.parameters.contains_key(track.name()) {
388 return Err(IntegralError::DuplicateTrack(track.name().to_owned()));
389 }
390 let projection = track.project(self.length);
391 let name = track.name().to_owned();
392 self.parameters
393 .insert(name, Box::new(BoundParameterTrack { track, projection }));
394 Ok(())
395 }
396
397 pub fn parameter(&self, name: &str) -> Option<&dyn ErasedParameterBinding> {
399 self.parameters.get(name).map(Box::as_ref)
400 }
401
402 pub fn typed_parameter<T: ParameterValue>(
404 &self,
405 name: &str,
406 ) -> Option<&BoundParameterTrack<T>> {
407 self.parameters
408 .get(name)
409 .and_then(|binding| binding.as_any().downcast_ref::<BoundParameterTrack<T>>())
410 }
411
412 pub fn parameter_names(&self) -> Vec<&str> {
414 self.parameters.keys().map(String::as_str).collect()
415 }
416}
417
418pub type DurationTrack = ParameterTrack<Time>;
420pub type DynamicsTrack = ParameterTrack<u8>;
422pub type RegisterTrack = ParameterTrack<i8>;
424pub type ArticulationTrack = ParameterTrack<Articulation>;
426pub type TimbreTrack = ParameterTrack<String>;
428
429#[derive(Clone, Debug, PartialEq, Eq, Error)]
431pub enum IntegralError {
432 #[error("invalid parameter name {0:?}")]
434 InvalidParameterName(String),
435 #[error("parameter track {0} is already bound")]
437 DuplicateTrack(String),
438 #[error(transparent)]
440 Parameter(#[from] ParameterError),
441}
442
443fn validate_parameter_name(name: String) -> Result<String, IntegralError> {
444 if name.trim().is_empty() {
445 return Err(IntegralError::InvalidParameterName(name));
446 }
447 if name
448 .chars()
449 .any(|ch| !(ch.is_ascii_alphanumeric() || matches!(ch, '/' | '-' | '_' | '.')))
450 {
451 return Err(IntegralError::InvalidParameterName(name));
452 }
453 Ok(name)
454}
455
456fn parameter_alphabet_id(name: &str) -> String {
457 format!("parameter/{name}-v1")
458}