1use std::any::Any;
4use std::collections::BTreeMap;
5use std::fmt::{Display, Formatter};
6use std::sync::Arc;
7
8use sim_lib_music_core::{Articulation, Channel, Time};
9use sim_lib_pitch_dissonance::ContextualSonanceOptions;
10use sim_lib_pitch_scale::{PlayerScale, Scale};
11use sim_lib_sound_tuning::Tuning;
12
13use crate::{ErasedParameterBinding, SerialEventId, SerialPlan, SerialRealization, VoiceId};
14
15fn validate_id(kind: &'static str, value: impl Into<String>) -> Result<String, String> {
16 let value = value.into();
17 if value.trim().is_empty() {
18 return Err(format!("{kind} cannot be empty"));
19 }
20 if value
21 .chars()
22 .any(|ch| !(ch.is_ascii_alphanumeric() || matches!(ch, '/' | '-' | '_' | '.')))
23 {
24 return Err(format!(
25 "{kind} must use ASCII letters, digits, /, -, _, or ."
26 ));
27 }
28 Ok(value)
29}
30
31#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
33pub struct RealizerId(String);
34
35impl RealizerId {
36 pub fn new(value: impl Into<String>) -> Result<Self, String> {
38 Ok(Self(validate_id("realizer-id", value)?))
39 }
40
41 pub fn as_str(&self) -> &str {
43 &self.0
44 }
45}
46
47impl Display for RealizerId {
48 fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
49 formatter.write_str(&self.0)
50 }
51}
52
53#[derive(Clone, Debug, PartialEq, Eq)]
55pub enum EventSound {
56 Notes,
58 Rest,
60}
61
62#[derive(Clone, Debug, PartialEq, Eq)]
64pub struct StrictPitchLayout {
65 pub register: i8,
67 pub octave_displacements: Vec<i8>,
69}
70
71impl StrictPitchLayout {
72 pub fn in_register(register: i8) -> Self {
74 Self {
75 register,
76 octave_displacements: Vec::new(),
77 }
78 }
79}
80
81#[derive(Copy, Clone, Debug, PartialEq, Eq)]
83pub enum TiePolicy {
84 None,
86 IntoNext,
88}
89
90#[derive(Copy, Clone, Debug, PartialEq, Eq)]
92pub enum SimultaneousRenderPolicy {
93 PreserveOnset,
95}
96
97#[derive(Clone, Debug, PartialEq, Eq)]
99pub struct StrictEventSpec {
100 pub sound: EventSound,
102 pub pitch_layout: StrictPitchLayout,
104 pub duration: Time,
106 pub velocity: u8,
108 pub channel: Channel,
110 pub articulation: Articulation,
112 pub tie: TiePolicy,
114}
115
116impl StrictEventSpec {
117 pub fn notes(
119 register: i8,
120 duration: Time,
121 velocity: u8,
122 channel: Channel,
123 articulation: Articulation,
124 ) -> Self {
125 Self {
126 sound: EventSound::Notes,
127 pitch_layout: StrictPitchLayout::in_register(register),
128 duration,
129 velocity,
130 channel,
131 articulation,
132 tie: TiePolicy::None,
133 }
134 }
135
136 pub fn rest(duration: Time) -> Self {
138 Self {
139 sound: EventSound::Rest,
140 pitch_layout: StrictPitchLayout::in_register(4),
141 duration,
142 velocity: 0,
143 channel: Channel::new(0).expect("MIDI channel zero is valid"),
144 articulation: Articulation::Normal,
145 tie: TiePolicy::None,
146 }
147 }
148}
149
150#[derive(Clone, Debug, PartialEq, Eq)]
152pub struct RegisterBounds {
153 pub lowest: i8,
155 pub highest: i8,
157}
158
159#[derive(Clone, Debug, PartialEq, Eq)]
161pub struct VoiceBounds {
162 pub max_notes_per_event: Option<usize>,
164}
165
166pub trait RealizationService: Any + Send + Sync {
168 fn as_any(&self) -> &dyn Any;
170}
171
172impl<T: Any + Send + Sync> RealizationService for T {
173 fn as_any(&self) -> &dyn Any {
174 self
175 }
176}
177
178#[derive(Clone, Default)]
180pub struct RealizationServices {
181 entries: BTreeMap<String, Arc<dyn RealizationService>>,
182}
183
184impl RealizationServices {
185 pub fn new() -> Self {
187 Self::default()
188 }
189
190 pub fn insert(
192 &mut self,
193 name: impl Into<String>,
194 service: Arc<dyn RealizationService>,
195 ) -> Option<Arc<dyn RealizationService>> {
196 self.entries.insert(name.into(), service)
197 }
198
199 pub fn get<T: Any + Send + Sync>(&self, name: &str) -> Option<&T> {
201 self.entries
202 .get(name)
203 .and_then(|service| service.as_ref().as_any().downcast_ref::<T>())
204 }
205
206 pub fn names(&self) -> Vec<&str> {
208 self.entries.keys().map(String::as_str).collect()
209 }
210}
211
212impl std::fmt::Debug for RealizationServices {
213 fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
214 formatter
215 .debug_struct("RealizationServices")
216 .field("names", &self.names())
217 .finish()
218 }
219}
220
221impl PartialEq for RealizationServices {
222 fn eq(&self, other: &Self) -> bool {
223 self.names() == other.names()
224 }
225}
226
227impl Eq for RealizationServices {}
228
229#[derive(Clone)]
231pub struct RealizationContext {
232 pub specs: BTreeMap<SerialEventId, StrictEventSpec>,
234 pub simultaneous_policy: SimultaneousRenderPolicy,
236 pub scale: Option<Scale>,
238 pub modal_scale: Option<PlayerScale>,
240 pub tuning: Option<Arc<dyn Tuning>>,
242 pub contextual_sonance: Option<ContextualSonanceOptions>,
244 pub register_bounds: BTreeMap<VoiceId, RegisterBounds>,
246 pub voice_bounds: BTreeMap<VoiceId, VoiceBounds>,
248 pub parameter_tracks: BTreeMap<String, Arc<dyn ErasedParameterBinding>>,
250 pub services: RealizationServices,
252}
253
254impl RealizationContext {
255 pub fn new(specs: BTreeMap<SerialEventId, StrictEventSpec>) -> Self {
257 Self {
258 specs,
259 simultaneous_policy: SimultaneousRenderPolicy::PreserveOnset,
260 scale: None,
261 modal_scale: None,
262 tuning: None,
263 contextual_sonance: None,
264 register_bounds: BTreeMap::new(),
265 voice_bounds: BTreeMap::new(),
266 parameter_tracks: BTreeMap::new(),
267 services: RealizationServices::default(),
268 }
269 }
270}
271
272impl std::fmt::Debug for RealizationContext {
273 fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
274 formatter
275 .debug_struct("RealizationContext")
276 .field("specs", &self.specs)
277 .field("simultaneous_policy", &self.simultaneous_policy)
278 .field("scale", &self.scale)
279 .field("modal_scale", &self.modal_scale)
280 .field("tuning", &self.tuning.as_ref().map(|tuning| tuning.name()))
281 .field("contextual_sonance", &self.contextual_sonance)
282 .field("register_bounds", &self.register_bounds)
283 .field("voice_bounds", &self.voice_bounds)
284 .field(
285 "parameter_tracks",
286 &self.parameter_tracks.keys().collect::<Vec<_>>(),
287 )
288 .field("services", &self.services)
289 .finish()
290 }
291}
292
293impl RealizationContext {
294 pub fn effective_modal_scale(&self) -> Option<PlayerScale> {
296 self.modal_scale
297 .clone()
298 .or_else(|| self.scale.map(PlayerScale::from_scale))
299 }
300}
301
302pub type StrictRealizationContext = RealizationContext;
304
305pub trait SerialRealizer: Send + Sync {
307 fn id(&self) -> &RealizerId;
309
310 fn realize(
312 &self,
313 plan: &SerialPlan,
314 context: &RealizationContext,
315 ) -> Result<SerialRealization, crate::StrictRealizationError>;
316}