summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJake Zerrer <him@jakezerrer.com>2025-11-25 14:06:37 -0500
committerJake Zerrer <him@jakezerrer.com>2025-11-26 13:08:28 -0500
commit0b6ceecd47cc1faf715419914c77f68a5d789727 (patch)
treedc362d870ccc5ba85a6f52e21fce87e6842f49ad
parentfb447b950e9e24e0d6a3ce3f3442fc9f82dd54c1 (diff)
Devlog notes
-rw-r--r--DEVLOG.md115
1 files changed, 115 insertions, 0 deletions
diff --git a/DEVLOG.md b/DEVLOG.md
index fa0838a..eaaf588 100644
--- a/DEVLOG.md
+++ b/DEVLOG.md
@@ -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.
+