|
| 1 | +^{:kindly/hide-code true |
| 2 | + :clay {:title "Noon — Composing Music with Clojure" |
| 3 | + :quarto {:author :pbaille |
| 4 | + :description "An introduction to noon, a Clojure library for composing MIDI music using layered harmonic abstractions." |
| 5 | + :type :post |
| 6 | + :date "2026-02-10" |
| 7 | + :category :music |
| 8 | + :tags [:music :harmony :functional-programming :midi] |
| 9 | + :image "noon_introduction/steps.png"}}} |
| 10 | + |
| 11 | +(ns music.noon-introduction) |
| 12 | + |
| 13 | +;; ## What is Noon? |
| 14 | +;; |
| 15 | +;; [Noon](https://github.com/pbaille/noon) is a Clojure library for composing and playing MIDI music. |
| 16 | +;; The name comes from French — "noon" translates to "MIDI." |
| 17 | +;; |
| 18 | +;; The motivation is simple: digital audio workstations (DAWs) are powerful but slow. |
| 19 | +;; The feedback loop — click, drag, listen, repeat — doesn't match how musicians actually think about music. |
| 20 | +;; Musicians think in terms of scales, intervals, chord progressions, melodic contour. |
| 21 | +;; Noon tries to bring that level of abstraction into code. |
| 22 | + |
| 23 | +;; ## The Core Model |
| 24 | +;; |
| 25 | +;; A noon score is a set of event maps. That's it. |
| 26 | +;; Each event represents a MIDI note with properties like position, duration, velocity, and pitch: |
| 27 | +;; |
| 28 | +;;```clj |
| 29 | +;; noon.events/DEFAULT_EVENT |
| 30 | +;; ;; => {:position 0, :duration 1, :channel 0, :track 0, |
| 31 | +;; ;; :velocity 80, :pitch {...}, :voice 0, :patch [0 4]} |
| 32 | +;;``` |
| 33 | +;; |
| 34 | +;; A fresh score contains a single event — middle C: |
| 35 | +;; |
| 36 | +;;```clj |
| 37 | +;; (score) |
| 38 | +;; ;; => #{{:position 0, :duration 1, ...}} |
| 39 | +;;``` |
| 40 | +;; |
| 41 | +;; Everything in noon is a **transformation** — a function from score to score. |
| 42 | +;; You build music by composing transformations: |
| 43 | +;; |
| 44 | +;;```clj |
| 45 | +;; (play dur2 (tup s0 s1 s2 s3)) |
| 46 | +;;``` |
| 47 | +;; |
| 48 | +;; This plays an ascending arpeggio at double duration. |
| 49 | +;; `dur2` doubles the duration, `tup` splits it into equal parts, |
| 50 | +;; and `s0` through `s3` are structural intervals (more on those soon). |
| 51 | + |
| 52 | +;; ## Building Blocks |
| 53 | +;; |
| 54 | +;; Three core functions handle most of the composition: |
| 55 | +;; |
| 56 | +;; **`lin`** — concatenate transformations in sequence (a melody): |
| 57 | +;; |
| 58 | +;;```clj |
| 59 | +;; (play (lin C0 E0 G0 B0)) |
| 60 | +;;``` |
| 61 | +;; |
| 62 | +;; **`par`** — stack transformations simultaneously (a chord): |
| 63 | +;; |
| 64 | +;;```clj |
| 65 | +;; (play (par C0 Eb0 G0)) ; C minor chord |
| 66 | +;;``` |
| 67 | +;; |
| 68 | +;; **`tup`** — like `lin`, but fitted to the current duration (a tuplet): |
| 69 | +;; |
| 70 | +;;```clj |
| 71 | +;; (play (tup c0 c3 c7 c10)) ; four notes in the space of one |
| 72 | +;;``` |
| 73 | +;; |
| 74 | +;; These compose freely. A chord progression with arpeggios: |
| 75 | +;; |
| 76 | +;;```clj |
| 77 | +;; (play (lin I IV V I) |
| 78 | +;; (each (tup s0 s1 s2))) |
| 79 | +;;``` |
| 80 | +;; |
| 81 | +;; And transformations can be chained using vectors: |
| 82 | +;; |
| 83 | +;;```clj |
| 84 | +;; (play [dur:2 (lin C0 E0 G0)]) |
| 85 | +;;``` |
| 86 | +;; |
| 87 | +;; A few more useful combinators: |
| 88 | +;; |
| 89 | +;; - `rep` — repeat a transformation accumulating results: `(rep 8 d1)` plays 8 ascending scale steps |
| 90 | +;; - `dup` — duplicate a score n times: `(dup 4)` repeats 4 times |
| 91 | +;; - `each` — apply a transformation to every event individually |
| 92 | +;; - `chans` — like `par` but on separate MIDI channels (for different instruments) |
| 93 | +;; |
| 94 | +;;```clj |
| 95 | +;; (play |
| 96 | +;; (chans |
| 97 | +;; [(patch :ocarina) (tup s0 s1 s2 s3) (rep 4 s1)] |
| 98 | +;; [(patch :vibraphone) vel3 (par s0 s1 s2)] |
| 99 | +;; [(patch :acoustic-bass) o1-]) |
| 100 | +;; (dup 4)) |
| 101 | +;;``` |
| 102 | + |
| 103 | +;; ## How Musicians Think About Pitch |
| 104 | +;; |
| 105 | +;; Before diving into noon's harmonic system, let's consider how musicians actually think about notes. |
| 106 | +;; |
| 107 | +;; When a jazz musician sees a G7 chord symbol, they don't think "play MIDI notes 55, 59, 62, 65." |
| 108 | +;; They think: "root, third, fifth, seventh — in G mixolydian." |
| 109 | +;; If the chord changes to Gm7, they adjust their mental framework — same structure, different scale. |
| 110 | +;; The *relationships* between notes matter more than the notes themselves. |
| 111 | +;; |
| 112 | +;; This is a crucial distinction. Most music software works at the lowest level — absolute pitches, raw MIDI numbers. |
| 113 | +;; But musicians navigate a hierarchy of abstractions: |
| 114 | +;; |
| 115 | +;; - "Go up a semitone" — **chromatic** thinking |
| 116 | +;; - "Go to the next note in the scale" — **diatonic** thinking |
| 117 | +;; - "Go to the next chord tone" — **structural** thinking |
| 118 | +;; - "Go to the next octave" or "change the key" — **tonic** thinking |
| 119 | +;; |
| 120 | +;; Noon encodes this hierarchy directly. |
| 121 | + |
| 122 | +;; ## The Four Layers |
| 123 | +;; |
| 124 | +;; Noon's harmonic system is built on four layers, each one selecting from the layer below: |
| 125 | +;; |
| 126 | +;; **1. Chromatic** — the 12 semitones of an octave: `[0 1 2 3 4 5 6 7 8 9 10 11]` |
| 127 | +;; |
| 128 | +;; This is the raw material — every possible note. |
| 129 | +;; |
| 130 | +;; **2. Diatonic** — a selection of the chromatic layer forming a scale: `[0 2 4 5 7 9 11]` |
| 131 | +;; |
| 132 | +;; This is the major scale — 7 of the 12 chromatic notes. Change this and you're in a different scale (dorian, melodic minor, etc.). |
| 133 | +;; |
| 134 | +;; **3. Structural** — a selection of the diatonic layer forming a chord: `[0 2 4]` |
| 135 | +;; |
| 136 | +;; This picks the 1st, 3rd, and 5th degrees of the scale — a triad. Change this and you get a different chord shape (tetrad, sus4, etc.). |
| 137 | +;; |
| 138 | +;; **4. Tonic** — the root: `[0]` |
| 139 | +;; |
| 140 | +;; The single reference point everything is built from. |
| 141 | +;; |
| 142 | +;; Each event in a noon score carries this full context under its `:pitch` key: |
| 143 | +;; |
| 144 | +;;```clj |
| 145 | +;; (:pitch noon.events/DEFAULT_EVENT) |
| 146 | +;; ;; => {:scale [0 2 4 5 7 9 11] ; C major |
| 147 | +;; ;; :structure [0 2 4] ; triad |
| 148 | +;; ;; :origin {:d 35, :c 60} ; middle C |
| 149 | +;; ;; :position {:t 0, :s 0, :d 0, :c 0}} |
| 150 | +;;``` |
| 151 | +;; |
| 152 | +;; The `:position` map holds an offset at each layer. |
| 153 | +;; This is where the note actually *is* within the harmonic context. |
| 154 | + |
| 155 | +;; ## Steps |
| 156 | +;; |
| 157 | +;; Each layer has a corresponding **step** operation. |
| 158 | +;; A step moves you to the next position *on that layer*: |
| 159 | +;; |
| 160 | +;;```clj |
| 161 | +;; (play (tup c0 c1 c2 c3 c4 c5 c6)) ; chromatic: semitone by semitone |
| 162 | +;; (play (tup d0 d1 d2 d3 d4 d5 d6)) ; diatonic: scale degree by degree |
| 163 | +;; (play (tup s0 s1 s2 s3)) ; structural: chord tone by chord tone |
| 164 | +;; (play (tup t0 t1 t2)) ; tonic: octave by octave |
| 165 | +;;``` |
| 166 | + |
| 167 | +;;  |
| 168 | + |
| 169 | +;; The chromatic run gives you all 7 semitones. |
| 170 | +;; The diatonic run gives you the major scale. |
| 171 | +;; The structural run gives you the arpeggio. |
| 172 | +;; The tonic run gives you octaves. |
| 173 | +;; Same syntax, different musical meaning — determined by the layer. |
| 174 | +;; |
| 175 | +;; Here's the key insight: **steps are always relative to the current harmonic context**. |
| 176 | +;; When you change the scale or structure, the same step operations produce different notes: |
| 177 | +;; |
| 178 | +;;```clj |
| 179 | +;; ;; Major scale |
| 180 | +;; (play dur:4 (lin d0 d1 d2 d3 d4 d5 d6 d7)) |
| 181 | +;; |
| 182 | +;; ;; Dorian scale — same code, different result |
| 183 | +;; (play dur:4 (scale :dorian) (lin d0 d1 d2 d3 d4 d5 d6 d7)) |
| 184 | +;; |
| 185 | +;; ;; Hungarian scale |
| 186 | +;; (play dur:4 (scale :hungarian) (lin d0 d1 d2 d3 d4 d5 d6 d7)) |
| 187 | +;;``` |
| 188 | + |
| 189 | +;;  |
| 190 | + |
| 191 | +;; The same goes for structural steps: |
| 192 | +;; |
| 193 | +;;```clj |
| 194 | +;; ;; Triad arpeggio |
| 195 | +;; (play (tup s0 s1 s2 s3)) |
| 196 | +;; |
| 197 | +;; ;; Tetrad arpeggio — same code, richer chord |
| 198 | +;; (play (structure :tetrad) (tup s0 s1 s2 s3)) |
| 199 | +;;``` |
| 200 | + |
| 201 | +;;  |
| 202 | + |
| 203 | +;; ## Changing the Context |
| 204 | +;; |
| 205 | +;; Several functions let you reshape the harmonic context: |
| 206 | +;; |
| 207 | +;; **`scale`** — change the scale: |
| 208 | +;; |
| 209 | +;;```clj |
| 210 | +;; (play (scale :melodic-minor) (rup 8 d1)) |
| 211 | +;;``` |
| 212 | +;; |
| 213 | +;; **`structure`** — change the chord structure: |
| 214 | +;; |
| 215 | +;;```clj |
| 216 | +;; (play (structure :tetrad) (tup s0 s1 s2 s3)) |
| 217 | +;;``` |
| 218 | +;; |
| 219 | +;; **`root`** — change the tonal center: |
| 220 | +;; |
| 221 | +;;```clj |
| 222 | +;; (play (root :Eb) (tup s0 s1 s2)) |
| 223 | +;;``` |
| 224 | +;; |
| 225 | +;; **`degree`** — move to a scale degree (mode change): |
| 226 | +;; |
| 227 | +;;```clj |
| 228 | +;; (play (lin I IV V I) (each (tup s0 s1 s2))) |
| 229 | +;;``` |
| 230 | + |
| 231 | +;;  |
| 232 | + |
| 233 | +;; Here `I`, `IV`, `V` are degree changes — they shift the harmonic context |
| 234 | +;; so that structural steps target the chord tones of each degree. |
| 235 | +;; The `each` function applies the arpeggio pattern to every chord individually. |
| 236 | + |
| 237 | +;; ## Mixing Layers |
| 238 | +;; |
| 239 | +;; The real power emerges when you mix layers. |
| 240 | +;; Since each event carries the full context, you can navigate between layers freely: |
| 241 | +;; |
| 242 | +;;```clj |
| 243 | +;; ;; Structural arpeggio with diatonic passing tones |
| 244 | +;; (play dur:2 |
| 245 | +;; (tup s0 s1 s2 s3) |
| 246 | +;; (each (tup d1 d1- d0))) |
| 247 | +;;``` |
| 248 | + |
| 249 | +;;  |
| 250 | + |
| 251 | +;; This creates a chord arpeggio (`s0 s1 s2 s3`) then decorates each chord tone |
| 252 | +;; with upper and lower neighbor scale notes (`d1 d1- d0`). |
| 253 | +;; The diatonic steps know which scale they're in; |
| 254 | +;; the structural steps know which chord they're targeting. |
| 255 | +;; They coexist naturally. |
| 256 | +;; |
| 257 | +;; An arpeggiated chord progression in harmonic minor: |
| 258 | +;; |
| 259 | +;;```clj |
| 260 | +;; (play (scale :harmonic-minor) |
| 261 | +;; (lin I IV VII I) |
| 262 | +;; (each (tup s0 s1 s2))) |
| 263 | +;;``` |
| 264 | + |
| 265 | +;;  |
| 266 | + |
| 267 | +;; ## Why This Matters |
| 268 | +;; |
| 269 | +;; In most music programming systems, if you want a chord arpeggio, you specify exact intervals: |
| 270 | +;; "0, 4, 7 semitones" for major, "0, 3, 7" for minor. |
| 271 | +;; If you change the key or the chord quality, you rewrite the intervals. |
| 272 | +;; |
| 273 | +;; In noon, `(tup s0 s1 s2)` means "play the first three chord tones" regardless of context. |
| 274 | +;; Put it in C major and you get C-E-G. |
| 275 | +;; Put it in D dorian and you get D-F-A. |
| 276 | +;; Change to a tetrad structure and `s3` appears as the seventh. |
| 277 | +;; The code expresses the musical *intent* — "arpeggiate the chord" — and the harmonic context handles the rest. |
| 278 | +;; |
| 279 | +;; This mirrors how musicians think. |
| 280 | +;; A jazz musician doesn't memorize separate fingerings for every chord in every key. |
| 281 | +;; They learn *patterns* — "1-3-5", "approach from below", "enclosure" — and apply them across contexts. |
| 282 | +;; Noon works the same way. |
| 283 | +;; |
| 284 | +;; --- |
| 285 | +;; |
| 286 | +;; Noon is open source — [github.com/pbaille/noon](https://github.com/pbaille/noon). |
| 287 | +;; There's also an [online playground](https://pbaille.github.io/noon/) where you can try it in the browser. |
0 commit comments