diff options
| author | Jake Zerrer <him@jakezerrer.com> | 2025-11-25 14:06:37 -0500 |
|---|---|---|
| committer | Jake Zerrer <him@jakezerrer.com> | 2025-11-26 13:08:28 -0500 |
| commit | 0b6ceecd47cc1faf715419914c77f68a5d789727 (patch) | |
| tree | dc362d870ccc5ba85a6f52e21fce87e6842f49ad | |
| parent | fb447b950e9e24e0d6a3ce3f3442fc9f82dd54c1 (diff) | |
Devlog notes
| -rw-r--r-- | DEVLOG.md | 115 |
1 files changed, 115 insertions, 0 deletions
@@ -311,3 +311,118 @@ innermost loop takes precedence. Document the bug that could occur if two tim object flows invocations shared an identity Write down idea about the difference between a tree and a dag Write down the static topology decision / summarize theory of time. + +## November 22nd, 2025 + +To think about: + + - What about wanting to change note durations live? Coupling notes directly to time objects + seems like it might be too strict + - What if phrases / groups were also time objects? I feel like I'm fundamentally questioning everything :/ + - Maybe create "static-note" which is a time object, and "dynamic note" which is not? + +Maybe this panic is not a big deal. I think I should go ahead and continue with "note as fixed duration". After all, a note doesn't _have to_ play during the entire duration. It's more like it _can_. Swing, variability, etc. can all still be accomplished via other means. + +## November 25th, 2025 + +Okay, time to write down some confusion. + +What do all the time objects eventually get merged into? + +Something with the following qualities: + - It is a data structure representing various output effect providers, e.g. MIDI + - Each output effect provider has its own... mergin semantics? + - Time objects are created and destroyed? + + Is it possible / does it make sense to differentiate over the + stream of all events emitted from a phrase? One dimension to differentiate + over would be the flow associated with a particular time object. I already + know how to do this - I could modify the reconcile-merge function to accomplish this. + +I think there is probably another dimension that I'm not thinking of. In my +original group operator, I emitted a value for content in the "enabled" state, +and I emitted the empty set in the "does not exist" state. My original `poly` +performed a set union of all active notes. This then would have been differerntiable; +I could have used the differentiate function from my new missionary.util. + +What is less clear to me is how to achieve the same behavior with time objects. +I think that I could modify note to return e.g. the empty set in my note +function. But how would I then group them? Would I want to couple the +grouping context with note? + +OH! Maybe we want to group-by e.g. :note? That is, each time object-producing +function would be able to direct its contents to a differentiable grouping operator +for that particular type? + +Remember that this was the original definition of poly and group: + +(defn poly + [& notes] + (m/signal (m/cp (apply union (m/?< (apply m/latest vector notes)))))) + +;; TODO: Group could actually wrap note, rather than using explicitly +;; WIll introduce a lot of GC churn, though +(defn group + [clock start end content] + (m/cp (let [content (m/signal content)] + (if (m/?< (m/latest #(<= start % end) clock)) + (m/?< content) + (m/amb #{}))))) + +Oh, here's an idea. What if we merged the union semantics from poly with the +lifecycle semantics of reconcile-merge? That is, rather than emitting from +each time object's flow, we instead unioned the latest elements from each time object? +Can I do that? + +Why did poly originally work? + +Each note emitted either the empty set or a set containing its value. +Multiple notes' groups were merged emit-wise: + +#{1} #{} #{} -> #{1} +#{1} #{2} #{} -> #{1 2} +#{} #{2} #{3} -> #{2 3} + +That is, _each note's state_ was sampled on each emit. This is due to the +behavior of m/latest. latest is fundamentally not a differentiable operator. + +The behavior of reconcile-merge is more like: + +#{1} -> #{1} +#{} -> #{} ;; Failure! We forgot about the presence of #{1} + +Could we turn that into: + +{} -> #{} +{1 true} -> #{1} +{2 true} -> #{1 2} +{1 false} -> #{2} +{3 true} -> #{3} + +Yes! We could! And in fact this is what the differentiate function does. + +Now, how do we get {1 true} and {1 false}? That is, where do we define +that 1 has been created and then destroyed? + +One of the things that m/latest relies on to work is the notion of "being +able to sample everything at once." That is, "sampling all notes at once."" + +The set-events function is allowing us to group lifecycle events by time-object +flow. + +We could use time-object lifecycle information to emit {id true} at the start +of a time object's lifecycle and {id false} at the end, but this would require +that we have a notion of identity for each time object. The question is, do +we always? Imagine (note ... (m/ap (m/?< clock))), e.g. the value of a note +is dynamic. Then, what is its identity? + +Ah! So this is one of the core differences between emit-wise grouping using +m/latest, and differentiated lifecycles. The former allows for _anonymous_ +object identities, while in the latter, objects really do need IDs. + +But do the IDs need to be visible? I think maybe not. (might be wrong, though.) +It might be possible to create identities for them within the body of reconcile- +merge. (Generate one, then create :up, then include on emit, and then :down). + +I like this idea. + |
