1use std::fmt;
9use std::str::FromStr;
10
11use crate::preset::mapper::{
12 MapContext, Mapped, MotionFxGroups, PitchEnvelopeGroups, SynthMapper, TimbreGroups,
13};
14use crate::preset::registry::{Provenance, Registry};
15use crate::synth::{
16 EffectFamily, GlideRule, GlideScaling, LfoSync, OscillatorLayout, Synth, SynthCapabilities,
17 SynthError, VelocityRule,
18};
19use crate::units::{Percent, Seconds, Semitones};
20
21mod catalogue;
22mod codec;
23pub mod laws;
24mod registry;
25mod template;
26mod writers;
27
28pub(crate) use catalogue::placement;
29pub use codec::{VitalFormat, VitalModel};
30pub use registry::{EngineScale, OPEN_ENGINE};
31pub use template::VitalTemplate;
32pub use writers::{
33 EFFECT_ORDER, EFFECT_ORDERS, FRAME_BASE64_CHARS, FRAME_SAMPLES, Keyframe, LfoShape,
34 MAX_LFO_POINTS, MAX_POSITION, ROUTE_SOURCES, Wavetable, default_polarity, effect_order,
35 effect_order_code,
36};
37
38pub const DEFAULT_STAMP: &str = "1.0.6";
44
45pub const RETAIL_BUILD: &str = "1.0.7";
49
50pub const RETAIL_ONLY_KEYS: [&str; 5] = [
53 "custom_warps",
54 "random_values",
55 "osc_1_spectral_morph_phase",
56 "osc_2_spectral_morph_phase",
57 "osc_3_spectral_morph_phase",
58];
59
60pub const MAX_PRESET_BYTES: usize = 64 << 20;
70
71pub static FORMAT: VitalFormat = VitalFormat::new(MAX_PRESET_BYTES);
73
74pub static CAPABILITIES: SynthCapabilities = SynthCapabilities {
78 oscillators: OscillatorLayout {
79 wavetable: 3,
80 sample: true,
81 sub: false,
82 noise: false,
83 },
84 voice_filters: 2,
85 fx_filter: true,
86 envelopes: 6,
87 lfos: 8,
88 random_lfos: 4,
89 macros: 4,
90 mod_slots: 64,
91 unison_voices: (1, 16),
92 polyphony_max: 32,
93 effects: &[
94 EffectFamily::Chorus,
95 EffectFamily::Compressor,
96 EffectFamily::Delay,
97 EffectFamily::Distortion,
98 EffectFamily::Eq,
99 EffectFamily::Filter,
100 EffectFamily::Flanger,
101 EffectFamily::Phaser,
102 EffectFamily::Reverb,
103 ],
104 effect_order_free: true,
105 effect_repeats: false,
106 lfo_sync: &[
107 LfoSync::Free,
108 LfoSync::Tempo,
109 LfoSync::Dotted,
110 LfoSync::Triplets,
111 LfoSync::KeyTrack,
112 ],
113 glide: GlideRule {
114 min: Seconds::from_const(0.000_976_562_5),
115 max: Seconds::from_const(16.0),
116 scaling: GlideScaling::Unknown,
117 },
118 velocity: VelocityRule {
119 default_track: Percent::from_const(0.0),
120 amp_path_power: 2,
121 provenance: Provenance::Measured,
122 },
123 bend_range_default: Semitones::from_const(2.0),
124 middle_c: None,
127};
128
129pub static MAPPER: VitalMapper = VitalMapper;
133
134#[derive(Clone, Copy, Debug, Default)]
136pub struct VitalMapper;
137
138impl SynthMapper for VitalMapper {
139 fn synth(&self) -> Synth {
140 Synth::Vital
141 }
142
143 fn pitch_envelope(&self, _input: &PitchEnvelopeGroups, _ctx: &MapContext<'_>) -> Mapped {
144 Mapped::default()
145 }
146
147 fn timbre(&self, _input: &TimbreGroups, _ctx: &MapContext<'_>) -> Mapped {
148 Mapped::default()
149 }
150
151 fn motion_fx(&self, _input: &MotionFxGroups, _ctx: &MapContext<'_>) -> Mapped {
152 Mapped::default()
153 }
154}
155
156pub(crate) fn registry() -> Result<&'static Registry, SynthError> {
158 registry::vital_registry().map(|vital| &vital.registry)
159}
160
161pub fn engine_scale(key: &str) -> Result<Option<EngineScale>, SynthError> {
167 registry::vital_registry().map(|vital| vital.scales.get(key).copied())
168}
169
170#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
172pub struct VitalVersion {
173 pub major: u32,
175 pub minor: u32,
177 pub patch: u32,
179}
180
181impl VitalVersion {
182 #[must_use]
186 pub fn accepted_by(&self, app: &Self) -> bool {
187 (self.major, self.minor) <= (app.major, app.minor)
188 }
189}
190
191impl FromStr for VitalVersion {
192 type Err = String;
193
194 fn from_str(text: &str) -> Result<Self, String> {
195 let parts: Vec<&str> = text.split('.').collect();
196 let number = |part: &str| {
197 if part.is_empty() || !part.bytes().all(|byte| byte.is_ascii_digit()) {
198 return None;
199 }
200 part.parse::<u32>().ok()
201 };
202 match parts.as_slice() {
203 [major, minor, patch] => match (number(major), number(minor), number(patch)) {
204 (Some(major), Some(minor), Some(patch)) => Ok(Self {
205 major,
206 minor,
207 patch,
208 }),
209 _ => Err(format!("{text:?} is not three whole numbers such as 1.5.5")),
210 },
211 _ => Err(format!("{text:?} is not three whole numbers such as 1.5.5")),
212 }
213 }
214}
215
216impl fmt::Display for VitalVersion {
217 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
218 write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
219 }
220}
221
222#[cfg(test)]
223mod tests {
224 use super::*;
225 use crate::preset::registry::{ParamRegistry, Physical};
226
227 fn version(text: &str) -> VitalVersion {
228 text.parse().unwrap()
229 }
230
231 #[test]
232 fn the_version_gate_ignores_the_patch() {
233 assert!(version("1.5.5").accepted_by(&version("1.5.1")));
234 assert!(!version("1.6.0").accepted_by(&version("1.5.5")));
235 assert!(!version("1.5.5").accepted_by(&version("1.0.6")));
236 assert!(version("1.0.9").accepted_by(&version("1.0.6")));
237 assert!(version("1.0.6").accepted_by(&version("1.5.5")));
238 }
239
240 #[test]
241 fn version_text_needs_three_numbers() {
242 for text in ["1.5", "a.b.c", "1.5.5.1", "", "1..5", "1.5.-1", "1.5.+1"] {
243 assert!(text.parse::<VitalVersion>().is_err(), "{text}");
244 }
245 assert_eq!(version("1.5.5").to_string(), "1.5.5");
246 assert!(version("1.10.0") > version("1.9.9"));
247 }
248
249 #[test]
250 fn capabilities_hold_and_match_the_registry() {
251 CAPABILITIES.check().unwrap();
252 let registry = registry().unwrap();
253 let default = |key: &str| {
254 registry
255 .get(key)
256 .unwrap()
257 .physical(registry.get(key).unwrap().default)
258 .unwrap()
259 };
260 assert_eq!(
261 default("pitch_bend_range"),
262 Physical::Semitones(CAPABILITIES.bend_range_default)
263 );
264 assert_eq!(
265 default("velocity_track"),
266 Physical::PercentBipolar(CAPABILITIES.velocity.default_track)
267 );
268 let glide = registry.get("portamento_time").unwrap();
269 assert_eq!(
270 glide.physical(glide.stored_min).unwrap(),
271 Physical::Seconds(CAPABILITIES.glide.min)
272 );
273 assert_eq!(
274 glide.physical(glide.stored_max).unwrap(),
275 Physical::Seconds(CAPABILITIES.glide.max)
276 );
277 assert_eq!(
278 registry.get("polyphony").unwrap().stored_max,
279 f64::from(CAPABILITIES.polyphony_max)
280 );
281 let unison = registry.get("osc_1_unison_voices").unwrap();
282 assert_eq!(
283 (unison.stored_min, unison.stored_max),
284 (
285 f64::from(CAPABILITIES.unison_voices.0),
286 f64::from(CAPABILITIES.unison_voices.1)
287 )
288 );
289 }
290
291 #[test]
292 fn const_unit_values_pass_their_checks() {
293 assert_eq!(
294 Seconds::new(0.000_976_562_5).unwrap(),
295 CAPABILITIES.glide.min
296 );
297 assert_eq!(Seconds::new(16.0).unwrap(), CAPABILITIES.glide.max);
298 assert_eq!(
299 Percent::new(0.0).unwrap(),
300 CAPABILITIES.velocity.default_track
301 );
302 assert_eq!(
303 Semitones::new(2.0).unwrap(),
304 CAPABILITIES.bend_range_default
305 );
306 }
307
308 #[test]
309 fn engine_scale_names_the_transform() {
310 assert_eq!(
311 engine_scale("env_1_attack").unwrap(),
312 Some(EngineScale::Quartic)
313 );
314 assert_eq!(engine_scale("nope").unwrap(), None);
315 }
316}