1use std::fmt::{Display, Formatter};
8use std::io::{Error, ErrorKind};
9
10use strum_macros::FromRepr;
11use uom::num::Zero;
12use uom::si::f32::{Frequency, Ratio, Time};
13use uom::si::ratio::percent;
14
15pub use decibels::*;
16pub use envelope::*;
17pub use io::*;
18pub use macro_control::*;
19pub use metadata::*;
20pub use point::*;
21pub use snapin::*;
22pub use unison::*;
23pub use version::*;
24
25use crate::effect::Effect;
26use crate::generator::{Generator, GeneratorId};
27use crate::modulation::Modulation;
28use crate::modulator::{Modulator, ModulatorContainer};
29
30mod decibels;
31pub mod effect;
32mod envelope;
33pub mod generator;
34mod io;
35mod macro_control;
36mod metadata;
37pub mod modulation;
38pub mod modulator;
39mod point;
40mod snapin;
41mod text;
42mod unison;
43mod version;
44
45const GENERATORS_MAX: GeneratorId = 32;
47
48const METADATA_LENGTH_MAX: usize = 64 * 1024;
51
52const MODULATORS_MAX: usize = 32;
54
55const MODULATOR_BLOCK_SIZE: usize = 100;
57
58const PATH_COMPONENT_COUNT_MAX: usize = 100; #[derive(Clone, Copy, Debug, Eq, FromRepr, PartialEq)]
65#[repr(u32)]
66pub enum NoteValue {
67 Quarter,
69 QuarterTriplet,
70 Eighth,
71 EightTriplet,
72 Sixteenth,
73 SixteenthTriplet,
74 ThirtySecond,
75 ThirtySecondTriplet,
76 SixtyFourth,
77}
78
79impl NoteValue {
80 pub(crate) fn from_id(id: u32) -> Result<Self, Error> {
81 Self::from_repr(id)
82 .ok_or_else(|| Error::new(ErrorKind::InvalidData, format!("Unknown note value {id}")))
83 }
84}
85
86impl Display for NoteValue {
87 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
88 use NoteValue::*;
89 let msg = match self {
90 Quarter => "1/4",
91 QuarterTriplet => "1/4T",
92 Eighth => "1/8",
93 EightTriplet => "1/8T",
94 Sixteenth => "1/16",
95 SixteenthTriplet => "1/16T",
96 ThirtySecond => "1/32",
97 ThirtySecondTriplet => "1/32T",
98 SixtyFourth => "1/64",
99 };
100 f.write_str(msg)
101 }
102}
103
104#[derive(Clone, Debug, PartialEq)]
107pub struct Rate {
108 pub frequency: Frequency,
109 pub numerator: u32,
110 pub denominator: NoteValue,
111
112 pub sync: bool,
115}
116
117#[derive(Copy, Clone, Debug, Default, Eq, FromRepr, PartialEq)]
118#[repr(u32)]
119pub enum LaneDestination {
120 Lane2 = 2,
123 Lane3 = 0,
124 #[default]
125 Master = 1,
126 Lane1 = 3,
127 Sideband = 5,
129}
130
131impl LaneDestination {
132 pub(crate) fn from_id(id: u32) -> Result<Self, Error> {
133 Self::from_repr(id).ok_or_else(|| {
134 Error::new(
135 ErrorKind::InvalidData,
136 format!("Unknown lane destination {id}"),
137 )
138 })
139 }
140}
141
142impl Display for LaneDestination {
143 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
144 use LaneDestination::*;
145 let msg = match self {
146 Lane2 => "Lane 2",
147 Lane3 => "Lane 3",
148 Master => "Master",
149 Lane1 => "Lane 1",
150 Sideband => "Sideband",
151 };
152 f.write_str(msg)
153 }
154}
155
156pub type LaneId = u8;
157
158#[derive(Debug, PartialEq)]
159pub struct Lane {
160 pub enabled: bool,
161
162 pub snapins: Vec<Snapin>,
164
165 pub destination: LaneDestination,
166
167 pub poly_count: u8,
169
170 pub mute: bool,
171 pub solo: bool,
172 pub gain: Decibels,
173 pub mix: Ratio,
174}
175
176impl Lane {
177 pub const COUNT: usize = 3;
178
179 pub fn find_effect<T: Effect>(&self) -> Option<(&Snapin, &T)> {
181 self.snapins
183 .iter()
184 .find(|snapin| snapin.effect.downcast_ref::<T>().is_some())
185 .map(|snapin| (snapin, snapin.effect.downcast_ref::<T>().unwrap()))
186 }
187}
188
189impl Default for Lane {
190 fn default() -> Self {
191 Self {
192 enabled: true,
193 snapins: Vec::new(),
194 destination: LaneDestination::Master,
195 poly_count: 0,
196 mute: false,
197 solo: false,
198 gain: Decibels::from_linear(1.0),
199 mix: Ratio::new::<percent>(100.0),
200 }
201 }
202}
203
204#[derive(Debug, PartialEq)]
205pub struct Preset {
206 pub format_version: Version<u32>,
207 pub generators: Vec<Box<dyn Generator>>,
208
209 pub mod_wheel_value: Ratio,
210
211 #[doc(alias = "portamento")]
212 pub glide_enabled: bool,
213 pub glide_time: f32,
214
215 #[doc(alias = "glide_auto")]
216 pub glide_legato: bool,
217
218 pub lanes: Vec<Lane>,
219
220 pub macro_controls: Vec<MacroControl>,
222
223 pub master_gain: f32,
225
226 pub master_pitch: f32,
227 pub metadata: Metadata,
228 pub modulations: Vec<Modulation>,
229 pub modulator_containers: Vec<ModulatorContainer>,
230 pub polyphony: u32,
231
232 pub retrigger_enabled: bool,
234 pub unison: Unison,
235}
236
237impl Default for Preset {
238 fn default() -> Self {
239 Self {
240 format_version: WRITE_SAME_AS.format_version(),
241 mod_wheel_value: Ratio::zero(),
242 glide_enabled: false,
243 glide_legato: false,
244 glide_time: 0.0,
245 generators: Vec::new(),
246 lanes: vec![
247 Lane {
248 destination: LaneDestination::Lane2,
249 ..Default::default()
250 },
251 Lane {
252 destination: LaneDestination::Lane3,
253 ..Default::default()
254 },
255 Lane {
256 destination: LaneDestination::Master,
257 ..Default::default()
258 },
259 ],
260 macro_controls: (1..=MacroControl::COUNT)
261 .map(|n| MacroControl::new(format!("Macro {}", n)))
262 .collect(),
263 master_gain: Decibels::ZERO.linear(),
264 master_pitch: 0.0,
265 metadata: Default::default(),
266 modulations: Vec::new(),
267 modulator_containers: Vec::new(),
268 polyphony: 8,
269 retrigger_enabled: true,
270 unison: Default::default(),
271 }
272 }
273}
274
275#[cfg(test)]
276pub(crate) mod test {
277 use std::fs::File;
278 use std::io;
279 use std::io::{Cursor, Read, Seek, SeekFrom, Write};
280 use std::path::Path;
281
282 use crate::tests::test_data_path;
283 use crate::*;
284
285 pub(crate) fn load_preset(components: &[&str]) -> io::Result<Preset> {
286 let mut path = test_data_path(&[]);
287 if !path.exists() {
288 panic!("Phase Plant test data path does not exist: {path:?}");
289 }
290
291 for component in components {
292 path = path.join(component);
293 }
294 Preset::read_file(&path)
295 }
296
297 #[allow(dead_code)]
300 const RELOAD_CREATES_FILE_ON_READ_ERROR: bool = true;
301
302 #[allow(dead_code)]
305 const RELOAD_CREATES_FILE_ON_WRITE_ERROR: bool = true;
306
307 #[must_use]
312 pub(crate) fn read_preset(dir_name: &str, file_name: &str) -> Preset {
313 load_preset(&[dir_name, file_name]).expect("preset")
314 }
317
318 pub(crate) fn read_effect_preset(effect_name: &str, file_name: &str) -> io::Result<Preset> {
319 let preset = load_preset(&["effects", effect_name, file_name])?;
320 Ok(preset)
323 }
324
325 pub(crate) fn read_generator_preset(
326 generator_name: &str,
327 file_name: &str,
328 ) -> io::Result<Preset> {
329 let preset = load_preset(&["generators", generator_name, file_name])?;
330 Ok(preset)
333 }
334
335 pub(crate) fn read_modulator_preset(
336 modulator_name: &str,
337 file_name: &str,
338 ) -> io::Result<Preset> {
339 let preset = load_preset(&["modulators", modulator_name, file_name])?;
340 Ok(preset)
343 }
344
345 fn _rewrite_preset(preset: &Preset, file_name: &str) -> Preset {
346 let mut write_cursor = Cursor::new(Vec::with_capacity(16 * 1024));
347 match preset.write(&mut write_cursor) {
348 Ok(_) => {
349 let name_str = Path::new(file_name)
350 .file_stem()
351 .map(|s| s.to_string_lossy().to_string());
352
353 #[cfg(disabled)]
355 {
356 write_cursor.seek(SeekFrom::Start(0)).unwrap();
357 let filename = format!(
358 "test-{}-{}.phaseplant",
359 name_str.clone().unwrap_or_default(),
360 uuid::Uuid::new_v4()
361 );
362 let path = std::env::temp_dir().join(&filename);
363 let mut file = File::create(&path).expect("Create file");
364 let mut out = Vec::with_capacity(write_cursor.position() as usize);
365 write_cursor.seek(SeekFrom::Start(0)).unwrap();
366 write_cursor.read_to_end(&mut out).unwrap();
367 file.write_all(&out).unwrap();
368 println!("Test preset written to {}", path.to_string_lossy());
369 }
370
371 write_cursor.seek(SeekFrom::Start(0)).unwrap();
372
373 #[cfg(disabled)]
374 {
375 let mut file = File::create("/tmp/reload.phaseplant").expect("Create file");
376 let mut out = Vec::with_capacity(write_cursor.position() as usize);
377 write_cursor.seek(SeekFrom::Start(0)).unwrap();
378 write_cursor.read_to_end(&mut out).unwrap();
379 file.write_all(&out).unwrap();
380 panic!("Debug file written to {file:?}");
381 }
382
383 match Preset::read(&mut write_cursor, name_str) {
384 Ok(written) => {
385 assert_eq!(preset.metadata.description, written.metadata.description);
390 assert_eq!(preset.metadata.author, written.metadata.author);
391 assert_eq!(preset.metadata.category, written.metadata.category);
392
393 assert_eq!(preset.macro_controls, written.macro_controls);
394
395 assert_eq!(preset.polyphony, written.polyphony);
396 assert_eq!(preset.unison, written.unison);
397
398 assert_eq!(
399 preset.generators.len(),
400 written.generators.len(),
401 "number of generators"
402 );
403 assert_eq!(preset.lanes.len(), written.lanes.len(), "number of lanes");
404 assert_eq!(
405 preset.macro_controls.len(),
406 written.macro_controls.len(),
407 "number of macro controls"
408 );
409 assert_eq!(
410 preset.modulator_containers.len(),
411 written.modulator_containers.len(),
412 "number of modulators"
413 );
414
415 written
416 }
417 Err(error) => {
418 if RELOAD_CREATES_FILE_ON_READ_ERROR {
419 let filename =
420 format!("reload-read-error-{}.phaseplant", uuid::Uuid::new_v4());
421 let path = std::env::temp_dir().join(filename);
422 let mut file = File::create(&path).expect("Create file");
423
424 let mut out = Vec::with_capacity(write_cursor.position() as usize);
425 write_cursor.seek(SeekFrom::Start(0)).unwrap();
426 write_cursor.read_to_end(&mut out).unwrap();
427 file.write_all(&out).unwrap();
428 panic!(
429 "{:?} - debug file written to {}",
430 error,
431 path.to_string_lossy()
432 );
433 }
434 panic!("{:?}", error);
435 }
436 }
437 }
438 Err(error) => {
439 if RELOAD_CREATES_FILE_ON_WRITE_ERROR {
440 let filename =
441 format!("reload-write-error-{}.phaseplant", uuid::Uuid::new_v4());
442 let path = std::env::temp_dir().join(filename);
443 let mut file = File::create(&path).expect("Create file");
444
445 let mut out = Vec::with_capacity(write_cursor.position() as usize);
446 write_cursor.seek(SeekFrom::Start(0)).unwrap();
447 write_cursor.read_to_end(&mut out).unwrap();
448 file.write_all(&out).unwrap();
449 panic!(
450 "{:?} - debug file written to {}",
451 error,
452 path.to_string_lossy()
453 );
454 }
455 panic!("{:?}", error);
456 }
457 }
458 }
459
460 #[test]
461 fn default() {
462 let preset = Preset::default();
463 assert_eq!(preset.format_version.major, 6);
464
465 let metadata = &preset.metadata;
467 assert!(metadata.author.is_none());
468 assert!(metadata.description.is_none());
469 assert!(metadata.name.is_none());
470
471 assert!(!preset.glide_enabled);
472 assert!(!preset.glide_legato);
473 assert_eq!(preset.glide_time, 0.0);
474
475 assert_eq!(preset.lanes.len(), 3);
476 preset.lanes.iter().for_each(|lane| {
477 assert!(lane.enabled);
478 assert_eq!(lane.poly_count, 0);
479 assert!(!lane.mute);
480 assert_eq!(lane.gain.linear(), 1.0);
481 assert_eq!(lane.mix.get::<percent>(), 100.0);
482 });
483 assert_eq!(preset.lanes[0].destination, LaneDestination::Lane2);
484 assert_eq!(preset.lanes[1].destination, LaneDestination::Lane3);
485 assert_eq!(preset.lanes[2].destination, LaneDestination::Master);
486
487 assert_eq!(preset.macro_controls.len(), 8);
488 assert_eq!(preset.macro_controls[0].name, "Macro 1");
489 assert_eq!(preset.macro_controls[1].name, "Macro 2");
490 assert_eq!(preset.macro_controls[2].name, "Macro 3");
491 assert_eq!(preset.macro_controls[3].name, "Macro 4");
492 assert_eq!(preset.macro_controls[4].name, "Macro 5");
493 assert_eq!(preset.macro_controls[5].name, "Macro 6");
494 assert_eq!(preset.macro_controls[6].name, "Macro 7");
495 assert_eq!(preset.macro_controls[7].name, "Macro 8");
496
497 assert_eq!(preset.master_gain, Decibels::ZERO.linear());
498 assert_eq!(preset.metadata.author, None);
499 assert_eq!(preset.metadata.category, None);
500 assert_eq!(preset.metadata.description, None);
501 assert_eq!(preset.metadata.name, None);
502 assert_eq!(preset.polyphony, 8);
503 assert!(preset.retrigger_enabled);
504
505 assert!(preset.lanes[0].snapins.is_empty());
506
507 let unison = &preset.unison;
508 assert!(!unison.enabled);
509 assert_eq!(unison.voices, 4);
510 assert_eq!(unison.mode, UnisonMode::Smooth);
511 assert_eq!(unison.detune_cents, 25.0);
512 assert_eq!(unison.spread.get::<percent>(), 0.0);
513 assert_eq!(unison.blend.get::<percent>(), 100.0);
514 assert_eq!(unison.bias.get::<percent>(), 0.0);
515 }
516}
517
518#[cfg(test)]
519mod tests {
520 use std::path::PathBuf;
521
522 pub(crate) fn test_data_path(components: &[&str]) -> PathBuf {
523 let mut parts = vec!["tests"];
524 parts.extend_from_slice(components);
525 parts.iter().collect::<PathBuf>()
526 }
527}