1use serde::{Deserialize, Serialize};
6
7use crate::synth::Synth;
8
9#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
11#[serde(deny_unknown_fields)]
12pub struct Entry {
13 pub id: String,
15 pub group: Group,
17 pub word: String,
19 pub aliases: Vec<String>,
21 pub meaning: String,
23 pub sense: Option<String>,
25 pub default: bool,
27 pub conflicts: Vec<String>,
30 pub rule: Rule,
32 pub example_pass: Option<Fragment>,
34 pub example_fail: Option<Fragment>,
36 pub roles: Vec<String>,
38 pub genres: Vec<String>,
40 pub tier: Tier,
42 pub n_sources: u16,
44 pub source: Vec<String>,
46 pub tag: Tag,
48 pub verified: String,
50 pub notes_trigger: bool,
52 pub controls: Vec<Control>,
54 pub symbols: Vec<String>,
56 pub bpm: Option<Bpm>,
58 pub scale: Option<String>,
60 pub ask: Option<Ask>,
62 pub default_pick: Option<DefaultPick>,
64}
65
66#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
68#[serde(rename_all = "kebab-case")]
69#[non_exhaustive]
70pub enum Group {
71 Notation,
73 Key,
75 Role,
77 Scale,
79 Mood,
81 Outside,
83 Chord,
85 Voicing,
87 VoiceLeading,
89 Progression,
91 Harmony,
93 Position,
95 Swing,
97 Feel,
99 Rhythm,
101 Euclid,
103 Articulation,
105 Bass,
107 Lead,
109 Arp,
111 Pad,
113 Phrase,
115 Genre,
117}
118
119impl Group {
120 #[must_use]
122 pub fn as_str(self) -> &'static str {
123 match self {
124 Self::Notation => "notation",
125 Self::Key => "key",
126 Self::Role => "role",
127 Self::Scale => "scale",
128 Self::Mood => "mood",
129 Self::Outside => "outside",
130 Self::Chord => "chord",
131 Self::Voicing => "voicing",
132 Self::VoiceLeading => "voice-leading",
133 Self::Progression => "progression",
134 Self::Harmony => "harmony",
135 Self::Position => "position",
136 Self::Swing => "swing",
137 Self::Feel => "feel",
138 Self::Rhythm => "rhythm",
139 Self::Euclid => "euclid",
140 Self::Articulation => "articulation",
141 Self::Bass => "bass",
142 Self::Lead => "lead",
143 Self::Arp => "arp",
144 Self::Pad => "pad",
145 Self::Phrase => "phrase",
146 Self::Genre => "genre",
147 }
148 }
149}
150
151#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
153pub enum Tier {
154 M1,
156 M2,
158 M3,
160}
161
162impl Tier {
163 pub const ALL: [Self; 3] = [Self::M1, Self::M2, Self::M3];
165
166 #[must_use]
169 pub fn floor(self) -> u8 {
170 match self {
171 Self::M1 => 100,
172 Self::M2 => 50,
173 Self::M3 => 25,
174 }
175 }
176}
177
178#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
180#[serde(rename_all = "lowercase")]
181pub enum Tag {
182 #[serde(rename = "source states")]
184 SourceStates,
185 Measured,
187 Derived,
189 Inferred,
191 Snippet,
193 #[serde(rename = "single source")]
195 SingleSource,
196 Proposal,
198}
199
200impl Tag {
201 #[must_use]
203 pub fn as_str(self) -> &'static str {
204 match self {
205 Self::SourceStates => "source states",
206 Self::Measured => "measured",
207 Self::Derived => "derived",
208 Self::Inferred => "inferred",
209 Self::Snippet => "snippet",
210 Self::SingleSource => "single source",
211 Self::Proposal => "proposal",
212 }
213 }
214}
215
216#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
219#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
220pub enum Rule {
221 PitchSet {
223 offsets: Vec<u8>,
225 colour: Vec<u8>,
228 giveaway: Vec<u8>,
230 },
231 Tonic {},
234 ChordTones {
236 offsets: Vec<u8>,
238 choose: Option<Choose>,
240 },
241 Voicing(Box<VoicingRule>),
243 Cycle {
245 roots: Vec<u8>,
247 chords: Vec<String>,
249 bars_per_chord: Option<f64>,
251 },
252 OnsetSet {
254 parts: Vec<PartSlots>,
256 },
257 OnsetShare {
259 slots: Vec<u8>,
261 min_share: f64,
263 },
264 CountPerBar {
266 min: u8,
268 max: u8,
270 },
271 Length {
273 unit: LengthUnit,
275 min: Option<f64>,
277 max: Option<f64>,
279 },
280 Velocity {
282 min: u8,
284 max: u8,
286 },
287 Overlap {
289 min_ms: f64,
291 },
292 Swing {
294 note: u8,
296 min_pct: f64,
298 max_pct: f64,
300 },
301 Shift {
303 min_ms: f64,
305 max_ms: f64,
307 },
308 Jitter {
310 max_ms: f64,
312 velocity: u8,
314 },
315 Register {
317 core: [u8; 2],
319 hard: [u8; 2],
321 voice: Voice,
323 },
324 Pattern {
326 k: u8,
328 n: u8,
330 rotation: u8,
332 },
333 Arp {
335 order: ArpOrder,
337 rate: f64,
339 gate: Option<[u8; 2]>,
341 #[serde(default, skip_serializing_if = "Vec::is_empty")]
343 degrees: Vec<u8>,
344 },
345 Repeat {
347 min_share: Option<f64>,
349 max_share: Option<f64>,
351 min_bars: Option<u8>,
353 },
354 PhraseLength {
356 every: Vec<u8>,
358 },
359 None {},
361}
362
363impl Rule {
364 #[must_use]
366 pub fn kind(&self) -> &'static str {
367 match self {
368 Self::PitchSet { .. } => "pitch_set",
369 Self::Tonic {} => "tonic",
370 Self::ChordTones { .. } => "chord_tones",
371 Self::Voicing(_) => "voicing",
372 Self::Cycle { .. } => "cycle",
373 Self::OnsetSet { .. } => "onset_set",
374 Self::OnsetShare { .. } => "onset_share",
375 Self::CountPerBar { .. } => "count_per_bar",
376 Self::Length { .. } => "length",
377 Self::Velocity { .. } => "velocity",
378 Self::Overlap { .. } => "overlap",
379 Self::Swing { .. } => "swing",
380 Self::Shift { .. } => "shift",
381 Self::Jitter { .. } => "jitter",
382 Self::Register { .. } => "register",
383 Self::Pattern { .. } => "pattern",
384 Self::Arp { .. } => "arp",
385 Self::Repeat { .. } => "repeat",
386 Self::PhraseLength { .. } => "phrase_length",
387 Self::None {} => "none",
388 }
389 }
390}
391
392#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
394#[serde(deny_unknown_fields)]
395pub struct Choose {
396 pub from: Vec<u8>,
398 pub at_least: u8,
400}
401
402#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
405#[serde(deny_unknown_fields)]
406pub struct VoicingRule {
407 pub shape: VoicingShape,
409 #[serde(default, skip_serializing_if = "Option::is_none")]
411 pub max_span: Option<u8>,
412 #[serde(default, skip_serializing_if = "Option::is_none")]
414 pub min_span: Option<u8>,
415 #[serde(default, skip_serializing_if = "Option::is_none")]
417 pub max_gap: Option<u8>,
418 #[serde(default, skip_serializing_if = "Option::is_none")]
420 pub min_gap: Option<u8>,
421 #[serde(default, skip_serializing_if = "Option::is_none")]
423 pub step: Option<u8>,
424 #[serde(default, skip_serializing_if = "Option::is_none")]
426 pub step_tolerance: Option<u8>,
427 #[serde(default, skip_serializing_if = "Option::is_none")]
429 pub min_notes: Option<u8>,
430 #[serde(default, skip_serializing_if = "Option::is_none")]
432 pub max_notes: Option<u8>,
433 #[serde(default, skip_serializing_if = "Option::is_none")]
435 pub min_pitch_classes: Option<u8>,
436 #[serde(default, skip_serializing_if = "Option::is_none")]
438 pub lowest: Option<u8>,
439 #[serde(default, skip_serializing_if = "Option::is_none")]
441 pub below: Option<u8>,
442 #[serde(default, skip_serializing_if = "Option::is_none")]
444 pub max_below: Option<u8>,
445 #[serde(default, skip_serializing_if = "Vec::is_empty")]
447 pub drop: Vec<u8>,
448 #[serde(default, skip_serializing_if = "Option::is_none")]
450 pub bass: Option<BassTone>,
451 #[serde(default, skip_serializing_if = "Vec::is_empty")]
453 pub degrees: Vec<u8>,
454 #[serde(default, skip_serializing_if = "Option::is_none")]
456 pub max_leap: Option<u8>,
457 #[serde(default, skip_serializing_if = "Vec::is_empty")]
459 pub floors: Vec<Floor>,
460}
461
462#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
464#[serde(rename_all = "kebab-case")]
465#[non_exhaustive]
466#[allow(
467 missing_docs,
468 reason = "each shape is the voicing word of the same name in the reference"
469)]
470pub enum VoicingShape {
471 Close,
472 Open,
473 Inversion,
474 Slash,
475 #[serde(rename = "drop-2")]
476 Drop2,
477 #[serde(rename = "drop-3")]
478 Drop3,
479 #[serde(rename = "drop-2-and-4")]
480 Drop2And4,
481 Shell,
482 RootlessA,
483 RootlessB,
484 Power,
485 Quartal,
486 Quintal,
487 NeoSoul,
488 Lush,
489 Wide,
490 Muddy,
491 Airy,
492 Thick,
493 IntervalFloor,
494 CommonTone,
495 NearestTone,
496 SmallestMotion,
497 NoParallels,
498 TopHold,
499 Planing,
500 LoopReturn,
501 Cluster,
502 Polychord,
503}
504
505#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
507#[serde(rename_all = "lowercase")]
508pub enum BassTone {
509 Root,
511 Third,
513 Fifth,
515 Seventh,
517 Named,
519}
520
521#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
523#[serde(deny_unknown_fields)]
524pub struct Floor {
525 pub interval: u8,
527 pub lowest: Option<u8>,
529}
530
531#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
533#[serde(deny_unknown_fields)]
534pub struct PartSlots {
535 pub part: String,
537 pub slots: Vec<u8>,
539}
540
541#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
543#[serde(rename_all = "lowercase")]
544pub enum LengthUnit {
545 Beat,
547 Step,
549}
550
551#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
553#[serde(rename_all = "lowercase")]
554pub enum Voice {
555 Mono,
557 Poly,
559 Either,
561}
562
563#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
565#[serde(rename_all = "kebab-case")]
566pub enum ArpOrder {
567 Up,
569 Down,
571 UpDown,
573 Sequence,
575}
576
577#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
579#[serde(deny_unknown_fields)]
580pub struct Fragment {
581 pub root: Option<u8>,
583 pub bpm: Option<f64>,
585 pub grid: u8,
587 pub notes: Vec<FragmentNote>,
589}
590
591#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
593#[serde(deny_unknown_fields)]
594pub struct FragmentNote {
595 pub pitch: u8,
597 pub start: u16,
599 pub len: f64,
601 pub vel: u8,
603 #[serde(default = "full_gate")]
605 pub gate: u8,
606 #[serde(default)]
608 pub offset_ms: f64,
609 #[serde(default)]
611 pub part: Option<String>,
612}
613
614fn full_gate() -> u8 {
615 100
616}
617
618#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
620#[serde(deny_unknown_fields)]
621pub struct Control {
622 pub synth: Synth,
624 pub label: String,
626 pub direction: String,
628 pub step: String,
630 pub order: u8,
632 pub source: Vec<String>,
634 pub tag: String,
636}
637
638#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
640#[serde(deny_unknown_fields)]
641pub struct Bpm {
642 pub low: f64,
644 pub center: Option<f64>,
646 pub high: f64,
648}
649
650#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
652#[serde(deny_unknown_fields)]
653pub struct Ask {
654 pub question: String,
656 pub options: Vec<String>,
658 pub default: String,
660 pub when: String,
662}
663
664#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
667#[serde(deny_unknown_fields)]
668pub struct DefaultPick {
669 pub scale: Option<String>,
671 pub alternatives: Vec<String>,
673 pub note: String,
675}
676
677#[cfg(test)]
678mod tests {
679 use super::*;
680
681 #[test]
682 fn rules_read_their_kind_and_reject_unknown_fields() {
683 let voicing: Rule =
684 serde_json::from_str(r#"{"kind": "voicing", "shape": "drop-2", "drop": [2]}"#).unwrap();
685 assert_eq!(voicing.kind(), "voicing");
686 let Rule::Voicing(rule) = &voicing else {
687 unreachable!()
688 };
689 assert_eq!(
690 (rule.shape, rule.drop.as_slice()),
691 (VoicingShape::Drop2, &[2][..])
692 );
693 let set: Rule = serde_json::from_str(
694 r#"{"kind": "pitch_set", "offsets": [0, 2, 3], "colour": [], "giveaway": []}"#,
695 )
696 .unwrap();
697 assert_eq!(set.kind(), "pitch_set");
698 assert!(serde_json::from_str::<Rule>(r#"{"kind": "none", "text": "x"}"#).is_err());
699 assert!(
700 serde_json::from_str::<Rule>(r#"{"kind": "voicing", "shape": "drop-2", "x": 1}"#)
701 .is_err()
702 );
703 assert!(serde_json::from_str::<Rule>(r#"{"kind": "free text"}"#).is_err());
704 assert!(serde_json::from_str::<Rule>(r#"{"kind": "voicing", "shape": "pretty"}"#).is_err());
705 }
706}