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:
parent
2d2eb0fc9f
commit
6b41c6db94
10 changed files with 103 additions and 42 deletions
|
|
@ -79,6 +79,12 @@ this way.
|
||||||
over which the node exists at all. Distinct from a `[:vis]` channel, which
|
over which the node exists at all. Distinct from a `[:vis]` channel, which
|
||||||
blinks an existing node on and off.
|
blinks an existing node on and off.
|
||||||
|
|
||||||
|
A shape's `:span` is in its parent's frames. An **instance's** (or a sound's)
|
||||||
|
`:span` is in its **own** frames — which part of what it places plays, from its
|
||||||
|
own 0 — and `:time :at` is where that first frame lands in the parent. Moving an
|
||||||
|
instance along its parent changes `:at` and nothing else; `node/placed-span` is
|
||||||
|
the one function that maps an own-time span out to the parent.
|
||||||
|
|
||||||
### Subjects and tracked features
|
### Subjects and tracked features
|
||||||
|
|
||||||
Scene nodes describe drawings, not tracking identity. A scene may also carry a
|
Scene nodes describe drawings, not tracking identity. A scene may also carry a
|
||||||
|
|
|
||||||
|
|
@ -103,7 +103,7 @@
|
||||||
output (js/OfflineAudioContext.
|
output (js/OfflineAudioContext.
|
||||||
2 (js/Math.ceil (* (/ frames fps) 44100)) 44100)]
|
2 (js/Math.ceil (* (/ frames fps) 44100)) 44100)]
|
||||||
(doseq [track tracks]
|
(doseq [track tracks]
|
||||||
(let [[start end] (or (:span track) [0 frames])
|
(let [[start end] (or (node/placed-span track) [0 frames])
|
||||||
start (max 0 start)
|
start (max 0 start)
|
||||||
end (min frames end)
|
end (min frames end)
|
||||||
{:keys [buffer fps]} (get sources (get-in track [:source :footage]))
|
{:keys [buffer fps]} (get sources (get-in track [:source :footage]))
|
||||||
|
|
|
||||||
|
|
@ -47,11 +47,11 @@
|
||||||
:known (vec (sort-by str (keys by-id)))}))))
|
:known (vec (sort-by str (keys by-id)))}))))
|
||||||
nodes (into
|
nodes (into
|
||||||
{:root {:id :root :name "stage" :kind :group :z "a1"}}
|
{:root {:id :root :name "stage" :kind :group :z "a1"}}
|
||||||
(map (fn [{:keys [uuid name z span at in center anchor drift phase]}]
|
(map (fn [{:keys [uuid name z span at center anchor drift phase]}]
|
||||||
(let [anchor (or anchor default-anchor)]
|
(let [anchor (or anchor default-anchor)]
|
||||||
[uuid {:id uuid :name name :kind :instance :of symbol
|
[uuid {:id uuid :name name :kind :instance :of symbol
|
||||||
:parent :root :z z :span span
|
:parent :root :z z :span span
|
||||||
:time {:mode :map :at at :in in :rate 1}
|
:time {:mode :map :at at :rate 1}
|
||||||
:channels {[:xform :pos] (if drift
|
:channels {[:xform :pos] (if drift
|
||||||
(position-track center anchor drift phase frames)
|
(position-track center anchor drift phase frames)
|
||||||
(ch/framed (mapv - center anchor)))
|
(ch/framed (mapv - center anchor)))
|
||||||
|
|
@ -59,11 +59,11 @@
|
||||||
[:xform :scale] scale}}]))
|
[:xform :scale] scale}}]))
|
||||||
instances))
|
instances))
|
||||||
nodes (into nodes
|
nodes (into nodes
|
||||||
(map (fn [{:keys [uuid linked-to z source span at in gain pan]}]
|
(map (fn [{:keys [uuid linked-to z source span at gain pan]}]
|
||||||
[uuid {:id uuid :kind :audio :parent :root :z z
|
[uuid {:id uuid :kind :audio :parent :root :z z
|
||||||
:linked-to (uuid-of uuid linked-to)
|
:linked-to (uuid-of uuid linked-to)
|
||||||
:source source :span span
|
:source source :span span
|
||||||
:time {:mode :map :at at :in in :rate 1}
|
:time {:mode :map :at at :rate 1}
|
||||||
:channels (cond-> {[:audio :gain] gain}
|
:channels (cond-> {[:audio :gain] gain}
|
||||||
pan (assoc [:audio :pan] pan))}])
|
pan (assoc [:audio :pan] pan))}])
|
||||||
audio))]
|
audio))]
|
||||||
|
|
|
||||||
|
|
@ -19,16 +19,18 @@
|
||||||
:over []}
|
:over []}
|
||||||
;; Audio placements are ordinary timeline nodes with channel parameters.
|
;; Audio placements are ordinary timeline nodes with channel parameters.
|
||||||
;; :linked-to is an editorial link; their spans and time maps are independent.
|
;; :linked-to is an editorial link; their spans and time maps are independent.
|
||||||
|
;; A span is in the placement's OWN frames and :at is where its frame 0 lands on
|
||||||
|
;; the stage, so every entrance below plays from its own start.
|
||||||
:audio
|
:audio
|
||||||
[{:id :voice-left :uuid #uuid "eeaa49c3-1238-469f-bf54-44929e379f6b"
|
[{:id :voice-left :uuid #uuid "eeaa49c3-1238-469f-bf54-44929e379f6b"
|
||||||
:linked-to :left :z "a3"
|
:linked-to :left :z "a3"
|
||||||
:source {:footage "f8cace9e-4ad3-4796-973c-c62eeebe3d01"}
|
:source {:footage "f8cace9e-4ad3-4796-973c-c62eeebe3d01"}
|
||||||
:span [0 280] :at 0 :in 0
|
:at 0 :span [0 280]
|
||||||
:gain {:animated? false :value 1.0}}
|
:gain {:animated? false :value 1.0}}
|
||||||
{:id :voice-right :uuid #uuid "468239dd-0e3a-4e5c-ac6f-1858430a0355"
|
{:id :voice-right :uuid #uuid "468239dd-0e3a-4e5c-ac6f-1858430a0355"
|
||||||
:linked-to :right :z "a4"
|
:linked-to :right :z "a4"
|
||||||
:source {:footage "f8cace9e-4ad3-4796-973c-c62eeebe3d01"}
|
:source {:footage "f8cace9e-4ad3-4796-973c-c62eeebe3d01"}
|
||||||
:span [48 260] :at 48 :in 0
|
:at 48 :span [0 212]
|
||||||
:gain {:animated? true :interp :linear
|
:gain {:animated? true :interp :linear
|
||||||
:keys {48 0.0, 60 1.0, 90 0.35, 115 0.9, 145 0.45,
|
:keys {48 0.0, 60 1.0, 90 0.35, 115 0.9, 145 0.45,
|
||||||
170 1.0, 195 0.4, 220 0.85, 245 1.0, 259 0.0}
|
170 1.0, 195 0.4, 220 0.85, 245 1.0, 259 0.0}
|
||||||
|
|
@ -49,29 +51,29 @@
|
||||||
:instances
|
:instances
|
||||||
[{:id :left :uuid #uuid "ee7321c8-faf1-46d7-8029-37771898accb"
|
[{:id :left :uuid #uuid "ee7321c8-faf1-46d7-8029-37771898accb"
|
||||||
:name "8625 left" :z "a1"
|
:name "8625 left" :z "a1"
|
||||||
:span [0 280] :at 0 :in 0
|
:at 0 :span [0 280]
|
||||||
:center [40 40] :drift [3 2] :phase 0}
|
:center [40 40] :drift [3 2] :phase 0}
|
||||||
{:id :right :uuid #uuid "1aa0da78-b4ed-4bb6-8d70-b09a3ec5e2c3"
|
{:id :right :uuid #uuid "1aa0da78-b4ed-4bb6-8d70-b09a3ec5e2c3"
|
||||||
:name "8625 right" :z "a2"
|
:name "8625 right" :z "a2"
|
||||||
:span [48 280] :at 48 :in 0
|
:at 48 :span [0 232]
|
||||||
:center [120 40] :drift [-3 2] :phase 17}
|
:center [120 40] :drift [-3 2] :phase 17}
|
||||||
{:id :top-third :uuid #uuid "23bb697d-eba7-4af6-a86c-606c50107088"
|
{:id :top-third :uuid #uuid "23bb697d-eba7-4af6-a86c-606c50107088"
|
||||||
:name "8625 top third" :z "a5"
|
:name "8625 top third" :z "a5"
|
||||||
:span [24 280] :at 24 :in 0
|
:at 24 :span [0 256]
|
||||||
:center [200 40] :drift [2 -3] :phase 31}
|
:center [200 40] :drift [2 -3] :phase 31}
|
||||||
{:id :top-fourth :uuid #uuid "f4f0241d-026e-4e50-9bea-a4ccde896d8a"
|
{:id :top-fourth :uuid #uuid "f4f0241d-026e-4e50-9bea-a4ccde896d8a"
|
||||||
:name "8625 top fourth" :z "a6"
|
:name "8625 top fourth" :z "a6"
|
||||||
:span [72 280] :at 72 :in 0
|
:at 72 :span [0 208]
|
||||||
:center [280 40] :drift [-2 -2] :phase 49}
|
:center [280 40] :drift [-2 -2] :phase 49}
|
||||||
{:id :bottom-left :uuid #uuid "8f594d72-a97f-4a32-82fd-08d1670a2218"
|
{:id :bottom-left :uuid #uuid "8f594d72-a97f-4a32-82fd-08d1670a2218"
|
||||||
:name "8625 bottom left" :z "a7"
|
:name "8625 bottom left" :z "a7"
|
||||||
:span [96 280] :at 96 :in 0
|
:at 96 :span [0 184]
|
||||||
:center [70 135] :drift [3 -2] :phase 63}
|
:center [70 135] :drift [3 -2] :phase 63}
|
||||||
{:id :bottom-middle :uuid #uuid "63f3fb32-9e94-4d68-a1c2-12e6de2d04b5"
|
{:id :bottom-middle :uuid #uuid "63f3fb32-9e94-4d68-a1c2-12e6de2d04b5"
|
||||||
:name "8625 bottom middle" :z "a8"
|
:name "8625 bottom middle" :z "a8"
|
||||||
:span [120 280] :at 120 :in 0
|
:at 120 :span [0 160]
|
||||||
:center [160 135] :drift [-2 3] :phase 81}
|
:center [160 135] :drift [-2 3] :phase 81}
|
||||||
{:id :bottom-right :uuid #uuid "fa338701-cb21-4d45-89f1-a5e706f045ec"
|
{:id :bottom-right :uuid #uuid "fa338701-cb21-4d45-89f1-a5e706f045ec"
|
||||||
:name "8625 bottom right" :z "a9"
|
:name "8625 bottom right" :z "a9"
|
||||||
:span [144 280] :at 144 :in 0
|
:at 144 :span [0 136]
|
||||||
:center [250 135] :drift [2 2] :phase 107}]}
|
:center [250 135] :drift [2 2] :phase 107}]}
|
||||||
|
|
|
||||||
|
|
@ -129,9 +129,10 @@
|
||||||
generating one in here would make this function's result depend on when it was
|
generating one in here would make this function's result depend on when it was
|
||||||
called, and this namespace is the pure one.
|
called, and this namespace is the pure one.
|
||||||
|
|
||||||
The instance's own time starts where it was dropped: `:at frame` with `:in 0`
|
The instance's own time starts where it was dropped: `:at frame` means frame 0
|
||||||
means local frame 0 of the symbol plays on `frame` of `host`, which is what
|
of the symbol plays on `frame` of `host`, which is what dragging something onto
|
||||||
dragging something onto a playhead is asking for.
|
a playhead is asking for. Its `:span` is in its OWN frames — the whole symbol,
|
||||||
|
0 to its length — wherever it was dropped; see `node/placed-span`.
|
||||||
|
|
||||||
Refused, returning the clip unchanged, when it would make a cycle: a symbol
|
Refused, returning the clip unchanged, when it would make a cycle: a symbol
|
||||||
cannot be placed inside itself or inside anything it places."
|
cannot be placed inside itself or inside anything it places."
|
||||||
|
|
@ -151,8 +152,8 @@
|
||||||
;; Lexicographic draw order, as `domain/paint` does it: a placement made
|
;; Lexicographic draw order, as `domain/paint` does it: a placement made
|
||||||
;; later sits above one made earlier, and neither has to renumber.
|
;; later sits above one made earlier, and neither has to renumber.
|
||||||
:z (str "z" (js/Date.now) "-" (name sid))
|
:z (str "z" (js/Date.now) "-" (name sid))
|
||||||
:span [frame (min end (+ frame (:frames target)))]
|
:span [0 (:frames target)]
|
||||||
:time {:mode :map :at frame :in 0 :rate 1}
|
:time {:mode :map :at frame :rate 1}
|
||||||
:channels {[:xform :pos] {:animated? false :value [x y]}}}))))
|
:channels {[:xform :pos] {:animated? false :value [x y]}}}))))
|
||||||
|
|
||||||
(defn fresh-id
|
(defn fresh-id
|
||||||
|
|
|
||||||
|
|
@ -103,6 +103,28 @@
|
||||||
(/ source-fps picture-fps))))
|
(/ source-fps picture-fps))))
|
||||||
f))
|
f))
|
||||||
|
|
||||||
|
(def timed-kinds
|
||||||
|
"Kinds that place something with a time of its own — a symbol, a sound — and
|
||||||
|
so read their `:span` in THAT time rather than in their parent's."
|
||||||
|
#{:instance :audio})
|
||||||
|
|
||||||
|
(defn placed-span
|
||||||
|
"Where a node exists, as `[in out)` in its PARENT's frames, or nil for always.
|
||||||
|
|
||||||
|
TWO MEANINGS OF `:span`, and this is the one place that knows both. A shape has
|
||||||
|
no time of its own, so its `:span` is already in its parent's frames. An
|
||||||
|
instance or a sound HAS a time of its own, and its `:span` is in that time: which
|
||||||
|
frames of what it places are played, from its own 0 however late it enters. Where
|
||||||
|
that first frame lands in the parent is `:at`, a separate fact — so moving an
|
||||||
|
instance along its parent is one write to `:at`, and the span, which says what
|
||||||
|
the instance IS, does not change when it is moved."
|
||||||
|
[n]
|
||||||
|
(when-let [[in out] (:span n)]
|
||||||
|
(if (timed-kinds (:kind n))
|
||||||
|
(let [{:keys [at rate] :or {at 0 rate 1}} (:time n)]
|
||||||
|
[at (+ at (/ (- out in) rate))])
|
||||||
|
[in out])))
|
||||||
|
|
||||||
(defn local-frame
|
(defn local-frame
|
||||||
"Apply a node's time map to the frame it was handed by its parent.
|
"Apply a node's time map to the frame it was handed by its parent.
|
||||||
|
|
||||||
|
|
@ -117,19 +139,21 @@
|
||||||
as two performances; offset is PER-NODE by design, because mouth lead applies
|
as two performances; offset is PER-NODE by design, because mouth lead applies
|
||||||
to performance nodes and not to the plate, which is the entire point of it."
|
to performance nodes and not to the plate, which is the entire point of it."
|
||||||
[n f]
|
[n f]
|
||||||
(let [{:keys [mode offset rate at in source-fps sample-fps]
|
(let [{:keys [mode offset rate at source-fps sample-fps]
|
||||||
ex :expose :or {mode :inherit}} (:time n)]
|
ex :expose :or {mode :inherit}} (:time n)]
|
||||||
(if (= mode :inherit)
|
(if (= mode :inherit)
|
||||||
f
|
f
|
||||||
(do
|
(do
|
||||||
(when (and (not (#{:instance :audio} (:kind n))) rate (not= rate 1.0) (not= rate 1))
|
(when (and (not (timed-kinds (:kind n))) rate (not= rate 1.0) (not= rate 1))
|
||||||
(throw (ex-info "time map :rate belongs to an instance or an audio node"
|
(throw (ex-info "time map :rate belongs to an instance or an audio node"
|
||||||
{:node (:id n) :time (:time n)})))
|
{:node (:id n) :time (:time n)})))
|
||||||
(when (and sample-fps (not (and source-fps (pos? source-fps))))
|
(when (and sample-fps (not (and source-fps (pos? source-fps))))
|
||||||
(throw (ex-info "picture sampling needs a positive source fps"
|
(throw (ex-info "picture sampling needs a positive source fps"
|
||||||
{:node (:id n) :time (:time n)})))
|
{:node (:id n) :time (:time n)})))
|
||||||
(cond-> (if (#{:instance :audio} (:kind n))
|
(cond-> (if (timed-kinds (:kind n))
|
||||||
(+ (or in 0) (* (or rate 1) (- f (or at 0))))
|
;; `:at` is where the span's first frame lands, so the parent's
|
||||||
|
;; `at` is this node's `in`.
|
||||||
|
(+ (or (first (:span n)) 0) (* (or rate 1) (- f (or at 0))))
|
||||||
f)
|
f)
|
||||||
sample-fps (sample-frame source-fps sample-fps)
|
sample-fps (sample-frame source-fps sample-fps)
|
||||||
ex (expose ex)
|
ex (expose ex)
|
||||||
|
|
@ -273,7 +297,9 @@
|
||||||
(conj "an instance's :rate must be positive")
|
(conj "an instance's :rate must be positive")
|
||||||
(nil? (:z n)) (conj "no :z — draw order is authored per scene, not implied by the tree")
|
(nil? (:z n)) (conj "no :z — draw order is authored per scene, not implied by the tree")
|
||||||
(and (:span n) (not= 2 (count (:span n))))
|
(and (:span n) (not= 2 (count (:span n))))
|
||||||
(conj ":span must be [in out]"))
|
(conj ":span must be [in out]")
|
||||||
|
(some? (get-in n [:time :in]))
|
||||||
|
(conj ":time has an :in — an instance's first frame is the start of its own :span"))
|
||||||
|
|
||||||
(into (when valid
|
(into (when valid
|
||||||
(for [[path _] (:channels n)
|
(for [[path _] (:channels n)
|
||||||
|
|
|
||||||
|
|
@ -166,10 +166,11 @@
|
||||||
(defn- in-span?
|
(defn- in-span?
|
||||||
"`:span` is Lottie's ip/op and Flash's PlaceObject/RemoveObject: the range over
|
"`:span` is Lottie's ip/op and Flash's PlaceObject/RemoveObject: the range over
|
||||||
which the node EXISTS, tested in the PARENT's frame space and therefore before
|
which the node EXISTS, tested in the PARENT's frame space and therefore before
|
||||||
the node's own time map runs. Distinct from `[:vis]`, which blinks an existing
|
the node's own time map runs — an instance's own-time span is mapped out by
|
||||||
node on and off. Half-open, so two adjacent spans do not both own a frame."
|
`node/placed-span`. Distinct from `[:vis]`, which blinks an existing node on and
|
||||||
|
off. Half-open, so two adjacent spans do not both own a frame."
|
||||||
[n f]
|
[n f]
|
||||||
(if-let [[in out] (:span n)]
|
(if-let [[in out] (node/placed-span n)]
|
||||||
(and (>= f in) (< f out))
|
(and (>= f in) (< f out))
|
||||||
true))
|
true))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -126,7 +126,11 @@
|
||||||
;; Which symbol an instance places. The one fact that makes an instance
|
;; Which symbol an instance places. The one fact that makes an instance
|
||||||
;; legible as an instance rather than as a node.
|
;; legible as an instance rather than as a node.
|
||||||
"of" (when (= :instance (:kind n)) (str (:of n)))
|
"of" (when (= :instance (:kind n)) (str (:of n)))
|
||||||
"span" (when start (str start " … " end))]
|
;; An instance's span is in its OWN frames and `at` is where it sits in
|
||||||
|
;; this symbol; a shape's span is in this symbol's frames. See
|
||||||
|
;; `node/placed-span`.
|
||||||
|
"span" (when start (str start " … " end))
|
||||||
|
"at" (when (node/timed-kinds (:kind n)) (str (get-in n [:time :at] 0)))]
|
||||||
(when (:paint? n) [drawing-keys sid id n frame])
|
(when (:paint? n) [drawing-keys sid id n frame])
|
||||||
[:div.row {:style {:margin-top "6px"}} [:span.dim "channels"]]
|
[:div.row {:style {:margin-top "6px"}} [:span.dim "channels"]]
|
||||||
[:dl.facts
|
[:dl.facts
|
||||||
|
|
|
||||||
|
|
@ -49,12 +49,13 @@
|
||||||
frame the exposure grid never samples is still authored on that frame, and that
|
frame the exposure grid never samples is still authored on that frame, and that
|
||||||
is where the row should show it."
|
is where the row should show it."
|
||||||
[n]
|
[n]
|
||||||
(let [{:keys [mode offset at in rate] :or {mode :inherit at 0 in 0 rate 1}} (:time n)]
|
(let [{:keys [mode offset at rate] :or {mode :inherit at 0 rate 1}} (:time n)
|
||||||
|
in (or (first (:span n)) 0)]
|
||||||
(if (= mode :inherit)
|
(if (= mode :inherit)
|
||||||
identity
|
identity
|
||||||
(fn [f]
|
(fn [f]
|
||||||
(let [f (- f (or offset 0))]
|
(let [f (- f (or offset 0))]
|
||||||
(if (and (#{:instance :audio} (:kind n)) (not (zero? rate)))
|
(if (and (node/timed-kinds (:kind n)) (not (zero? rate)))
|
||||||
(js/Math.round (+ at (/ (- f in) rate)))
|
(js/Math.round (+ at (/ (- f in) rate)))
|
||||||
f))))))
|
f))))))
|
||||||
|
|
||||||
|
|
@ -107,7 +108,12 @@
|
||||||
;; child at this node's local frame — so the nested
|
;; child at this node's local frame — so the nested
|
||||||
;; walk carries it down unchanged.
|
;; walk carries it down unchanged.
|
||||||
self (comp ->open (local->parent n))
|
self (comp ->open (local->parent n))
|
||||||
span (mapv ->open (or (:span n) [0 (:frames sym)]))
|
span (mapv ->open
|
||||||
|
(or (node/placed-span
|
||||||
|
(cond-> n
|
||||||
|
(and (= :instance (:kind n)) (nil? (:span n)))
|
||||||
|
(assoc :span [0 (get-in clip [:symbols (:of n) :frames])])))
|
||||||
|
[0 (:frames sym)]))
|
||||||
row {:path rpath
|
row {:path rpath
|
||||||
:depth depth
|
:depth depth
|
||||||
:label (node-label id n)
|
:label (node-label id n)
|
||||||
|
|
@ -205,11 +211,13 @@
|
||||||
|
|
||||||
(defn- track-cell [{:keys [span keys dense?]} frames]
|
(defn- track-cell [{:keys [span keys dense?]} frames]
|
||||||
[:div.tl-track
|
[:div.tl-track
|
||||||
(when span
|
;; Clipped to the ruler: an instance longer than the room left in its
|
||||||
[:div {:class (str "tl-span" (when dense? " dense"))
|
;; symbol still plays its own frames from 0, it is just cut off at the end.
|
||||||
:style {:left (edge% (first span) frames)
|
(when-let [[in out] (when span [(max 0 (first span)) (min frames (second span))])]
|
||||||
:width (str (* 100 (/ (- (second span) (first span))
|
(when (< in out)
|
||||||
(max 1 frames))) "%")}}])
|
[:div {:class (str "tl-span" (when dense? " dense"))
|
||||||
|
:style {:left (edge% in frames)
|
||||||
|
:width (str (* 100 (/ (- out in) (max 1 frames))) "%")}}]))
|
||||||
;; A dense channel has a value on every frame, so ticking each one is a solid
|
;; A dense channel has a value on every frame, so ticking each one is a solid
|
||||||
;; block that says less than the bar behind it already does.
|
;; block that says less than the bar behind it already does.
|
||||||
(when-not dense?
|
(when-not dense?
|
||||||
|
|
|
||||||
|
|
@ -30,8 +30,8 @@
|
||||||
:parent :root :z "a1" :span [0 4]
|
:parent :root :z "a1" :span [0 4]
|
||||||
:channels {[:xform :pos] (ch/framed [100 50])}}
|
:channels {[:xform :pos] (ch/framed [100 50])}}
|
||||||
:right {:id :right :kind :instance :of :sym/test
|
:right {:id :right :kind :instance :of :sym/test
|
||||||
:parent :root :z "a2" :span [2 6]
|
:parent :root :z "a2" :span [0 4]
|
||||||
:time {:mode :map :at 2 :in 0 :rate 1}
|
:time {:mode :map :at 2 :rate 1}
|
||||||
:channels {[:xform :pos] (ch/framed [120 50])}}}})
|
:channels {[:xform :pos] (ch/framed [120 50])}}}})
|
||||||
(assoc-in [:symbols :sym/test]
|
(assoc-in [:symbols :sym/test]
|
||||||
(assoc (get-in source [:symbols :main]) :id :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 (distinct (map #(:name (val %)) symbols))))))))
|
||||||
(is (= 7 (count (filter #(= :instance (:kind %))
|
(is (= 7 (count (filter #(= :instance (:kind %))
|
||||||
(vals (get-in document [:symbols :main :nodes]))))))
|
(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)
|
(let [left (placement document :left)
|
||||||
scale (get-in left [:channels [:xform :scale]])
|
scale (get-in left [:channels [:xform :scale]])
|
||||||
anchor (get-in left [:channels [:xform :anchor] :value])
|
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.
|
;; 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-of :right) (:linked-to (placement document :voice-right))))
|
||||||
(is (uuid? (: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
|
(is (= 0.5 (ch/value-at
|
||||||
(get-in (placement document :voice-right)
|
(get-in (placement document :voice-right)
|
||||||
[:channels [:audio :gain]]) 54)))
|
[:channels [:audio :gain]]) 54)))
|
||||||
|
|
@ -222,7 +223,8 @@
|
||||||
(testing "an instance can go into any symbol, and spans that symbol's frames"
|
(testing "an instance can go into any symbol, and spans that symbol's frames"
|
||||||
(let [[n] (vals (get-in c [:symbols :outer :nodes]))]
|
(let [[n] (vals (get-in c [:symbols :outer :nodes]))]
|
||||||
(is (= :inner (:of n)))
|
(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"
|
(testing "placing is refused when it would make a cycle"
|
||||||
(is (clip/contains-symbol? c :outer :inner))
|
(is (clip/contains-symbol? c :outer :inner))
|
||||||
(is (not (clip/contains-symbol? c :inner :outer)))
|
(is (not (clip/contains-symbol? c :inner :outer)))
|
||||||
|
|
@ -245,12 +247,23 @@
|
||||||
(is (= {:id :symbol-1 :name "symbol-1" :frames 180 :nodes {}}
|
(is (= {:id :symbol-1 :name "symbol-1" :frames 180 :nodes {}}
|
||||||
(clip/symbol made :symbol-1))
|
(clip/symbol made :symbol-1))
|
||||||
"empty, and as long as the rest of what it was placed in")
|
"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])))
|
(select-keys (get-in made [:symbols :outer :nodes u]) [:of :span :time])))
|
||||||
(is (empty? (clip/problems made)))
|
(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 :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")))
|
(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
|
(deftest a-frame-is-carried-down-through-the-instances-a-row-path-names
|
||||||
(let [c (nested)
|
(let [c (nested)
|
||||||
[id] (keys (get-in c [:symbols :outer :nodes]))]
|
[id] (keys (get-in c [:symbols :outer :nodes]))]
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue