diff options
Diffstat (limited to 'DEVLOG.md')
| -rw-r--r-- | DEVLOG.md | 420 |
1 files changed, 420 insertions, 0 deletions
@@ -1187,3 +1187,423 @@ more or less ready to lock in. The only question in my mind is how 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. + +## December 9th, 2025 + +A few more observations: + +1. parallel composition is closely related to instruments. (Instruments are trees playing in parallel.) +2. It might make sense to pass instrument specifiers down as arguments + +## December 10th, 2025 + +What about tags? + +Consider: + + +```clojure +;; tetris +(p + (tag :melody + (f (l _ (l _ _) _ (l _ _)) + (l _ (l _ _) _ (l _ _)) + (l _ (l r _) _ _) + (l _ _ _ r) + (l (l r _) (l r _) _ (l _ _)) + (l _ (l r _) _ (l _ _)) + (l _ (l _ _) _ _) + (l _ _ _ r))) + (tag :bass + (f (rate 4 (l _ _)) + (rate 4 (l _ _)) + (l (rate 2 (l _ _)) (rate 2 (l _ _))) + (l _ _ _ _ _ _ _ _) + (rate 4 (l _ _)) + (rate 4 (l _ _)) + (l (rate 2 (l _ _)) (rate 2 (l _ _))) + (rate 4 (l _ _))))) +``` + +When compiled, each interval of the above (except the outer p) would be tagged with either :melody or :bass. + + +```clojure +(tag :theme + (p + (tag :melody + (f (l _ (l _ _) _ (l _ _)) + (l _ (l _ _) _ (l _ _)) + (l _ (l r _) _ _) + (l _ _ _ r) + (l (l r _) (l r _) _ (l _ _)) + (l _ (l r _) _ (l _ _)) + (l _ (l _ _) _ _) + (l _ _ _ r))) + (tag :bass + (f (rate 4 (l _ _)) + (rate 4 (l _ _)) + (l (rate 2 (l _ _)) (rate 2 (l _ _))) + (l _ _ _ _ _ _ _ _) + (rate 4 (l _ _)) + (rate 4 (l _ _)) + (l (rate 2 (l _ _)) (rate 2 (l _ _))) + (rate 4 (l _ _)))))) +``` + +Here, every interval would also be tagged with :theme. + +What might we do with tags? +We could turn the output into tuples like this: + + +``` +[0 1 #{:theme :melody} :c4] +[0 1 #{:theme :bass} :d4] +``` + +That information could aid in interpretation at playback time? + + +Note that every slot (that is, every `_`) is a place where a tag +set can be placed. + +Without a tagging context, the tag is just the empty set. + +That is: + +```clojure +(f (l _ (l _ _) _ (l _ _)) + (l _ (l _ _) _ (l _ _)) + (l _ (l r _) _ _) + (l _ _ _ r) + (l (l r _) (l r _) _ (l _ _)) + (l _ (l r _) _ (l _ _)) + (l _ (l _ _) _ _) + (l _ _ _ r)) +``` + +Would result in each slot (that is, each interval) having a +tag set of `#{}`. + +Let's see how tagging would accomplish a few goals. + +1. Play midi notes, alternating between two instrument types. +2. Play two voices, swapping positions. +3. Write a phrase, and then output it both in OSC and MIDI +4. Write a phrase based on intervals, and then map those intervals + to a scale. +5. Same as 4, but move the scale based on a knob. During t1, the + knob swaps between ionian and mixolydian. During t2, the knob + swaps between dorian and locrian. +6. Two different instruments playing the same phrase, one shifted up + an octave +7. A sine wave adds tremolo to one note in a chord. The frequency + of that tremolo is dictated by a knob. + +### No. 1: + +```clojure + +(import [midi-notes :as m]) + +(def melody + (tag + :m/notes + (l :m/c1 :m/c2 :m/c3 :m/c2))) + +(def inst-changes + (tag + :inst + (l :synth-1 :synth-2))) + +(def song + (tag + :song + (p melody inst-changes))) + +[0 1 #{:song :m/notes} :m/c1] +[0 2 #{:song :inst} :synth-1] +[1 2 #{:song :m/notes} :m/c2] +[2 3 #{:song :m/notes} :m/c3] +[2 4 #{:song :inst} :synth-2] +[3 4 #{:song :m/notes} :m/c2] +``` + +### No. 2: + +```clojure +(import [midi-notes :as m]) + +(def voice-1 + (tag + :m/notes + (l :m/a1 :m/a2))) + +(def voice-2 + (tag + :m/notes + (l :m/a2 :m/a1))) + +(def song + (p + (tag :v1 voice-1) + (tag :v2 voice-2))) + +[0 1 #{:v1 :m/notes} :m/a1] +[0 1 #{:v2 :m/notes} :m/a2] +[1 2 #{:v1 :m/notes} :m/a2] +[1 2 #{:v2 :m/notes} :m/a1] + +``` + +### No. 3 + +```clojure +(def melody + (tag + :an/notes + (l :an/a1 :an/a2 :an/a3))) + +[0 1 #{:an/notes} :an/a1] +[1 2 #{:an/notes} :an/a2] +[2 3 #{:an/notes} :an/a3] +``` + +One thing interesting about the above: +:an/a1 could include its own tag. that is, +it could be replaced with: + +```clojure +(def a1 + (tag + :an/notes + (l :an/a1))) +``` + +Then the whole thing could be rewritten: + +```clojure +(def a1 + (tag + :an/notes + (l :an/a1))) + +(def a2 + (tag + :an/notes + (l :an/a2))) + +(def a3 + (tag + :an/notes + (l :an/a3))) + +(def melody + (l a1 a2 a3)) + +[0 1 #{:an/notes} :an/a1] +[1 2 #{:an/notes} :an/a2] +[2 3 #{:an/notes} :an/a3] +``` + +Note that the result is the same. + +### No. 4 + +```clojure +(def d1 + (tag + :scale/degree + (l :sd/d1))) + +(def d2 + (tag + :scale/degree + (l :sd/d2))) + +(def d3 + (tag + :scale/degree + (l :sd/d3))) + +(def ionian + (tag + :mode + (l :ionian))) + +(def mixolydian + (tag + :mode + (l :mixolydian))) + +(def melody + (tag :tune + (p + (l d1 d2 d3) + (l ionian mixolydian)))) + +[0 1 #{:tune :scale/degree} :sd/d1] +[0 1.5 #{:tune :mode} :ionian] +[1 2 #{:tune :scale/degree} :sd/d2] +[1.5 3 #{:tune :mode} :mixolydian] +[2 3 #{:tune :scale/degree} :sd/d3] +``` + +I think this one makes sense. + +### No. 5 + +```clojure +(def d1 + (tag + :scale/degree + (l :sd/d1))) + +(def d2 + (tag + :scale/degree + (l :sd/d2))) + +(def d3 + (tag + :scale/degree + (l :sd/d3))) + +(def ionian + (tag + :mode + (l :ionian))) + +(def mixolydian + (tag + :mode + (l :mixolydian))) + +(def dorian ...) +(def locrian ...) + +(def melody + (tag :tune + (p + (l d1 d2 d3) + (l + ;; x: flow combinator + (x + (m/ap + (if (< 0 (rescale 0 1 (m/?< (cv :k1-knob-1))) 0.5) + ionian + mixolydian))) + (x + (m/ap + (if (< 0 (rescale 0 1 (m/?< (cv :k1-knob-1))) 0.5) + dorian + locrian))))))) + +[0 1 #{:tune :scale/degree} :sd/d1] +[0 1.5 #{:tune :mode} first-flow] +[1 2 #{:tune :scale/degree} :sd/d2] +[1.5 3 #{:tune :mode} second-flow] +[2 3 #{:tune :scale/degree} :sd/d3] +``` + +Hm, we're back to flows in the value position. +This makes me think that we should _always have a flow in value position_. +This would obviate the x combinator introduced in +the above example. + +Another insight that came from this is that there should be a global +called cv that allows you to fetch control values by name. + +Is :k1-knob-1 compositional? +And how do I ensure that the existence of :k1-knob-1 is known +at compile time as an imput, so I can raise an exception if +it isn't connected to anything? + +```clojure +(def d1 + (tag + :scale/degree + (l (m/ap :sd/d1)))) + +(def ionian + (tag + :mode + (l (m/ap :ionian)))) +``` + +Also, notice that I'm returning items that are themselves tagged entities from the flow. +I think that this implies that tags are part of values. + +Put differently, a value has two components, :tag and :value. + + +## No. 6 + +This is a dumb way to accomplish no.6: + +```clojue +(def c3 + (tag + :an/notes + (l :an/c3))) + +(def d3 + (tag + :an/notes + (l :an/d3))) + +(def e3 + (tag + :an/notes + (l :an/e3))) + +(def f3 + (tag + :an/notes + (l :an/f3))) + +(def riff + (l c3 d3 e3 f3)) + +(def song + (p + (tag :inst-1 + riff) + (tag :inst-3 + (paste riff c4 d4 e4 f4)))) +``` + +I don't love this one. + +### No. 7 + +TODO I'm not even sure where to begin right now. + +--- + +Summarizing a few insights: + +1. Flows will exist in value position. This implies that + values should always be wrapped in a flow. +2. Flows will return tagged items. This implies that tags + must be part of value, not separate metadata. +3. Tags returned by flows should somehow merge with their + container tags. This seems hard? +4. It still isn't clear how we'll avoid naming collisions + with input labels. +5. Interpretation of tagged values remains an open question. +6. It isn't clear how to represent temporally-relative + structures, like make the next note one higher than the + previous. +7. Similarly, counterpoint-like structures cannot currently + be expressed in a relative way. + +Next, need to think about interpretation. + +Should tags be a set? That loses hierarchical information. +Is that bad? + +Oh, what about temporally relative structures, such as +1 tone? +And counterpoint-like relations, like in no. 6? + |
