From 3ab3079cc173baea9f6e1cf27ca75f959d00cc9e Mon Sep 17 00:00:00 2001 From: Jake Zerrer Date: Wed, 26 Nov 2025 15:10:20 -0500 Subject: Devlog updates --- DEVLOG.md | 256 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 256 insertions(+) diff --git a/DEVLOG.md b/DEVLOG.md index e56a920..25e7557 100644 --- a/DEVLOG.md +++ b/DEVLOG.md @@ -931,3 +931,259 @@ TODO upcoming: - Inspired by strudel, define a language of musical modifiers - Read notes of Nov. 28 + +## December 3rd, 2025 + +Unheard made sound for the first time yesterday! Very exciting. + +Some ideas from yesterday that I want to carry forward: + +1. Create a function that takes channel, etc., and returns a note flow. +2. Play with using dynamic variables? For... idk. + +WHOA! Strudel keeps blowing my mind. +I just discovered that anything can take a pattern, e.g. scales: https://strudel.cc/workshop/first-notes/#scales +WTF, so cool! This has broken my brain. + +Trying to unpack this. +I guess this is how I'd think about this: each pattern describes +values. (This is the hole in the mini-notation pattern.) Each pattern +is associated with an _attribute type_: One might be pitch, +another instrument. The attribute patterns are all merged together +as if by parallel composition. In my DSL, I would think about the +value of each attribute being a tuple of e.g. [:pitch :e4] or +[:instrument :piano]. + +What does this mean for me? + +One takeaway (I think) is that a composition has multiple instrument-like +things, and that each instrument-like thing is a union of these +various patterened attributes. At the top level, all of these unions play +together via parallel composition. + +What would it mean for me to completely invert my playback model, +where each instrument is a flow rather than each note? I don't think +this is quite right, though. + +In particular, I'm not sure how to merge this idea into my functional composition +model. + +But there is definitely something cool here: the attributes of +an instrument are the timewise union of all patterns. + +Like, what if a rhythmic flow took as its input flows of other associated +properties. (But what is a rhythmic flow? That idea doesn't exist yet.) + +Need to keep thinking about this + +I'm looking at this version of compiled-tetris: + +```clojure +(defn n [>ch >val >vel] + (let [[ch val vel] (m/?< (m/latest vector >ch >val >vel))] + {ch {val :on}}))) + +(def compiled-tetris + (p + (let [n (fn [val] (n (m/ap 0) (m/ap val) (m/ap 100)))] + (f (l (n e5) (l (n b4) (n c5)) (n d5) (l (n c5) (n b4))) + (l (n a4) (l (n a4) (n c5)) (n e5) (l (n d5) (n c5))) + (l (n b4) (l r (n c5)) (n d5) (n e5)) + (l (n c5) (n a4) (n a4) r) + (l (l r (n d5)) (l r (n f5)) (n a5) (l (n g5) (n f5))) + (l (n e5) (l r (n c5)) (n e5) (l (n d5) (n c5))) + (l (n b4) (l (n b4) (n c5)) (n d5) (n e5)) + (l (n c5) (n a4) (n a4) r))) + + (let [n (fn [val] (n (m/ap 1) (m/ap val) (m/ap 100)))] + (f (rate 4 (l (n e2) (n e3))) + (rate 4 (l (n a2) (n a3))) + (l (rate 2 (l (n gs2) (n gs3))) (rate 2 (l (n e2) (n e3)))) + (l (n a2) (n a3) (n a2) (n a3) (n a2) (n a3) (n b1) (n c2)) + (rate 4 (l (n d2) (n d3))) + (rate 4 (l (n c2) (n c3))) + (l (rate 2 (l (n b1) (n b2))) (rate 2 (l (n e2) (n e3)))) + (rate 4 (l (n a1) (n a2))))))) +``` + +Notice how it's possible to arbitrarily parameterize the various qualities of `note`. +Cool. But how can I make it possible to _also_ parameterize an attribute of the notes +using strudel syntax? For example, I also want octave to impact this instruments in +this phrase: + +```clojure +(defn n [>ch >val >vel] + (let [[ch val vel] (m/?< (m/latest vector >ch >val >vel))] + {ch {val :on}}))) + +(def compiled-tetris + (p + ;; melody block + (let [n (fn [val] (n (m/ap 0) (m/ap val) (m/ap 100)))] + (f (l (n e5) (l (n b4) (n c5)) (n d5) (l (n c5) (n b4))) + (l (n a4) (l (n a4) (n c5)) (n e5) (l (n d5) (n c5))) + (l (n b4) (l r (n c5)) (n d5) (n e5)) + (l (n c5) (n a4) (n a4) r) + (l (l r (n d5)) (l r (n f5)) (n a5) (l (n g5) (n f5))) + (l (n e5) (l r (n c5)) (n e5) (l (n d5) (n c5))) + (l (n b4) (l (n b4) (n c5)) (n d5) (n e5)) + (l (n c5) (n a4) (n a4) r))) + + ;; bass block + (let [n (fn [val] (n (m/ap 1) (m/ap val) (m/ap 100)))] + (f (rate 4 (l (n e2) (n e3))) + (rate 4 (l (n a2) (n a3))) + (l (rate 2 (l (n gs2) (n gs3))) (rate 2 (l (n e2) (n e3)))) + (l (n a2) (n a3) (n a2) (n a3) (n a2) (n a3) (n b1) (n c2)) + (rate 4 (l (n d2) (n d3))) + (rate 4 (l (n c2) (n c3))) + (l (rate 2 (l (n b1) (n b2))) (rate 2 (l (n e2) (n e3)))) + (rate 4 (l (n a1) (n a2))))) + + ;; octave block + (l (octave 0) (octave 1)))) + +``` + +Here, octave and note both are returning flows. How do we define +the merge semantics of octave block? Should it merge with the bass +block? The melody block? The melody block has many entities in it. + +This feels on the one hand like a lexical problem. _Maybe_ the +solution should be limited to the speific semantics of the strudel +mini-notation format. But I think probably not? + +Oh, this is very helpful: +https://strudel.cc/learn/effects/#signal-chain + +Strudel has made this very concrete. Each pattern gets a sound, +asdr, some filters, effects, and delay/reverb. + +What if I don't want that? + +Let's jump to a very different idea: name trees +The idea is that nested phrases introduce nested names + +```clojure +(def a (phrase ...)) + +{`[a] ... + `[b a] ...} + +(def b + (phrase (a))) +``` + +Hm, what if `a` is used twice in `b`? +Oh, I wrote about this on Nov. 28. The answer has to do with providing the name at invocation time. + +```clojure +(def b (phrase ...)) + +(def a + (phrase (b :b))) + +(a :a) + +{[:a :b] ... + [:a] ...} +``` + +I bring this up now because it might relate to this merging question. + +```clojure +(defn n [>ch >val >vel] + (let [[ch val vel] (m/?< (m/latest vector >ch >val >vel))] + {ch {val :on}}))) + +(def a + (phrase + (let [n (fn [val] (n (m/ap 0) (m/ap val) (m/ap 100)))] + (f (l (n e5) (l (n b4) (n c5)) (n d5) (l (n c5) (n b4))) + (l (n a4) (l (n a4) (n c5)) (n e5) (l (n d5) (n c5))) + (l (n b4) (l r (n c5)) (n d5) (n e5)) + (l (n c5) (n a4) (n a4) r) + (l (l r (n d5)) (l r (n f5)) (n a5) (l (n g5) (n f5))) + (l (n e5) (l r (n c5)) (n e5) (l (n d5) (n c5))) + (l (n b4) (l (n b4) (n c5)) (n d5) (n e5)) + (l (n c5) (n a4) (n a4) r))))) + +(def b + (phrase + (let [n (fn [val] (n (m/ap 1) (m/ap val) (m/ap 100)))] + (f (rate 4 (l (n e2) (n e3))) + (rate 4 (l (n a2) (n a3))) + (l (rate 2 (l (n gs2) (n gs3))) (rate 2 (l (n e2) (n e3)))) + (l (n a2) (n a3) (n a2) (n a3) (n a2) (n a3) (n b1) (n c2)) + (rate 4 (l (n d2) (n d3))) + (rate 4 (l (n c2) (n c3))) + (l (rate 2 (l (n b1) (n b2))) (rate 2 (l (n e2) (n e3)))) + (rate 4 (l (n a1) (n a2))))))) + +(def octave + (phrase + (l (octave 0) (octave 1)))) + +(def tetris + (phrase + (p + ;; melody block + (a :melody) + (octave :melody) + + ;; bass block + (b :bass) + (octave :bass) + ))) + +(tetris :t) + +;; end up with +[[0 1 [[:t :melody] melody-note-flow-1]] + [0 1 [[:t :melody] melody-note-flow-2]] + [0 1 [[:t :melody] melody-octave-flow]]] + +;; melody-note-flow-1 might emit +{:kind :note + :note 60} + +;; melody-octave-flow might emit +{:kind :octave-transform + :dx 10} + +;; Maybe elements with the same name +;; are paired with elements of a different kind? +;; e.g. + +[{:kind :note :note 60} {:kind :octave-transform :dx 10} ;; note-1 + {:kind :note :note 70} {:kind :octave-transform :dx 10} ;; note-2 + ] + +;; Kind of interesting. But where does this merge order come from? Is it global? +;; Maybe local to a phrase? +;; And crucially: how do we know how to interpret any of this when it comes out the pipe at the end? + +;; One answer to the merge question is: merging must be order-independent. Though, I don't know how +;; to prevent merging issues caused by duplicate invocations of the same kind. + + ;; TODO: + ;; What about something with two notes, one octave transform, two cc params? Can these multiply? + ;; [note-1 octave cc-1] + ;; [note-1 octave cc-2] + ;; [note-2 octave cc-1] + ;; [note-2 octave cc-2] + ;; This doesn't really work, does it! Suddenly each note is appearing twice. + +;; This is a contrived example, though. Is there a better one? +``` + +--- + +Oh, but here's another cool observation. The data structure that feeds +into `timeline` - that is, a list of [start end value] tuples - is +more or less ready to lock in. The only question in my mind is how +`v` needs to be defined. + +This is a key observation to document: +I can support arbitrary musical syntaxes, they just need to +compile down to the [start end value] representation. -- cgit v1.2.3