Animate seven stage symbols and bound playback memory
This commit is contained in:
parent
ffb95543a3
commit
39ee37db02
8 changed files with 126 additions and 27 deletions
|
|
@ -44,10 +44,13 @@
|
||||||
`:head` written as a dense track in one and as framed identity in the other, so
|
`:head` written as a dense track in one and as framed identity in the other, so
|
||||||
the button that switches between them switches a document field and nothing
|
the button that switches between them switches a document field and nothing
|
||||||
else."
|
else."
|
||||||
{:demo (entry :demo "demo" demo/clip nil)
|
{:demo {:label "demo" :entry (delay (entry :demo "demo" demo/clip nil))}
|
||||||
:swarm (entry :swarm "swarm" @swarm/clip @swarm/store)
|
:swarm {:label "swarm" :entry (delay (entry :swarm "swarm" @swarm/clip @swarm/store))}
|
||||||
:take (entry :take "take" @take/clip @take/store)
|
:take {:label "take" :entry (delay (entry :take "take" @take/clip @take/store))}
|
||||||
:take-locked (entry :take-locked "locked" @take/locked @take/store)})
|
:take-locked {:label "locked" :entry (delay (entry :take-locked "locked" @take/locked @take/store))}})
|
||||||
|
|
||||||
|
(defn clip-entry [id]
|
||||||
|
(some-> (get-in clips [id :entry]) deref))
|
||||||
|
|
||||||
(def default
|
(def default
|
||||||
{;; --- the document ---
|
{;; --- the document ---
|
||||||
|
|
@ -60,7 +63,7 @@
|
||||||
;; footage's. That is what deleting `makeXform` buys — the framing became a
|
;; footage's. That is what deleting `makeXform` buys — the framing became a
|
||||||
;; transform on a node, so nothing downstream of the freeze knows the frame
|
;; transform on a node, so nothing downstream of the freeze knows the frame
|
||||||
;; size — and it is why ui/player no longer hardcodes 320x200.
|
;; size — and it is why ui/player no longer hardcodes 320x200.
|
||||||
:clip (select-keys (:take clips) [:fps :frames :width :height :audio :display-fps])
|
:clip (select-keys (clip-entry :take) [:fps :frames :width :height :audio :display-fps])
|
||||||
|
|
||||||
;; Which ingested footage to detect, and what the last load said. The list
|
;; Which ingested footage to detect, and what the last load said. The list
|
||||||
;; comes from the server — tier 3 is the backend's since step 9 — so there is
|
;; comes from the server — tier 3 is the backend's since step 9 — so there is
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,41 @@
|
||||||
(ns arthur.demo.stage
|
(ns arthur.demo.stage
|
||||||
"A saved take placed twice on a stage. The EDN is the authored layout."
|
"A saved take placed seven times on a stage. The EDN is the authored layout."
|
||||||
(:require [cljs.reader :as reader]
|
(:require [arthur.domain.channel :as ch]
|
||||||
|
[cljs.reader :as reader]
|
||||||
[shadow.resource :as rc]))
|
[shadow.resource :as rc]))
|
||||||
|
|
||||||
(def layout (reader/read-string (rc/inline "arthur/demo/stage_8625.edn")))
|
(def layout (reader/read-string (rc/inline "arthur/demo/stage_8625.edn")))
|
||||||
|
|
||||||
|
(defn- position-track [center anchor drift phase frames]
|
||||||
|
(let [base (mapv - center anchor)
|
||||||
|
[dx dy] drift
|
||||||
|
wave (fn [f period] (js/Math.sin (* 2 js/Math.PI (/ (+ f phase) period))))
|
||||||
|
x0 (wave 0 96)
|
||||||
|
y0 (wave 0 132)]
|
||||||
|
(ch/keyed
|
||||||
|
(into {}
|
||||||
|
(for [f (conj (vec (range 0 frames 20)) (dec frames))]
|
||||||
|
[f [(+ (first base) (* dx (- (wave f 96) x0)))
|
||||||
|
(+ (second base) (* dy (- (wave f 132) y0)))]]))
|
||||||
|
:linear)))
|
||||||
|
|
||||||
(defn compose [source]
|
(defn compose [source]
|
||||||
(let [{:keys [name width height frames symbol instances audio]} layout
|
(let [{:keys [name width height frames symbol instances audio scale]} layout
|
||||||
|
default-anchor (or (:anchor layout)
|
||||||
|
[(/ (:width source) 2) (/ (:height source) 2)])
|
||||||
original (get-in source [:timelines :main])
|
original (get-in source [:timelines :main])
|
||||||
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 [id name z span at in pos scale]}]
|
(map (fn [{:keys [id name z span at in center anchor drift phase]}]
|
||||||
|
(let [anchor (or anchor default-anchor)]
|
||||||
[id {:id id :name name :kind :symbol :of symbol
|
[id {:id id :name name :kind :symbol :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 :in in :rate 1}
|
||||||
:channels {[:xform :pos] {:animated? false :value pos}
|
:channels {[:xform :pos] (if drift
|
||||||
[:xform :scale] {:animated? false :value scale}}}])
|
(position-track center anchor drift phase frames)
|
||||||
|
(ch/framed (mapv - center anchor)))
|
||||||
|
[:xform :anchor] {:animated? false :value anchor}
|
||||||
|
[:xform :scale] scale}}]))
|
||||||
instances))
|
instances))
|
||||||
nodes (into nodes
|
nodes (into nodes
|
||||||
(map (fn [{:keys [id linked-to z source span at in gain pan]}]
|
(map (fn [{:keys [id linked-to z source span at in gain pan]}]
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,22 @@
|
||||||
;; A local stage sketch. The source is the saved, post-processed IMG_8625.MOV
|
;; A local stage sketch. The source is the saved, post-processed IMG_8625.MOV
|
||||||
;; clip in the project store; its dense channel blocks remain shared by both
|
;; clip in the project store; its dense channel blocks are shared by all seven
|
||||||
;; instances. Positions and timing are authored here in stage pixels and frames.
|
;; instances. Centers, drift and timing are authored in stage pixels and frames.
|
||||||
{:source-project "4379f900-bdd2-409b-acf6-32081f8ce01f"
|
{:source-project "4379f900-bdd2-409b-acf6-32081f8ce01f"
|
||||||
:source-cid "f8cace9e-4ad3-4796-973c-c62eeebe3d01"
|
:source-cid "f8cace9e-4ad3-4796-973c-c62eeebe3d01"
|
||||||
:symbol :sym/face-8625
|
:symbol :sym/face-8625
|
||||||
:name "8625 stage study"
|
:name "8625 stage study"
|
||||||
:width 320 :height 200 :frames 280
|
:width 320 :height 200 :frames 280
|
||||||
|
;; Each :center below places the source clip's center on the stage. A symbol
|
||||||
|
;; can author :anchor to override that default for an off-center drawing.
|
||||||
|
;; Each placement reads this pulse in its own local time, so the staggered
|
||||||
|
;; entrances start their growth at different moments on the master timeline.
|
||||||
|
:scale {:animated? true :interp :linear
|
||||||
|
:keys {0 [0.4 0.4], 12 [0.56 0.56], 24 [0.48 0.48],
|
||||||
|
48 [0.52 0.52], 72 [0.48 0.48], 96 [0.52 0.52],
|
||||||
|
120 [0.48 0.48], 144 [0.52 0.52], 168 [0.48 0.48],
|
||||||
|
192 [0.52 0.52], 216 [0.48 0.48], 240 [0.52 0.52],
|
||||||
|
279 [0.48 0.48]}
|
||||||
|
: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.
|
||||||
:audio
|
:audio
|
||||||
|
|
@ -26,7 +37,22 @@
|
||||||
:instances
|
:instances
|
||||||
[{:id :left :name "8625 left" :z "a1"
|
[{:id :left :name "8625 left" :z "a1"
|
||||||
:span [0 280] :at 0 :in 0
|
:span [0 280] :at 0 :in 0
|
||||||
:pos [12 38] :scale [0.62 0.62]}
|
:center [40 40] :drift [3 2] :phase 0}
|
||||||
{:id :right :name "8625 right" :z "a2"
|
{:id :right :name "8625 right" :z "a2"
|
||||||
:span [48 280] :at 48 :in 0
|
:span [48 280] :at 48 :in 0
|
||||||
:pos [108 38] :scale [0.62 0.62]}]}
|
:center [120 40] :drift [-3 2] :phase 17}
|
||||||
|
{:id :top-third :name "8625 top third" :z "a5"
|
||||||
|
:span [24 280] :at 24 :in 0
|
||||||
|
:center [200 40] :drift [2 -3] :phase 31}
|
||||||
|
{:id :top-fourth :name "8625 top fourth" :z "a6"
|
||||||
|
:span [72 280] :at 72 :in 0
|
||||||
|
:center [280 40] :drift [-2 -2] :phase 49}
|
||||||
|
{:id :bottom-left :name "8625 bottom left" :z "a7"
|
||||||
|
:span [96 280] :at 96 :in 0
|
||||||
|
:center [70 135] :drift [3 -2] :phase 63}
|
||||||
|
{:id :bottom-middle :name "8625 bottom middle" :z "a8"
|
||||||
|
:span [120 280] :at 120 :in 0
|
||||||
|
:center [160 135] :drift [-2 3] :phase 81}
|
||||||
|
{:id :bottom-right :name "8625 bottom right" :z "a9"
|
||||||
|
:span [144 280] :at 144 :in 0
|
||||||
|
:center [250 135] :drift [2 2] :phase 107}]}
|
||||||
|
|
|
||||||
|
|
@ -172,8 +172,11 @@
|
||||||
(defn- interpolate [ch f left right]
|
(defn- interpolate [ch f left right]
|
||||||
(let [a (get (:keys ch) left)]
|
(let [a (get (:keys ch) left)]
|
||||||
(if (and (= :linear (:interp ch)) right (>= f left) (> right left))
|
(if (and (= :linear (:interp ch)) right (>= f left) (> right left))
|
||||||
(let [b (get (:keys ch) right)]
|
(let [b (get (:keys ch) right)
|
||||||
(+ a (* (/ (- f left) (- right left)) (- b a))))
|
t (/ (- f left) (- right left))]
|
||||||
|
(if (vector? a)
|
||||||
|
(mapv (fn [x y] (+ x (* t (- y x)))) a b)
|
||||||
|
(+ a (* t (- b a)))))
|
||||||
a)))
|
a)))
|
||||||
|
|
||||||
(defn- keyed-at
|
(defn- keyed-at
|
||||||
|
|
@ -291,6 +294,15 @@
|
||||||
(defn problems
|
(defn problems
|
||||||
"Human-readable reasons this map is not a channel. Empty means it is one."
|
"Human-readable reasons this map is not a channel. Empty means it is one."
|
||||||
[ch]
|
[ch]
|
||||||
|
(let [values (when (map? (:keys ch)) (vals (:keys ch)))
|
||||||
|
first-value (first values)
|
||||||
|
linear-values? (or (every? number? values)
|
||||||
|
(and (vector? first-value)
|
||||||
|
(pos? (count first-value))
|
||||||
|
(every? (fn [v] (and (vector? v)
|
||||||
|
(= (count v) (count first-value))
|
||||||
|
(every? number? v)))
|
||||||
|
values)))]
|
||||||
(cond-> []
|
(cond-> []
|
||||||
(not (map? ch))
|
(not (map? ch))
|
||||||
(conj "not a map")
|
(conj "not a map")
|
||||||
|
|
@ -315,8 +327,8 @@
|
||||||
(conj (str ":interp " (:interp ch) " is not implemented"))
|
(conj (str ":interp " (:interp ch) " is not implemented"))
|
||||||
|
|
||||||
(and (map? ch) (= :linear (:interp ch))
|
(and (map? ch) (= :linear (:interp ch))
|
||||||
(or (:dense ch) (not (every? number? (vals (:keys ch))))))
|
(or (:dense ch) (not linear-values?)))
|
||||||
(conj ":linear interpolation needs numeric keyed values")
|
(conj ":linear interpolation needs numeric keys of one shape")
|
||||||
|
|
||||||
(and (map? ch) (seq (:over ch)))
|
(and (map? ch) (seq (:over ch)))
|
||||||
(conj ":over layers are not implemented (port-plan step 2 scope)")
|
(conj ":over layers are not implemented (port-plan step 2 scope)")
|
||||||
|
|
@ -328,4 +340,4 @@
|
||||||
(not (and (number? (:scale (:dense ch))) (pos? (:scale (:dense ch))))))
|
(not (and (number? (:scale (:dense ch))) (pos? (:scale (:dense ch))))))
|
||||||
(conj (str ":dense :scale is " (pr-str (:scale (:dense ch)))
|
(conj (str ":dense :scale is " (pr-str (:scale (:dense ch)))
|
||||||
" — a fixed-point scale is a positive number the stored integers"
|
" — a fixed-point scale is a positive number the stored integers"
|
||||||
" were multiplied by"))))
|
" were multiplied by")))))
|
||||||
|
|
|
||||||
|
|
@ -29,4 +29,4 @@
|
||||||
(defn entry [id]
|
(defn entry [id]
|
||||||
(if (= id (:id @loaded))
|
(if (= id (:id @loaded))
|
||||||
@loaded
|
@loaded
|
||||||
(get db/clips id)))
|
(db/clip-entry id)))
|
||||||
|
|
|
||||||
|
|
@ -151,7 +151,14 @@
|
||||||
(js/performance.mark "arthur/blit:start")
|
(js/performance.mark "arthur/blit:start")
|
||||||
(canvas/blit! canvas ras ramp))
|
(canvas/blit! canvas ras ramp))
|
||||||
(js/performance.measure "arthur/resolve+draw" "arthur/paint:start" "arthur/blit:start")
|
(js/performance.measure "arthur/resolve+draw" "arthur/paint:start" "arthur/blit:start")
|
||||||
(js/performance.measure "arthur/paint" "arthur/paint:start"))))
|
(js/performance.measure "arthur/paint" "arthur/paint:start")
|
||||||
|
;; User Timing entries otherwise accumulate forever in the browser's
|
||||||
|
;; performance timeline during playback. DevTools captures the events as
|
||||||
|
;; they happen; keeping copies on the page serves no purpose.
|
||||||
|
(js/performance.clearMarks "arthur/paint:start")
|
||||||
|
(js/performance.clearMarks "arthur/blit:start")
|
||||||
|
(js/performance.clearMeasures "arthur/resolve+draw")
|
||||||
|
(js/performance.clearMeasures "arthur/paint"))))
|
||||||
|
|
||||||
(defonce ^:private watch
|
(defonce ^:private watch
|
||||||
;; A scene swap changes the resolver and not the frame number, so the loop
|
;; A scene swap changes the resolver and not the frame number, so the loop
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,15 @@
|
||||||
(is (= [:a :a :a :a :b :b :b :b :b :b :b :b :c :c]
|
(is (= [:a :a :a :a :b :b :b :b :b :b :b :b :c :c]
|
||||||
(mapv #(ch/value-at c %) (range 0 14))))))
|
(mapv #(ch/value-at c %) (range 0 14))))))
|
||||||
|
|
||||||
|
(deftest linear-vector-keys-interpolate-each-component
|
||||||
|
(let [c (ch/keyed {0 [0.4 0.6], 10 [0.6 0.4]} :linear)
|
||||||
|
frames [0 5 10 5 2]
|
||||||
|
cursor (ch/cursor c)]
|
||||||
|
(is (empty? (ch/problems c)))
|
||||||
|
(is (= [0.5 0.5] (ch/value-at c 5)))
|
||||||
|
(is (= (mapv #(ch/value-at c %) frames)
|
||||||
|
(mapv #(ch/sample! cursor %) frames)))))
|
||||||
|
|
||||||
(deftest a-frame-before-the-first-key-reads-the-first-key
|
(deftest a-frame-before-the-first-key-reads-the-first-key
|
||||||
;; The JS activeKey clamps low, and that is kept: a channel's first key is the
|
;; The JS activeKey clamps low, and that is kept: a channel's first key is the
|
||||||
;; pose the part starts in. Having NO value is a different question — it is a
|
;; pose the part starts in. Having NO value is a different question — it is a
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,8 @@
|
||||||
[arthur.demo.stage :as stage]
|
[arthur.demo.stage :as stage]
|
||||||
[arthur.domain.channel :as ch]
|
[arthur.domain.channel :as ch]
|
||||||
[arthur.domain.clip :as clip]
|
[arthur.domain.clip :as clip]
|
||||||
[arthur.domain.leaf :as leaf]))
|
[arthur.domain.leaf :as leaf]
|
||||||
|
[arthur.domain.node :as node]))
|
||||||
|
|
||||||
(def source
|
(def source
|
||||||
{:name "source" :fps 30 :width 320 :height 200
|
{:name "source" :fps 30 :width 320 :height 200
|
||||||
|
|
@ -45,7 +46,28 @@
|
||||||
(is (= #{:main :sym/face-8625} (set (keys (:timelines document)))))
|
(is (= #{:main :sym/face-8625} (set (keys (:timelines document)))))
|
||||||
(is (= :sym/face-8625 (get-in document [:timelines :main :nodes :left :of])))
|
(is (= :sym/face-8625 (get-in document [:timelines :main :nodes :left :of])))
|
||||||
(is (= :sym/face-8625 (get-in document [:timelines :main :nodes :right :of])))
|
(is (= :sym/face-8625 (get-in document [:timelines :main :nodes :right :of])))
|
||||||
|
(is (= 7 (count (filter #(= :symbol (:kind %))
|
||||||
|
(vals (get-in document [:timelines :main :nodes]))))))
|
||||||
(is (= [48 280] (get-in document [:timelines :main :nodes :right :span])))
|
(is (= [48 280] (get-in document [:timelines :main :nodes :right :span])))
|
||||||
|
(let [scale (get-in document [:timelines :main :nodes :left
|
||||||
|
:channels [:xform :scale]])
|
||||||
|
anchor (get-in document [:timelines :main :nodes :left
|
||||||
|
:channels [:xform :anchor] :value])
|
||||||
|
pos (get-in document [:timelines :main :nodes :left
|
||||||
|
:channels [:xform :pos]])
|
||||||
|
start-pos (ch/value-at pos 0)]
|
||||||
|
(is (= [160 100] anchor) "the source center becomes a stored pivot")
|
||||||
|
(is (= [-120 -60] start-pos))
|
||||||
|
(is (not= start-pos (ch/value-at pos 40)) "the face drifts during playback")
|
||||||
|
(is (= [0.4 0.4] (ch/value-at scale 0)))
|
||||||
|
(is (= [0.56 0.56] (ch/value-at scale 12)))
|
||||||
|
(is (= [0.52 0.52] (ch/value-at scale 48)))
|
||||||
|
(doseq [f [0 12 48]]
|
||||||
|
(let [m (node/local! (node/mat) start-pos 0 (ch/value-at scale f) [0 0] anchor)
|
||||||
|
out (js/Float64Array. 2)]
|
||||||
|
(node/apply-pt! out 0 m 160 100)
|
||||||
|
(is (= [40 40] [(aget out 0) (aget out 1)])
|
||||||
|
"the face center stays put while it scales"))))
|
||||||
(is (= :right (get-in document [:timelines :main :nodes :voice-right :linked-to])))
|
(is (= :right (get-in document [:timelines :main :nodes :voice-right :linked-to])))
|
||||||
(is (= [48 260] (get-in document [:timelines :main :nodes :voice-right :span])))
|
(is (= [48 260] (get-in document [:timelines :main :nodes :voice-right :span])))
|
||||||
(is (= 0.5 (ch/value-at
|
(is (= 0.5 (ch/value-at
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue