Skip to main content

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}