treble_lang/ast/mini.rs
1//! Mini-notation AST — the pattern language inside double quotes.
2
3use super::program::{Accidental, NoteLetter, Ramp};
4
5/// A generated melody: `solo(low..high, steps)`.
6///
7/// Walks the scale degrees `low..=high` in a weighted random walk, `steps`
8/// notes per cycle. Deterministic per pattern and cycle — it evolves from
9/// cycle to cycle but replays identically from the same buffer, like `?` and
10/// `[a|b]`. `steps` may ramp (`solo(0..7, 4..16)`), so a solo can densify
11/// across the line's `ramp` span.
12#[derive(Debug, Clone, PartialEq)]
13pub struct Solo {
14 pub low: i32,
15 pub high: i32,
16 pub steps: Ramp<u32>,
17}
18
19/// The top-level mini-notation tree (contents of a quoted pattern string).
20/// Represents a full cycle that will be looped.
21#[derive(Debug, Clone, PartialEq)]
22pub struct MiniNotation {
23 pub sequence: Sequence,
24}
25
26/// An ordered list of steps that share their parent's time equally
27/// (unless weights `@N` are present).
28#[derive(Debug, Clone, PartialEq)]
29pub struct Sequence {
30 pub steps: Vec<Step>,
31}
32
33/// A single step in a sequence: an atom, zero or more modifiers, and an
34/// optional velocity.
35///
36/// Modifiers stack and are applied left-to-right in written order, so `x*8?`
37/// first expands into eight slots and then gives each of them a drop chance.
38///
39/// Velocity is a **field rather than a [`Modifier`]** because it is a property
40/// of the step, not a transformation of the slots a modifier produced. The
41/// slot-generating modifiers (`*N`, `!N`, `(k,n)`) run first and build the
42/// slots; a velocity written anywhere in the run — `x:0.6*4` or `x*4:0.6` —
43/// therefore applies to every slot the step generates, and cannot be read as
44/// applying to only the slots that existed when it was written. It also makes
45/// "at most one velocity per step" a type invariant instead of a check.
46#[derive(Debug, Clone, PartialEq)]
47pub struct Step {
48 pub atom: Atom,
49 pub modifiers: Vec<Modifier>,
50 /// `:v`, or `1.0` for the accent spelling `X` — an **absolute** velocity in
51 /// `0.0..=1.0` that overrides the line's `vel` for this step. `None` takes
52 /// the line's `vel`. May travel across the line's `ramp` span, which is how
53 /// a per-step swell (`x:0.3..0.9`) is written.
54 ///
55 /// On a group or an alternation this is the velocity of everything the step
56 /// sounds, except where an inner step names its own.
57 pub velocity: Option<Ramp<f64>>,
58 /// Where the step was written — atom, modifiers and velocity — as a byte
59 /// range in the notation string between the quotes. An editor uses it to
60 /// point from a sounding note back to the text that made it.
61 pub source: Source,
62}
63
64impl Step {
65 /// A step with no modifiers and no velocity.
66 pub fn bare(atom: Atom) -> Self {
67 Self {
68 atom,
69 modifiers: Vec::new(),
70 velocity: None,
71 source: Source::default(),
72 }
73 }
74}
75
76/// A byte range in a notation string, end exclusive.
77///
78/// Positions are not part of what a step means, so every `Source` compares
79/// equal to every other. Two notations that differ only in spacing are the same
80/// line: a test can write its expected AST without counting columns, and a
81/// session does not mistake a re-indented line for an edited one and restart
82/// its ramps.
83#[derive(Debug, Clone, Copy, Default, Eq)]
84pub struct Source {
85 pub start: u32,
86 pub end: u32,
87}
88
89impl PartialEq for Source {
90 fn eq(&self, _: &Self) -> bool {
91 true
92 }
93}
94
95/// The core building blocks of the mini-notation.
96#[derive(Debug, Clone, PartialEq)]
97pub enum Atom {
98 /// A pitched note: `c4`, `eb3`, `f#5`
99 Note(Note),
100 /// A scale degree: `0`, `3`, `7`
101 Degree(i32),
102 /// A drum trigger: `x`, or `X` for the accented spelling — which is stored
103 /// as this atom with [`Step::velocity`] set to `1.0`, so a consumer that
104 /// honours `:v` honours `X` without knowing it exists.
105 Trigger,
106 /// Silence for this slot: `~`
107 Rest,
108 /// Hold/tie the previous event: `_`
109 Hold,
110 /// A grouped subsequence `[c4 e4 g4]`, a chord `[c4,e4,g4]`, or a random
111 /// choice `[c4|e4|g4]` — see [`GroupMode`].
112 Group(Group),
113 /// Cycle through alternatives: `<c4 e4 g4>`
114 Alternation(Alternation),
115 /// A generated melody: `solo(0..7, 8)` — see [`Solo`].
116 Solo(Solo),
117}
118
119/// A pitched note with letter, accidental, and octave.
120#[derive(Debug, Clone, Copy, PartialEq, Eq)]
121pub struct Note {
122 pub letter: NoteLetter,
123 pub accidental: Accidental,
124 pub octave: u8,
125}
126
127/// How the layers of a bracketed group relate to each other.
128#[derive(Debug, Clone, Copy, PartialEq, Eq)]
129pub enum GroupMode {
130 /// A single layer that subdivides the parent step: `[c4 e4]`.
131 Subdivide,
132 /// Comma-separated layers sounding together: `[c4,e4,g4]`.
133 Chord,
134 /// Pipe-separated layers, one picked per cycle: `[c4|e4|g4]`.
135 Random,
136}
137
138/// A bracketed group `[...]`.
139///
140/// `mode` records which separator was used. A group carries either commas or
141/// pipes, never both, so the two never need precedence against each other.
142/// A single-layer group is always [`GroupMode::Subdivide`].
143#[derive(Debug, Clone, PartialEq)]
144pub struct Group {
145 pub mode: GroupMode,
146 pub layers: Vec<Sequence>,
147}
148
149/// An alternation `<...>` — cycles through the inner sequence steps, one per
150/// loop iteration.
151#[derive(Debug, Clone, PartialEq)]
152pub struct Alternation {
153 pub sequence: Sequence,
154}
155
156/// Modifiers that can be appended to any atom.
157#[derive(Debug, Clone, PartialEq)]
158pub enum Modifier {
159 /// `*N` — repeat within the time slot. May ramp: `x*4..16`.
160 Repeat(Ramp<u32>),
161 /// `/N` — stretch over N cycles.
162 Slow(u32),
163 /// `!N` — replicate as N separate equal steps.
164 Replicate(u32),
165 /// `(onsets, positions[, offset])` — Euclidean rhythm. Onsets and positions
166 /// may ramp, which is how a density crescendo is written: `x(4..16,4)`.
167 Euclidean(Ramp<u32>, Ramp<u32>, Option<u32>),
168 /// `?` — chance of silence. `None` means the default 50%; `?0.25` carries
169 /// an explicit probability in `0.0..=1.0`, which may ramp: `?0.1..0.9`.
170 Drop(Option<Ramp<f64>>),
171 /// `@N` — proportional duration weight.
172 Weight(u32),
173}