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}
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}