An instance's span is in its own frames

A shape's :span stays in its parent's frames, but an instance's or a sound's
is now in its own: dropping a symbol at frame 97 gives it span 0 … length and
:at 97, so moving it along its parent is one write to :at. :time :in is gone
(it was the span's start written twice); node/placed-span maps an own-time
span out to the parent for playback, the mixer and the timeline rows, and
node/problems reports a stale :in.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Olive Vaughn 2026-09-29 12:53:21 -04:00
parent 2d2eb0fc9f
commit 6b41c6db94
10 changed files with 103 additions and 42 deletions

View file

@ -30,8 +30,8 @@
:parent :root :z "a1" :span [0 4]
:channels {[:xform :pos] (ch/framed [100 50])}}
:right {:id :right :kind :instance :of :sym/test
:parent :root :z "a2" :span [2 6]
:time {:mode :map :at 2 :in 0 :rate 1}
:parent :root :z "a2" :span [0 4]
:time {:mode :map :at 2 :rate 1}
:channels {[:xform :pos] (ch/framed [120 50])}}}})
(assoc-in [:symbols :sym/test]
(assoc (get-in source [:symbols :main]) :id :sym/test)))
@ -171,7 +171,8 @@
(is (= 7 (count (distinct (map #(:name (val %)) symbols))))))))
(is (= 7 (count (filter #(= :instance (:kind %))
(vals (get-in document [:symbols :main :nodes]))))))
(is (= [48 280] (:span (placement document :right))))
(is (= [0 232] (:span (placement document :right))) "its own frames, from its own 0")
(is (= [48 280] (node/placed-span (placement document :right))) "and where that sits on the stage")
(let [left (placement document :left)
scale (get-in left [:channels [:xform :scale]])
anchor (get-in left [:channels [:xform :anchor] :value])
@ -195,7 +196,7 @@
;; resolves to a node at all; this checks it resolves to the RIGHT one.
(is (= (uuid-of :right) (:linked-to (placement document :voice-right))))
(is (uuid? (:linked-to (placement document :voice-right)))))
(is (= [48 260] (:span (placement document :voice-right))))
(is (= [48 260] (node/placed-span (placement document :voice-right))))
(is (= 0.5 (ch/value-at
(get-in (placement document :voice-right)
[:channels [:audio :gain]]) 54)))
@ -222,7 +223,8 @@
(testing "an instance can go into any symbol, and spans that symbol's frames"
(let [[n] (vals (get-in c [:symbols :outer :nodes]))]
(is (= :inner (:of n)))
(is (= [5 15] (:span n)) "as long as what it places, not as the space it is in")))
(is (= [0 10] (:span n)) "its own frames: all of what it places, from its own 0")
(is (= [5 15] (node/placed-span n)) "and where that lands in the symbol it is in")))
(testing "placing is refused when it would make a cycle"
(is (clip/contains-symbol? c :outer :inner))
(is (not (clip/contains-symbol? c :inner :outer)))
@ -245,12 +247,23 @@
(is (= {:id :symbol-1 :name "symbol-1" :frames 180 :nodes {}}
(clip/symbol made :symbol-1))
"empty, and as long as the rest of what it was placed in")
(is (= {:of :symbol-1 :span [20 200] :time {:mode :map :at 20 :in 0 :rate 1}}
(is (= {:of :symbol-1 :span [0 180] :time {:mode :map :at 20 :rate 1}}
(select-keys (get-in made [:symbols :outer :nodes u]) [:of :span :time])))
(is (empty? (clip/problems made)))
(is (= c (clip/new-symbol c :outer :inner 0 u)) "an id already in use is refused")
(is (= c (clip/new-symbol c :outer id 200 u)) "past the end is refused")))
(deftest an-instance-span-is-in-its-own-frames
(let [n {:id :i :kind :instance :of :x :z "a1" :span [3 13]
:time {:mode :map :at 40 :rate 2}}]
(is (= [40 45] (node/placed-span n)) "ten own frames at double rate is five")
(is (= 3 (node/local-frame n 40)) "the parent's :at is the span's first frame")
(is (= 11 (node/local-frame n 44)))
(is (= [2 9] (node/placed-span {:kind :poly :span [2 9]}))
"a shape has no time of its own, so its span is already the parent's")
(is (seq (node/problems (assoc-in n [:time :in] 3)))
"a stale :in is reported rather than silently ignored")))
(deftest a-frame-is-carried-down-through-the-instances-a-row-path-names
(let [c (nested)
[id] (keys (get-in c [:symbols :outer :nodes]))]