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}
59
60impl Step {
61 /// A step with no modifiers and no velocity.
62 pub fn bare(atom: Atom) -> Self {
63 Self {
64 atom,
65 modifiers: Vec::new(),
66 velocity: None,
67 }
68 }
69}
70
71/// The core building blocks of the mini-notation.
72#[derive(Debug, Clone, PartialEq)]
73pub enum Atom {
74 /// A pitched note: `c4`, `eb3`, `f#5`
75 Note(Note),
76 /// A scale degree: `0`, `3`, `7`
77 Degree(i32),
78 /// A drum trigger: `x`, or `X` for the accented spelling — which is stored
79 /// as this atom with [`Step::velocity`] set to `1.0`, so a consumer that
80 /// honours `:v` honours `X` without knowing it exists.
81 Trigger,
82 /// Silence for this slot: `~`
83 Rest,
84 /// Hold/tie the previous event: `_`
85 Hold,
86 /// A grouped subsequence `[c4 e4 g4]`, a chord `[c4,e4,g4]`, or a random
87 /// choice `[c4|e4|g4]` — see [`GroupMode`].
88 Group(Group),
89 /// Cycle through alternatives: `<c4 e4 g4>`
90 Alternation(Alternation),
91 /// A generated melody: `solo(0..7, 8)` — see [`Solo`].
92 Solo(Solo),
93}
94
95/// A pitched note with letter, accidental, and octave.
96#[derive(Debug, Clone, Copy, PartialEq, Eq)]
97pub struct Note {
98 pub letter: NoteLetter,
99 pub accidental: Accidental,
100 pub octave: u8,
101}
102
103/// How the layers of a bracketed group relate to each other.
104#[derive(Debug, Clone, Copy, PartialEq, Eq)]
105pub enum GroupMode {
106 /// A single layer that subdivides the parent step: `[c4 e4]`.
107 Subdivide,
108 /// Comma-separated layers sounding together: `[c4,e4,g4]`.
109 Chord,
110 /// Pipe-separated layers, one picked per cycle: `[c4|e4|g4]`.
111 Random,
112}
113
114/// A bracketed group `[...]`.
115///
116/// `mode` records which separator was used. A group carries either commas or
117/// pipes, never both, so the two never need precedence against each other.
118/// A single-layer group is always [`GroupMode::Subdivide`].
119#[derive(Debug, Clone, PartialEq)]
120pub struct Group {
121 pub mode: GroupMode,
122 pub layers: Vec<Sequence>,
123}
124
125/// An alternation `<...>` — cycles through the inner sequence steps, one per
126/// loop iteration.
127#[derive(Debug, Clone, PartialEq)]
128pub struct Alternation {
129 pub sequence: Sequence,
130}
131
132/// Modifiers that can be appended to any atom.
133#[derive(Debug, Clone, PartialEq)]
134pub enum Modifier {
135 /// `*N` — repeat within the time slot. May ramp: `x*4..16`.
136 Repeat(Ramp<u32>),
137 /// `/N` — stretch over N cycles.
138 Slow(u32),
139 /// `!N` — replicate as N separate equal steps.
140 Replicate(u32),
141 /// `(onsets, positions[, offset])` — Euclidean rhythm. Onsets and positions
142 /// may ramp, which is how a density crescendo is written: `x(4..16,4)`.
143 Euclidean(Ramp<u32>, Ramp<u32>, Option<u32>),
144 /// `?` — chance of silence. `None` means the default 50%; `?0.25` carries
145 /// an explicit probability in `0.0..=1.0`, which may ramp: `?0.1..0.9`.
146 Drop(Option<Ramp<f64>>),
147 /// `@N` — proportional duration weight.
148 Weight(u32),
149}