summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJake Zerrer <him@jakezerrer.com>2025-11-26 15:10:20 -0500
committerJake Zerrer <him@jakezerrer.com>2025-12-01 15:49:41 -0500
commit322b66627fd619c2ce0f2a35eae043e9304ee8bc (patch)
tree848d78c8ff464eeb1e055a60e87de39fd3a1f62b
parentf940b2f1452b0cccfbb501c14c7f348e5dc1d2e6 (diff)
Devlog updates
-rw-r--r--DEVLOG.md505
1 files changed, 505 insertions, 0 deletions
diff --git a/DEVLOG.md b/DEVLOG.md
index eaaf588..e56a920 100644
--- a/DEVLOG.md
+++ b/DEVLOG.md
@@ -426,3 +426,508 @@ merge. (Generate one, then create :up, then include on emit, and then :down).
I like this idea.
+## November 28th, 2025
+
+Very happy with wednesday's progress. Time to nail down a few more things.
+
+1. How should I match phrases up to input sources?
+2. How should I account for there eventually being multiple input media
+ types, like OSC, DMX, etc., all with their own semantics?
+3. How should input sources flow through phrase composition?
+
+Currently, `note` and `phrase` are called differently in a way that doesn't
+make much sense to me. I need to play with that syntax a bit, too.
+
+There's another interesting question that is really sticky. Imagine for
+a moment that I eventually want to take musical-like inputs from multiple
+sources, e.g. midi and OSC. I want to perform a similar mapping on the
+output side. Imagine I want to map from MIDI in to OSC out. This suggests
+that using MIDI as my internal representation for harmonic information
+probably isn't the best idea, even if it is the easiest. Another example
+is non-standard tunings, or music where one wants frequency-level control.
+Okay, so what would the internal representation of harmonic information
+be? I want something fairly flexible but not crazy. Probably just
+[frequency amplitude] to begin with.
+
+Anyway. I need to start thinking about these things, which are all related.
+
+Let's do some experimenting.
+
+Here is a current example:
+
+```clojure
+(defn triad
+ [>c >tonic]
+ (phrase
+ ;; This is a major cord,
+ ;; held 32 32nd notes.
+ ;; The tonic can vary.
+ (note >c 0 0 32 >tonic)
+ (note >c 0 0 32 (m/latest #(+ % 4) >tonic))
+ (note >c 0 0 32 (m/latest #(+ % 7) >tonic))))
+
+(defn drums
+ [>clock]
+ (phrase (note >clock 1 1 1 (m/ap kick))
+ (note >clock 1 9 1 (m/ap kick))
+ (note >clock 1 17 1 (m/ap kick))
+ (note >clock 1 25 1 (m/ap kick))
+ (note >clock 1 1 1 (m/ap hat))
+ (note >clock 1 5 1 (m/ap hat))
+ (note >clock 1 9 1 (m/ap hat))
+ (note >clock 1 13 1 (m/ap hat))
+ (note >clock 1 17 1 (m/ap hat))
+ (note >clock 1 21 1 (m/ap hat))
+ (note >clock 1 25 1 (m/ap hat))
+ (note >clock 1 29 1 (m/ap hat))
+ (note >clock 1 5 1 (m/ap snare))
+ (note >clock 1 13 1 (m/ap snare))
+ (note >clock 1 21 1 (m/ap snare))
+ (note >clock 1 29 1 (m/ap snare))))
+
+(defn song'
+ [>clock >tonic]
+ (phrase ((triad >clock >tonic) 0)
+ ((triad >clock (m/latest #(+ % 12) >tonic)) 0)
+ ((drums >clock) 0)))
+```
+
+Things that stand out:
+1. It is interesting that `phrase` doesn't take a flow.
+2. I don't like that `note` takes a midi :ch argument. Too
+ coupled with a particular output medium. (But I also don't want
+ to get _too far_ from the specific output medium and risk creating
+ surprising behavior.)
+3. These examples aren't actually great because we don't actually do
+ much that is interesting with argument passing.
+4. The invocation of triad and drums in song is gross.
+5. It is unclear how happens-before relationships would be structured.
+6. We pass clock in explicitly, but it isn't actually clear why.
+7. Should we encourage phrases to use argument names that are coupled
+ to input types, or to give them names that are meaningful to the
+ music itself?
+8. How do I want to specify outputs?
+
+Here is a first pass at rewriting:
+
+```clojure
+(defn triad
+ [>tonic]
+ (phrase
+ ;; This is a major cord,
+ ;; held 32 32nd notes.
+ ;; The tonic can vary.
+ (note 0 0 32 >tonic)
+ (note 0 0 32 (m/latest #(+ % 4) >tonic))
+ (note 0 0 32 (m/latest #(+ % 7) >tonic))))
+
+(defn drums
+ []
+ (phrase (note 1 1 1 (m/ap kick))
+ (note 1 9 1 (m/ap kick))
+ (note 1 17 1 (m/ap kick))
+ (note 1 25 1 (m/ap kick))
+ (note 1 1 1 (m/ap hat))
+ (note 1 5 1 (m/ap hat))
+ (note 1 9 1 (m/ap hat))
+ (note 1 13 1 (m/ap hat))
+ (note 1 17 1 (m/ap hat))
+ (note 1 21 1 (m/ap hat))
+ (note 1 25 1 (m/ap hat))
+ (note 1 29 1 (m/ap hat))
+ (note 1 5 1 (m/ap snare))
+ (note 1 13 1 (m/ap snare))
+ (note 1 21 1 (m/ap snare))
+ (note 1 29 1 (m/ap snare))))
+
+(defn song'
+ [>tonic]
+ (phrase ((triad >tonic) 0)
+ ((triad (m/latest #(+ % 12) >tonic)) 0)
+ ((drums) 0)))
+```
+
+Looks cleaner without clock. This doesn't mean that
+clock can't be passed explicitly, just that it isn't necessary
+by default.
+
+What about specifying outputs? Already there is an interesting
+decision to make. On drums, would one want to treat each drum as
+its own output, or treat them as simply different parts of the same
+instrument?
+
+What is an output? Is it an instrument? I think the only honest answer
+is that this question can't be answered in general. Sometimes it will
+make sense to think of a cow bell as an instrument, and sometimes as a
+component of a percussion kit.
+
+The point of an output, I think, is to somehow group output information
+together. I think it is fair to say that everything on the same output
+will share some semantics. From a programming perspective, the state
+of an output should be uniform: a single data structure, and a single
+merge operation.
+
+The data structure and merge operation shouldn't be re-written for each
+instrument. There will only be a few of these. Keyed instruments like
+pianos will be able to share a single data structure and merge operation,
+even if a harpsichord won't make use of velocity information.
+
+When writing a phrase, one specify the data structure and merge
+operation. This actually isn't a property of the phrase, though, it
+is a property of the particular part of the phrase that is being
+described. Remember that a phrase may have many instruments in it,
+and therefore many data structures and merge ops.
+
+This makes me wonder: beyond phrase and time object, is there another
+concept that I should introduce? Something like "instrument"?
+
+```clojure
+(defn triad
+ [>tonic]
+ (phrase
+ (instrument keyboard
+ (note 0 32 >tonic)
+ (note 0 32 (m/latest #(+ % 4) >tonic))
+ (note 0 32 (m/latest #(+ % 7) >tonic)))))
+
+(defn drums
+ []
+ (phrase
+ (instrument percussion
+ (note 1 (m/ap kick))
+ (note 9 1 (m/ap kick))
+ (note 17 1 (m/ap kick))
+ (note 25 1 (m/ap kick))
+ (note 1 1 (m/ap hat))
+ (note 5 1 (m/ap hat))
+ (note 9 1 (m/ap hat))
+ (note 13 1 (m/ap hat))
+ (note 17 1 (m/ap hat))
+ (note 21 1 (m/ap hat))
+ (note 25 1 (m/ap hat))
+ (note 29 1 (m/ap hat))
+ (note 5 1 (m/ap snare))
+ (note 13 1 (m/ap snare))
+ (note 21 1 (m/ap snare))
+ (note 29 1 (m/ap snare)))))
+```
+
+Oh, interesting. `triad` isn't just a keyboard concept!
+
+Instrument feels like a pretty specific name. I don't think I love that.
+
+But it is bringing up an interesting thought. Phrases compose together,
+but it seems like things like `notes` _do_ have some kind of wrapper
+context that separates them for a phrase. We can actually see this distinction
+repeated in the programming context, with the presence of `lift`.
+
+Maybe `lift` and `instrument` are the same thing?
+
+How would the generic concept of a triad be expressed outside
+of the instrument context? Here's what I mean. I want to support the
+creation of the music theory concept of a triad and import it into any
+instrument such that it can be used in any phrase.
+
+Oh, maybe the problem is that the triad helper function should actually just
+be something that returns three flows, one for each note in the interval.
+Or even a flow-returning function that takes a root and a degree?
+e.g.
+
+```clojure
+(defn piano
+ [>root]
+ (let [triad (theory/triad :major >root)])
+ (phrase
+ (instrument keyboard :keyboard ;; keyboard is the name of a data structure / merge op;
+ (note 1 32 (triad 1)) ;; :keyboard is declaring an abstract output destination
+ (note 1 32 (triad 3))
+ (note 1 32 (triad 5)))))
+
+(defn drums
+ []
+ (phrase
+ (instrument percussion :drums
+ (note 1 (m/ap kick))
+ (note 9 1 (m/ap kick))
+ (note 17 1 (m/ap kick))
+ (note 25 1 (m/ap kick))
+ (note 1 1 (m/ap hat))
+ (note 5 1 (m/ap hat))
+ (note 9 1 (m/ap hat))
+ (note 13 1 (m/ap hat))
+ (note 17 1 (m/ap hat))
+ (note 21 1 (m/ap hat))
+ (note 25 1 (m/ap hat))
+ (note 29 1 (m/ap hat))
+ (note 5 1 (m/ap snare))
+ (note 13 1 (m/ap snare))
+ (note 21 1 (m/ap snare))
+ (note 29 1 (m/ap snare)))))
+
+(defn song'
+ [>root]
+ (phrase ((piano >root) 0)
+ ((piano (m/latest #(+ % 12) >root)) 0)
+ ((drums) 0)))
+```
+
+Question: How would I handle the fact that triad might represent notes
+as notes, while an output method might represent in frequencies? I think
+I would want some kind of `note` protocol that can convert between different
+representations.
+
+I think it does make sense that, unlike phrases, outputs / instruments
+can't be nested.
+
+Can a phrase have multiple instruments / outputs? I think the
+answer should probably be "yes". That would mean abstractions could
+return instruments rather than phrases. This would be nice because
+it would remove a layer of unnecessary naming in the naming tree. Example:
+
+```clojure
+(defn piano
+ [>root]
+ (let [triad (theory/triad :major >root)])
+ (instrument keyboard :keyboard ;; keyboard is the name of a data structure / merge op;
+ (note 1 32 (triad 1)) ;; :keyboard is declaring an abstract output destination
+ (note 1 32 (triad 3))
+ (note 1 32 (triad 5))))
+
+(defn drums
+ []
+ (instrument percussion :drums
+ (note 1 (m/ap kick))
+ (note 9 1 (m/ap kick))
+ (note 17 1 (m/ap kick))
+ (note 25 1 (m/ap kick))
+ (note 1 1 (m/ap hat))
+ (note 5 1 (m/ap hat))
+ (note 9 1 (m/ap hat))
+ (note 13 1 (m/ap hat))
+ (note 17 1 (m/ap hat))
+ (note 21 1 (m/ap hat))
+ (note 25 1 (m/ap hat))
+ (note 29 1 (m/ap hat))
+ (note 5 1 (m/ap snare))
+ (note 13 1 (m/ap snare))
+ (note 21 1 (m/ap snare))
+ (note 29 1 (m/ap snare))))
+
+(defn song'
+ [>root]
+ (phrase :first-verse
+ ((piano >root) 0)
+ ((piano (m/latest #(+ % 12) >root)) 0)
+ ((drums) 0)))
+```
+
+Okay, important to note here:
+1. I think the name associated with the call to `instrument` is actually
+ setting the name on the implicit `phrase` that the call to instrument is
+ returning.
+2. A goal that I am trying to achieve here is implicit, unambiguous naming. Here,
+ I've accomplished that for drums ([:first-verse :drums]) but not for piano.
+ Let's try again?
+
+```clojure
+(defn piano
+ [>root]
+ (let [triad (theory/triad :major >root)])
+ (instrument keyboard
+ (note 1 32 (triad 1))
+ (note 1 32 (triad 3))
+ (note 1 32 (triad 5))))
+
+(defn drums
+ []
+ (instrument percussion
+ (note 1 (m/ap kick))
+ (note 9 1 (m/ap kick))
+ (note 17 1 (m/ap kick))
+ (note 25 1 (m/ap kick))
+ (note 1 1 (m/ap hat))
+ (note 5 1 (m/ap hat))
+ (note 9 1 (m/ap hat))
+ (note 13 1 (m/ap hat))
+ (note 17 1 (m/ap hat))
+ (note 21 1 (m/ap hat))
+ (note 25 1 (m/ap hat))
+ (note 29 1 (m/ap hat))
+ (note 5 1 (m/ap snare))
+ (note 13 1 (m/ap snare))
+ (note 21 1 (m/ap snare))
+ (note 29 1 (m/ap snare))))
+
+(defn song'
+ [>root]
+ (phrase
+ ((piano >root) :piano-1 0)
+ ((piano (m/latest #(+ % 12) >root)) :piano-2 0)
+ ((drums) :drums 0)))
+```
+
+Here, `phrase` is returning an invokable object that receives a name. If I were
+to repeat song twice, we would get:
+
+```clojure
+
+(defn song
+ [>root]
+ (phrase
+ ((song' >root) :first-verse 0)
+ ((song' >root) :second-verse 32)))
+```
+
+Now we have the following unambiguous output addresses:
+
+{:first-verse [:piano-1 :piano-2 :drums]
+ :second-verse [:piano-1 :piano-2 :drums]}
+
+
+Here's an aesthetic goal - make phrase invocation look more like:
+
+```clojure
+(defn song'
+ [>root]
+ (phrase
+ (piano >root :piano-1 0)
+ (piano (m/latest #(+ % 12) >root) :piano-2 0)
+ (drums :drums 0)))
+
+(defn song
+ [>root]
+ (phrase
+ (song' >root :first-verse 0)
+ (song' >root :second-verse 32)))
+```
+
+I'm still not sure that I understand what a `note` is. What are other
+objects like it? You could make something like a `pulse`, representing a
+sine wave controlling an LFO. I guess from a programming perspective,
+a note is an instance of some kind of object that becomes a signal
+associated with output state and an ouput 'context' that explains how
+similar objects get merged together. That output context receives a
+name, and the name is guaranteed to be unique through multiple
+nestings.
+
+Here's a question. Should phrases be responsible for renaming their
+children? This could help control name explosions, where a long composition
+could have many many names to map before playback.
+
+Yeah, that seems worth exploring. You could have some kind of rename map:
+
+(phrase
+ {[:first-verse :piano-1] :piano-1
+ [:second-verse :piano-1] :piano-1
+ [:first-verse :piano-2] :piano-2
+ [:second-verse :piano-2] :piano-2
+ [:first-verse :drums] :drums
+ [:second-verse :drums] :drums})
+
+This would mean that you could do
+
+```clojure
+(play {:piano-1 midi-out-1 :piano-2 midi-out-2 :piano-3 midi-out-3} song)
+```
+
+Big question: Why is there this difference between the way
+I represent inputs (arguments) and outputs?
+
+Questions from today that remain open:
+
+1. What does this look like with a more complex example?
+2. What do happens-before relationships look like? (Marco?)
+3. Why do inputs look familiar (arguments) while outputs look strange (keywords)?
+4. Do we need this instrument / output concept? What is it, exactly? What does it do?
+5. How would a swung clock fit into all of this? Here again, time seems a little
+ different. Swung clocks need to interact with timeline selection.
+ Time objects should probably specify their clock. At playback time,
+ you define one or more clocks.
+6. I don't curently have a good answer to crossfades.
+7. Need to brainstorm more on loops / goto / portals and their relationship
+ with phrases.
+
+Insights:
+
+The point of an output, I think, is to somehow group output information
+together. I think it is fair to say that everything on the same output
+will share some semantics. From a programming perspective, the state
+of an output should be uniform: a single data structure, and a single
+merge operation.
+
+The data structure and merge operation shouldn't be re-written for each
+instrument. There will only be a few of these. Keyed instruments like
+pianos will be able to share a single data structure and merge operation,
+even if a harpsichord won't make use of velocity information.
+
+From a programming perspective,
+a note is an instance of some kind of object that becomes a signal
+associated with output state and an ouput 'context' that explains how
+similar objects get merged together. That output context receives a
+name, and the name is guaranteed to be unique through multiple
+nestings.
+
+Music theory concepts like triad can be expsosed as flow-returning functions.
+
+Outputs / instruments can't be nested, unlike phrases.
+
+A phrase can contain multiple instruments / outputs.
+
+Create a note protocol that generalizes over concrete note representation.
+
+Phrases can simplify the names of their children by providing a rename map.
+
+Concepts to test:
+
+1. Confirm that working on a phrase in isolation and working on a whole piece are
+ similarly simple
+
+Other random thoughts:
+
+It seems worth giving time objects explicit
+Every leaf must be a time object
+At this point, time objects should contain other metadata, like their path from
+enclosing phrases
+Time objects and phrases should have a similar... calling convention? with regards to
+offset (almost certainly) and duration (maybe)
+Time objects will eventually need a serializable identity for display purposes
+Both time objects and phrases should be reified for display/interactivity
+purposes
+
+## November 29th, 2025
+
+As an exercise, I'd like to create a function that compiles strudel
+mini notation down to my IR.
+
+https://strudel.cc/learn/mini-notation/
+https://strudel.cc/learn/mondo-notation/
+https://strudel.cc/learn/factories/
+https://strudel.cc/learn/time-modifiers/
+
+And why this?
+
+https://strudel.cc/learn/stepwise/
+
+Also do a dissection of strudel's alignment system:
+
+https://strudel.cc/technical-manual/alignment/
+
+And this section on voicing:
+
+https://strudel.cc/understand/voicings/
+
+---
+
+What would it mean to reify temporal specifiers, so that they become objects
+which can be shortened / lengthened through a series of operations? Keeping in
+mind that these little windows encapsulate arbitrary values. They would compose
+into a callable which can somehow be invoked with the time object itself, e.g.
+the note(s)
+
+
+## December 1st, 2025
+
+TODO upcoming:
+
+ - Inspired by strudel, define a language of musical modifiers
+ - Read notes of Nov. 28