One word for one thing: it is a cel
Four words had accumulated for a node that puts a symbol inside another symbol.
`instance` was the document's, from the model. `placement` was the stage and
export work's. `occurrence` came in with the lane model. `exposure` came in with
me, because it is what an animator would say. Three bodies of work each brought
a word and none of them retired anybody else's, which is how you get a codebase
that reads like three people describing the same object over each other.
It is a CEL. One drawing, held for some duration. `cel` was already the view's
word — `.tl-cel`, the cel strip — so choosing it was also the smallest change,
and the app already says "drawing" for the content, which is what frees the word
up: historically a cel IS the celluloid with the drawing on it, and that sense
has somewhere else to live here.
instance the `:kind`. The general thing, anywhere in a document.
cel an instance in a lane. UI labels, command names, prose.
lane the group with `:layout :sequence`.
drawing the content a cel names.
placement kept ONLY for where a node sits — `nest/placement` and the
transform that puts a face on the stage. Retired as a noun for the
node itself.
occurrence gone.
AND IT SETTLES A COLLISION I SHOULD HAVE SEEN EARLIER. `:time :expose` already
existed and means something else entirely: how many frames each step of a
subtree lasts, which is what shooting on twos is. Had the block been called an
exposure too, `node/expose`, `clock/exposed-frame` and `subs/render ::exposure`
would have been permanently confusable with it. Choosing `cel` lets the word
`exposure` keep the thing it actually names, and every remaining use of it in
`src` is now that one.
`:layout :sequence` stays as the field, and it is the one place two words are
kept deliberately: the layout names the RULE — children follow one another and
may not overlap — and a group carrying it is called a lane. `node/lane?` says so
where the two meet.
The second view is traditionally the exposure sheet. It will be the CEL SHEET,
for one vocabulary.
Renamed with a script and then read, because a blind pass does real damage: it
produced "an cel" thirty times, renamed the `::exposure` sub that is about the
`:expose` grid, and turned an "exposure grid" into a "cel grid" in two
docstrings. All three classes are fixed. `arthur.domain.sequence` is now
`arthur.domain.lane`, which is what its test file was already called.
424 tests, 5,749 assertions, and both browser flows — `test/browser/lane.mjs`,
renamed too.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
76106d36ee
commit
598c186c4f
21 changed files with 427 additions and 419 deletions
|
|
@ -822,7 +822,7 @@ collaborator's keying. The fix is addressing, not an algorithm:
|
||||||
|
|
||||||
```
|
```
|
||||||
palette
|
palette
|
||||||
sequence/:sid
|
lane/:sid
|
||||||
clip/:cid/timing clip rate
|
clip/:cid/timing clip rate
|
||||||
clip/:cid/subject/:sid tracked subject and settings
|
clip/:cid/subject/:sid tracked subject and settings
|
||||||
clip/:cid/feature/:fid tracked feature and settings
|
clip/:cid/feature/:fid tracked feature and settings
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
# The Lane Model
|
# The Lane Model
|
||||||
|
|
||||||
Revised 2026-09-30. Target design. Occurrence ownership, source playback, the
|
Revised 2026-09-30. Target design. Cel ownership, source playback, the
|
||||||
content and exposure commands, placement anywhere in a lane, a one-row cel strip
|
content and cel commands, placement anywhere in a lane, a one-row cel strip
|
||||||
and the correction-layer evaluator are implemented; the commands that produce a
|
and the correction-layer evaluator are implemented; the commands that produce a
|
||||||
correction, overwrite, the retiming commands and the remaining views are not.
|
correction, overwrite, the retiming commands and the remaining views are not.
|
||||||
See the status note under
|
See the status note under
|
||||||
|
|
@ -21,7 +21,7 @@ and conflicting proposals in `animation-model.md`, `timing-model.md`, and
|
||||||
## Goal and compatibility policy
|
## Goal and compatibility policy
|
||||||
|
|
||||||
Arthur is one animation document with several ways to see and edit it: drawing
|
Arthur is one animation document with several ways to see and edit it: drawing
|
||||||
on the stage, arranging clips, timing exposures, editing curves, and generating
|
on the stage, arranging clips, timing cels, editing curves, and generating
|
||||||
motion from footage. Each view exposes relevant facts and invokes shared editing
|
motion from footage. Each view exposes relevant facts and invokes shared editing
|
||||||
operations. Switching views must preserve the meaning of the work.
|
operations. Switching views must preserve the meaning of the work.
|
||||||
|
|
||||||
|
|
@ -48,61 +48,61 @@ rules. Minimizing field count is secondary to representing independent choices.
|
||||||
export, and optimized cursors must agree with it.
|
export, and optimized cursors must agree with it.
|
||||||
- Existence, visibility, and missing measured data remain distinct facts.
|
- Existence, visibility, and missing measured data remain distinct facts.
|
||||||
|
|
||||||
## Content, occurrences, lanes, and rows
|
## Content, cels, lanes, and rows
|
||||||
|
|
||||||
These have different identities and responsibilities:
|
These have different identities and responsibilities:
|
||||||
|
|
||||||
| Concept | Owns | Example |
|
| Concept | Owns | Example |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| Content | Reusable nodes and their animation | Drawing `a2`, an animated head, or a sound asset |
|
| Content | Reusable nodes and their animation | Drawing `a2`, an animated head, or a sound asset |
|
||||||
| Occurrence | One use of content, its interval, source playback, and local treatment | `a2` exposed on frames 12–16 |
|
| Cel | One use of content, its interval, source playback, and local treatment | `a2` exposed on frames 12–16 |
|
||||||
| Lane | A sequence of occurrences and shared properties | The girl's drawings and the girl's overall transform |
|
| Lane | A sequence of cels and shared properties | The girl's drawings and the girl's overall transform |
|
||||||
| View row or column | Presentation and editor state | Timeline row, exposure-sheet column, or property curve |
|
| View row or column | Presentation and editor state | Timeline row, cel-sheet column, or property curve |
|
||||||
|
|
||||||
Use the existing instance/node identity mechanism for occurrences. An exposure
|
Use the existing instance/node identity mechanism for cels. A cel
|
||||||
should not acquire a second identity system just because it is shown as a cel.
|
should not acquire a second identity system just because it is shown as a cel.
|
||||||
Lanes group occurrences; they do not introduce another node-holding content type.
|
Lanes group cels; they do not introduce another node-holding content type.
|
||||||
The concrete candidate below uses existing group and instance nodes; its ownership
|
The concrete candidate below uses existing group and instance nodes; its ownership
|
||||||
boundaries are part of the design. It is now the implemented shape, and the field
|
boundaries are part of the design. It is now the implemented shape, and the field
|
||||||
spellings below are the ones the runtime reads.
|
spellings below are the ones the runtime reads.
|
||||||
|
|
||||||
Each occurrence has a stable ID. Moving it, changing its hold, swapping its source,
|
Each cel has a stable ID. Moving it, changing its hold, swapping its source,
|
||||||
or trimming it preserves that ID. Repeating it creates a new occurrence that may
|
or trimming it preserves that ID. Repeating it creates a new cel that may
|
||||||
reference the same content. A split retains the original ID on the left and gives
|
reference the same content. A split retains the original ID on the left and gives
|
||||||
the right piece a new ID; commands return the resulting selection explicitly.
|
the right piece a new ID; commands return the resulting selection explicitly.
|
||||||
|
|
||||||
Occurrences are the canonical authored arrangement. A source-at-time channel or
|
Cels are the canonical authored arrangement. A source-at-time channel or
|
||||||
interval index may be compiled from them for evaluation, but is not a second
|
interval index may be compiled from them for evaluation, but is not a second
|
||||||
editable copy of the schedule. This replaces the earlier proposal that every cel
|
editable copy of the schedule. This replaces the earlier proposal that every cel
|
||||||
must be represented solely as a source key. Ordinary property animation still
|
must be represented solely as a source key. Ordinary property animation still
|
||||||
uses lightweight keys; it does not need occurrence objects.
|
uses lightweight keys; it does not need cel objects.
|
||||||
|
|
||||||
A sequence lane has non-overlapping half-open occurrence intervals `[start,end)`
|
A lane has non-overlapping half-open cel intervals `[start,end)`
|
||||||
in its own time space. Uncovered intervals are gaps. Empty lanes are valid.
|
in its own time space. Uncovered intervals are gaps. Empty lanes are valid.
|
||||||
Compositing and simultaneous sounds are represented by multiple lanes or ordinary
|
Compositing and simultaneous sounds are represented by multiple lanes or ordinary
|
||||||
scene composition; an accidental overlap never silently selects a winner.
|
scene composition; an accidental overlap never silently selects a winner.
|
||||||
Transitions, if added, need explicit overlap and mixing semantics.
|
Transitions, if added, need explicit overlap and mixing semantics.
|
||||||
|
|
||||||
Properties can belong to content, one occurrence, or the lane. For example:
|
Properties can belong to content, one cel, or the lane. For example:
|
||||||
|
|
||||||
- Rotate the reusable drawing: all its uses change.
|
- Rotate the reusable drawing: all its uses change.
|
||||||
- Rotate one occurrence: only that exposure changes.
|
- Rotate one cel: only that cel changes.
|
||||||
- Animate the lane's rotation: whichever drawing is showing follows it.
|
- Animate the lane's rotation: whichever drawing is showing follows it.
|
||||||
|
|
||||||
An occurrence can have its own transform, gain, corrections, and source timing
|
A cel can have its own transform, gain, corrections, and source timing
|
||||||
while remaining a block in the same timeline row. Independent treatment never
|
while remaining a block in the same timeline row. Independent treatment never
|
||||||
requires a new row or an otherwise unnecessary wrapper symbol.
|
requires a new row or an otherwise unnecessary wrapper symbol.
|
||||||
|
|
||||||
### Concrete candidate: a sequence group and ordinary instances
|
### Concrete candidate: a lane and ordinary instances
|
||||||
|
|
||||||
A lane is a group node with `:layout :sequence`. Its occurrences are ordinary
|
A lane is a group node with `:layout :sequence`. Its cels are ordinary
|
||||||
instance nodes whose `:parent` points to the group. All remain in their symbol's
|
instance nodes whose `:parent` points to the group. All remain in their symbol's
|
||||||
flat node map. The sequence constraint is document semantics; which rows the UI
|
flat node map. The sequence constraint is document semantics; which rows the UI
|
||||||
expands remains editor state. Ordinary groups retain unconstrained composition.
|
expands remains editor state. Ordinary groups retain unconstrained composition.
|
||||||
|
|
||||||
This example is a document the runtime accepts, built and evaluated by
|
This example is a document the runtime accepts, built and evaluated by
|
||||||
`frontend/test/arthur/domain/lane_test.cljs`. Times here are zero-based. Channels
|
`frontend/test/arthur/domain/lane_test.cljs`. Times here are zero-based. Channels
|
||||||
use the existing representation; occurrence source references and playback have
|
use the existing representation; cel source references and playback have
|
||||||
replaced the `[:source]` channel, which no longer exists.
|
replaced the `[:source]` channel, which no longer exists.
|
||||||
|
|
||||||
```clojure
|
```clojure
|
||||||
|
|
@ -112,14 +112,14 @@ replaced the `[:source]` channel, which no longer exists.
|
||||||
:channels {[:xform :rot]
|
:channels {[:xform :rot]
|
||||||
{:animated? true :interp :linear :keys {0 0, 6 30, 12 0}}}}
|
{:animated? true :interp :linear :keys {0 0, 6 30, 12 0}}}}
|
||||||
|
|
||||||
:exposure-a
|
:cel-a
|
||||||
{:id :exposure-a :kind :instance :parent :girl :z "a"
|
{:id :cel-a :kind :instance :parent :girl :z "a"
|
||||||
:time {:at 0 :rate 1} :span [0 4]
|
:time {:at 0 :rate 1} :span [0 4]
|
||||||
:source {:symbol :drawing-a}
|
:source {:symbol :drawing-a}
|
||||||
:playback {:in 0 :speed 0 :end :stop}}
|
:playback {:in 0 :speed 0 :end :stop}}
|
||||||
|
|
||||||
:exposure-b
|
:cel-b
|
||||||
{:id :exposure-b :kind :instance :parent :girl :z "b"
|
{:id :cel-b :kind :instance :parent :girl :z "b"
|
||||||
:time {:at 4 :rate 1} :span [0 4]
|
:time {:at 4 :rate 1} :span [0 4]
|
||||||
:source {:symbol :drawing-b}
|
:source {:symbol :drawing-b}
|
||||||
:playback {:in 0 :speed 0 :end :stop}
|
:playback {:in 0 :speed 0 :end :stop}
|
||||||
|
|
@ -133,20 +133,20 @@ replaced the `[:source]` channel, which no longer exists.
|
||||||
:playback {:in 3 :speed 1 :end :stop}}}
|
:playback {:in 3 :speed 1 :end :stop}}}
|
||||||
```
|
```
|
||||||
|
|
||||||
The lane's rotation reads lane time. Each occurrence's channels read occurrence
|
The lane's rotation reads lane time. Each cel's channels read cel
|
||||||
time. Its content reads source time. The stills sample frame 0, while the insert
|
time. Its content reads source time. The stills sample frame 0, while the insert
|
||||||
samples source frames 3, 4, 5, and 6. The group transform composes with the
|
samples source frames 3, 4, 5, and 6. The group transform composes with the
|
||||||
occurrence transform and then the content's own transform.
|
cel transform and then the content's own transform.
|
||||||
|
|
||||||
`:span` remains in the node's own coordinates, consistent with ordinary nodes.
|
`:span` remains in the node's own coordinates, consistent with ordinary nodes.
|
||||||
The interval in lane time is derived through `:time`; do not also store parent
|
The interval in lane time is derived through `:time`; do not also store parent
|
||||||
start/end values. Sequence children require finite intervals and positive
|
start/end values. Sequence children require finite intervals and positive
|
||||||
placement rates. Ordering and overlap checks use the mapped intervals, not `:z`.
|
placement rates. Ordering and overlap checks use the mapped intervals, not `:z`.
|
||||||
The sequence group may contain visual occurrences or audio occurrences; its
|
The sequence group may contain visual cels or audio cels; its
|
||||||
capability must reject an incompatible mixture rather than infer it per frame.
|
capability must reject an incompatible mixture rather than infer it per frame.
|
||||||
|
|
||||||
A source reference is fixed within an occurrence. The lane changes content when
|
A source reference is fixed within a cel. The lane changes content when
|
||||||
another occurrence becomes active. This is a deliberate revision of the original
|
another cel becomes active. This is a deliberate revision of the original
|
||||||
diagnosis that making `:of` a channel was necessary to avoid vertical growth:
|
diagnosis that making `:of` a channel was necessary to avoid vertical growth:
|
||||||
multiple instances can occupy one row when the view presents their containing
|
multiple instances can occupy one row when the view presents their containing
|
||||||
sequence. A lane-level source schedule is therefore derived, not authored twice.
|
sequence. A lane-level source schedule is therefore derived, not authored twice.
|
||||||
|
|
@ -157,14 +157,14 @@ The current implementation makes framed sources play and keyed sources hold.
|
||||||
Retire that rule. Channel storage shape must not determine playback behavior.
|
Retire that rule. Channel storage shape must not determine playback behavior.
|
||||||
Adding or removing a key must not turn a still into an animation or vice versa.
|
Adding or removing a key must not turn a still into an animation or vice versa.
|
||||||
|
|
||||||
An occurrence names content and describes how its source time is sampled. In the
|
A cel names content and describes how its source time is sampled. In the
|
||||||
basic case, after mapping lane time into occurrence time:
|
basic case, after mapping lane time into cel time:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
source_time = in_point + speed × occurrence_time
|
source_time = in_point + speed × cel_time
|
||||||
```
|
```
|
||||||
|
|
||||||
A newly created occurrence starts at local time zero. Moving it preserves this
|
A newly created cel starts at local time zero. Moving it preserves this
|
||||||
origin relative to its content. Trimming can narrow its local support without
|
origin relative to its content. Trimming can narrow its local support without
|
||||||
resetting that origin; split pieces likewise preserve the source and property
|
resetting that origin; split pieces likewise preserve the source and property
|
||||||
values at the cut. Trimming, slipping, and retiming are distinct operations with
|
values at the cut. Trimming, slipping, and retiming are distinct operations with
|
||||||
|
|
@ -174,7 +174,7 @@ explicitly different effects on the interval and the source map.
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| Hold a drawing | Constant source frame, equivalently speed 0 |
|
| Hold a drawing | Constant source frame, equivalently speed 0 |
|
||||||
| Play an animated symbol | Advancing source time, normally speed 1 |
|
| Play an animated symbol | Advancing source time, normally speed 1 |
|
||||||
| Cut between animations | Several occurrences, each with its own in-point and speed |
|
| Cut between animations | Several cels, each with its own in-point and speed |
|
||||||
| Mix stills and animation in a lane | Constant and advancing maps in the same sequence |
|
| Mix stills and animation in a lane | Constant and advancing maps in the same sequence |
|
||||||
|
|
||||||
The source reference itself is discrete and never numerically interpolated.
|
The source reference itself is discrete and never numerically interpolated.
|
||||||
|
|
@ -188,28 +188,28 @@ source, hold an endpoint, or loop an explicit range. A still uses a valid consta
|
||||||
frame. A loop uses a nonempty half-open range and a defined modulo rule. Playback
|
frame. A loop uses a nonempty half-open range and a defined modulo rule. Playback
|
||||||
never guesses these policies from whether a channel happens to have keys.
|
never guesses these policies from whether a channel happens to have keys.
|
||||||
|
|
||||||
Audio shares occurrence arrangement, trimming, gain ownership, and clock mapping.
|
Audio shares cel arrangement, trimming, gain ownership, and clock mapping.
|
||||||
It does not inherit visual frame-hold semantics: holding one audio sample is not
|
It does not inherit visual frame-hold semantics: holding one audio sample is not
|
||||||
an audio freeze effect. Validate supported playback policies by media capability.
|
an audio freeze effect. Validate supported playback policies by media capability.
|
||||||
Actual audio scheduling must follow active occurrences, including gaps and cuts,
|
Actual audio scheduling must follow active cels, including gaps and cuts,
|
||||||
rather than playing every sound reachable through a structural reference.
|
rather than playing every sound reachable through a structural reference.
|
||||||
|
|
||||||
## Time spaces and sampling
|
## Time spaces and sampling
|
||||||
|
|
||||||
Name the relevant space whenever an API accepts a time or range: project,
|
Name the relevant space whenever an API accepts a time or range: project,
|
||||||
symbol/lane, occurrence, or source. Store authored frame coordinates exactly;
|
symbol/lane, cel, or source. Store authored frame coordinates exactly;
|
||||||
avoid cumulative rounding when moving through nested mappings. Quantize at a
|
avoid cumulative rounding when moving through nested mappings. Quantize at a
|
||||||
declared sampling boundary, not at every traversal step. Audio also needs its
|
declared sampling boundary, not at every traversal step. Audio also needs its
|
||||||
continuous clock/sample space rather than visual frame quantization.
|
continuous clock/sample space rather than visual frame quantization.
|
||||||
|
|
||||||
A hold is an evaluable time map with no unique inverse. A loop can map many
|
A hold is an evaluable time map with no unique inverse. A loop can map many
|
||||||
displayed occurrences to one source time. APIs must distinguish forward sampling
|
displayed cels to one source time. APIs must distinguish forward sampling
|
||||||
from inverse editing, and expose enough context to resolve an occurrence or
|
from inverse editing, and expose enough context to resolve a cel or
|
||||||
explicitly refuse an ambiguous operation. Do not report a missing time map merely
|
explicitly refuse an ambiguous operation. Do not report a missing time map merely
|
||||||
because inversion is unavailable.
|
because inversion is unavailable.
|
||||||
|
|
||||||
Separate invertible placement timing from source sampling. A zero source speed
|
Separate invertible placement timing from source sampling. A zero source speed
|
||||||
can mean hold without making the occurrence's own edit clock non-invertible.
|
can mean hold without making the cel's own edit clock non-invertible.
|
||||||
Reparenting through changing transforms or non-invertible timing must either
|
Reparenting through changing transforms or non-invertible timing must either
|
||||||
preserve the full result by an explicit bake or return a reason it cannot; a
|
preserve the full result by an explicit bake or return a reason it cannot; a
|
||||||
matrix captured at one frame does not prove preservation across the animation.
|
matrix captured at one frame does not prove preservation across the animation.
|
||||||
|
|
@ -220,12 +220,12 @@ transform. An explicit whole-subtree stepping operation can exist separately.
|
||||||
|
|
||||||
Share the quantization primitive where possible, but retain its units, phase,
|
Share the quantization primitive where possible, but retain its units, phase,
|
||||||
rounding policy, and order relative to retiming and lead. The original suggestion
|
rounding policy, and order relative to retiming and lead. The original suggestion
|
||||||
that exposure and picture-rate sampling are simply one floor is insufficient:
|
that cel and picture-rate sampling are simply one floor is insufficient:
|
||||||
noninteger grids and source-frame quantization require specified behavior.
|
noninteger grids and source-frame quantization require specified behavior.
|
||||||
Identity timing can be implicit; remove `:time :mode` if it only duplicates that.
|
Identity timing can be implicit; remove `:time :mode` if it only duplicates that.
|
||||||
|
|
||||||
An occurrence interval is authored. Lane content extent is derived from its
|
A cel interval is authored. Lane content extent is derived from its
|
||||||
occurrences, including the explicit end of the last one. A separately authored
|
cels, including the explicit end of the last one. A separately authored
|
||||||
container trim/window is legitimate when it intentionally gates children. Do not
|
container trim/window is legitimate when it intentionally gates children. Do not
|
||||||
conflate that window with occupied extent or infer a final hold from the next key
|
conflate that window with occupied extent or infer a final hold from the next key
|
||||||
when no next key exists. A range of frame numbers alone cannot encode visibility
|
when no next key exists. A range of frame numbers alone cannot encode visibility
|
||||||
|
|
@ -236,23 +236,23 @@ or a missing measurement.
|
||||||
Every view issues the same domain commands. A command accepts an explicit target
|
Every view issues the same domain commands. A command accepts an explicit target
|
||||||
and edit policy, computes a valid change, and returns the change, resulting
|
and edit policy, computes a valid change, and returns the change, resulting
|
||||||
selection, and any refusal reason. A button and a drag must not implement two
|
selection, and any refusal reason. A button and a drag must not implement two
|
||||||
versions of exposure extension.
|
versions of cel extension.
|
||||||
|
|
||||||
An edit target identifies the symbol, occurrence path, selected entities or
|
An edit target identifies the symbol, cel path, selected entities or
|
||||||
properties, and the time range with its space. Navigation also distinguishes
|
properties, and the time range with its space. Navigation also distinguishes
|
||||||
editing shared content directly from editing it through a particular occurrence.
|
editing shared content directly from editing it through a particular cel.
|
||||||
Crossing a source cut must not silently redirect an active drawing edit to a
|
Crossing a source cut must not silently redirect an active drawing edit to a
|
||||||
different symbol: retain the explicit content target until navigation changes it.
|
different symbol: retain the explicit content target until navigation changes it.
|
||||||
|
|
||||||
Core commands include new drawing, reuse drawing, duplicate drawing, make unique,
|
Core commands include new drawing, reuse drawing, duplicate drawing, make unique,
|
||||||
blank range, split, trim, move, extend exposure, slip source, retime, and apply a
|
blank range, split, trim, move, extend cel, slip source, retime, and apply a
|
||||||
bounded property edit. Ripple/overwrite policy and the set of affected lanes are
|
bounded property edit. Ripple/overwrite policy and the set of affected lanes are
|
||||||
explicit command arguments. Preview consequences before committing a gesture.
|
explicit command arguments. Preview consequences before committing a gesture.
|
||||||
|
|
||||||
New drawing creates fresh empty content and an occurrence. Blank range removes
|
New drawing creates fresh empty content and a cel. Blank range removes
|
||||||
content coverage without inventing a hidden drawing. These are different actions.
|
content coverage without inventing a hidden drawing. These are different actions.
|
||||||
Reuse creates another occurrence pointing at existing content. Duplicate creates
|
Reuse creates another cel pointing at existing content. Duplicate creates
|
||||||
a new content identity. Make unique rebinds the selected occurrence only.
|
a new content identity. Make unique rebinds the selected cel only.
|
||||||
|
|
||||||
Copy semantics must specify nested sharing. A normal content copy duplicates its
|
Copy semantics must specify nested sharing. A normal content copy duplicates its
|
||||||
owned nodes and channels while preserving references to other reusable symbols.
|
owned nodes and channels while preserving references to other reusable symbols.
|
||||||
|
|
@ -268,57 +268,57 @@ and detects conflicts at the owned data being changed. One leaf per channel does
|
||||||
not solve simultaneous edits to different keys of that same channel; define a
|
not solve simultaneous edits to different keys of that same channel; define a
|
||||||
conflict policy rather than claiming that granularity solves all collaboration.
|
conflict policy rather than claiming that granularity solves all collaboration.
|
||||||
|
|
||||||
### Default timing behavior: exposure edits preserve lane keys
|
### Default timing behavior: cel edits preserve lane keys
|
||||||
|
|
||||||
Working default from the follow-up discussion: extending a drawing's hold changes
|
Working default from the follow-up discussion: extending a drawing's hold changes
|
||||||
exposure timing, leaving lane animation at its authored times. The user raised
|
cel timing, leaving lane animation at its authored times. The user raised
|
||||||
keeping keyframes in place as a possibility; this is the proposed predictable
|
keeping keyframes in place as a possibility; this is the proposed predictable
|
||||||
default, not a claim that they selected every timing policy below.
|
default, not a claim that they selected every timing policy below.
|
||||||
|
|
||||||
Ownership supplies the remaining rule: properties attached to an occurrence
|
Ownership supplies the remaining rule: properties attached to a cel
|
||||||
travel with it. Extending its end does not stretch those properties; moving it
|
travel with it. Extending its end does not stretch those properties; moving it
|
||||||
changes where their existing local times land. No per-key attachment flag is
|
changes where their existing local times land. No per-key attachment flag is
|
||||||
needed to recover ownership that the document already expresses.
|
needed to recover ownership that the document already expresses.
|
||||||
|
|
||||||
For the concrete example, extend `:exposure-a` by two lane frames with ripple:
|
For the concrete example, extend `:cel-a` by two lane frames with ripple:
|
||||||
|
|
||||||
| Fact | Before | After |
|
| Fact | Before | After |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| Exposure A's lane interval | `[0,4)` | `[0,6)` |
|
| Cel A's lane interval | `[0,4)` | `[0,6)` |
|
||||||
| Exposure B's lane interval | `[4,8)` | `[6,10)` |
|
| Cel B's lane interval | `[4,8)` | `[6,10)` |
|
||||||
| Animated insert's lane interval | `[8,12)` | `[10,14)` |
|
| Animated insert's lane interval | `[8,12)` | `[10,14)` |
|
||||||
| Girl's rotation peak | Lane frame 6 | Lane frame 6 |
|
| Girl's rotation peak | Lane frame 6 | Lane frame 6 |
|
||||||
| B's position change | B frame 1, lane frame 5 | B frame 1, lane frame 7 |
|
| B's position change | B frame 1, lane frame 5 | B frame 1, lane frame 7 |
|
||||||
| Insert's first source frame | Source frame 3 | Source frame 3 |
|
| Insert's first source frame | Source frame 3 | Source frame 3 |
|
||||||
|
|
||||||
The rotation peak now coincides with a different point in the drawing sequence.
|
The rotation peak now coincides with a different point in the drawing sequence.
|
||||||
That is the intended consequence of changing exposures underneath timed motion.
|
That is the intended consequence of changing cels underneath timed motion.
|
||||||
The position correction stays attached to drawing B's occurrence. Neither the
|
The position correction stays attached to drawing B's cel. Neither the
|
||||||
background's keys nor audio on another lane moves.
|
background's keys nor audio on another lane moves.
|
||||||
|
|
||||||
The command contract for this edit names the symbol and occurrence, a delta in
|
The command contract for this edit names the symbol and cel, a delta in
|
||||||
lane frames, `:ripple` behavior, and an explicit scope of exposure timing. It
|
lane frames, `:ripple` behavior, and an explicit scope of cel timing. It
|
||||||
extends A's local support by the delta converted through A's placement rate,
|
extends A's local support by the delta converted through A's placement rate,
|
||||||
and shifts subsequent occurrence placements by that delta in lane time. It does
|
and shifts subsequent cel placements by that delta in lane time. It does
|
||||||
not modify any channel's key map, source in-point, or playback speed. Reject a
|
not modify any channel's key map, source in-point, or playback speed. Reject a
|
||||||
nonpositive resulting duration. Validate and commit the entire change together.
|
nonpositive resulting duration. Validate and commit the entire change together.
|
||||||
|
|
||||||
The symbol's authored end is another explicit boundary: preview an overflow and
|
The symbol's authored end is another explicit boundary: preview an overflow and
|
||||||
offer to extend the symbol or cancel. A command can request that extension as
|
offer to extend the symbol or cancel. A command can request that extension as
|
||||||
part of its transaction; it must not silently truncate later occurrences or grow
|
part of its transaction; it must not silently truncate later cels or grow
|
||||||
other uses of a shared symbol. In the example, a 12-frame symbol needs an explicit
|
other uses of a shared symbol. In the example, a 12-frame symbol needs an explicit
|
||||||
extension to 14 frames or the edit must be refused without partial changes.
|
extension to 14 frames or the edit must be refused without partial changes.
|
||||||
|
|
||||||
Retime performance is a separate operation over explicitly selected occurrences
|
Retime performance is a separate operation over explicitly selected cels
|
||||||
and channels. It applies the same time transformation to their relevant clocks,
|
and channels. It applies the same time transformation to their relevant clocks,
|
||||||
keys, and correction supports. Stretching an interval requires a defined warp
|
keys, and correction supports. Stretching an interval requires a defined warp
|
||||||
and interpolation behavior; it is not merely moving keys whose frame numbers
|
and interpolation behavior; it is not merely moving keys whose frame numbers
|
||||||
happen to lie inside the selection. Until supported, refuse this operation
|
happen to lie inside the selection. Until supported, refuse this operation
|
||||||
rather than approximating it with an exposure ripple.
|
rather than approximating it with a cel ripple.
|
||||||
|
|
||||||
The initial UI should default stage transforms to the lane when drawing in a cel
|
The initial UI should default stage transforms to the lane when drawing in a cel
|
||||||
workflow, so movement usually remains independent of exposure timing. The
|
workflow, so movement usually remains independent of cel timing. The
|
||||||
inspector names the target: lane motion, this occurrence, or shared drawing.
|
inspector names the target: lane motion, this cel, or shared drawing.
|
||||||
Changing that scope is explicit. It changes what the edit means, not just which
|
Changing that scope is explicit. It changes what the edit means, not just which
|
||||||
panel happens to be open.
|
panel happens to be open.
|
||||||
|
|
||||||
|
|
@ -368,19 +368,19 @@ merely two drawings that happen to look related.
|
||||||
Derived library grouping may collect drawings used by a single lane. This is a
|
Derived library grouping may collect drawings used by a single lane. This is a
|
||||||
convenience, not ownership or deletion authority. Reference discovery for cycle
|
convenience, not ownership or deletion authority. Reference discovery for cycle
|
||||||
validation, copying, and deletion examines all structural references, including
|
validation, copying, and deletion examines all structural references, including
|
||||||
currently inactive occurrences. Authored folders, favorites, and labels remain
|
currently inactive cels. Authored folders, favorites, and labels remain
|
||||||
legitimate user data even when the UI could have suggested defaults.
|
legitimate user data even when the UI could have suggested defaults.
|
||||||
|
|
||||||
## UX: location, selection, and controls
|
## UX: location, selection, and controls
|
||||||
|
|
||||||
The breadcrumb sits above the timeline and states the editing location, shared
|
The breadcrumb sits above the timeline and states the editing location, shared
|
||||||
content identity, and occurrence context when applicable. Show local time and
|
content identity, and cel context when applicable. Show local time and
|
||||||
its project context where a useful mapping exists. Holds and loops need an honest
|
its project context where a useful mapping exists. Holds and loops need an honest
|
||||||
description instead of a fictitious unique global frame.
|
description instead of a fictitious unique global frame.
|
||||||
|
|
||||||
Creation controls next to the breadcrumb act in that explicit location. Selection
|
Creation controls next to the breadcrumb act in that explicit location. Selection
|
||||||
does not secretly change where a new symbol goes. A shared drawing indicates its
|
does not secretly change where a new symbol goes. A shared drawing indicates its
|
||||||
reuse and offers Make this occurrence unique. Names help identify content;
|
reuse and offers Make this cel unique. Names help identify content;
|
||||||
linked-use indicators must rely on IDs, because different drawings can share names.
|
linked-use indicators must rely on IDs, because different drawings can share names.
|
||||||
|
|
||||||
| Surface | Primary scope and controls |
|
| Surface | Primary scope and controls |
|
||||||
|
|
@ -390,7 +390,7 @@ linked-use indicators must rely on IDs, because different drawings can share nam
|
||||||
| Cel action strip | New drawing, duplicate drawing, hold longer/shorter, blank range |
|
| Cel action strip | New drawing, duplicate drawing, hold longer/shorter, blank range |
|
||||||
| Lane header | Lane selection, lock, mute/solo where applicable, onion settings, expansion |
|
| Lane header | Lane selection, lock, mute/solo where applicable, onion settings, expansion |
|
||||||
| Stage tools | Drawing and transform modes, active target and scope |
|
| Stage tools | Drawing and transform modes, active target and scope |
|
||||||
| Inspector | Selected content/occurrence/lane properties and valid key controls |
|
| Inspector | Selected content/cel/lane properties and valid key controls |
|
||||||
|
|
||||||
Cel actions have visible contextual buttons, shortcuts, a context menu, and
|
Cel actions have visible contextual buttons, shortcuts, a context menu, and
|
||||||
command-palette entries. These are different entrances to the same commands.
|
command-palette entries. These are different entrances to the same commands.
|
||||||
|
|
@ -411,52 +411,52 @@ preferences can be saved separately.
|
||||||
## A session, revised
|
## A session, revised
|
||||||
|
|
||||||
1. In `main`, create a girl lane and a new drawing. Draw; use New drawing (`N`)
|
1. In `main`, create a girl lane and a new drawing. Draw; use New drawing (`N`)
|
||||||
to create the next one with the previous exposure ghosted behind it.
|
to create the next one with the previous cel ghosted behind it.
|
||||||
2. Use Duplicate drawing (`D`) when the current shapes are the starting point.
|
2. Use Duplicate drawing (`D`) when the current shapes are the starting point.
|
||||||
Use Reuse drawing for a deliberately linked exposure. The UI shows the
|
Use Reuse drawing for a deliberately linked cel. The UI shows the
|
||||||
difference before an edit can change other uses.
|
difference before an edit can change other uses.
|
||||||
3. Time the performance. Hold longer (`H`) extends the selected occurrence and
|
3. Time the performance. Hold longer (`H`) extends the selected cel and
|
||||||
ripples later occurrences in the explicitly targeted lane. A trim gesture
|
ripples later cels in the explicitly targeted lane. A trim gesture
|
||||||
can use overwrite instead. The preview shows which boundaries will move.
|
can use overwrite instead. The preview shows which boundaries will move.
|
||||||
4. Choose a two-frame default exposure for newly created drawings, or run a
|
4. Choose a two-frame default cel for newly created drawings, or run a
|
||||||
separate Retime exposures command on a selected range. This does not quantize
|
separate Retime cels command on a selected range. This does not quantize
|
||||||
lane transforms or silently retime already authored exposures.
|
lane transforms or silently retime already authored cels.
|
||||||
5. Place the background in a lane below. Its source holds one frame throughout
|
5. Place the background in a lane below. Its source holds one frame throughout
|
||||||
its occurrence. Key the lane's X position at the beginning and end and choose
|
its cel. Key the lane's X position at the beginning and end and choose
|
||||||
linear interpolation. The background slides while the girl's drawings cut.
|
linear interpolation. The background slides while the girl's drawings cut.
|
||||||
6. Select three frames on the girl's lane, choose Return motion, and rotate to
|
6. Select three frames on the girl's lane, choose Return motion, and rotate to
|
||||||
the desired peak. A bounded rotation correction affects the girl across any
|
the desired peak. A bounded rotation correction affects the girl across any
|
||||||
drawing boundaries in that range. Existing motion survives outside it.
|
drawing boundaries in that range. Existing motion survives outside it.
|
||||||
7. Insert a playing animated symbol among the girl's held drawings. Set that
|
7. Insert a playing animated symbol among the girl's held drawings. Set that
|
||||||
occurrence's source playback to advance. No lane conversion is required.
|
cel's source playback to advance. No lane conversion is required.
|
||||||
|
|
||||||
The timeline shows named exposure blocks with property marks and optional curve
|
The timeline shows named cel blocks with property marks and optional curve
|
||||||
subrows. The exposure sheet shows the same occurrences by frame and lane. The
|
subrows. The cel sheet shows the same cels by frame and lane. The
|
||||||
graph editor edits the same properties; the stage resolves the same document.
|
graph editor edits the same properties; the stage resolves the same document.
|
||||||
Onion skin is configurable and counts neighboring exposure events, skipping gaps
|
Onion skin is configurable and counts neighboring cel events, skipping gaps
|
||||||
by default; a long hold does not consume the budget. Repeated uses of the same
|
by default; a long hold does not consume the budget. Repeated uses of the same
|
||||||
drawing remain distinct events. Deduplicating identical ghosts is a display option.
|
drawing remain distinct events. Deduplicating identical ghosts is a display option.
|
||||||
|
|
||||||
## Proof obligations and implementation order
|
## Proof obligations and implementation order
|
||||||
|
|
||||||
The source-channel prototype has been removed: an occurrence names one symbol
|
The source-channel prototype has been removed: a cel names one symbol
|
||||||
and carries its own playback clock, and `node/problems` rejects the old
|
and carries its own playback clock, and `node/problems` rejects the old
|
||||||
`[:source]` channel. What a lane IS lives in `arthur.domain.symbol` beside the
|
`[:source]` channel. What a lane IS lives in `arthur.domain.symbol` beside the
|
||||||
other rules about a node map; `arthur.domain.sequence` holds the commands over
|
other rules about a node map; `arthur.domain.lane` holds the commands over
|
||||||
one — add lane, place a drawing (new, reused or duplicated), make unique, split,
|
one — add lane, place a drawing (new, reused or duplicated), make unique, split,
|
||||||
trim, move, blank and extend hold. Each is one history step, and each refuses rather than
|
trim, move, blank and extend hold. Each is one history step, and each refuses rather than
|
||||||
half-applying. The timeline draws a lane's occurrences as cel blocks on the
|
half-applying. The timeline draws a lane's cels as cel blocks on the
|
||||||
lane's own row, and offers Make unique only where the selected exposure actually
|
lane's own row, and offers Make unique only where the selected cel actually
|
||||||
shares its drawing.
|
shares its drawing.
|
||||||
|
|
||||||
There is ONE placement function and a position argument, so appending is not a
|
There is ONE placement function and a position argument, so appending is not a
|
||||||
different operation from inserting: `:end` is a position like any other, the one
|
different operation from inserting: `:end` is a position like any other, the one
|
||||||
where nothing has to move. Placing ripples — occurrences at or after the
|
where nothing has to move. Placing ripples — cels at or after the
|
||||||
position move later by the new exposure's duration — and `:keep` versus
|
position move later by the new cel's duration — and `:keep` versus
|
||||||
`:grow-symbol` still decides what happens at the shot's end. OVERWRITE is not a
|
`:grow-symbol` still decides what happens at the shot's end. OVERWRITE is not a
|
||||||
policy argument yet, deliberately: taking frames away from the occurrence
|
policy argument yet, deliberately: taking frames away from the cel
|
||||||
already there is trimming, and until `trim` exists, placement that would need it
|
already there is trimming, and until `trim` exists, placement that would need it
|
||||||
refuses instead of approximating it. A position inside an existing exposure
|
refuses instead of approximating it. A position inside an existing cel
|
||||||
refuses too, and names `split` — one command does not quietly perform two.
|
refuses too, and names `split` — one command does not quietly perform two.
|
||||||
|
|
||||||
Splitting turned out to cost almost nothing, which is evidence for the
|
Splitting turned out to cost almost nothing, which is evidence for the
|
||||||
|
|
@ -474,7 +474,7 @@ of independence is only made where it is kept.
|
||||||
|
|
||||||
THE SHOT LENGTH IS AUTHORED, which is the decision the range commands forced.
|
THE SHOT LENGTH IS AUTHORED, which is the decision the range commands forced.
|
||||||
`:frames` is the symbol's window — how long the shot IS — and the occupied
|
`:frames` is the symbol's window — how long the shot IS — and the occupied
|
||||||
extent of its lanes is a different fact derived from the occurrences. A command
|
extent of its lanes is a different fact derived from the cels. A command
|
||||||
grows the window only when the caller says `:grow-symbol`, and never shrinks it:
|
grows the window only when the caller says `:grow-symbol`, and never shrinks it:
|
||||||
blanking the end of a shot leaves a shot with empty frames at the end, because
|
blanking the end of a shot leaves a shot with empty frames at the end, because
|
||||||
that is a true statement about what somebody authored, and deriving the window
|
that is a true statement about what somebody authored, and deriving the window
|
||||||
|
|
@ -487,12 +487,12 @@ which carries the ripple and shot-length policies because it needs them.
|
||||||
Move is one write to `:time :at` and REFUSES a destination that would overlap,
|
Move is one write to `:time :at` and REFUSES a destination that would overlap,
|
||||||
because moving a drawing and re-timing the ones around it are different
|
because moving a drawing and re-timing the ones around it are different
|
||||||
intentions — clear the room with `blank` or `trim` first, which is the
|
intentions — clear the room with `blank` or `trim` first, which is the
|
||||||
composition. Blank leaves a gap and does not close it; an exposure wholly inside
|
composition. Blank leaves a gap and does not close it; a cel wholly inside
|
||||||
the range goes, one overlapping an end is trimmed to it, and the one spanning
|
the range goes, one overlapping an end is trimmed to it, and the one spanning
|
||||||
the range is split. Their drawings stay in the library, since a lane does not
|
the range is split. Their drawings stay in the library, since a lane does not
|
||||||
own its content.
|
own its content.
|
||||||
|
|
||||||
All three are the same geometry as `split`: a `:span` is in the occurrence's own
|
All three are the same geometry as `split`: a `:span` is in the cel's own
|
||||||
frames, so moving an edge is one write and `:time` and `:playback` are never
|
frames, so moving an edge is one write and `:time` and `:playback` are never
|
||||||
touched. That is why trimming the front of a playing insert starts it later into
|
touched. That is why trimming the front of a playing insert starts it later into
|
||||||
its animation instead of restarting it — the difference between trimming and
|
its animation instead of restarting it — the difference between trimming and
|
||||||
|
|
@ -512,7 +512,7 @@ plumbing, since `node/problems` already reports every channel's problems.
|
||||||
Both halves of ownership are under test at lane level: a three-frame correction
|
Both halves of ownership are under test at lane level: a three-frame correction
|
||||||
on the girl's lane reaches across the drawing boundary beneath it and leaves
|
on the girl's lane reaches across the drawing boundary beneath it and leaves
|
||||||
every frame outside its support identical, and a correction owned by one
|
every frame outside its support identical, and a correction owned by one
|
||||||
exposure travels with that exposure when a hold before it grows.
|
cel travels with that cel when a hold before it grows.
|
||||||
|
|
||||||
Regeneration keeps them, which is the obligation the layer design exists to
|
Regeneration keeps them, which is the obligation the layer design exists to
|
||||||
meet: `rebased` replaces a base and carries its corrections across, and a
|
meet: `rebased` replaces a base and carries its corrections across, and a
|
||||||
|
|
@ -529,7 +529,7 @@ report.
|
||||||
|
|
||||||
Still unbuilt: overwrite as a placement policy (which is now `blank` then
|
Still unbuilt: overwrite as a placement policy (which is now `blank` then
|
||||||
`place`, composed inside one transaction), slip source, retime, and deleting
|
`place`, composed inside one transaction), slip source, retime, and deleting
|
||||||
reused content. A lane cannot hold AUDIO occurrences — `sequence-problems`
|
reused content. A lane cannot hold AUDIO cels — `lane-problems`
|
||||||
requires visual ones, though this document says a lane may hold either and
|
requires visual ones, though this document says a lane may hold either and
|
||||||
should reject only a mixture.
|
should reject only a mixture.
|
||||||
|
|
||||||
|
|
@ -537,7 +537,7 @@ What is NOT implemented is a command that produces a layer — the doc's Constan
|
||||||
adjustment, Ramp and Return motion — and with it the question of how a view
|
adjustment, Ramp and Return motion — and with it the question of how a view
|
||||||
offers those three over a selected range, and how it offers a conflict for
|
offers those three over a selected range, and how it offers a conflict for
|
||||||
resolution. Overwrite, the range and retiming
|
resolution. Overwrite, the range and retiming
|
||||||
commands (blank, trim, move, slip source, retime) and the exposure-sheet view
|
commands (blank, trim, move, slip source, retime) and the cel-sheet view
|
||||||
are also not implemented; a refusal is the current behavior where the model
|
are also not implemented; a refusal is the current behavior where the model
|
||||||
demands an explicit choice nobody has made yet. The suite stands at 424 tests
|
demands an explicit choice nobody has made yet. The suite stands at 424 tests
|
||||||
and 5,749 assertions, with `frontend/test/browser/sequence.mjs` driving the
|
and 5,749 assertions, with `frontend/test/browser/sequence.mjs` driving the
|
||||||
|
|
@ -552,23 +552,23 @@ expanding the interface:
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| Same drawing exposed twice, then one made unique | Linked edits affect both before copying and only the selected content after |
|
| Same drawing exposed twice, then one made unique | Linked edits affect both before copying and only the selected content after |
|
||||||
| Holds, playing inserts, nonzero in-points, and gaps on one lane | Source behavior is independent of property key count and channel encoding |
|
| Holds, playing inserts, nonzero in-points, and gaps on one lane | Source behavior is independent of property key count and channel encoding |
|
||||||
| Adjacent occurrences, final hold, split, trim, ripple, and overwrite | Exact boundaries, stable IDs, deterministic collision handling |
|
| Adjacent cels, final hold, split, trim, ripple, and overwrite | Exact boundaries, stable IDs, deterministic collision handling |
|
||||||
| Extend a hold under lane keys and occurrence-local corrections | Lane key times remain fixed; later occurrence corrections travel with their owners; source playback origins survive |
|
| Extend a hold under lane keys and cel-local corrections | Lane key times remain fixed; later cel corrections travel with their owners; source playback origins survive |
|
||||||
| Ripple beyond the symbol end | Explicit extent policy; refusal leaves the document unchanged; resizing and retiming undo together |
|
| Ripple beyond the symbol end | Explicit extent policy; refusal leaves the document unchanged; resizing and retiming undo together |
|
||||||
| Girl on twos over a moving background | Drawing cadence does not quantize either lane's continuous properties |
|
| Girl on twos over a moving background | Drawing cadence does not quantize either lane's continuous properties |
|
||||||
| Three-frame correction crossing a drawing boundary | Exact support, same result outside it, one undo step |
|
| Three-frame correction crossing a drawing boundary | Exact support, same result outside it, one undo step |
|
||||||
| Nested retiming, holds, loops, and fractional sampling | Explicit time spaces; ambiguous inverse edits cannot silently choose a target |
|
| Nested retiming, holds, loops, and fractional sampling | Explicit time spaces; ambiguous inverse edits cannot silently choose a target |
|
||||||
| Audio inside changing source occurrences | Only active intervals sound, with correct trim and source timing |
|
| Audio inside changing source cels | Only active intervals sound, with correct trim and source timing |
|
||||||
| Regenerate with corrections and a topology change | Compatible edits survive; incompatible ones produce actionable conflicts |
|
| Regenerate with corrections and a topology change | Compatible edits survive; incompatible ones produce actionable conflicts |
|
||||||
| Reference cycles and deletion of reused content | Inactive references are validated too; no dangling references |
|
| Reference cycles and deletion of reused content | Inactive references are validated too; no dangling references |
|
||||||
| Save/load and command undo/redo | Identity, source maps, corrections, and evaluation round-trip |
|
| Save/load and command undo/redo | Identity, source maps, corrections, and evaluation round-trip |
|
||||||
| Timeline and exposure-sheet invocation of one command | Identical document changes and selection targets |
|
| Timeline and cel-sheet invocation of one command | Identical document changes and selection targets |
|
||||||
| Random forward/backward seeks and export | Reference and optimized evaluation agree, including defaults and absence |
|
| Random forward/backward seeks and export | Reference and optimized evaluation agree, including defaults and absence |
|
||||||
| Concurrent commands on overlapping and disjoint targets | Transactions remain valid; conflicts are explicit and undo preserves others' work |
|
| Concurrent commands on overlapping and disjoint targets | Transactions remain valid; conflicts are explicit and undo preserves others' work |
|
||||||
|
|
||||||
Implementation order: occurrence ownership and playback semantics; shared
|
Implementation order: cel ownership and playback semantics; shared
|
||||||
commands and validation; correction layers and time-addressing contracts; then
|
commands and validation; correction layers and time-addressing contracts; then
|
||||||
breadcrumb, cel strip, and an exposure-sheet projection. Use those two temporal
|
breadcrumb, cel strip, and a cel-sheet projection. Use those two temporal
|
||||||
views plus direct stage editing to prove the model before broadening the UI.
|
views plus direct stage editing to prove the model before broadening the UI.
|
||||||
|
|
||||||
A new presentation should not require duplicate animation state. A genuinely new
|
A new presentation should not require duplicate animation state. A genuinely new
|
||||||
|
|
|
||||||
|
|
@ -22,10 +22,10 @@
|
||||||
(defn compose
|
(defn compose
|
||||||
"The authored layout plus a source clip -> the composed stage document.
|
"The authored layout plus a source clip -> the composed stage document.
|
||||||
|
|
||||||
A PLACEMENT IS KEYED BY ITS :uuid, not by the authored id. The authored id
|
AN INSTANCE IS KEYED BY ITS :uuid, not by the authored id. The authored id
|
||||||
(`:left`, `:voice-right`) is a handle for reading the EDN and for the
|
(`:left`, `:voice-right`) is a handle for reading the EDN and for the
|
||||||
`:linked-to` written there; it does not appear in the document this returns.
|
`:linked-to` written there; it does not appear in the document this returns.
|
||||||
What replaces it is an identity that means one placement and nothing else: seven
|
What replaces it is an identity that means one instance and nothing else: seven
|
||||||
instances of one symbol are seven different things to name — to export on their
|
instances of one symbol are seven different things to name — to export on their
|
||||||
own, to link a voice to, to point at later — and an id like `:left` is a
|
own, to link a voice to, to point at later — and an id like `:left` is a
|
||||||
description of where a thing sits, which is exactly what changes when the stage
|
description of where a thing sits, which is exactly what changes when the stage
|
||||||
|
|
@ -44,7 +44,7 @@
|
||||||
by-id (into {} (map (juxt :id :uuid)) (concat instances audio))
|
by-id (into {} (map (juxt :id :uuid)) (concat instances audio))
|
||||||
uuid-of (fn [what id]
|
uuid-of (fn [what id]
|
||||||
(or (get by-id id)
|
(or (get by-id id)
|
||||||
(throw (ex-info "the stage layout names a placement that is not there"
|
(throw (ex-info "the stage layout names an instance that is not there"
|
||||||
{:in what :id id
|
{:in what :id id
|
||||||
:known (vec (sort-by str (keys by-id)))}))))
|
:known (vec (sort-by str (keys by-id)))}))))
|
||||||
nodes (into
|
nodes (into
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@
|
||||||
`other` to its id here.
|
`other` to its id here.
|
||||||
|
|
||||||
AN ID THAT IS TAKEN IS RENAMED, never merged: two symbols that happen to share
|
AN ID THAT IS TAKEN IS RENAMED, never merged: two symbols that happen to share
|
||||||
an id are two drawings, and an occurrence's `:source :symbol` inside the copy is
|
an id are two drawings, and a cel's `:source :symbol` inside the copy is
|
||||||
rewritten to follow. `wanted` maps a root's id in `other` to the id it should preferably get,
|
rewritten to follow. `wanted` maps a root's id in `other` to the id it should preferably get,
|
||||||
which is how a symbol made from footage is called what the person typed rather
|
which is how a symbol made from footage is called what the person typed rather
|
||||||
than `:main`.
|
than `:main`.
|
||||||
|
|
@ -36,7 +36,7 @@
|
||||||
(some #{%} (vals ids)))]
|
(some #{%} (vals ids)))]
|
||||||
(assoc ids sid (clip/free-id taken? (get wanted sid sid)))))
|
(assoc ids sid (clip/free-id taken? (get wanted sid sid)))))
|
||||||
{} (sort-by str reach))
|
{} (sort-by str reach))
|
||||||
;; Occurrence identity and timing stay put; content references follow
|
;; Cel identity and timing stay put; content references follow
|
||||||
;; the symbol IDs assigned in the destination document.
|
;; the symbol IDs assigned in the destination document.
|
||||||
repoint (fn [n ids]
|
repoint (fn [n ids]
|
||||||
(if (node/source n)
|
(if (node/source n)
|
||||||
|
|
|
||||||
|
|
@ -111,7 +111,7 @@
|
||||||
;; A LAYER HAS NO TIME SPACE OF ITS OWN. Its `:support` and its values' keys are
|
;; A LAYER HAS NO TIME SPACE OF ITS OWN. Its `:support` and its values' keys are
|
||||||
;; in the frames the base channel's keys are in — the node's own. A correction on
|
;; in the frames the base channel's keys are in — the node's own. A correction on
|
||||||
;; a lane is therefore in lane frames and crosses the drawing boundaries under
|
;; a lane is therefore in lane frames and crosses the drawing boundaries under
|
||||||
;; it; a correction on one occurrence is in that occurrence's frames and travels
|
;; it; a correction on one cel is in that cel's frames and travels
|
||||||
;; with it when it moves. Ownership already answered the question, so there is no
|
;; with it when it moves. Ownership already answered the question, so there is no
|
||||||
;; field to get wrong.
|
;; field to get wrong.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -190,7 +190,7 @@
|
||||||
own (symbol/resolver sym store palette
|
own (symbol/resolver sym store palette
|
||||||
(assoc opts :pose-tracks pose-tracks
|
(assoc opts :pose-tracks pose-tracks
|
||||||
:source-fps (:fps clip)))
|
:source-fps (:fps clip)))
|
||||||
;; Each occurrence owns its source resolver and mutable buffers.
|
;; Each cel owns its source resolver and mutable buffers.
|
||||||
children (into {}
|
children (into {}
|
||||||
(for [[id n] nodes
|
(for [[id n] nodes
|
||||||
:when (= :instance (:kind n))
|
:when (= :instance (:kind n))
|
||||||
|
|
@ -282,7 +282,7 @@
|
||||||
middle goes there; without one — a drop on the timeline — the drawing stays
|
middle goes there; without one — a drop on the timeline — the drawing stays
|
||||||
where it was drawn.
|
where it was drawn.
|
||||||
|
|
||||||
THE UUID IS AN ARGUMENT. A placement's identity is the key it has in the node
|
THE UUID IS AN ARGUMENT. An instance's identity is the key it has in the node
|
||||||
map — it is what `:linked-to`, an export target and a saved leaf all name — so
|
map — it is what `:linked-to`, an export target and a saved leaf all name — so
|
||||||
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.
|
||||||
|
|
@ -307,7 +307,7 @@
|
||||||
:name (symbol-name clip sid)
|
:name (symbol-name clip sid)
|
||||||
:kind :instance
|
:kind :instance
|
||||||
:parent nil
|
:parent nil
|
||||||
;; Lexicographic draw order, as `domain/paint` does it: a placement made
|
;; Lexicographic draw order, as `domain/paint` does it: an instance 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 [0 (:frames target)]
|
:span [0 (:frames target)]
|
||||||
|
|
@ -406,7 +406,7 @@
|
||||||
missing (remove (:symbols clip) (node/sources n))]
|
missing (remove (:symbols clip) (node/sources n))]
|
||||||
(str "symbol " (pr-str sid) " instance " (pr-str id)
|
(str "symbol " (pr-str sid) " instance " (pr-str id)
|
||||||
" names missing symbol " (pr-str missing)))
|
" names missing symbol " (pr-str missing)))
|
||||||
;; Pose tracks belong to this occurrence's single source symbol.
|
;; Pose tracks belong to this cel's single source symbol.
|
||||||
(for [[sid sym] (:symbols clip)
|
(for [[sid sym] (:symbols clip)
|
||||||
[id n] (:nodes sym)
|
[id n] (:nodes sym)
|
||||||
:when (= :instance (:kind n))
|
:when (= :instance (:kind n))
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
(ns arthur.domain.sequence
|
(ns arthur.domain.lane
|
||||||
"The commands over a lane of occurrences: make one, put drawings in it, change
|
"The commands over a lane of cels: make one, put drawings in it, change
|
||||||
how long they are exposed, and decide which of them share content.
|
how long they are exposed, and decide which of them share content.
|
||||||
|
|
||||||
WHAT A LANE IS lives in `arthur.domain.symbol`, beside the other rules about a
|
WHAT A LANE IS lives in `arthur.domain.symbol`, beside the other rules about a
|
||||||
node map: a group with `:layout :sequence`, whose children are non-overlapping
|
node map: a group with `:layout :sequence`, whose children are non-overlapping
|
||||||
visual occurrences. This namespace only changes them.
|
visual cels. This namespace only changes them.
|
||||||
|
|
||||||
EVERY COMMAND IS ONE STEP AND ALL OF IT. Each returns `{:clip :selection}` or
|
EVERY COMMAND IS ONE STEP AND ALL OF IT. Each returns `{:clip :selection}` or
|
||||||
`{:refused reason}` — never a half-applied edit, and never a document that
|
`{:refused reason}` — never a half-applied edit, and never a document that
|
||||||
|
|
@ -13,7 +13,7 @@
|
||||||
caller's `:extent`, and decoupling shared content is its own command instead
|
caller's `:extent`, and decoupling shared content is its own command instead
|
||||||
of something an ordinary edit does silently.
|
of something an ordinary edit does silently.
|
||||||
|
|
||||||
IDS FOR OCCURRENCES COME FROM THE CALLER, because an occurrence's identity is
|
IDS FOR CELS COME FROM THE CALLER, because a cel's identity is
|
||||||
a uuid and this namespace is pure. Ids for new CONTENT are derived from the
|
a uuid and this namespace is pure. Ids for new CONTENT are derived from the
|
||||||
drawing being copied — `clip/free-id` is pure too, and `drawing-a-2` says what
|
drawing being copied — `clip/free-id` is pure too, and `drawing-a-2` says what
|
||||||
it came from in a way `symbol-7` does not."
|
it came from in a way `symbol-7` does not."
|
||||||
|
|
@ -40,19 +40,19 @@
|
||||||
|
|
||||||
THE SHOT LENGTH IS AUTHORED. `:frames` is the symbol's window — how long the
|
THE SHOT LENGTH IS AUTHORED. `:frames` is the symbol's window — how long the
|
||||||
shot IS — and the occupied extent of its lanes is a different fact derived
|
shot IS — and the occupied extent of its lanes is a different fact derived
|
||||||
from the occurrences. A command may GROW the window when the caller says
|
from the cels. A command may GROW the window when the caller says
|
||||||
`:grow-symbol`, and never shrinks it: emptying the end of a shot leaves a shot
|
`:grow-symbol`, and never shrinks it: emptying the end of a shot leaves a shot
|
||||||
with empty frames at the end, which is a true statement about what somebody
|
with empty frames at the end, which is a true statement about what somebody
|
||||||
authored. Deriving the window from the extent instead would make deleting the
|
authored. Deriving the window from the extent instead would make deleting the
|
||||||
last drawing silently shorten the film.
|
last drawing silently shorten the film.
|
||||||
|
|
||||||
So there are two numbers and this function keeps them apart: `needed` is where
|
So there are two numbers and this function keeps them apart: `needed` is where
|
||||||
the occurrences reach, `:frames` is what was authored, and the only way the
|
the cels reach, `:frames` is what was authored, and the only way the
|
||||||
second follows the first is a caller asking."
|
second follows the first is a caller asking."
|
||||||
[clip sid nodes selection extent]
|
[clip sid nodes selection extent]
|
||||||
(let [sym (clip/symbol clip sid)
|
(let [sym (clip/symbol clip sid)
|
||||||
reach (for [[id n] nodes :when (node/sequence? n)
|
reach (for [[id n] nodes :when (node/lane? n)
|
||||||
child (symbol/sequence-members nodes id)
|
child (symbol/lane-cels nodes id)
|
||||||
:let [m (lane-map nodes id)
|
:let [m (lane-map nodes id)
|
||||||
end (second (node/placed-span child))]]
|
end (second (node/placed-span child))]]
|
||||||
(when m (+ (:at m) (/ end (:rate m)))))
|
(when m (+ (:at m) (/ end (:rate m)))))
|
||||||
|
|
@ -70,13 +70,13 @@
|
||||||
:selection selection})))
|
:selection selection})))
|
||||||
|
|
||||||
;; ---------------------------------------------------------------------------
|
;; ---------------------------------------------------------------------------
|
||||||
;; the geometry every exposure edit is made of
|
;; the geometry every cel edit is made of
|
||||||
;;
|
;;
|
||||||
;; A `:span` is in the occurrence's OWN frames and its `:time` says where those
|
;; A `:span` is in the cel's OWN frames and its `:time` says where those
|
||||||
;; land in the lane. So moving an edge of an exposure is one write to `:span`,
|
;; land in the lane. So moving an edge of a cel is one write to `:span`,
|
||||||
;; and `:time` and `:playback` are untouched — which is why trimming the front
|
;; and `:time` and `:playback` are untouched — which is why trimming the front
|
||||||
;; of a playing insert starts it later in its source instead of resetting it,
|
;; of a playing insert starts it later in its source instead of resetting it,
|
||||||
;; and why the two halves of a split go on meaning what the one exposure meant.
|
;; and why the two halves of a split go on meaning what the one cel meant.
|
||||||
;; Trim, split and blank are all this one operation, applied differently.
|
;; Trim, split and blank are all this one operation, applied differently.
|
||||||
|
|
||||||
(defn- local
|
(defn- local
|
||||||
|
|
@ -91,8 +91,8 @@
|
||||||
(assoc-in n [:span (case which :in 0 :out 1)] (local n f)))
|
(assoc-in n [:span (case which :in 0 :out 1)] (local n f)))
|
||||||
|
|
||||||
(defn extend-hold
|
(defn extend-hold
|
||||||
"Change one held occurrence's duration by `delta` lane frames and ripple its
|
"Change one held cel's duration by `delta` lane frames and ripple its
|
||||||
later siblings. Lane channels, occurrence channels and source clocks stay put.
|
later siblings. Lane channels, cel channels and source clocks stay put.
|
||||||
Returns {:clip :selection} or {:refused :required-frames?}; never partially edits."
|
Returns {:clip :selection} or {:refused :required-frames?}; never partially edits."
|
||||||
[clip sid id delta {:keys [extent] :or {extent :keep}}]
|
[clip sid id delta {:keys [extent] :or {extent :keep}}]
|
||||||
(let [nodes (get-in clip [:symbols sid :nodes])
|
(let [nodes (get-in clip [:symbols sid :nodes])
|
||||||
|
|
@ -101,21 +101,21 @@
|
||||||
rate (:rate (node/time-of n))
|
rate (:rate (node/time-of n))
|
||||||
span (:span n)
|
span (:span n)
|
||||||
m (when lane (lane-map nodes (:id lane)))
|
m (when lane (lane-map nodes (:id lane)))
|
||||||
;; The LANE's own shape, not the whole symbol's: refusing an exposure
|
;; The LANE's own shape, not the whole symbol's: refusing a cel
|
||||||
;; edit over some unrelated defect elsewhere in the symbol would be
|
;; edit over some unrelated defect elsewhere in the symbol would be
|
||||||
;; this command answering for a part of the document it never touches.
|
;; this command answering for a part of the document it never touches.
|
||||||
broken (first (symbol/sequence-problems nodes))]
|
broken (first (symbol/lane-problems nodes))]
|
||||||
(cond
|
(cond
|
||||||
(not (node/sequence? lane)) {:refused "select an occurrence in a sequence lane"}
|
(not (node/lane? lane)) {:refused "select a cel in a lane"}
|
||||||
broken {:refused broken}
|
broken {:refused broken}
|
||||||
(not (and (integer? delta) (not (zero? delta)))) {:refused "hold change must be a nonzero whole number of lane frames"}
|
(not (and (integer? delta) (not (zero? delta)))) {:refused "hold change must be a nonzero whole number of lane frames"}
|
||||||
(not (zero? (:speed (node/playback-of n)))) {:refused "hold length applies to a held drawing"}
|
(not (zero? (:speed (node/playback-of n)))) {:refused "hold length applies to a held drawing"}
|
||||||
(nil? m) {:refused "exposure timing through a stepped or looping lane is not supported"}
|
(nil? m) {:refused "cel timing through a stepped or looping lane is not supported"}
|
||||||
(<= (+ (second span) (* rate delta)) (first span)) {:refused "a drawing must keep a positive exposure"}
|
(<= (+ (second span) (* rate delta)) (first span)) {:refused "a drawing must keep a positive cel"}
|
||||||
:else
|
:else
|
||||||
(let [[_ boundary] (node/placed-span n)
|
(let [[_ boundary] (node/placed-span n)
|
||||||
later (filter #(>= (first (node/placed-span %)) boundary)
|
later (filter #(>= (first (node/placed-span %)) boundary)
|
||||||
(symbol/sequence-members nodes (:id lane)))
|
(symbol/lane-cels nodes (:id lane)))
|
||||||
nodes (assoc-in nodes [id :span 1] (+ (second span) (* rate delta)))
|
nodes (assoc-in nodes [id :span 1] (+ (second span) (* rate delta)))
|
||||||
nodes (reduce (fn [ns sibling]
|
nodes (reduce (fn [ns sibling]
|
||||||
(update-in ns [(:id sibling) :time :at] (fnil + 0) delta))
|
(update-in ns [(:id sibling) :time :at] (fnil + 0) delta))
|
||||||
|
|
@ -123,7 +123,7 @@
|
||||||
(finish clip sid nodes id extent)))))
|
(finish clip sid nodes id extent)))))
|
||||||
|
|
||||||
(defn split
|
(defn split
|
||||||
"Cut occurrence `id` in two at lane frame `cut`. The left piece keeps its
|
"Cut cel `id` in two at lane frame `cut`. The left piece keeps its
|
||||||
identity; the right gets `new-id`.
|
identity; the right gets `new-id`.
|
||||||
|
|
||||||
NOTHING BUT `:span` DIFFERS between the two pieces. They keep one `:time`, so
|
NOTHING BUT `:span` DIFFERS between the two pieces. They keep one `:time`, so
|
||||||
|
|
@ -133,7 +133,7 @@
|
||||||
in-points that could be wrong. A held drawing holds the same frame on both
|
in-points that could be wrong. A held drawing holds the same frame on both
|
||||||
sides; a playing insert plays on through the cut without a seam. That is what
|
sides; a playing insert plays on through the cut without a seam. That is what
|
||||||
`:span` being in the node's OWN coordinates buys, and it is why splitting
|
`:span` being in the node's OWN coordinates buys, and it is why splitting
|
||||||
needs no shot-length policy: the pieces occupy the frames the one exposure
|
needs no shot-length policy: the pieces occupy the frames the one cel
|
||||||
occupied.
|
occupied.
|
||||||
|
|
||||||
The right piece is the selection, because it is the piece that was made."
|
The right piece is the selection, because it is the piece that was made."
|
||||||
|
|
@ -144,11 +144,11 @@
|
||||||
{:keys [at rate]} (node/time-of n)
|
{:keys [at rate]} (node/time-of n)
|
||||||
[lo hi] (or (node/placed-span n) [nil nil])]
|
[lo hi] (or (node/placed-span n) [nil nil])]
|
||||||
(cond
|
(cond
|
||||||
(not (node/sequence? lane)) {:refused "select an occurrence in a sequence lane"}
|
(not (node/lane? lane)) {:refused "select a cel in a lane"}
|
||||||
(not (integer? cut)) {:refused "a cut is a whole lane frame"}
|
(not (integer? cut)) {:refused "a cut is a whole lane frame"}
|
||||||
(contains? nodes new-id) {:refused "the new occurrence ID is already used"}
|
(contains? nodes new-id) {:refused "the new cel ID is already used"}
|
||||||
(not (and lo (< lo cut hi)))
|
(not (and lo (< lo cut hi)))
|
||||||
{:refused (str "frame " cut " is not inside this exposure")}
|
{:refused (str "frame " cut " is not inside this cel")}
|
||||||
:else
|
:else
|
||||||
(let [nodes (-> nodes
|
(let [nodes (-> nodes
|
||||||
(assoc id (edged n :out cut))
|
(assoc id (edged n :out cut))
|
||||||
|
|
@ -157,10 +157,10 @@
|
||||||
(finish clip sid nodes new-id :keep)))))
|
(finish clip sid nodes new-id :keep)))))
|
||||||
|
|
||||||
(defn trim
|
(defn trim
|
||||||
"Move one edge of occurrence `id` to lane frame `to`, without disturbing a
|
"Move one edge of cel `id` to lane frame `to`, without disturbing a
|
||||||
single other occurrence.
|
single other cel.
|
||||||
|
|
||||||
TRIM NARROWS. Lengthening an exposure is `extend-hold`, which carries a ripple
|
TRIM NARROWS. Lengthening a cel is `extend-hold`, which carries a ripple
|
||||||
policy and a shot-length policy because it needs them; letting trim grow as
|
policy and a shot-length policy because it needs them; letting trim grow as
|
||||||
well would give one gesture two sets of rules and a way to overlap its
|
well would give one gesture two sets of rules and a way to overlap its
|
||||||
neighbour. `edge` is `:in` or `:out`.
|
neighbour. `edge` is `:in` or `:out`.
|
||||||
|
|
@ -174,15 +174,15 @@
|
||||||
lane (get nodes (:parent n))
|
lane (get nodes (:parent n))
|
||||||
[lo hi] (or (node/placed-span n) [nil nil])]
|
[lo hi] (or (node/placed-span n) [nil nil])]
|
||||||
(cond
|
(cond
|
||||||
(not (node/sequence? lane)) {:refused "select an occurrence in a sequence lane"}
|
(not (node/lane? lane)) {:refused "select a cel in a lane"}
|
||||||
(not (#{:in :out} edge)) {:refused "an edge is :in or :out"}
|
(not (#{:in :out} edge)) {:refused "an edge is :in or :out"}
|
||||||
(not (integer? to)) {:refused "an edge goes to a whole lane frame"}
|
(not (integer? to)) {:refused "an edge goes to a whole lane frame"}
|
||||||
(not (and lo (< lo to hi)))
|
(not (and lo (< lo to hi)))
|
||||||
{:refused (str "frame " to " is not inside this exposure; trim narrows it")}
|
{:refused (str "frame " to " is not inside this cel; trim narrows it")}
|
||||||
:else (finish clip sid (assoc nodes id (edged n edge to)) id :keep))))
|
:else (finish clip sid (assoc nodes id (edged n edge to)) id :keep))))
|
||||||
|
|
||||||
(defn move
|
(defn move
|
||||||
"Put occurrence `id` at lane frame `to`, leaving every other occurrence and
|
"Put cel `id` at lane frame `to`, leaving every other cel and
|
||||||
its own length, source and corrections alone.
|
its own length, source and corrections alone.
|
||||||
|
|
||||||
One write to `:time :at`. A destination that would overlap a neighbour is
|
One write to `:time :at`. A destination that would overlap a neighbour is
|
||||||
|
|
@ -196,40 +196,40 @@
|
||||||
lane (get nodes (:parent n))
|
lane (get nodes (:parent n))
|
||||||
{:keys [at]} (node/time-of n)]
|
{:keys [at]} (node/time-of n)]
|
||||||
(cond
|
(cond
|
||||||
(not (node/sequence? lane)) {:refused "select an occurrence in a sequence lane"}
|
(not (node/lane? lane)) {:refused "select a cel in a lane"}
|
||||||
(not (integer? to)) {:refused "an exposure moves to a whole lane frame"}
|
(not (integer? to)) {:refused "a cel moves to a whole lane frame"}
|
||||||
(nil? (node/placed-span n)) {:refused "an occurrence needs a span to move"}
|
(nil? (node/placed-span n)) {:refused "a cel needs a span to move"}
|
||||||
:else
|
:else
|
||||||
(let [moved (update-in n [:time :at] (fnil + 0) (- to (first (node/placed-span n))))]
|
(let [moved (update-in n [:time :at] (fnil + 0) (- to (first (node/placed-span n))))]
|
||||||
(if (not= to (first (node/placed-span moved)))
|
(if (not= to (first (node/placed-span moved)))
|
||||||
{:refused "exposure timing through a stepped or looping lane is not supported"}
|
{:refused "cel timing through a stepped or looping lane is not supported"}
|
||||||
(finish clip sid (assoc nodes id moved) id :keep))))))
|
(finish clip sid (assoc nodes id moved) id :keep))))))
|
||||||
|
|
||||||
(defn blank
|
(defn blank
|
||||||
"Clear lane frames `[a b)` of lane `lane-id`, leaving a GAP.
|
"Clear lane frames `[a b)` of lane `lane-id`, leaving a GAP.
|
||||||
|
|
||||||
A gap is not a drawing. Nothing is invented to cover those frames and nothing
|
A gap is not a drawing. Nothing is invented to cover those frames and nothing
|
||||||
closes the hole — the occurrences after it stay where they are, because
|
closes the hole — the cels after it stay where they are, because
|
||||||
emptying frames and re-timing a performance are different intentions.
|
emptying frames and re-timing a performance are different intentions.
|
||||||
|
|
||||||
What it does to each exposure it meets is the edge geometry above: one wholly
|
What it does to each cel it meets is the edge geometry above: one wholly
|
||||||
inside is removed, one overlapping an end is trimmed to it, and the one that
|
inside is removed, one overlapping an end is trimmed to it, and the one that
|
||||||
spans the whole range is split, which is the only case that needs `id`. Their
|
spans the whole range is split, which is the only case that needs `id`. Their
|
||||||
drawings stay in the library — a lane does not own its content, and a drawing
|
drawings stay in the library — a lane does not own its content, and a drawing
|
||||||
whose last exposure is gone is still a drawing somebody made."
|
whose last cel is gone is still a drawing somebody made."
|
||||||
[clip sid lane-id [a b] {:keys [id]}]
|
[clip sid lane-id [a b] {:keys [id]}]
|
||||||
(let [nodes (get-in clip [:symbols sid :nodes])
|
(let [nodes (get-in clip [:symbols sid :nodes])
|
||||||
lane (get nodes lane-id)
|
lane (get nodes lane-id)
|
||||||
members (when (node/sequence? lane) (symbol/sequence-members nodes lane-id))
|
members (when (node/lane? lane) (symbol/lane-cels nodes lane-id))
|
||||||
spanning (when members
|
spanning (when members
|
||||||
(first (filter #(let [[lo hi] (node/placed-span %)] (and (< lo a) (> hi b)))
|
(first (filter #(let [[lo hi] (node/placed-span %)] (and (< lo a) (> hi b)))
|
||||||
members)))]
|
members)))]
|
||||||
(cond
|
(cond
|
||||||
(not (node/sequence? lane)) {:refused "select a sequence lane"}
|
(not (node/lane? lane)) {:refused "select a lane"}
|
||||||
(not (and (integer? a) (integer? b) (< a b)))
|
(not (and (integer? a) (integer? b) (< a b)))
|
||||||
{:refused "a range to blank is whole lane frames, and not empty"}
|
{:refused "a range to blank is whole lane frames, and not empty"}
|
||||||
(and spanning (or (nil? id) (contains? nodes id)))
|
(and spanning (or (nil? id) (contains? nodes id)))
|
||||||
{:refused "blanking inside one exposure splits it, which needs a free ID for the remainder"}
|
{:refused "blanking inside one cel splits it, which needs a free ID for the remainder"}
|
||||||
:else
|
:else
|
||||||
(let [nodes (reduce
|
(let [nodes (reduce
|
||||||
(fn [ns n]
|
(fn [ns n]
|
||||||
|
|
@ -258,10 +258,10 @@
|
||||||
;; putting drawings in a lane
|
;; putting drawings in a lane
|
||||||
|
|
||||||
(defn- held
|
(defn- held
|
||||||
"A one-frame held occurrence of `drawing-id`, starting at lane frame `at`.
|
"A one-frame held cel of `drawing-id`, starting at lane frame `at`.
|
||||||
|
|
||||||
Held rather than playing, and one frame rather than the length of what it
|
Held rather than playing, and one frame rather than the length of what it
|
||||||
places: an exposure's duration is the lane's business — `extend-hold` is how
|
places: a cel's duration is the lane's business — `extend-hold` is how
|
||||||
it changes — and reading it off the content would make placing a ten-frame
|
it changes — and reading it off the content would make placing a ten-frame
|
||||||
animation and holding its first drawing the same gesture."
|
animation and holding its first drawing the same gesture."
|
||||||
[id lane-id drawing-id at]
|
[id lane-id drawing-id at]
|
||||||
|
|
@ -281,16 +281,16 @@
|
||||||
"Where lane `lane-id`'s occupied frames stop, in its own time."
|
"Where lane `lane-id`'s occupied frames stop, in its own time."
|
||||||
[nodes lane-id]
|
[nodes lane-id]
|
||||||
(apply max 0 (map #(second (node/placed-span %))
|
(apply max 0 (map #(second (node/placed-span %))
|
||||||
(symbol/sequence-members nodes lane-id))))
|
(symbol/lane-cels nodes lane-id))))
|
||||||
|
|
||||||
(defn- place
|
(defn- place
|
||||||
"Put a held occurrence of `drawing-id` into `lane-id` at lane frame `at`, and
|
"Put a held cel of `drawing-id` into `lane-id` at lane frame `at`, and
|
||||||
RIPPLE: everything starting at or after it moves later by its duration.
|
RIPPLE: everything starting at or after it moves later by its duration.
|
||||||
|
|
||||||
There is one placement function and `:end` is a position like any other, so
|
There is one placement function and `:end` is a position like any other, so
|
||||||
appending is not a different operation from inserting — the end is just where
|
appending is not a different operation from inserting — the end is just where
|
||||||
nothing has to move. Overwriting is the other policy and is NOT this: taking
|
nothing has to move. Overwriting is the other policy and is NOT this: taking
|
||||||
frames away from the occurrence already there is trimming, which is its own
|
frames away from the cel already there is trimming, which is its own
|
||||||
command and not something placing a drawing should do on the quiet.
|
command and not something placing a drawing should do on the quiet.
|
||||||
|
|
||||||
`:frame` in the result is where it landed, in the open symbol's time, for a
|
`:frame` in the result is where it landed, in the open symbol's time, for a
|
||||||
|
|
@ -301,7 +301,7 @@
|
||||||
n (held id lane-id drawing-id at)
|
n (held id lane-id drawing-id at)
|
||||||
[lo hi] (node/placed-span n)
|
[lo hi] (node/placed-span n)
|
||||||
later (filter #(>= (first (node/placed-span %)) lo)
|
later (filter #(>= (first (node/placed-span %)) lo)
|
||||||
(symbol/sequence-members nodes lane-id))
|
(symbol/lane-cels nodes lane-id))
|
||||||
nodes (reduce (fn [ns sibling]
|
nodes (reduce (fn [ns sibling]
|
||||||
(update-in ns [(:id sibling) :time :at] (fnil + 0) (- hi lo)))
|
(update-in ns [(:id sibling) :time :at] (fnil + 0) (- hi lo)))
|
||||||
(assoc nodes id n) later)
|
(assoc nodes id n) later)
|
||||||
|
|
@ -311,31 +311,31 @@
|
||||||
(:clip result) (assoc :frame (+ (:at m) (/ at (:rate m)))))))
|
(:clip result) (assoc :frame (+ (:at m) (/ at (:rate m)))))))
|
||||||
|
|
||||||
(defn- placeable
|
(defn- placeable
|
||||||
"Why a held occurrence cannot go into `lane-id` at `at`, or nil."
|
"Why a held cel cannot go into `lane-id` at `at`, or nil."
|
||||||
[clip sid lane-id id at]
|
[clip sid lane-id id at]
|
||||||
(let [nodes (get-in clip [:symbols sid :nodes])
|
(let [nodes (get-in clip [:symbols sid :nodes])
|
||||||
lane (get nodes lane-id)
|
lane (get nodes lane-id)
|
||||||
;; INSIDE an exposure is not a position for another one. Splitting that
|
;; INSIDE a cel is not a position for another one. Splitting that
|
||||||
;; exposure is what makes it two, and doing it here would be one command
|
;; cel is what makes it two, and doing it here would be one command
|
||||||
;; quietly performing two: the caller asks for `split` and then places.
|
;; quietly performing two: the caller asks for `split` and then places.
|
||||||
inside (when (number? at)
|
inside (when (number? at)
|
||||||
(some (fn [n] (let [[lo hi] (node/placed-span n)]
|
(some (fn [n] (let [[lo hi] (node/placed-span n)]
|
||||||
(when (< lo at hi) n)))
|
(when (< lo at hi) n)))
|
||||||
(symbol/sequence-members nodes lane-id)))]
|
(symbol/lane-cels nodes lane-id)))]
|
||||||
(cond
|
(cond
|
||||||
(not (node/sequence? lane)) "select a sequence lane"
|
(not (node/lane? lane)) "select a lane"
|
||||||
(contains? nodes id) "the new occurrence ID is already used"
|
(contains? nodes id) "the new cel ID is already used"
|
||||||
(not (or (= :end at) (and (integer? at) (not (neg? at)))))
|
(not (or (= :end at) (and (integer? at) (not (neg? at)))))
|
||||||
"a position is :end or a whole lane frame"
|
"a position is :end or a whole lane frame"
|
||||||
inside (str "frame " at " is inside an exposure; split it first")
|
inside (str "frame " at " is inside a cel; split it first")
|
||||||
(nil? (lane-map nodes lane-id)) "drawing creation through a stepped or looping lane is not supported"
|
(nil? (lane-map nodes lane-id)) "drawing creation through a stepped or looping lane is not supported"
|
||||||
:else (first (symbol/sequence-problems nodes)))))
|
:else (first (symbol/lane-problems nodes)))))
|
||||||
|
|
||||||
(defn append-drawing
|
(defn append-drawing
|
||||||
"Append fresh empty content and a held occurrence of it. IDs come from the
|
"Append fresh empty content and a held cel of it. IDs come from the
|
||||||
caller so a command is deterministic and replayable.
|
caller so a command is deterministic and replayable.
|
||||||
|
|
||||||
Fresh content, not a blank range: a lane with no occurrence over a frame shows
|
Fresh content, not a blank range: a lane with no cel over a frame shows
|
||||||
nothing there already, and a drawing nobody has drawn in is a different thing
|
nothing there already, and a drawing nobody has drawn in is a different thing
|
||||||
from a gap."
|
from a gap."
|
||||||
[clip sid lane-id id drawing-id {:keys [at extent] :or {extent :keep at :end}}]
|
[clip sid lane-id id drawing-id {:keys [at extent] :or {extent :keep at :end}}]
|
||||||
|
|
@ -347,8 +347,8 @@
|
||||||
sid lane-id id drawing-id at extent)))
|
sid lane-id id drawing-id at extent)))
|
||||||
|
|
||||||
(defn reuse-drawing
|
(defn reuse-drawing
|
||||||
"Append a held occurrence of content the document ALREADY has, so the same
|
"Append a held cel of content the document ALREADY has, so the same
|
||||||
drawing is exposed twice and editing it changes both exposures.
|
drawing is exposed twice and editing it changes both cels.
|
||||||
|
|
||||||
This is the command `make-unique` is the undo of, and the reason they are two
|
This is the command `make-unique` is the undo of, and the reason they are two
|
||||||
commands: reuse is a decision to share, and sharing is not something to
|
commands: reuse is a decision to share, and sharing is not something to
|
||||||
|
|
@ -381,17 +381,17 @@
|
||||||
:id id})))
|
:id id})))
|
||||||
|
|
||||||
(defn duplicate-drawing
|
(defn duplicate-drawing
|
||||||
"Append a held occurrence of a COPY of what occurrence `id` places, for when
|
"Append a held cel of a COPY of what cel `id` places, for when
|
||||||
the drawing on screen is the starting point for the next one.
|
the drawing on screen is the starting point for the next one.
|
||||||
|
|
||||||
The copy is of the content only. The new exposure is a plain one-frame hold
|
The copy is of the content only. The new cel is a plain one-frame hold
|
||||||
rather than a copy of `id`'s own transform or corrections: those belong to
|
rather than a copy of `id`'s own transform or corrections: those belong to
|
||||||
that exposure, and carrying them over would make duplicating a drawing quietly
|
that cel, and carrying them over would make duplicating a drawing quietly
|
||||||
duplicate the treatment of one use of it."
|
duplicate the treatment of one use of it."
|
||||||
[clip sid id new-id {:keys [at extent deep?] :or {extent :keep at :end}}]
|
[clip sid id new-id {:keys [at extent deep?] :or {extent :keep at :end}}]
|
||||||
(let [n (get-in clip [:symbols sid :nodes id])
|
(let [n (get-in clip [:symbols sid :nodes id])
|
||||||
from (node/source n)]
|
from (node/source n)]
|
||||||
(if-let [why (or (when-not from "select an occurrence to duplicate")
|
(if-let [why (or (when-not from "select a cel to duplicate")
|
||||||
(when-not (clip/symbol clip from) "the drawing it places is missing")
|
(when-not (clip/symbol clip from) "the drawing it places is missing")
|
||||||
(placeable clip sid (:parent n) new-id at))]
|
(placeable clip sid (:parent n) new-id at))]
|
||||||
{:refused why}
|
{:refused why}
|
||||||
|
|
@ -399,10 +399,10 @@
|
||||||
(place c sid (:parent n) new-id copy at extent)))))
|
(place c sid (:parent n) new-id copy at extent)))))
|
||||||
|
|
||||||
(defn make-unique
|
(defn make-unique
|
||||||
"Point occurrence `id` at a private copy of its content, leaving every other
|
"Point cel `id` at a private copy of its content, leaving every other
|
||||||
occurrence of that drawing sharing the original.
|
cel of that drawing sharing the original.
|
||||||
|
|
||||||
Refused when nothing else uses it: a drawing with one exposure is already
|
Refused when nothing else uses it: a drawing with one cel is already
|
||||||
unique, and answering with a silent copy would leave a second identical symbol
|
unique, and answering with a silent copy would leave a second identical symbol
|
||||||
in the library for no reason a person could see."
|
in the library for no reason a person could see."
|
||||||
[clip sid id {:keys [deep?]}]
|
[clip sid id {:keys [deep?]}]
|
||||||
|
|
@ -412,7 +412,7 @@
|
||||||
[oid on] (:nodes osym)
|
[oid on] (:nodes osym)
|
||||||
:when (and (= from (node/source on)) (not= [sid id] [osid oid]))]
|
:when (and (= from (node/source on)) (not= [sid id] [osid oid]))]
|
||||||
[osid oid])]
|
[osid oid])]
|
||||||
(if-let [why (or (when-not from "select an occurrence to make unique")
|
(if-let [why (or (when-not from "select a cel to make unique")
|
||||||
(when-not (clip/symbol clip from) "the drawing it places is missing")
|
(when-not (clip/symbol clip from) "the drawing it places is missing")
|
||||||
(when (empty? elsewhere) "nothing else uses this drawing"))]
|
(when (empty? elsewhere) "nothing else uses this drawing"))]
|
||||||
{:refused why}
|
{:refused why}
|
||||||
|
|
@ -63,7 +63,7 @@
|
||||||
local (symbol/frame-of r id)
|
local (symbol/frame-of r id)
|
||||||
inst? (= :instance (:kind (get nodes id)))
|
inst? (= :instance (:kind (get nodes id)))
|
||||||
;; WHICH symbol, and which frame of it, are both read off the
|
;; WHICH symbol, and which frame of it, are both read off the
|
||||||
;; occurrence: inside a held cel is its drawing on the frame
|
;; cel: inside a held cel is its drawing on the frame
|
||||||
;; the hold pins, not on `local`, and inside a playing insert
|
;; the hold pins, not on `local`, and inside a playing insert
|
||||||
;; is its animation at its own in-point and speed.
|
;; is its animation at its own in-point and speed.
|
||||||
shown (when (number? local)
|
shown (when (number? local)
|
||||||
|
|
@ -124,7 +124,7 @@
|
||||||
(partition 2 pts)))))))
|
(partition 2 pts)))))))
|
||||||
|
|
||||||
(defn audio-tracks
|
(defn audio-tracks
|
||||||
"Flatten audible source intervals through occurrence and parent clocks.
|
"Flatten audible source intervals through cel and parent clocks.
|
||||||
A held visual source is silent. Every returned track carries a source offset,
|
A held visual source is silent. Every returned track carries a source offset,
|
||||||
an output interval, and automation mapped into the open symbol's time."
|
an output interval, and automation mapped into the open symbol's time."
|
||||||
[clip sid]
|
[clip sid]
|
||||||
|
|
@ -298,7 +298,7 @@
|
||||||
only a move that keeps the PICTURE needs a frame, for the matrix."
|
only a move that keeps the PICTURE needs a frame, for the matrix."
|
||||||
[clip sid path]
|
[clip sid path]
|
||||||
(let [;; Structurally, a row leads into a symbol only where it names one:
|
(let [;; Structurally, a row leads into a symbol only where it names one:
|
||||||
;; an occurrence does, and the lane holding it does not, so the walk
|
;; a cel does, and the lane holding it does not, so the walk
|
||||||
;; stops at a lane rather than picking the drawing showing now — which
|
;; stops at a lane rather than picking the drawing showing now — which
|
||||||
;; would make where a row lives depend on the playhead.
|
;; would make where a row lives depend on the playhead.
|
||||||
only (fn [sid id]
|
only (fn [sid id]
|
||||||
|
|
|
||||||
|
|
@ -136,13 +136,13 @@
|
||||||
;; ---------------------------------------------------------------------------
|
;; ---------------------------------------------------------------------------
|
||||||
;; time maps
|
;; time maps
|
||||||
;;
|
;;
|
||||||
;; Exposure, mouth lead and a symbol instance's timing are ONE mechanism, and
|
;; Cel, mouth lead and a symbol instance's timing are ONE mechanism, and
|
||||||
;; seeing that is what keeps them from being three implementations that disagree
|
;; seeing that is what keeps them from being three implementations that disagree
|
||||||
;; at the edges.
|
;; at the edges.
|
||||||
|
|
||||||
(defn expose
|
(defn expose
|
||||||
"Hold a frame back onto an exposure grid: 1 = on 1s, 2 = on 2s. Frame 5 at
|
"Hold a frame back onto an exposure grid: 1 = on 1s, 2 = on 2s. Frame 5 at
|
||||||
exposure 2 reads the pose from frame 4.
|
cel 2 reads the pose from frame 4.
|
||||||
|
|
||||||
FLOOR, NEVER ROUND. Rounding would let an output frame read a pose from the
|
FLOOR, NEVER ROUND. Rounding would let an output frame read a pose from the
|
||||||
FUTURE, which is a lead — a separate control, applied after this one, for a
|
FUTURE, which is a lead — a separate control, applied after this one, for a
|
||||||
|
|
@ -170,7 +170,7 @@
|
||||||
"A node's own time as the affine map it is: `{:at a :rate r}`, meaning a frame
|
"A node's own time as the affine map it is: `{:at a :rate r}`, meaning a frame
|
||||||
`p` of its parent is frame `r·(p − a)` of its own. THE SAME FOR EVERY NODE. A
|
`p` of its parent is frame `r·(p − a)` of its own. THE SAME FOR EVERY NODE. A
|
||||||
node with no time map is `{:at 0 :rate 1}`, reading its parent's frames as its
|
node with no time map is `{:at 0 :rate 1}`, reading its parent's frames as its
|
||||||
own; a mouth lead's `:offset` is folded into `:at`. Exposure and picture
|
own; a mouth lead's `:offset` is folded into `:at`. Cel and picture
|
||||||
sampling are floors, not part of the map, and are left out: this is the map a
|
sampling are floors, not part of the map, and are left out: this is the map a
|
||||||
move preserves and a timeline row draws with, and `local-frame` is what reads
|
move preserves and a timeline row draws with, and `local-frame` is what reads
|
||||||
a frame, floors and the lead in their load-bearing order."
|
a frame, floors and the lead in their load-bearing order."
|
||||||
|
|
@ -207,13 +207,13 @@
|
||||||
;; what an instance places
|
;; what an instance places
|
||||||
;;
|
;;
|
||||||
(defn source
|
(defn source
|
||||||
"The symbol used by this occurrence. Sequence groups arrange occurrences;
|
"The symbol used by this cel. Sequence groups arrange cels;
|
||||||
a row is a view of that group, not one row per source."
|
a row is a view of that group, not one row per source."
|
||||||
[n]
|
[n]
|
||||||
(when (= :instance (:kind n)) (get-in n [:source :symbol])))
|
(when (= :instance (:kind n)) (get-in n [:source :symbol])))
|
||||||
|
|
||||||
(defn sources
|
(defn sources
|
||||||
"Structural references, including occurrences outside the playhead."
|
"Structural references, including cels outside the playhead."
|
||||||
[n]
|
[n]
|
||||||
(if-let [sid (source n)] #{sid} #{}))
|
(if-let [sid (source n)] #{sid} #{}))
|
||||||
|
|
||||||
|
|
@ -221,7 +221,7 @@
|
||||||
(merge {:in 0 :speed 1 :end :stop} (:playback n)))
|
(merge {:in 0 :speed 1 :end :stop} (:playback n)))
|
||||||
|
|
||||||
(defn source-time
|
(defn source-time
|
||||||
"Invertible occurrence -> source map, or nil for holds and endpoint policies.
|
"Invertible cel -> source map, or nil for holds and endpoint policies.
|
||||||
Forward sampling remains available through `placed-frame` in every case."
|
Forward sampling remains available through `placed-frame` in every case."
|
||||||
[n]
|
[n]
|
||||||
(let [{:keys [in speed end]} (playback-of n)]
|
(let [{:keys [in speed end]} (playback-of n)]
|
||||||
|
|
@ -229,9 +229,9 @@
|
||||||
{:at (- (/ in speed)) :rate speed})))
|
{:at (- (/ in speed)) :rate speed})))
|
||||||
|
|
||||||
(defn placed-frame
|
(defn placed-frame
|
||||||
"Sample source time without changing the occurrence's property clock:
|
"Sample source time without changing the cel's property clock:
|
||||||
`{:symbol :frame}`, the symbol shown and which of its frames. `length` is that
|
`{:symbol :frame}`, the symbol shown and which of its frames. `length` is that
|
||||||
symbol's frame count. Nil means no source contribution — this occurrence
|
symbol's frame count. Nil means no source contribution — this cel
|
||||||
places nothing, or its playback has run past what there is to show."
|
places nothing, or its playback has run past what there is to show."
|
||||||
[n f length]
|
[n f length]
|
||||||
(when-let [sid (source n)]
|
(when-let [sid (source n)]
|
||||||
|
|
@ -245,7 +245,14 @@
|
||||||
(when (and (<= 0 frame) (< frame length))
|
(when (and (<= 0 frame) (< frame length))
|
||||||
{:symbol sid :frame frame})))))
|
{:symbol sid :frame frame})))))
|
||||||
|
|
||||||
(defn sequence? [n]
|
(defn lane?
|
||||||
|
"Is this group a LANE — a succession of cels rather than a composition?
|
||||||
|
|
||||||
|
`:layout :sequence` is the field because it names the RULE: children follow
|
||||||
|
one another and may not overlap. A group carrying it is called a lane, which
|
||||||
|
is the one place two words are kept for one thing, and they are kept apart on
|
||||||
|
purpose — the layout says what the rule is, the noun says what the thing is."
|
||||||
|
[n]
|
||||||
(and (= :group (:kind n)) (= :sequence (:layout n))))
|
(and (= :group (:kind n)) (= :sequence (:layout n))))
|
||||||
|
|
||||||
(defn finite-number? [v] (and (number? v) (js/Number.isFinite v)))
|
(defn finite-number? [v] (and (number? v) (js/Number.isFinite v)))
|
||||||
|
|
@ -255,11 +262,11 @@
|
||||||
|
|
||||||
ORDER IS LOAD-BEARING: expose first, then offset. Flooring onto a grid and
|
ORDER IS LOAD-BEARING: expose first, then offset. Flooring onto a grid and
|
||||||
shifting against the clock do not commute — shift first and the floor discards
|
shifting against the clock do not commute — shift first and the floor discards
|
||||||
it on most frames, so the lead slider reads as doing nothing at exposures above
|
it on most frames, so the lead slider reads as doing nothing at cels above
|
||||||
1, which is indistinguishable from the slider being unwired.
|
1, which is indistinguishable from the slider being unwired.
|
||||||
|
|
||||||
Composed along the parent chain, outermost first, by symbol/eval-frame. Two
|
Composed along the parent chain, outermost first, by symbol/eval-frame. Two
|
||||||
rules fall out and they are different rules: exposure INHERITS STRICTLY,
|
rules fall out and they are different rules: cel INHERITS STRICTLY,
|
||||||
because a head cutting on odd frames against a mouth cutting on even ones reads
|
because a head cutting on odd frames against a mouth cutting on even ones reads
|
||||||
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."
|
||||||
|
|
@ -425,7 +432,7 @@
|
||||||
(finite-number? speed) (<= 0 speed)
|
(finite-number? speed) (<= 0 speed)
|
||||||
(#{:stop :hold :loop} end)))))
|
(#{:stop :hold :loop} end)))))
|
||||||
(conj "playback needs a nonnegative finite :in and :speed, and :end :stop, :hold or :loop")
|
(conj "playback needs a nonnegative finite :in and :speed, and :end :stop, :hold or :loop")
|
||||||
(and (:layout n) (not (sequence? n)))
|
(and (:layout n) (not (lane? n)))
|
||||||
(conj ":layout :sequence belongs to a group")
|
(conj ":layout :sequence belongs to a group")
|
||||||
(and (= k :audio) (not (some (:source n) [:footage :sound])))
|
(and (= k :audio) (not (some (:source n) [:footage :sound])))
|
||||||
(conj "an audio node needs a :source :footage or :sound")
|
(conj "an audio node needs a :source :footage or :sound")
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
(:require [arthur.domain.node :as node]))
|
(:require [arthur.domain.node :as node]))
|
||||||
|
|
||||||
(defn prepare
|
(defn prepare
|
||||||
"Sort exposure tracks once when building a resolver."
|
"Sort pose tracks once when building a resolver."
|
||||||
[tracks]
|
[tracks]
|
||||||
(into {}
|
(into {}
|
||||||
(map (fn [[group entries]]
|
(map (fn [[group entries]]
|
||||||
|
|
@ -37,8 +37,8 @@
|
||||||
untouched."
|
untouched."
|
||||||
[clip sid instance group at source]
|
[clip sid instance group at source]
|
||||||
(let [inst (get-in clip [:symbols sid :nodes instance])
|
(let [inst (get-in clip [:symbols sid :nodes instance])
|
||||||
;; A cut is checked against the ONE symbol this occurrence places. Which
|
;; A cut is checked against the ONE symbol this cel places. Which
|
||||||
;; drawing a lane shows is a question about the lane's other occurrences,
|
;; drawing a lane shows is a question about the lane's other cels,
|
||||||
;; and each of them owns its own tracks — so there is nothing to union.
|
;; and each of them owns its own tracks — so there is nothing to union.
|
||||||
placed (get-in clip [:symbols (node/source inst)])
|
placed (get-in clip [:symbols (node/source inst)])
|
||||||
length (or (:frames placed) 0)
|
length (or (:frames placed) 0)
|
||||||
|
|
@ -70,7 +70,7 @@
|
||||||
clip)))
|
clip)))
|
||||||
|
|
||||||
(defn problems
|
(defn problems
|
||||||
"Errors in one symbol instance's exposure tracks."
|
"Errors in one symbol instance's pose tracks."
|
||||||
[tracks source-frames groups]
|
[tracks source-frames groups]
|
||||||
(cond
|
(cond
|
||||||
(nil? tracks) []
|
(nil? tracks) []
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@
|
||||||
[arthur.domain.wire :as wire]))
|
[arthur.domain.wire :as wire]))
|
||||||
|
|
||||||
(def schema-version
|
(def schema-version
|
||||||
"3 stores occurrence source references and explicit playback clocks. Older
|
"3 stores cel source references and explicit playback clocks. Older
|
||||||
source-channel documents are unsupported; there is no compatibility conversion."
|
source-channel documents are unsupported; there is no compatibility conversion."
|
||||||
3)
|
3)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -90,8 +90,8 @@
|
||||||
[nodes id]
|
[nodes id]
|
||||||
(dec (count (lineage nodes id))))
|
(dec (count (lineage nodes id))))
|
||||||
|
|
||||||
(defn sequence-members
|
(defn lane-cels
|
||||||
"The occurrences of lane `lane`, in the order they are exposed.
|
"The cels of lane `lane`, in the order they are exposed.
|
||||||
|
|
||||||
Sorted by where they START, not by `:z`: a lane's blocks follow one another in
|
Sorted by where they START, not by `:z`: a lane's blocks follow one another in
|
||||||
time, and two of them cannot be in the same place for `:z` to decide between.
|
time, and two of them cannot be in the same place for `:z` to decide between.
|
||||||
|
|
@ -102,13 +102,13 @@
|
||||||
(sort-by (juxt #(or (first (node/placed-span %)) 0) #(str (:id %))))
|
(sort-by (juxt #(or (first (node/placed-span %)) 0) #(str (:id %))))
|
||||||
vec))
|
vec))
|
||||||
|
|
||||||
(defn sequence-problems
|
(defn lane-problems
|
||||||
"What makes a lane not a lane. A SEQUENCE is the one composition rule the node
|
"What makes a lane not a lane. A SEQUENCE is the one composition rule the node
|
||||||
map carries — ordinary groups compose freely — so it is checked here, beside
|
map carries — ordinary groups compose freely — so it is checked here, beside
|
||||||
the parent and stencil references, rather than wherever a command happens to
|
the parent and stencil references, rather than wherever a command happens to
|
||||||
build one.
|
build one.
|
||||||
|
|
||||||
Occurrences must be visual, finite and non-overlapping. An accidental overlap
|
Cels must be visual, finite and non-overlapping. An accidental overlap
|
||||||
is refused rather than resolved by draw order: two drawings exposed on one
|
is refused rather than resolved by draw order: two drawings exposed on one
|
||||||
frame of one lane is a document nobody meant to write, and picking a winner
|
frame of one lane is a document nobody meant to write, and picking a winner
|
||||||
would hide it. Empty lanes are valid — a lane is made before it is filled."
|
would hide it. Empty lanes are valid — a lane is made before it is filled."
|
||||||
|
|
@ -116,7 +116,7 @@
|
||||||
(vec
|
(vec
|
||||||
(mapcat
|
(mapcat
|
||||||
(fn [[id lane]]
|
(fn [[id lane]]
|
||||||
(when (node/sequence? lane)
|
(when (node/lane? lane)
|
||||||
(let [children (filter #(= id (:parent %)) (vals nodes))
|
(let [children (filter #(= id (:parent %)) (vals nodes))
|
||||||
valid? (fn [n]
|
valid? (fn [n]
|
||||||
(and (= :instance (:kind n))
|
(and (= :instance (:kind n))
|
||||||
|
|
@ -126,9 +126,9 @@
|
||||||
intervals (sort-by first (map node/placed-span (filter valid? children)))]
|
intervals (sort-by first (map node/placed-span (filter valid? children)))]
|
||||||
(concat
|
(concat
|
||||||
(for [n children :when (not (valid? n))]
|
(for [n children :when (not (valid? n))]
|
||||||
(str "sequence " id " needs finite visual occurrences: " (:id n)))
|
(str "sequence " id " needs finite visual cels: " (:id n)))
|
||||||
(when (some (fn [[[_ b] [c _]]] (> b c)) (partition 2 1 intervals))
|
(when (some (fn [[[_ b] [c _]]] (> b c)) (partition 2 1 intervals))
|
||||||
[(str "sequence " id " has overlapping occurrences")])))))
|
[(str "lane " id " has overlapping cels")])))))
|
||||||
nodes)))
|
nodes)))
|
||||||
|
|
||||||
(defn order
|
(defn order
|
||||||
|
|
@ -613,7 +613,7 @@
|
||||||
(if-not (map? nodes)
|
(if-not (map? nodes)
|
||||||
[":nodes must be a map of id -> node"]
|
[":nodes must be a map of id -> node"]
|
||||||
(-> []
|
(-> []
|
||||||
(into (sequence-problems nodes))
|
(into (lane-problems nodes))
|
||||||
(into (for [[id n] nodes
|
(into (for [[id n] nodes
|
||||||
:when (not= id (:id n))]
|
:when (not= id (:id n))]
|
||||||
(str "node under key " (pr-str id) " has :id " (pr-str (:id n)))))
|
(str "node under key " (pr-str id) " has :id " (pr-str (:id n)))))
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
[arthur.domain.gesture :as gesture]
|
[arthur.domain.gesture :as gesture]
|
||||||
[arthur.domain.nest :as nest]
|
[arthur.domain.nest :as nest]
|
||||||
[arthur.domain.node :as node]
|
[arthur.domain.node :as node]
|
||||||
[arthur.domain.sequence :as sequence]
|
[arthur.domain.lane :as lane]
|
||||||
[arthur.events.edit :as edit]
|
[arthur.events.edit :as edit]
|
||||||
[arthur.events.paint :as paint]
|
[arthur.events.paint :as paint]
|
||||||
[arthur.events.playback :as playback]
|
[arthur.events.playback :as playback]
|
||||||
|
|
@ -27,7 +27,7 @@
|
||||||
[:clip :symbols sid :nodes id :kind]))]
|
[:clip :symbols sid :nodes id :kind]))]
|
||||||
(cond-> (-> db
|
(cond-> (-> db
|
||||||
(assoc-in [:ui :selection] selection)
|
(assoc-in [:ui :selection] selection)
|
||||||
(update :ui dissoc :points :sequence-retry))
|
(update :ui dissoc :points :lane-retry))
|
||||||
(and (= :node kind) path (not sound?))
|
(and (= :node kind) path (not sound?))
|
||||||
(update-in [:ui :expanded] (fnil into #{}) (rest (reductions conj [] (pop path))))))))
|
(update-in [:ui :expanded] (fnil into #{}) (rest (reductions conj [] (pop path))))))))
|
||||||
|
|
||||||
|
|
@ -35,33 +35,33 @@
|
||||||
::set-tone
|
::set-tone
|
||||||
(fn [db [_ tone]] (assoc-in db [:ui :tone] tone)))
|
(fn [db [_ tone]] (assoc-in db [:ui :tone] tone)))
|
||||||
|
|
||||||
(defn apply-sequence-command
|
(defn apply-lane-command
|
||||||
"Commit a successful domain command as one history step. A refused command
|
"Commit a successful domain command as one history step. A refused command
|
||||||
leaves the document and history untouched; an overflow offers an explicit retry."
|
leaves the document and history untouched; an overflow offers an explicit retry."
|
||||||
[db sid result retry]
|
[db sid result retry]
|
||||||
(if-let [why (:refused result)]
|
(if-let [why (:refused result)]
|
||||||
(-> db
|
(-> db
|
||||||
(assoc-in [:project :status] why)
|
(assoc-in [:project :status] why)
|
||||||
(assoc-in [:ui :sequence-retry]
|
(assoc-in [:ui :lane-retry]
|
||||||
(when (:required-frames result) retry)))
|
(when (:required-frames result) retry)))
|
||||||
(let [[_ selected-sid _ path] (get-in db [:ui :selection])
|
(let [[_ selected-sid _ path] (get-in db [:ui :selection])
|
||||||
prefix (if (and (= sid selected-sid) (seq path)) (pop path) [])]
|
prefix (if (and (= sid selected-sid) (seq path)) (pop path) [])]
|
||||||
(-> db
|
(-> db
|
||||||
(edit/transaction (constantly (:clip result)))
|
(edit/transaction (constantly (:clip result)))
|
||||||
(assoc-in [:ui :selection] [:node sid (:selection result) (conj prefix (:selection result))])
|
(assoc-in [:ui :selection] [:node sid (:selection result) (conj prefix (:selection result))])
|
||||||
(update :ui dissoc :sequence-retry)))))
|
(update :ui dissoc :lane-retry)))))
|
||||||
|
|
||||||
(rf/reg-event-db
|
(rf/reg-event-db
|
||||||
::new-lane
|
::new-lane
|
||||||
(fn [db _]
|
(fn [db _]
|
||||||
(let [clip (:clip (store/entry (:clip/current db)))
|
(let [clip (:clip (store/entry (:clip/current db)))
|
||||||
sid (get-in db [:ui :open])]
|
sid (get-in db [:ui :open])]
|
||||||
(apply-sequence-command db sid (sequence/add-lane clip sid (random-uuid)) nil))))
|
(apply-lane-command db sid (lane/add-lane clip sid (random-uuid)) nil))))
|
||||||
|
|
||||||
(defn- committed
|
(defn- committed
|
||||||
"One appending command, as effects: commit it, and look at what it made.
|
"One appending command, as effects: commit it, and look at what it made.
|
||||||
|
|
||||||
Seeking is the whole reason these are `-fx` events. An appended exposure lands
|
Seeking is the whole reason these are `-fx` events. An appended cel lands
|
||||||
past the end of the lane, off the playhead, and a drawing you cannot see is not
|
past the end of the lane, off the playhead, and a drawing you cannot see is not
|
||||||
one you can draw in. An inserted one is already under the playhead and the
|
one you can draw in. An inserted one is already under the playhead and the
|
||||||
seek is a no-op, which is the same rule and not a second one."
|
seek is a no-op, which is the same rule and not a second one."
|
||||||
|
|
@ -71,23 +71,23 @@
|
||||||
{:keys [at rate]} (:time (nest/inside clip st (get-in db [:ui :open])
|
{:keys [at rate]} (:time (nest/inside clip st (get-in db [:ui :open])
|
||||||
(if (seq path) (pop path) [])
|
(if (seq path) (pop path) [])
|
||||||
(get-in db [:playback :frame])))]
|
(get-in db [:playback :frame])))]
|
||||||
(cond-> {:db (apply-sequence-command db sid result retry)}
|
(cond-> {:db (apply-lane-command db sid result retry)}
|
||||||
(and (:clip result) (:frame result) rate)
|
(and (:clip result) (:frame result) rate)
|
||||||
(assoc :dispatch [::playback/seek (+ at (/ (:frame result) rate))]))))
|
(assoc :dispatch [::playback/seek (+ at (/ (:frame result) rate))]))))
|
||||||
|
|
||||||
(defn- selected-lane
|
(defn- selected-lane
|
||||||
"The lane a command should act in: the selected lane itself, or the one
|
"The lane a command should act in: the selected lane itself, or the one
|
||||||
holding the selected occurrence."
|
holding the selected cel."
|
||||||
[clip sid id]
|
[clip sid id]
|
||||||
(let [n (get-in clip [:symbols sid :nodes id])]
|
(let [n (get-in clip [:symbols sid :nodes id])]
|
||||||
(if (node/sequence? n) id (:parent n))))
|
(if (node/lane? n) id (:parent n))))
|
||||||
|
|
||||||
(rf/reg-event-fx
|
(rf/reg-event-fx
|
||||||
::append-drawing
|
::append-drawing
|
||||||
(fn [{:keys [db]} [_ extent]]
|
(fn [{:keys [db]} [_ extent]]
|
||||||
(let [clip (:clip (store/entry (:clip/current db)))
|
(let [clip (:clip (store/entry (:clip/current db)))
|
||||||
[_ sid id] (get-in db [:ui :selection])
|
[_ sid id] (get-in db [:ui :selection])
|
||||||
result (sequence/append-drawing clip sid (selected-lane clip sid id)
|
result (lane/append-drawing clip sid (selected-lane clip sid id)
|
||||||
(random-uuid) (clip/fresh-id clip)
|
(random-uuid) (clip/fresh-id clip)
|
||||||
{:extent (or extent :keep)})]
|
{:extent (or extent :keep)})]
|
||||||
(committed db sid result [::append-drawing :grow-symbol]))))
|
(committed db sid result [::append-drawing :grow-symbol]))))
|
||||||
|
|
@ -97,7 +97,7 @@
|
||||||
(fn [{:keys [db]} [_ extent]]
|
(fn [{:keys [db]} [_ extent]]
|
||||||
(let [clip (:clip (store/entry (:clip/current db)))
|
(let [clip (:clip (store/entry (:clip/current db)))
|
||||||
[_ sid id] (get-in db [:ui :selection])
|
[_ sid id] (get-in db [:ui :selection])
|
||||||
result (sequence/reuse-drawing clip sid (selected-lane clip sid id) (random-uuid)
|
result (lane/reuse-drawing clip sid (selected-lane clip sid id) (random-uuid)
|
||||||
(node/source (get-in clip [:symbols sid :nodes id]))
|
(node/source (get-in clip [:symbols sid :nodes id]))
|
||||||
{:extent (or extent :keep)})]
|
{:extent (or extent :keep)})]
|
||||||
(committed db sid result [::reuse-drawing :grow-symbol]))))
|
(committed db sid result [::reuse-drawing :grow-symbol]))))
|
||||||
|
|
@ -107,7 +107,7 @@
|
||||||
(fn [{:keys [db]} [_ extent deep?]]
|
(fn [{:keys [db]} [_ extent deep?]]
|
||||||
(let [clip (:clip (store/entry (:clip/current db)))
|
(let [clip (:clip (store/entry (:clip/current db)))
|
||||||
[_ sid id] (get-in db [:ui :selection])
|
[_ sid id] (get-in db [:ui :selection])
|
||||||
result (sequence/duplicate-drawing clip sid id (random-uuid)
|
result (lane/duplicate-drawing clip sid id (random-uuid)
|
||||||
{:extent (or extent :keep) :deep? deep?})]
|
{:extent (or extent :keep) :deep? deep?})]
|
||||||
(committed db sid result [::duplicate-drawing :grow-symbol deep?]))))
|
(committed db sid result [::duplicate-drawing :grow-symbol deep?]))))
|
||||||
|
|
||||||
|
|
@ -120,67 +120,67 @@
|
||||||
(let [clip (:clip (store/entry (:clip/current db)))
|
(let [clip (:clip (store/entry (:clip/current db)))
|
||||||
[_ sid id] (get-in db [:ui :selection])
|
[_ sid id] (get-in db [:ui :selection])
|
||||||
lane (selected-lane clip sid id)
|
lane (selected-lane clip sid id)
|
||||||
at (sequence/lane-frame clip sid lane (get-in db [:playback :frame]))
|
at (lane/lane-frame clip sid lane (get-in db [:playback :frame]))
|
||||||
result (if at
|
result (if at
|
||||||
(sequence/append-drawing clip sid lane (random-uuid) (clip/fresh-id clip)
|
(lane/append-drawing clip sid lane (random-uuid) (clip/fresh-id clip)
|
||||||
{:at at :extent (or extent :keep)})
|
{:at at :extent (or extent :keep)})
|
||||||
{:refused "this lane's frames are not the open symbol's"})]
|
{:refused "this lane's frames are not the open symbol's"})]
|
||||||
(committed db sid result [::insert-drawing :grow-symbol]))))
|
(committed db sid result [::insert-drawing :grow-symbol]))))
|
||||||
|
|
||||||
(rf/reg-event-db
|
(rf/reg-event-db
|
||||||
::split-exposure
|
::split-cel
|
||||||
(fn [db _]
|
(fn [db _]
|
||||||
(let [clip (:clip (store/entry (:clip/current db)))
|
(let [clip (:clip (store/entry (:clip/current db)))
|
||||||
[_ sid id] (get-in db [:ui :selection])
|
[_ sid id] (get-in db [:ui :selection])
|
||||||
cut (sequence/lane-frame clip sid (:parent (get-in clip [:symbols sid :nodes id]))
|
cut (lane/lane-frame clip sid (:parent (get-in clip [:symbols sid :nodes id]))
|
||||||
(get-in db [:playback :frame]))]
|
(get-in db [:playback :frame]))]
|
||||||
(apply-sequence-command
|
(apply-lane-command
|
||||||
db sid (if cut
|
db sid (if cut
|
||||||
(sequence/split clip sid id cut (random-uuid))
|
(lane/split clip sid id cut (random-uuid))
|
||||||
{:refused "this lane's frames are not the open symbol's"})
|
{:refused "this lane's frames are not the open symbol's"})
|
||||||
nil))))
|
nil))))
|
||||||
|
|
||||||
(defn- at-playhead
|
(defn- at-playhead
|
||||||
"The selected occurrence, its lane, and the playhead as a frame of that lane's
|
"The selected cel, its lane, and the playhead as a frame of that lane's
|
||||||
own time — or a refusal in place of the frame where there is no single one."
|
own time — or a refusal in place of the frame where there is no single one."
|
||||||
[db]
|
[db]
|
||||||
(let [clip (:clip (store/entry (:clip/current db)))
|
(let [clip (:clip (store/entry (:clip/current db)))
|
||||||
[_ sid id] (get-in db [:ui :selection])
|
[_ sid id] (get-in db [:ui :selection])
|
||||||
n (get-in clip [:symbols sid :nodes id])]
|
n (get-in clip [:symbols sid :nodes id])]
|
||||||
{:clip clip :sid sid :id id :node n
|
{:clip clip :sid sid :id id :node n
|
||||||
:at (sequence/lane-frame clip sid (:parent n) (get-in db [:playback :frame]))}))
|
:at (lane/lane-frame clip sid (:parent n) (get-in db [:playback :frame]))}))
|
||||||
|
|
||||||
(rf/reg-event-db
|
(rf/reg-event-db
|
||||||
::trim-exposure
|
::trim-cel
|
||||||
(fn [db [_ edge]]
|
(fn [db [_ edge]]
|
||||||
(let [{:keys [clip sid id at]} (at-playhead db)]
|
(let [{:keys [clip sid id at]} (at-playhead db)]
|
||||||
(apply-sequence-command
|
(apply-lane-command
|
||||||
db sid (if at
|
db sid (if at
|
||||||
(sequence/trim clip sid id edge at)
|
(lane/trim clip sid id edge at)
|
||||||
{:refused "this lane's frames are not the open symbol's"})
|
{:refused "this lane's frames are not the open symbol's"})
|
||||||
nil))))
|
nil))))
|
||||||
|
|
||||||
(rf/reg-event-db
|
(rf/reg-event-db
|
||||||
::move-exposure
|
::move-cel
|
||||||
(fn [db _]
|
(fn [db _]
|
||||||
(let [{:keys [clip sid id at]} (at-playhead db)]
|
(let [{:keys [clip sid id at]} (at-playhead db)]
|
||||||
(apply-sequence-command
|
(apply-lane-command
|
||||||
db sid (if at
|
db sid (if at
|
||||||
(sequence/move clip sid id at)
|
(lane/move clip sid id at)
|
||||||
{:refused "this lane's frames are not the open symbol's"})
|
{:refused "this lane's frames are not the open symbol's"})
|
||||||
nil))))
|
nil))))
|
||||||
|
|
||||||
(rf/reg-event-db
|
(rf/reg-event-db
|
||||||
::blank-exposure
|
::blank-cel
|
||||||
;; The selected exposure's own frames, so the range needs no second gesture and
|
;; The selected cel's own frames, so the range needs no second gesture and
|
||||||
;; the case that would split an exposure cannot arise.
|
;; the case that would split a cel cannot arise.
|
||||||
(fn [db _]
|
(fn [db _]
|
||||||
(let [{:keys [clip sid node]} (at-playhead db)
|
(let [{:keys [clip sid node]} (at-playhead db)
|
||||||
span (node/placed-span node)]
|
span (node/placed-span node)]
|
||||||
(apply-sequence-command
|
(apply-lane-command
|
||||||
db sid (if (and span (every? integer? span))
|
db sid (if (and span (every? integer? span))
|
||||||
(sequence/blank clip sid (:parent node) span {})
|
(lane/blank clip sid (:parent node) span {})
|
||||||
{:refused "select an exposure that starts and ends on whole lane frames"})
|
{:refused "select a cel that starts and ends on whole lane frames"})
|
||||||
nil))))
|
nil))))
|
||||||
|
|
||||||
(rf/reg-event-db
|
(rf/reg-event-db
|
||||||
|
|
@ -188,21 +188,21 @@
|
||||||
(fn [db [_ deep?]]
|
(fn [db [_ deep?]]
|
||||||
(let [clip (:clip (store/entry (:clip/current db)))
|
(let [clip (:clip (store/entry (:clip/current db)))
|
||||||
[_ sid id] (get-in db [:ui :selection])]
|
[_ sid id] (get-in db [:ui :selection])]
|
||||||
(apply-sequence-command db sid (sequence/make-unique clip sid id {:deep? deep?}) nil))))
|
(apply-lane-command db sid (lane/make-unique clip sid id {:deep? deep?}) nil))))
|
||||||
|
|
||||||
(rf/reg-event-db
|
(rf/reg-event-db
|
||||||
::extend-hold
|
::extend-hold
|
||||||
(fn [db [_ delta extent]]
|
(fn [db [_ delta extent]]
|
||||||
(let [clip (:clip (store/entry (:clip/current db)))
|
(let [clip (:clip (store/entry (:clip/current db)))
|
||||||
[_ sid id] (get-in db [:ui :selection])
|
[_ sid id] (get-in db [:ui :selection])
|
||||||
result (sequence/extend-hold clip sid id delta {:extent (or extent :keep)})]
|
result (lane/extend-hold clip sid id delta {:extent (or extent :keep)})]
|
||||||
(apply-sequence-command db sid result [::extend-hold delta :grow-symbol]))))
|
(apply-lane-command db sid result [::extend-hold delta :grow-symbol]))))
|
||||||
|
|
||||||
(rf/reg-event-fx
|
(rf/reg-event-fx
|
||||||
::sequence-retry
|
::lane-retry
|
||||||
(fn [{:keys [db]} _]
|
(fn [{:keys [db]} _]
|
||||||
(if-let [event (get-in db [:ui :sequence-retry])]
|
(if-let [event (get-in db [:ui :lane-retry])]
|
||||||
{:db (update db :ui dissoc :sequence-retry) :dispatch event}
|
{:db (update db :ui dissoc :lane-retry) :dispatch event}
|
||||||
{})))
|
{})))
|
||||||
|
|
||||||
(rf/reg-event-db
|
(rf/reg-event-db
|
||||||
|
|
|
||||||
|
|
@ -84,14 +84,14 @@
|
||||||
Four things, and each for its own reason:
|
Four things, and each for its own reason:
|
||||||
|
|
||||||
the node itself;
|
the node itself;
|
||||||
everything ABOVE it, because a placement's transform is relative to its
|
everything ABOVE it, because an instance's transform is relative to its
|
||||||
parent and dropping the chain would move the thing being isolated;
|
parent and dropping the chain would move the thing being isolated;
|
||||||
everything BELOW it, because a group instance is its children;
|
everything BELOW it, because a group instance is its children;
|
||||||
any audio track `:linked-to` it, because the link is the statement that this
|
any audio track `:linked-to` it, because the link is the statement that this
|
||||||
sound belongs to that placement, and a face exported without its voice is
|
sound belongs to that instance, and a face exported without its voice is
|
||||||
not the thing that was asked for.
|
not the thing that was asked for.
|
||||||
|
|
||||||
Siblings go. That is the whole point: what comes out is one placement, where it
|
Siblings go. That is the whole point: what comes out is one instance, where it
|
||||||
sits, in the symbol it sits in."
|
sits, in the symbol it sits in."
|
||||||
[nodes id]
|
[nodes id]
|
||||||
(let [up (loop [i id acc #{}]
|
(let [up (loop [i id acc #{}]
|
||||||
|
|
@ -117,9 +117,9 @@
|
||||||
"The symbol with only `id` and its kin kept. `nil` leaves it alone.
|
"The symbol with only `id` and its kin kept. `nil` leaves it alone.
|
||||||
|
|
||||||
The FRAME SPACE IS UNTOUCHED, which is what makes this different from exporting
|
The FRAME SPACE IS UNTOUCHED, which is what makes this different from exporting
|
||||||
the symbol a placement plays. Rooting at `:sym/face-8625` renders the drawing in
|
the symbol an instance plays. Rooting at `:sym/face-8625` renders the drawing in
|
||||||
its own time, identically for all seven placements. Isolating one placement
|
its own time, identically for all seven instances. Isolating one instance
|
||||||
renders the STAGE — its length, its rate, the placement's span, drift and scale
|
renders the STAGE — its length, its rate, the instance's span, drift and scale
|
||||||
— with the other six removed. The first is the drawing; the second is that face
|
— with the other six removed. The first is the drawing; the second is that face
|
||||||
on the stage, and they are different deliverables."
|
on the stage, and they are different deliverables."
|
||||||
[sym id]
|
[sym id]
|
||||||
|
|
|
||||||
|
|
@ -74,10 +74,11 @@
|
||||||
::exposure
|
::exposure
|
||||||
:<- [::symbol]
|
:<- [::symbol]
|
||||||
(fn [sym _]
|
(fn [sym _]
|
||||||
;; Exposure lives on the symbol's root node and is INHERITED, so reading it
|
;; NOT a cel. This is `:time :expose` — how many frames each step of a
|
||||||
;; there is reading it everywhere. The transport shows it so that `exposure 2`
|
;; subtree lasts, which is what shooting on twos means — and it lives on the
|
||||||
;; is visibly doing something at the transport rather than only inside the
|
;; symbol's root node and is INHERITED, so reading it there is reading it
|
||||||
;; document.
|
;; everywhere. The transport shows it so that `exposure 2` is visibly doing
|
||||||
|
;; something rather than only inside the document.
|
||||||
(or (get-in sym [:nodes :root :time :expose]) 1)))
|
(or (get-in sym [:nodes :root :time :expose]) 1)))
|
||||||
|
|
||||||
(rf/reg-sub
|
(rf/reg-sub
|
||||||
|
|
@ -115,7 +116,7 @@
|
||||||
(defn- placed?
|
(defn- placed?
|
||||||
"Does row `path` from symbol `sid` still name an instance, all the way down?"
|
"Does row `path` from symbol `sid` still name an instance, all the way down?"
|
||||||
[clip sid path]
|
[clip sid path]
|
||||||
;; Only an occurrence names a symbol. A lane is a group, so a row path that
|
;; Only a cel names a symbol. A lane is a group, so a row path that
|
||||||
;; ends at the lane rather than at one of its cels names no placement — which
|
;; ends at the lane rather than at one of its cels names no placement — which
|
||||||
;; is the structural answer, and does not move as the lane cuts.
|
;; is the structural answer, and does not move as the lane cuts.
|
||||||
(reduce (fn [sid id]
|
(reduce (fn [sid id]
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
[re-frame.core :as rf]))
|
[re-frame.core :as rf]))
|
||||||
|
|
||||||
(rf/reg-sub ::selection (fn [db _] (get-in db [:ui :selection])))
|
(rf/reg-sub ::selection (fn [db _] (get-in db [:ui :selection])))
|
||||||
(rf/reg-sub ::sequence-retry (fn [db _] (get-in db [:ui :sequence-retry])))
|
(rf/reg-sub ::lane-retry (fn [db _] (get-in db [:ui :lane-retry])))
|
||||||
(rf/reg-sub ::tone (fn [db _] (get-in db [:ui :tone])))
|
(rf/reg-sub ::tone (fn [db _] (get-in db [:ui :tone])))
|
||||||
(rf/reg-sub ::tool (fn [db _] (get-in db [:ui :tool])))
|
(rf/reg-sub ::tool (fn [db _] (get-in db [:ui :tool])))
|
||||||
(rf/reg-sub ::draft (fn [db _] (get-in db [:ui :draft])))
|
(rf/reg-sub ::draft (fn [db _] (get-in db [:ui :draft])))
|
||||||
|
|
|
||||||
|
|
@ -215,7 +215,7 @@
|
||||||
[facts
|
[facts
|
||||||
"name" (or (:name n) (brief id))
|
"name" (or (:name n) (brief id))
|
||||||
"id" (brief id)
|
"id" (brief id)
|
||||||
;; Which symbol an occurrence places. The one fact that makes an instance
|
;; Which symbol a cel 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 (node/source n)))
|
"of" (when (= :instance (:kind n)) (str (node/source n)))
|
||||||
;; A span is in the node's OWN frames and `at` is where its frame 0 sits
|
;; A span is in the node's OWN frames and `at` is where its frame 0 sits
|
||||||
|
|
@ -239,7 +239,7 @@
|
||||||
;; tracing a face
|
;; tracing a face
|
||||||
;;
|
;;
|
||||||
;; THE FACE'S OWN FACTS ONLY, and both of them are keyed to the face rather than
|
;; THE FACE'S OWN FACTS ONLY, and both of them are keyed to the face rather than
|
||||||
;; to a placement of it: which of its frames its drawings were made over, and
|
;; to an instance of it: which of its frames its drawings were made over, and
|
||||||
;; what its origin does between those frames. See `domain/trace`.
|
;; what its origin does between those frames. See `domain/trace`.
|
||||||
;;
|
;;
|
||||||
;; Whether the footage is SHOWING is deliberately not here. It is a viewing aid
|
;; Whether the footage is SHOWING is deliberately not here. It is a viewing aid
|
||||||
|
|
@ -435,11 +435,11 @@
|
||||||
node @(rf/subscribe [::sub/selected-node])
|
node @(rf/subscribe [::sub/selected-node])
|
||||||
tracked? (seq (owners clip))
|
tracked? (seq (owners clip))
|
||||||
;; The face the tracing section is about: the SELECTED PLACEMENT's symbol,
|
;; The face the tracing section is about: the SELECTED PLACEMENT's symbol,
|
||||||
;; or, when the selection is not a placement or there is none, the OPEN
|
;; or, when the selection is not an instance or there is none, the OPEN
|
||||||
;; symbol — which is the face itself when a face is open to be drawn over.
|
;; symbol — which is the face itself when a face is open to be drawn over.
|
||||||
;; That last case had no section at all before, and it is the one where
|
;; That last case had no section at all before, and it is the one where
|
||||||
;; the keys are actually being set.
|
;; the keys are actually being set.
|
||||||
;; The symbol this occurrence places, or nil for a lane: which face a
|
;; The symbol this cel places, or nil for a lane: which face a
|
||||||
;; lane traces is not a question with one answer, and naming the drawing
|
;; lane traces is not a question with one answer, and naming the drawing
|
||||||
;; showing now would move the section under the playhead.
|
;; showing now would move the section under the playhead.
|
||||||
placed (node/source (peek node))
|
placed (node/source (peek node))
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@
|
||||||
(:require [clojure.string :as str]
|
(:require [clojure.string :as str]
|
||||||
[arthur.domain.node :as node]
|
[arthur.domain.node :as node]
|
||||||
[arthur.domain.nest :as nest]
|
[arthur.domain.nest :as nest]
|
||||||
[arthur.domain.sequence :as sequence]
|
[arthur.domain.lane :as lane]
|
||||||
[arthur.domain.symbol :as symbol]
|
[arthur.domain.symbol :as symbol]
|
||||||
[arthur.domain.trace :as trace]
|
[arthur.domain.trace :as trace]
|
||||||
[arthur.events.playback :as pb]
|
[arthur.events.playback :as pb]
|
||||||
|
|
@ -48,7 +48,7 @@
|
||||||
frame 48 of the symbol it is in, and drawing it at 0 puts every placement's
|
frame 48 of the symbol it is in, and drawing it at 0 puts every placement's
|
||||||
keys in the same place however staggered they are.
|
keys in the same place however staggered they are.
|
||||||
|
|
||||||
Exposure is not inverted, because it is a floor and has no inverse: a key on a
|
Cel is not inverted, because it is a floor and has no inverse: a key on a
|
||||||
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]
|
||||||
|
|
@ -140,16 +140,16 @@
|
||||||
(distinct))
|
(distinct))
|
||||||
(vals channels))
|
(vals channels))
|
||||||
:dense? (boolean (some :dense (vals channels)))}]
|
:dense? (boolean (some :dense (vals channels)))}]
|
||||||
;; AN OCCURRENCE IS NOT A ROW. A lane's drawings are cel
|
;; AN CEL IS NOT A ROW. A lane's drawings are cel
|
||||||
;; blocks on the lane's own row, so a lane of twelve
|
;; blocks on the lane's own row, so a lane of twelve
|
||||||
;; exposures is one row and not twelve — which is the
|
;; cels is one row and not twelve — which is the
|
||||||
;; vertical growth that made a keyed source look
|
;; vertical growth that made a keyed source look
|
||||||
;; necessary. The occurrence is still the thing selected
|
;; necessary. The cel is still the thing selected
|
||||||
;; and addressed; only its presentation is shared.
|
;; and addressed; only its presentation is shared.
|
||||||
(if (node/sequence? (get-in sym [:nodes (:parent n)]))
|
(if (node/lane? (get-in sym [:nodes (:parent n)]))
|
||||||
[]
|
[]
|
||||||
(let [row (cond-> row
|
(let [row (cond-> row
|
||||||
(node/sequence? n)
|
(node/lane? n)
|
||||||
(assoc :cels
|
(assoc :cels
|
||||||
(mapv (fn [child]
|
(mapv (fn [child]
|
||||||
{:id (:id child)
|
{:id (:id child)
|
||||||
|
|
@ -158,7 +158,7 @@
|
||||||
:source (node/source child)
|
:source (node/source child)
|
||||||
:span (mapv self (node/placed-span child))
|
:span (mapv self (node/placed-span child))
|
||||||
:select [:node sid (:id child) (conj path (:id child))]})
|
:select [:node sid (:id child) (conj path (:id child))]})
|
||||||
(symbol/sequence-members (:nodes sym) id))))]
|
(symbol/lane-cels (:nodes sym) id))))]
|
||||||
(if-not open?
|
(if-not open?
|
||||||
[row]
|
[row]
|
||||||
(-> [row]
|
(-> [row]
|
||||||
|
|
@ -175,7 +175,7 @@
|
||||||
|
|
||||||
(defn sound-rows
|
(defn sound-rows
|
||||||
"Audio rows use the same flattened intervals as the mixer, including source
|
"Audio rows use the same flattened intervals as the mixer, including source
|
||||||
in-points, occurrence speeds, parent timing, and silence beneath visual holds."
|
in-points, cel speeds, parent timing, and silence beneath visual holds."
|
||||||
[clip sid expanded]
|
[clip sid expanded]
|
||||||
(if-not (get-in clip [:symbols sid]) []
|
(if-not (get-in clip [:symbols sid]) []
|
||||||
(vec
|
(vec
|
||||||
|
|
@ -228,19 +228,19 @@
|
||||||
clip @(rf/subscribe [::render/clip])
|
clip @(rf/subscribe [::render/clip])
|
||||||
[_ sid id] @(rf/subscribe [::sub/selection])
|
[_ sid id] @(rf/subscribe [::sub/selection])
|
||||||
n (get-in clip [:symbols sid :nodes id])
|
n (get-in clip [:symbols sid :nodes id])
|
||||||
lane (if (node/sequence? n) n (get-in clip [:symbols sid :nodes (:parent n)]))
|
lane (if (node/lane? n) n (get-in clip [:symbols sid :nodes (:parent n)]))
|
||||||
lane? (node/sequence? lane)
|
lane? (node/lane? lane)
|
||||||
cel? (and lane? (= :instance (:kind n)) (some? (node/source n)))
|
cel? (and lane? (= :instance (:kind n)) (some? (node/source n)))
|
||||||
held? (and cel? (zero? (:speed (node/playback-of n))))
|
held? (and cel? (zero? (:speed (node/playback-of n))))
|
||||||
;; Shared use is shown rather than discovered: the button that decouples
|
;; Shared use is shown rather than discovered: the button that decouples
|
||||||
;; an exposure is only offered where there is something to decouple from.
|
;; a cel is only offered where there is something to decouple from.
|
||||||
shared? (and cel? (< 1 (count (for [[_ sym] (:symbols clip)
|
shared? (and cel? (< 1 (count (for [[_ sym] (:symbols clip)
|
||||||
[_ other] (:nodes sym)
|
[_ other] (:nodes sym)
|
||||||
:when (= (node/source n) (node/source other))]
|
:when (= (node/source n) (node/source other))]
|
||||||
other))))
|
other))))
|
||||||
;; Both act at the playhead, so both are offered only where the playhead
|
;; Both act at the playhead, so both are offered only where the playhead
|
||||||
;; is somewhere they mean something.
|
;; is somewhere they mean something.
|
||||||
at (when lane? (sequence/lane-frame clip sid (:id lane) frame))
|
at (when lane? (lane/lane-frame clip sid (:id lane) frame))
|
||||||
insertable? (and lane? (integer? at))
|
insertable? (and lane? (integer? at))
|
||||||
splittable? (and cel? (integer? at)
|
splittable? (and cel? (integer? at)
|
||||||
(let [[lo hi] (node/placed-span n)] (< lo at hi)))]
|
(let [[lo hi] (node/placed-span n)] (< lo at hi)))]
|
||||||
|
|
@ -273,37 +273,37 @@
|
||||||
:title "a new drawing at the playhead; later drawings ripple later"
|
:title "a new drawing at the playhead; later drawings ripple later"
|
||||||
:on-click #(rf/dispatch [::ui/insert-drawing])} "insert"]
|
:on-click #(rf/dispatch [::ui/insert-drawing])} "insert"]
|
||||||
[:button {:disabled (not splittable?)
|
[:button {:disabled (not splittable?)
|
||||||
:title "cut this exposure in two at the playhead; the picture does not change"
|
:title "cut this cel in two at the playhead; the picture does not change"
|
||||||
:on-click #(rf/dispatch [::ui/split-exposure])} "split"]
|
:on-click #(rf/dispatch [::ui/split-cel])} "split"]
|
||||||
[:button {:disabled (not cel?)
|
[:button {:disabled (not cel?)
|
||||||
:title "expose this same drawing again — one drawing, two exposures"
|
:title "expose this same drawing again — one drawing, two cels"
|
||||||
:on-click #(rf/dispatch [::ui/reuse-drawing])} "reuse"]
|
:on-click #(rf/dispatch [::ui/reuse-drawing])} "reuse"]
|
||||||
[:button {:disabled (not cel?)
|
[:button {:disabled (not cel?)
|
||||||
:title "append a copy of this drawing, to draw the next one over it"
|
:title "append a copy of this drawing, to draw the next one over it"
|
||||||
:on-click #(rf/dispatch [::ui/duplicate-drawing])} "duplicate"]
|
:on-click #(rf/dispatch [::ui/duplicate-drawing])} "duplicate"]
|
||||||
[:button {:disabled (not shared?)
|
[:button {:disabled (not shared?)
|
||||||
:title "give this exposure its own copy; other exposures keep sharing"
|
:title "give this cel its own copy; other cels keep sharing"
|
||||||
:on-click #(rf/dispatch [::ui/make-unique])} "make unique"]
|
:on-click #(rf/dispatch [::ui/make-unique])} "make unique"]
|
||||||
[:button {:disabled (not splittable?)
|
[:button {:disabled (not splittable?)
|
||||||
:title "start this exposure at the playhead; nothing else moves"
|
:title "start this cel at the playhead; nothing else moves"
|
||||||
:on-click #(rf/dispatch [::ui/trim-exposure :in])} "trim in"]
|
:on-click #(rf/dispatch [::ui/trim-cel :in])} "trim in"]
|
||||||
[:button {:disabled (not splittable?)
|
[:button {:disabled (not splittable?)
|
||||||
:title "end this exposure at the playhead; nothing else moves"
|
:title "end this cel at the playhead; nothing else moves"
|
||||||
:on-click #(rf/dispatch [::ui/trim-exposure :out])} "trim out"]
|
:on-click #(rf/dispatch [::ui/trim-cel :out])} "trim out"]
|
||||||
[:button {:disabled (not (and cel? (integer? at)))
|
[:button {:disabled (not (and cel? (integer? at)))
|
||||||
:title "put this exposure at the playhead; refused if something is there"
|
:title "put this cel at the playhead; refused if something is there"
|
||||||
:on-click #(rf/dispatch [::ui/move-exposure])} "move here"]
|
:on-click #(rf/dispatch [::ui/move-cel])} "move here"]
|
||||||
[:button {:disabled (not cel?)
|
[:button {:disabled (not cel?)
|
||||||
:title "clear this exposure's frames, leaving a gap; later drawings stay put"
|
:title "clear this cel's frames, leaving a gap; later drawings stay put"
|
||||||
:on-click #(rf/dispatch [::ui/blank-exposure])} "blank"]
|
:on-click #(rf/dispatch [::ui/blank-cel])} "blank"]
|
||||||
[:button {:disabled (not held?)
|
[:button {:disabled (not held?)
|
||||||
:title "shorten this exposure; ripple later drawings, keeping lane keys fixed"
|
:title "shorten this cel; ripple later drawings, keeping lane keys fixed"
|
||||||
:on-click #(rf/dispatch [::ui/extend-hold -1])} "hold −"]
|
:on-click #(rf/dispatch [::ui/extend-hold -1])} "hold −"]
|
||||||
[:button {:disabled (not held?)
|
[:button {:disabled (not held?)
|
||||||
:title "extend this exposure; ripple later drawings, keeping lane keys fixed"
|
:title "extend this cel; ripple later drawings, keeping lane keys fixed"
|
||||||
:on-click #(rf/dispatch [::ui/extend-hold 1])} "hold +"]
|
:on-click #(rf/dispatch [::ui/extend-hold 1])} "hold +"]
|
||||||
(when @(rf/subscribe [::sub/sequence-retry])
|
(when @(rf/subscribe [::sub/lane-retry])
|
||||||
[:button {:on-click #(rf/dispatch [::ui/sequence-retry])} "extend shot and apply"])
|
[:button {:on-click #(rf/dispatch [::ui/lane-retry])} "extend shot and apply"])
|
||||||
[:span.spacer]
|
[:span.spacer]
|
||||||
[:span.dim (str frame " / " frames)]
|
[:span.dim (str frame " / " frames)]
|
||||||
;; Measured in the loop, not derived from the clock — the whole question
|
;; Measured in the loop, not derived from the clock — the whole question
|
||||||
|
|
@ -475,7 +475,7 @@
|
||||||
:when (< in out)]
|
:when (< in out)]
|
||||||
^{:key (str id)}
|
^{:key (str id)}
|
||||||
[:button.tl-cel
|
[:button.tl-cel
|
||||||
{:title (str label " · select occurrence; double-click to edit shared drawing")
|
{:title (str label " · select cel; double-click to edit shared drawing")
|
||||||
:style {:position "absolute" :left (edge% in frames)
|
:style {:position "absolute" :left (edge% in frames)
|
||||||
:width (str (* 100 (/ (- out in) (max 1 frames))) "%")
|
:width (str (* 100 (/ (- out in) (max 1 frames))) "%")
|
||||||
:top "2px" :bottom "2px" :overflow "hidden" :padding "0 3px"}
|
:top "2px" :bottom "2px" :overflow "hidden" :padding "0 3px"}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
[arthur.domain.node :as node]
|
[arthur.domain.node :as node]
|
||||||
[arthur.domain.palette :as pal]
|
[arthur.domain.palette :as pal]
|
||||||
[arthur.domain.pick :as pick]
|
[arthur.domain.pick :as pick]
|
||||||
[arthur.domain.sequence :as sequence]
|
[arthur.domain.lane :as lane]
|
||||||
[arthur.domain.symbol :as symbol]))
|
[arthur.domain.symbol :as symbol]))
|
||||||
|
|
||||||
(defn drawing [id x frames]
|
(defn drawing [id x frames]
|
||||||
|
|
@ -18,17 +18,17 @@
|
||||||
:channels {[:geom :size] (ch/framed 4)
|
:channels {[:geom :size] (ch/framed 4)
|
||||||
[:xform :pos] (ch/framed [x 0])}}}})
|
[:xform :pos] (ch/framed [x 0])}}}})
|
||||||
|
|
||||||
(defn occurrence [id source at duration speed]
|
(defn cel [id source at duration speed]
|
||||||
{:id id :kind :instance :parent :girl :z (name id)
|
{:id id :kind :instance :parent :girl :z (name id)
|
||||||
:source {:symbol source} :playback {:in 0 :speed speed :end :stop}
|
:source {:symbol source} :playback {:in 0 :speed speed :end :stop}
|
||||||
:time {:at at :rate 1} :span [0 duration]})
|
:time {:at at :rate 1} :span [0 duration]})
|
||||||
|
|
||||||
(defn document []
|
(defn document []
|
||||||
(let [a (occurrence :a :drawing-a 0 4 0)
|
(let [a (cel :a :drawing-a 0 4 0)
|
||||||
b (assoc-in (occurrence :b :drawing-b 4 4 0)
|
b (assoc-in (cel :b :drawing-b 4 4 0)
|
||||||
[:channels [:xform :pos]] (ch/keyed {0 [0 0] 1 [2 0]} :hold))
|
[:channels [:xform :pos]] (ch/keyed {0 [0 0] 1 [2 0]} :hold))
|
||||||
insert (assoc-in (occurrence :insert :wave 8 4 1) [:playback :in] 3)]
|
insert (assoc-in (cel :insert :wave 8 4 1) [:playback :in] 3)]
|
||||||
{:name "exposures" :fps 24 :width 320 :height 200
|
{:name "cels" :fps 24 :width 320 :height 200
|
||||||
:symbols
|
:symbols
|
||||||
{:main {:id :main :frames 12
|
{:main {:id :main :frames 12
|
||||||
:nodes {:girl {:id :girl :kind :group :layout :sequence :z "b"
|
:nodes {:girl {:id :girl :kind :group :layout :sequence :z "b"
|
||||||
|
|
@ -58,9 +58,9 @@
|
||||||
(into {} (map (fn [[f ops]] [f (js/Math.round (:plate ops))])) at)))
|
(into {} (map (fn [[f ops]] [f (js/Math.round (:plate ops))])) at)))
|
||||||
(is (nil? (get-in at [4 [:a :mark]])) "half-open cuts have a single owner")))
|
(is (nil? (get-in at [4 [:a :mark]])) "half-open cuts have a single owner")))
|
||||||
|
|
||||||
(deftest exposure-ripple-keeps-lane-keys-and-moves-occurrence-corrections
|
(deftest cel-ripple-keeps-lane-keys-and-moves-cel-corrections
|
||||||
(let [doc (document)
|
(let [doc (document)
|
||||||
result (sequence/extend-hold doc :main :a 2 {:extent :grow-symbol})
|
result (lane/extend-hold doc :main :a 2 {:extent :grow-symbol})
|
||||||
after (:clip result)
|
after (:clip result)
|
||||||
nodes (get-in after [:symbols :main :nodes])]
|
nodes (get-in after [:symbols :main :nodes])]
|
||||||
(is (= :a (:selection result)))
|
(is (= :a (:selection result)))
|
||||||
|
|
@ -78,19 +78,19 @@
|
||||||
(is (= 320 (get-in at [10 [:insert :mark]])) "insert starts on source frame 3"))
|
(is (= 320 (get-in at [10 [:insert :mark]])) "insert starts on source frame 3"))
|
||||||
(is (empty? (clip/problems after)))
|
(is (empty? (clip/problems after)))
|
||||||
(is (= (assoc-in doc [:symbols :main :frames] 14)
|
(is (= (assoc-in doc [:symbols :main :frames] 14)
|
||||||
(:clip (sequence/extend-hold after :main :a -2 {})))
|
(:clip (lane/extend-hold after :main :a -2 {})))
|
||||||
"shrinking restores content, except the explicitly grown shot")))
|
"shrinking restores content, except the explicitly grown shot")))
|
||||||
|
|
||||||
(deftest overflow-and-invalid-edits-are-atomic
|
(deftest overflow-and-invalid-edits-are-atomic
|
||||||
(let [doc (document)
|
(let [doc (document)
|
||||||
result (sequence/extend-hold doc :main :a 2 {})]
|
result (lane/extend-hold doc :main :a 2 {})]
|
||||||
(is (:refused result))
|
(is (:refused result))
|
||||||
(is (= 14 (:required-frames result)))
|
(is (= 14 (:required-frames result)))
|
||||||
(is (not (contains? result :clip)))
|
(is (not (contains? result :clip)))
|
||||||
(doseq [delta [0 -4 0.5 js/NaN]]
|
(doseq [delta [0 -4 0.5 js/NaN]]
|
||||||
(is (:refused (sequence/extend-hold doc :main :a delta {}))))
|
(is (:refused (lane/extend-hold doc :main :a delta {}))))
|
||||||
(is (:refused (sequence/extend-hold doc :main :insert 1 {})))
|
(is (:refused (lane/extend-hold doc :main :insert 1 {})))
|
||||||
(is (:refused (sequence/extend-hold doc :main :missing 1 {})))))
|
(is (:refused (lane/extend-hold doc :main :missing 1 {})))))
|
||||||
|
|
||||||
(deftest a-gap-is-an-uncovered-interval
|
(deftest a-gap-is-an-uncovered-interval
|
||||||
(let [doc (update-in (document) [:symbols :main :nodes] dissoc :b)
|
(let [doc (update-in (document) [:symbols :main :nodes] dissoc :b)
|
||||||
|
|
@ -148,8 +148,8 @@
|
||||||
(is (= 99 (get-in (sample edited [0 4]) [0 [:a :mark]])))
|
(is (= 99 (get-in (sample edited [0 4]) [0 [:a :mark]])))
|
||||||
(is (= 139 (get-in (sample edited [0 4]) [4 [:b :mark]]))))))
|
(is (= 139 (get-in (sample edited [0 4]) [4 [:b :mark]]))))))
|
||||||
|
|
||||||
(deftest occurrence-identities-and-playback-round-trip
|
(deftest cel-identities-and-playback-round-trip
|
||||||
(let [doc (:clip (sequence/extend-hold (document) :main :a 2 {:extent :grow-symbol}))
|
(let [doc (:clip (lane/extend-hold (document) :main :a 2 {:extent :grow-symbol}))
|
||||||
leaves (leaf/leaves :project doc)]
|
leaves (leaf/leaves :project doc)]
|
||||||
(is (= doc (leaf/clip :project leaves)))
|
(is (= doc (leaf/clip :project leaves)))
|
||||||
(is (contains? leaves "clip/project/symbol/main/node/a"))
|
(is (contains? leaves "clip/project/symbol/main/node/a"))
|
||||||
|
|
@ -163,7 +163,7 @@
|
||||||
|
|
||||||
(deftest one-transaction-undoes-the-ripple-and-shot-extension
|
(deftest one-transaction-undoes-the-ripple-and-shot-extension
|
||||||
(let [doc (document)
|
(let [doc (document)
|
||||||
after (:clip (sequence/extend-hold doc :main :a 2 {:extent :grow-symbol}))
|
after (:clip (lane/extend-hold doc :main :a 2 {:extent :grow-symbol}))
|
||||||
before-leaves (leaf/leaves :p doc)
|
before-leaves (leaf/leaves :p doc)
|
||||||
after-leaves (leaf/leaves :p after)
|
after-leaves (leaf/leaves :p after)
|
||||||
h (-> nil history/hold (history/record before-leaves after-leaves 0) history/settle)
|
h (-> nil history/hold (history/record before-leaves after-leaves 0) history/settle)
|
||||||
|
|
@ -174,19 +174,19 @@
|
||||||
(is (= after-leaves (:leaves redo)))))
|
(is (= after-leaves (:leaves redo)))))
|
||||||
|
|
||||||
(deftest create-lane-and-append-drawings
|
(deftest create-lane-and-append-drawings
|
||||||
(let [doc (:clip (sequence/add-lane (clip/blank) :main :girl))
|
(let [doc (:clip (lane/add-lane (clip/blank) :main :girl))
|
||||||
a (:clip (sequence/append-drawing doc :main :girl :a :drawing-a {}))
|
a (:clip (lane/append-drawing doc :main :girl :a :drawing-a {}))
|
||||||
b (:clip (sequence/append-drawing a :main :girl :b :drawing-b {}))]
|
b (:clip (lane/append-drawing a :main :girl :b :drawing-b {}))]
|
||||||
(is (empty? (clip/problems b)))
|
(is (empty? (clip/problems b)))
|
||||||
(is (= [1 2] (node/placed-span (get-in b [:symbols :main :nodes :b]))))
|
(is (= [1 2] (node/placed-span (get-in b [:symbols :main :nodes :b]))))
|
||||||
(is (= 0 (get-in b [:symbols :main :nodes :b :playback :speed])))
|
(is (= 0 (get-in b [:symbols :main :nodes :b :playback :speed])))
|
||||||
(is (:refused (sequence/append-drawing b :main :girl :a :new {})))))
|
(is (:refused (lane/append-drawing b :main :girl :a :new {})))))
|
||||||
|
|
||||||
(deftest fractional-placement-rates-convert-the-hold-delta
|
(deftest fractional-placement-rates-convert-the-hold-delta
|
||||||
(let [doc (-> (document)
|
(let [doc (-> (document)
|
||||||
(assoc-in [:symbols :main :nodes :a :time :rate] 2)
|
(assoc-in [:symbols :main :nodes :a :time :rate] 2)
|
||||||
(assoc-in [:symbols :main :nodes :a :span] [0 8]))
|
(assoc-in [:symbols :main :nodes :a :span] [0 8]))
|
||||||
after (:clip (sequence/extend-hold doc :main :a 2 {:extent :grow-symbol}))]
|
after (:clip (lane/extend-hold doc :main :a 2 {:extent :grow-symbol}))]
|
||||||
(is (= [0 12] (get-in after [:symbols :main :nodes :a :span])))
|
(is (= [0 12] (get-in after [:symbols :main :nodes :a :span])))
|
||||||
(is (= [6 10] (node/placed-span (get-in after [:symbols :main :nodes :b]))))))
|
(is (= [6 10] (node/placed-span (get-in after [:symbols :main :nodes :b]))))))
|
||||||
|
|
||||||
|
|
@ -196,7 +196,7 @@
|
||||||
((symbol/resolver sym nil pal/index-of nil) 0))
|
((symbol/resolver sym nil pal/index-of nil) 0))
|
||||||
"omitted style colour must not crash a missing cursor")))
|
"omitted style colour must not crash a missing cursor")))
|
||||||
|
|
||||||
(deftest audio-follows-only-the-playing-occurrence
|
(deftest audio-follows-only-the-playing-cel
|
||||||
(let [voice {:id :voice :kind :audio :z "a" :source {:sound "voice"}
|
(let [voice {:id :voice :kind :audio :z "a" :source {:sound "voice"}
|
||||||
:span [0 10]
|
:span [0 10]
|
||||||
:channels {[:audio :gain] (ch/keyed {0 0 5 1} :linear)}}
|
:channels {[:audio :gain] (ch/keyed {0 0 5 1} :linear)}}
|
||||||
|
|
@ -208,7 +208,7 @@
|
||||||
(is (= [8 12] (node/placed-span track)))
|
(is (= [8 12] (node/placed-span track)))
|
||||||
(is (= [3 7] (:span track)) "the source in-point trims the audio too")
|
(is (= [3 7] (:span track)) "the source in-point trims the audio too")
|
||||||
(is (= {5 0 10 1} (get-in track [:channels [:audio :gain] :keys])))
|
(is (= {5 0 10 1} (get-in track [:channels [:audio :gain] :keys])))
|
||||||
(let [moved (:clip (sequence/extend-hold doc :main :a 2 {:extent :grow-symbol}))
|
(let [moved (:clip (lane/extend-hold doc :main :a 2 {:extent :grow-symbol}))
|
||||||
[track] (nest/audio-tracks moved :main)]
|
[track] (nest/audio-tracks moved :main)]
|
||||||
(is (= [10 14] (node/placed-span track)))
|
(is (= [10 14] (node/placed-span track)))
|
||||||
(is (= [3 7] (:span track))))
|
(is (= [3 7] (:span track))))
|
||||||
|
|
@ -231,56 +231,56 @@
|
||||||
|
|
||||||
(deftest enclosing-retiming-is-respected-when-extending-the-shot
|
(deftest enclosing-retiming-is-respected-when-extending-the-shot
|
||||||
(let [doc (assoc-in (document) [:symbols :main :nodes :girl :time] {:at 8 :rate 2})
|
(let [doc (assoc-in (document) [:symbols :main :nodes :girl :time] {:at 8 :rate 2})
|
||||||
result (sequence/extend-hold doc :main :a 2 {})]
|
result (lane/extend-hold doc :main :a 2 {})]
|
||||||
(is (= 15 (:required-frames result)))
|
(is (= 15 (:required-frames result)))
|
||||||
(is (nil? (:clip result)))
|
(is (nil? (:clip result)))
|
||||||
(is (= 15 (get-in (sequence/extend-hold doc :main :a 2 {:extent :grow-symbol})
|
(is (= 15 (get-in (lane/extend-hold doc :main :a 2 {:extent :grow-symbol})
|
||||||
[:clip :symbols :main :frames])))))
|
[:clip :symbols :main :frames])))))
|
||||||
|
|
||||||
(deftest reuse-shares-content-and-make-unique-decouples-one-exposure
|
(deftest reuse-shares-content-and-make-unique-decouples-one-cel
|
||||||
(let [doc (document)
|
(let [doc (document)
|
||||||
shared (:clip (sequence/reuse-drawing doc :main :girl :c :drawing-a
|
shared (:clip (lane/reuse-drawing doc :main :girl :c :drawing-a
|
||||||
{:extent :grow-symbol}))
|
{:extent :grow-symbol}))
|
||||||
edit (fn [c sym x]
|
edit (fn [c sym x]
|
||||||
(assoc-in c [:symbols sym :nodes :mark :channels [:xform :pos]]
|
(assoc-in c [:symbols sym :nodes :mark :channels [:xform :pos]]
|
||||||
(ch/framed [x 0])))]
|
(ch/framed [x 0])))]
|
||||||
(is (:refused (sequence/reuse-drawing doc :main :girl :c :drawing-a {}))
|
(is (:refused (lane/reuse-drawing doc :main :girl :c :drawing-a {}))
|
||||||
"the shot has to be extended on purpose")
|
"the shot has to be extended on purpose")
|
||||||
(is (= :drawing-a (node/source (get-in shared [:symbols :main :nodes :c]))))
|
(is (= :drawing-a (node/source (get-in shared [:symbols :main :nodes :c]))))
|
||||||
(is (= [12 13] (node/placed-span (get-in shared [:symbols :main :nodes :c]))))
|
(is (= [12 13] (node/placed-span (get-in shared [:symbols :main :nodes :c]))))
|
||||||
(is (empty? (clip/problems shared)))
|
(is (empty? (clip/problems shared)))
|
||||||
;; One drawing, two exposures: the edit arrives at both.
|
;; One drawing, two cels: the edit arrives at both.
|
||||||
(let [at (sample (edit shared :drawing-a 99) [0 12])]
|
(let [at (sample (edit shared :drawing-a 99) [0 12])]
|
||||||
(is (= 99 (get-in at [0 [:a :mark]])))
|
(is (= 99 (get-in at [0 [:a :mark]])))
|
||||||
(is (= 99 (get-in at [12 [:c :mark]]))))
|
(is (= 99 (get-in at [12 [:c :mark]]))))
|
||||||
(let [unique (:clip (sequence/make-unique shared :main :c {}))]
|
(let [unique (:clip (lane/make-unique shared :main :c {}))]
|
||||||
(is (= :drawing-a-2 (node/source (get-in unique [:symbols :main :nodes :c]))))
|
(is (= :drawing-a-2 (node/source (get-in unique [:symbols :main :nodes :c]))))
|
||||||
(is (= (:nodes (get-in shared [:symbols :drawing-a]))
|
(is (= (:nodes (get-in shared [:symbols :drawing-a]))
|
||||||
(:nodes (get-in unique [:symbols :drawing-a-2])))
|
(:nodes (get-in unique [:symbols :drawing-a-2])))
|
||||||
"a copy of the same drawing, not an empty one")
|
"a copy of the same drawing, not an empty one")
|
||||||
(is (= :drawing-a (node/source (get-in unique [:symbols :main :nodes :a])))
|
(is (= :drawing-a (node/source (get-in unique [:symbols :main :nodes :a])))
|
||||||
"the other exposure keeps the original")
|
"the other cel keeps the original")
|
||||||
(let [at (sample (edit unique :drawing-a 99) [0 12])]
|
(let [at (sample (edit unique :drawing-a 99) [0 12])]
|
||||||
(is (= 99 (get-in at [0 [:a :mark]])))
|
(is (= 99 (get-in at [0 [:a :mark]])))
|
||||||
(is (= 10 (get-in at [12 [:c :mark]])) "the exposure made unique is untouched"))
|
(is (= 10 (get-in at [12 [:c :mark]])) "the cel made unique is untouched"))
|
||||||
(let [at (sample (edit unique :drawing-a-2 99) [0 12])]
|
(let [at (sample (edit unique :drawing-a-2 99) [0 12])]
|
||||||
(is (= 10 (get-in at [0 [:a :mark]])) "and does not reach back"))
|
(is (= 10 (get-in at [0 [:a :mark]])) "and does not reach back"))
|
||||||
(is (empty? (clip/problems unique))))
|
(is (empty? (clip/problems unique))))
|
||||||
;; Nothing else places drawing-b, so there is nothing to decouple from.
|
;; Nothing else places drawing-b, so there is nothing to decouple from.
|
||||||
(is (:refused (sequence/make-unique doc :main :b {})))
|
(is (:refused (lane/make-unique doc :main :b {})))
|
||||||
(is (:refused (sequence/make-unique doc :main :girl {}))
|
(is (:refused (lane/make-unique doc :main :girl {}))
|
||||||
"a lane places nothing itself")))
|
"a lane places nothing itself")))
|
||||||
|
|
||||||
(deftest duplicate-copies-the-drawing-and-not-the-exposure
|
(deftest duplicate-copies-the-drawing-and-not-the-cel
|
||||||
(let [doc (document)
|
(let [doc (document)
|
||||||
made (:clip (sequence/duplicate-drawing doc :main :b :d {:extent :grow-symbol}))
|
made (:clip (lane/duplicate-drawing doc :main :b :d {:extent :grow-symbol}))
|
||||||
n (get-in made [:symbols :main :nodes :d])]
|
n (get-in made [:symbols :main :nodes :d])]
|
||||||
(is (= :drawing-b-2 (node/source n)))
|
(is (= :drawing-b-2 (node/source n)))
|
||||||
(is (= (:nodes (get-in doc [:symbols :drawing-b]))
|
(is (= (:nodes (get-in doc [:symbols :drawing-b]))
|
||||||
(:nodes (get-in made [:symbols :drawing-b-2]))))
|
(:nodes (get-in made [:symbols :drawing-b-2]))))
|
||||||
(is (= [12 13] (node/placed-span n)))
|
(is (= [12 13] (node/placed-span n)))
|
||||||
(is (= {:in 0 :speed 0 :end :stop} (:playback n)))
|
(is (= {:in 0 :speed 0 :end :stop} (:playback n)))
|
||||||
(is (nil? (:channels n)) "B's own position correction belongs to B's exposure")
|
(is (nil? (:channels n)) "B's own position correction belongs to B's cel")
|
||||||
(is (= (get-in doc [:symbols :main :nodes :b])
|
(is (= (get-in doc [:symbols :main :nodes :b])
|
||||||
(get-in made [:symbols :main :nodes :b]))
|
(get-in made [:symbols :main :nodes :b]))
|
||||||
"the drawing duplicated is left as it was")
|
"the drawing duplicated is left as it was")
|
||||||
|
|
@ -293,7 +293,7 @@
|
||||||
{:id :part :kind :instance :z "b" :span [0 1]
|
{:id :part :kind :instance :z "b" :span [0 1]
|
||||||
:time {:at 0 :rate 1} :source {:symbol :wave}
|
:time {:at 0 :rate 1} :source {:symbol :wave}
|
||||||
:playback {:in 0 :speed 0 :end :stop}})
|
:playback {:in 0 :speed 0 :end :stop}})
|
||||||
copy (fn [opts] (:clip (sequence/duplicate-drawing
|
copy (fn [opts] (:clip (lane/duplicate-drawing
|
||||||
doc :main :a :d (merge {:extent :grow-symbol} opts))))
|
doc :main :a :d (merge {:extent :grow-symbol} opts))))
|
||||||
shallow (copy {})
|
shallow (copy {})
|
||||||
deep (copy {:deep? true})]
|
deep (copy {:deep? true})]
|
||||||
|
|
@ -306,19 +306,19 @@
|
||||||
|
|
||||||
(deftest reuse-refuses-what-would-not-be-a-document
|
(deftest reuse-refuses-what-would-not-be-a-document
|
||||||
(let [doc (document)]
|
(let [doc (document)]
|
||||||
(is (:refused (sequence/reuse-drawing doc :main :girl :c :nothing-here {})))
|
(is (:refused (lane/reuse-drawing doc :main :girl :c :nothing-here {})))
|
||||||
(is (:refused (sequence/reuse-drawing doc :main :girl :c :main {:extent :grow-symbol}))
|
(is (:refused (lane/reuse-drawing doc :main :girl :c :main {:extent :grow-symbol}))
|
||||||
"a symbol cannot go inside itself")
|
"a symbol cannot go inside itself")
|
||||||
(is (:refused (sequence/reuse-drawing doc :main :girl :a :drawing-a {:extent :grow-symbol}))
|
(is (:refused (lane/reuse-drawing doc :main :girl :a :drawing-a {:extent :grow-symbol}))
|
||||||
"an occurrence ID in use is not free")
|
"a cel ID in use is not free")
|
||||||
(is (:refused (sequence/reuse-drawing doc :main :plate :c :drawing-a {})))
|
(is (:refused (lane/reuse-drawing doc :main :plate :c :drawing-a {})))
|
||||||
(is (:refused (sequence/duplicate-drawing doc :main :girl :d {})))))
|
(is (:refused (lane/duplicate-drawing doc :main :girl :d {})))))
|
||||||
|
|
||||||
(deftest drawing-on-twos-does-not-quantize-the-lane-transform
|
(deftest drawing-on-twos-does-not-quantize-the-lane-transform
|
||||||
;; Exposure length IS the drawing cadence, and it is the only thing on twos
|
;; Cel length IS the drawing cadence, and it is the only thing on twos
|
||||||
;; here: the lane's transform has its own clock and keeps moving every frame.
|
;; here: the lane's transform has its own clock and keeps moving every frame.
|
||||||
;; Stepping it would be the cel cadence leaking into continuous motion.
|
;; Stepping it would be the cel cadence leaking into continuous motion.
|
||||||
(let [cel (fn [id source at] (occurrence id source at 2 0))
|
(let [cel (fn [id source at] (cel id source at 2 0))
|
||||||
doc (-> (document)
|
doc (-> (document)
|
||||||
(update-in [:symbols :main :nodes] dissoc :a :b :insert)
|
(update-in [:symbols :main :nodes] dissoc :a :b :insert)
|
||||||
(update-in [:symbols :main :nodes] merge
|
(update-in [:symbols :main :nodes] merge
|
||||||
|
|
@ -337,7 +337,7 @@
|
||||||
|
|
||||||
(defn- drawn
|
(defn- drawn
|
||||||
"What every frame draws, as sorted values, so a picture can be compared
|
"What every frame draws, as sorted values, so a picture can be compared
|
||||||
without naming the occurrences that produced it."
|
without naming the cels that produced it."
|
||||||
[doc fs]
|
[doc fs]
|
||||||
(let [at (sample doc fs)]
|
(let [at (sample doc fs)]
|
||||||
(mapv #(sort (vals (get at %))) fs)))
|
(mapv #(sort (vals (get at %))) fs)))
|
||||||
|
|
@ -347,7 +347,7 @@
|
||||||
keys-of #(get-in % [:symbols :main :nodes :girl :channels [:xform :pos] :keys])
|
keys-of #(get-in % [:symbols :main :nodes :girl :channels [:xform :pos] :keys])
|
||||||
spans #(mapv (fn [id] (node/placed-span (get-in % [:symbols :main :nodes id])))
|
spans #(mapv (fn [id] (node/placed-span (get-in % [:symbols :main :nodes id])))
|
||||||
[:a :n :b :insert])
|
[:a :n :b :insert])
|
||||||
r (sequence/append-drawing doc :main :girl :n :drawing-n
|
r (lane/append-drawing doc :main :girl :n :drawing-n
|
||||||
{:at 4 :extent :grow-symbol})]
|
{:at 4 :extent :grow-symbol})]
|
||||||
(is (= [[0 4] [4 5] [5 9] [9 13]] (spans (:clip r))))
|
(is (= [[0 4] [4 5] [5 9] [9 13]] (spans (:clip r))))
|
||||||
(is (= 13 (get-in r [:clip :symbols :main :frames])))
|
(is (= 13 (get-in r [:clip :symbols :main :frames])))
|
||||||
|
|
@ -356,45 +356,45 @@
|
||||||
(is (= 4 (:frame r)))
|
(is (= 4 (:frame r)))
|
||||||
(is (empty? (clip/problems (:clip r))))
|
(is (empty? (clip/problems (:clip r))))
|
||||||
;; The same command with no room refuses, and says how much it needs.
|
;; The same command with no room refuses, and says how much it needs.
|
||||||
(is (= 13 (:required-frames (sequence/append-drawing doc :main :girl :n :drawing-n {:at 4}))))
|
(is (= 13 (:required-frames (lane/append-drawing doc :main :girl :n :drawing-n {:at 4}))))
|
||||||
;; At the very front everything moves.
|
;; At the very front everything moves.
|
||||||
(is (= [[1 5] [0 1] [5 9] [9 13]]
|
(is (= [[1 5] [0 1] [5 9] [9 13]]
|
||||||
(spans (:clip (sequence/append-drawing doc :main :girl :n :drawing-n
|
(spans (:clip (lane/append-drawing doc :main :girl :n :drawing-n
|
||||||
{:at 0 :extent :grow-symbol})))))
|
{:at 0 :extent :grow-symbol})))))
|
||||||
;; Inside an exposure is not a position for another one.
|
;; Inside a cel is not a position for another one.
|
||||||
(is (re-find #"split it first"
|
(is (re-find #"split it first"
|
||||||
(:refused (sequence/append-drawing doc :main :girl :n :drawing-n
|
(:refused (lane/append-drawing doc :main :girl :n :drawing-n
|
||||||
{:at 2 :extent :grow-symbol}))))
|
{:at 2 :extent :grow-symbol}))))
|
||||||
(is (:refused (sequence/append-drawing doc :main :girl :n :drawing-n
|
(is (:refused (lane/append-drawing doc :main :girl :n :drawing-n
|
||||||
{:at -1 :extent :grow-symbol})))
|
{:at -1 :extent :grow-symbol})))
|
||||||
(is (:refused (sequence/append-drawing doc :main :girl :n :drawing-n
|
(is (:refused (lane/append-drawing doc :main :girl :n :drawing-n
|
||||||
{:at ##Inf :extent :grow-symbol})))
|
{:at ##Inf :extent :grow-symbol})))
|
||||||
;; Reuse and duplicate take a position too; it is one placement rule.
|
;; Reuse and duplicate take a position too; it is one placement rule.
|
||||||
(is (= [4 5] (node/placed-span
|
(is (= [4 5] (node/placed-span
|
||||||
(get-in (sequence/reuse-drawing doc :main :girl :n :drawing-b
|
(get-in (lane/reuse-drawing doc :main :girl :n :drawing-b
|
||||||
{:at 4 :extent :grow-symbol})
|
{:at 4 :extent :grow-symbol})
|
||||||
[:clip :symbols :main :nodes :n]))))
|
[:clip :symbols :main :nodes :n]))))
|
||||||
(is (= [4 5] (node/placed-span
|
(is (= [4 5] (node/placed-span
|
||||||
(get-in (sequence/duplicate-drawing doc :main :b :n
|
(get-in (lane/duplicate-drawing doc :main :b :n
|
||||||
{:at 4 :extent :grow-symbol})
|
{:at 4 :extent :grow-symbol})
|
||||||
[:clip :symbols :main :nodes :n]))))))
|
[:clip :symbols :main :nodes :n]))))))
|
||||||
|
|
||||||
(deftest splitting-an-exposure-changes-nothing-that-is-drawn
|
(deftest splitting-an-cel-changes-nothing-that-is-drawn
|
||||||
(let [doc (document)
|
(let [doc (document)
|
||||||
fs (range 12)
|
fs (range 12)
|
||||||
before (drawn doc fs)]
|
before (drawn doc fs)]
|
||||||
(doseq [[label id cut] [["a held drawing" :a 2]
|
(doseq [[label id cut] [["a held drawing" :a 2]
|
||||||
["an exposure with a correction of its own" :b 6]
|
["a cel with a correction of its own" :b 6]
|
||||||
["a playing insert" :insert 10]]]
|
["a playing insert" :insert 10]]]
|
||||||
(testing label
|
(testing label
|
||||||
(let [r (sequence/split doc :main id cut :right)
|
(let [r (lane/split doc :main id cut :right)
|
||||||
after (:clip r)]
|
after (:clip r)]
|
||||||
(is (= :right (:selection r)))
|
(is (= :right (:selection r)))
|
||||||
(is (= before (drawn after fs)) "the same picture, frame for frame")
|
(is (= before (drawn after fs)) "the same picture, frame for frame")
|
||||||
(is (= (node/placed-span (get-in doc [:symbols :main :nodes id]))
|
(is (= (node/placed-span (get-in doc [:symbols :main :nodes id]))
|
||||||
[(first (node/placed-span (get-in after [:symbols :main :nodes id])))
|
[(first (node/placed-span (get-in after [:symbols :main :nodes id])))
|
||||||
(second (node/placed-span (get-in after [:symbols :main :nodes :right])))])
|
(second (node/placed-span (get-in after [:symbols :main :nodes :right])))])
|
||||||
"the pieces occupy the frames the exposure did")
|
"the pieces occupy the frames the cel did")
|
||||||
(is (= cut (second (node/placed-span (get-in after [:symbols :main :nodes id])))
|
(is (= cut (second (node/placed-span (get-in after [:symbols :main :nodes id])))
|
||||||
(first (node/placed-span (get-in after [:symbols :main :nodes :right])))))
|
(first (node/placed-span (get-in after [:symbols :main :nodes :right])))))
|
||||||
(is (= (:time (get-in doc [:symbols :main :nodes id]))
|
(is (= (:time (get-in doc [:symbols :main :nodes id]))
|
||||||
|
|
@ -405,19 +405,19 @@
|
||||||
(is (= 12 (get-in after [:symbols :main :frames])) "and no shot-length question")
|
(is (= 12 (get-in after [:symbols :main :frames])) "and no shot-length question")
|
||||||
(is (empty? (clip/problems after))))))))
|
(is (empty? (clip/problems after))))))))
|
||||||
|
|
||||||
(deftest split-refuses-anything-that-is-not-one-cut-inside-one-exposure
|
(deftest split-refuses-anything-that-is-not-one-cut-inside-one-cel
|
||||||
(let [doc (document)]
|
(let [doc (document)]
|
||||||
(doseq [cut [0 4 8 12 -1 2.5 ##NaN nil]]
|
(doseq [cut [0 4 8 12 -1 2.5 ##NaN nil]]
|
||||||
(is (:refused (sequence/split doc :main :b cut :right)) (str "cut at " (pr-str cut))))
|
(is (:refused (lane/split doc :main :b cut :right)) (str "cut at " (pr-str cut))))
|
||||||
(is (:refused (sequence/split doc :main :girl 2 :right)) "a lane is not an exposure")
|
(is (:refused (lane/split doc :main :girl 2 :right)) "a lane is not a cel")
|
||||||
(is (:refused (sequence/split doc :main :plate 2 :right)) "nor is a shape outside one")
|
(is (:refused (lane/split doc :main :plate 2 :right)) "nor is a shape outside one")
|
||||||
(is (:refused (sequence/split doc :main :a 2 :b)) "the new ID has to be free")))
|
(is (:refused (lane/split doc :main :a 2 :b)) "the new ID has to be free")))
|
||||||
|
|
||||||
(deftest split-then-place-puts-a-drawing-inside-a-hold
|
(deftest split-then-place-puts-a-drawing-inside-a-hold
|
||||||
;; The two commands the doc asks for, composed: neither one guesses.
|
;; The two commands the doc asks for, composed: neither one guesses.
|
||||||
(let [doc (document)
|
(let [doc (document)
|
||||||
cut (:clip (sequence/split doc :main :a 2 :right))
|
cut (:clip (lane/split doc :main :a 2 :right))
|
||||||
r (sequence/append-drawing cut :main :girl :n :drawing-n
|
r (lane/append-drawing cut :main :girl :n :drawing-n
|
||||||
{:at 2 :extent :grow-symbol})
|
{:at 2 :extent :grow-symbol})
|
||||||
after (:clip r)]
|
after (:clip r)]
|
||||||
(is (= [[0 2] [2 3] [3 5] [5 9] [9 13]]
|
(is (= [[0 2] [2 3] [3 5] [5 9] [9 13]]
|
||||||
|
|
@ -459,14 +459,14 @@
|
||||||
(is (= b (:leaves (history/undo h a))))
|
(is (= b (:leaves (history/undo h a))))
|
||||||
(is (= c (leaf/clip :p a)) "a correction needs no codec of its own"))))
|
(is (= c (leaf/clip :p a)) "a correction needs no codec of its own"))))
|
||||||
|
|
||||||
(deftest a-correction-on-one-exposure-travels-with-it
|
(deftest a-correction-on-one-cel-travels-with-it
|
||||||
;; The other half of ownership: a layer on an occurrence is in that
|
;; The other half of ownership: a layer on a cel is in that
|
||||||
;; occurrence's own frames, so moving the exposure moves the correction and
|
;; cel's own frames, so moving the cel moves the correction and
|
||||||
;; nothing has to say so.
|
;; nothing has to say so.
|
||||||
(let [beat (ch/layer :beat [0 2] :offset (ch/framed [7 0]))
|
(let [beat (ch/layer :beat [0 2] :offset (ch/framed [7 0]))
|
||||||
doc (update-in (document) [:symbols :main :nodes :b :channels [:xform :pos] :over]
|
doc (update-in (document) [:symbols :main :nodes :b :channels [:xform :pos] :over]
|
||||||
(fnil conj []) beat)
|
(fnil conj []) beat)
|
||||||
moved (:clip (sequence/extend-hold doc :main :a 2 {:extent :grow-symbol}))]
|
moved (:clip (lane/extend-hold doc :main :a 2 {:extent :grow-symbol}))]
|
||||||
;; Stated as the difference from the same document without the correction,
|
;; Stated as the difference from the same document without the correction,
|
||||||
;; so the claim is about WHERE the layer applies and not about arithmetic.
|
;; so the claim is about WHERE the layer applies and not about arithmetic.
|
||||||
(let [nudge (fn [with without f]
|
(let [nudge (fn [with without f]
|
||||||
|
|
@ -475,7 +475,7 @@
|
||||||
(is (= [7 7 0 0] (mapv #(nudge doc (document) %) [4 5 6 7]))
|
(is (= [7 7 0 0] (mapv #(nudge doc (document) %) [4 5 6 7]))
|
||||||
"B's first two frames, which are lane frames 4 and 5")
|
"B's first two frames, which are lane frames 4 and 5")
|
||||||
(is (= [7 7 0 0]
|
(is (= [7 7 0 0]
|
||||||
(mapv #(nudge moved (:clip (sequence/extend-hold (document) :main :a 2
|
(mapv #(nudge moved (:clip (lane/extend-hold (document) :main :a 2
|
||||||
{:extent :grow-symbol}))
|
{:extent :grow-symbol}))
|
||||||
%)
|
%)
|
||||||
[6 7 8 9]))
|
[6 7 8 9]))
|
||||||
|
|
@ -488,9 +488,9 @@
|
||||||
(defn- spans [clip ids]
|
(defn- spans [clip ids]
|
||||||
(mapv #(node/placed-span (get-in clip [:symbols :main :nodes %])) ids))
|
(mapv #(node/placed-span (get-in clip [:symbols :main :nodes %])) ids))
|
||||||
|
|
||||||
(deftest trimming-narrows-one-exposure-and-moves-nothing-else
|
(deftest trimming-narrows-one-cel-and-moves-nothing-else
|
||||||
(let [doc (document)
|
(let [doc (document)
|
||||||
r (sequence/trim doc :main :b :out 6)
|
r (lane/trim doc :main :b :out 6)
|
||||||
after (:clip r)]
|
after (:clip r)]
|
||||||
(is (= [[0 4] [4 6] [8 12]] (spans after [:a :b :insert])))
|
(is (= [[0 4] [4 6] [8 12]] (spans after [:a :b :insert])))
|
||||||
(is (= :b (:selection r)))
|
(is (= :b (:selection r)))
|
||||||
|
|
@ -505,7 +505,7 @@
|
||||||
;; were, so the frames that survive show exactly what they showed.
|
;; were, so the frames that survive show exactly what they showed.
|
||||||
(let [doc (document)
|
(let [doc (document)
|
||||||
before (sample doc [10 11])
|
before (sample doc [10 11])
|
||||||
after (:clip (sequence/trim doc :main :insert :in 10))]
|
after (:clip (lane/trim doc :main :insert :in 10))]
|
||||||
(is (= [10 12] (node/placed-span (get-in after [:symbols :main :nodes :insert]))))
|
(is (= [10 12] (node/placed-span (get-in after [:symbols :main :nodes :insert]))))
|
||||||
(is (= (:playback (get-in doc [:symbols :main :nodes :insert]))
|
(is (= (:playback (get-in doc [:symbols :main :nodes :insert]))
|
||||||
(:playback (get-in after [:symbols :main :nodes :insert]))))
|
(:playback (get-in after [:symbols :main :nodes :insert]))))
|
||||||
|
|
@ -520,13 +520,13 @@
|
||||||
["past its end" :out 9]
|
["past its end" :out 9]
|
||||||
["before its start" :in 2]
|
["before its start" :in 2]
|
||||||
["off a whole frame" :out 5.5]]]
|
["off a whole frame" :out 5.5]]]
|
||||||
(is (:refused (sequence/trim doc :main :b edge to)) label))
|
(is (:refused (lane/trim doc :main :b edge to)) label))
|
||||||
(is (:refused (sequence/trim doc :main :b :middle 6)))
|
(is (:refused (lane/trim doc :main :b :middle 6)))
|
||||||
(is (:refused (sequence/trim doc :main :girl :out 6)) "a lane is not an exposure")))
|
(is (:refused (lane/trim doc :main :girl :out 6)) "a lane is not a cel")))
|
||||||
|
|
||||||
(deftest moving-an-exposure-keeps-its-length-and-its-source-origin
|
(deftest moving-an-cel-keeps-its-length-and-its-source-origin
|
||||||
(let [doc (update-in (document) [:symbols :main :nodes] dissoc :b)
|
(let [doc (update-in (document) [:symbols :main :nodes] dissoc :b)
|
||||||
r (sequence/move doc :main :insert 4)
|
r (lane/move doc :main :insert 4)
|
||||||
after (:clip r)]
|
after (:clip r)]
|
||||||
(is (= [[0 4] [4 8]] (spans after [:a :insert])))
|
(is (= [[0 4] [4 8]] (spans after [:a :insert])))
|
||||||
(is (= :insert (:selection r)))
|
(is (= :insert (:selection r)))
|
||||||
|
|
@ -539,19 +539,19 @@
|
||||||
|
|
||||||
(deftest a-move-onto-an-occupied-frame-is-refused-rather-than-rippled
|
(deftest a-move-onto-an-occupied-frame-is-refused-rather-than-rippled
|
||||||
(let [doc (document)]
|
(let [doc (document)]
|
||||||
(is (:refused (sequence/move doc :main :insert 6)) "it would overlap B")
|
(is (:refused (lane/move doc :main :insert 6)) "it would overlap B")
|
||||||
(is (:refused (sequence/move doc :main :insert 4.5)))
|
(is (:refused (lane/move doc :main :insert 4.5)))
|
||||||
(is (:refused (sequence/move doc :main :girl 2)))
|
(is (:refused (lane/move doc :main :girl 2)))
|
||||||
;; Clearing the room first is the composition, and then it goes.
|
;; Clearing the room first is the composition, and then it goes.
|
||||||
(let [cleared (:clip (sequence/blank doc :main :girl [4 8] {}))]
|
(let [cleared (:clip (lane/blank doc :main :girl [4 8] {}))]
|
||||||
(is (= [[0 4] [4 8]] (spans (:clip (sequence/move cleared :main :insert 4))
|
(is (= [[0 4] [4 8]] (spans (:clip (lane/move cleared :main :insert 4))
|
||||||
[:a :insert]))))))
|
[:a :insert]))))))
|
||||||
|
|
||||||
(deftest blanking-leaves-a-gap-and-does-not-close-it
|
(deftest blanking-leaves-a-gap-and-does-not-close-it
|
||||||
(let [doc (document)
|
(let [doc (document)
|
||||||
r (sequence/blank doc :main :girl [5 7] {:id :rest})
|
r (lane/blank doc :main :girl [5 7] {:id :rest})
|
||||||
after (:clip r)]
|
after (:clip r)]
|
||||||
;; B spanned the range, so it became two exposures with a hole between them.
|
;; B spanned the range, so it became two cels with a hole between them.
|
||||||
(is (= [[0 4] [4 5] [7 8] [8 12]] (spans after [:a :b :rest :insert])))
|
(is (= [[0 4] [4 5] [7 8] [8 12]] (spans after [:a :b :rest :insert])))
|
||||||
(is (= :rest (:selection r)))
|
(is (= :rest (:selection r)))
|
||||||
(let [at (sample after [4 5 6 7])]
|
(let [at (sample after [4 5 6 7])]
|
||||||
|
|
@ -562,9 +562,9 @@
|
||||||
(is (= 12 (get-in after [:symbols :main :frames])))
|
(is (= 12 (get-in after [:symbols :main :frames])))
|
||||||
(is (empty? (clip/problems after)))))
|
(is (empty? (clip/problems after)))))
|
||||||
|
|
||||||
(deftest blanking-a-whole-exposure-removes-it-and-keeps-its-drawing
|
(deftest blanking-a-whole-cel-removes-it-and-keeps-its-drawing
|
||||||
(let [doc (document)
|
(let [doc (document)
|
||||||
after (:clip (sequence/blank doc :main :girl [4 8] {}))]
|
after (:clip (lane/blank doc :main :girl [4 8] {}))]
|
||||||
(is (nil? (get-in after [:symbols :main :nodes :b])))
|
(is (nil? (get-in after [:symbols :main :nodes :b])))
|
||||||
(is (= [[0 4] [8 12]] (spans after [:a :insert])) "and moves nothing")
|
(is (= [[0 4] [8 12]] (spans after [:a :insert])) "and moves nothing")
|
||||||
(is (= (get-in doc [:symbols :drawing-b]) (get-in after [:symbols :drawing-b]))
|
(is (= (get-in doc [:symbols :drawing-b]) (get-in after [:symbols :drawing-b]))
|
||||||
|
|
@ -573,7 +573,7 @@
|
||||||
|
|
||||||
(deftest blanking-a-range-trims-what-it-only-partly-covers
|
(deftest blanking-a-range-trims-what-it-only-partly-covers
|
||||||
(let [doc (document)
|
(let [doc (document)
|
||||||
after (:clip (sequence/blank doc :main :girl [3 9] {}))]
|
after (:clip (lane/blank doc :main :girl [3 9] {}))]
|
||||||
(is (= [[0 3] [9 12]] (spans after [:a :insert])))
|
(is (= [[0 3] [9 12]] (spans after [:a :insert])))
|
||||||
(is (nil? (get-in after [:symbols :main :nodes :b])))
|
(is (nil? (get-in after [:symbols :main :nodes :b])))
|
||||||
(is (= (get-in (sample doc [9]) [9 [:insert :mark]])
|
(is (= (get-in (sample doc [9]) [9 [:insert :mark]])
|
||||||
|
|
@ -583,28 +583,28 @@
|
||||||
|
|
||||||
(deftest blank-refuses-what-it-cannot-do-in-one-piece
|
(deftest blank-refuses-what-it-cannot-do-in-one-piece
|
||||||
(let [doc (document)]
|
(let [doc (document)]
|
||||||
(is (re-find #"free ID" (:refused (sequence/blank doc :main :girl [5 7] {})))
|
(is (re-find #"free ID" (:refused (lane/blank doc :main :girl [5 7] {})))
|
||||||
"splitting an exposure needs an ID for the remainder")
|
"splitting a cel needs an ID for the remainder")
|
||||||
(is (:refused (sequence/blank doc :main :girl [5 7] {:id :a})) "and a free one")
|
(is (:refused (lane/blank doc :main :girl [5 7] {:id :a})) "and a free one")
|
||||||
(is (:refused (sequence/blank doc :main :girl [7 5] {})))
|
(is (:refused (lane/blank doc :main :girl [7 5] {})))
|
||||||
(is (:refused (sequence/blank doc :main :girl [5 5] {})))
|
(is (:refused (lane/blank doc :main :girl [5 5] {})))
|
||||||
(is (:refused (sequence/blank doc :main :girl [5 6.5] {})))
|
(is (:refused (lane/blank doc :main :girl [5 6.5] {})))
|
||||||
(is (:refused (sequence/blank doc :main :plate [0 2] {})))))
|
(is (:refused (lane/blank doc :main :plate [0 2] {})))))
|
||||||
|
|
||||||
(deftest the-shot-length-is-authored-and-emptying-a-lane-does-not-shorten-it
|
(deftest the-shot-length-is-authored-and-emptying-a-lane-does-not-shorten-it
|
||||||
;; The window and the occupied extent are two facts. A shot with nothing in
|
;; The window and the occupied extent are two facts. A shot with nothing in
|
||||||
;; the last half is a shot somebody authored that long, and deleting the last
|
;; the last half is a shot somebody authored that long, and deleting the last
|
||||||
;; drawing must not quietly shorten the film.
|
;; drawing must not quietly shorten the film.
|
||||||
(let [doc (document)
|
(let [doc (document)
|
||||||
empty-lane (:clip (sequence/blank doc :main :girl [0 12] {}))]
|
empty-lane (:clip (lane/blank doc :main :girl [0 12] {}))]
|
||||||
(is (empty? (symbol/sequence-members (get-in empty-lane [:symbols :main :nodes]) :girl)))
|
(is (empty? (symbol/lane-cels (get-in empty-lane [:symbols :main :nodes]) :girl)))
|
||||||
(is (= 12 (get-in empty-lane [:symbols :main :frames])))
|
(is (= 12 (get-in empty-lane [:symbols :main :frames])))
|
||||||
(is (empty? (clip/problems empty-lane)))
|
(is (empty? (clip/problems empty-lane)))
|
||||||
;; Growing is still the caller's word, and only ever grows.
|
;; Growing is still the caller's word, and only ever grows.
|
||||||
(is (:refused (sequence/append-drawing empty-lane :main :girl :n :drawing-n {:at 20})))
|
(is (:refused (lane/append-drawing empty-lane :main :girl :n :drawing-n {:at 20})))
|
||||||
(is (= 21 (get-in (sequence/append-drawing empty-lane :main :girl :n :drawing-n
|
(is (= 21 (get-in (lane/append-drawing empty-lane :main :girl :n :drawing-n
|
||||||
{:at 20 :extent :grow-symbol})
|
{:at 20 :extent :grow-symbol})
|
||||||
[:clip :symbols :main :frames])))
|
[:clip :symbols :main :frames])))
|
||||||
(is (= 12 (get-in (:clip (sequence/trim doc :main :insert :out 9))
|
(is (= 12 (get-in (:clip (lane/trim doc :main :insert :out 9))
|
||||||
[:symbols :main :frames]))
|
[:symbols :main :frames]))
|
||||||
"and trimming the last exposure leaves the window where it was")))
|
"and trimming the last cel leaves the window where it was")))
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
(ns arthur.events.sequence-test
|
(ns arthur.events.lane-test
|
||||||
(:require [cljs.test :refer [deftest is]]
|
(:require [cljs.test :refer [deftest is]]
|
||||||
[arthur.domain.lane-test :as fixture]
|
[arthur.domain.lane-test :as fixture]
|
||||||
[arthur.domain.sequence :as sequence]
|
[arthur.domain.lane :as lane]
|
||||||
[arthur.events.ui :as ui]
|
[arthur.events.ui :as ui]
|
||||||
[arthur.domain.history :as history]
|
[arthur.domain.history :as history]
|
||||||
[arthur.domain.leaf :as leaf]
|
[arthur.domain.leaf :as leaf]
|
||||||
[arthur.footage.store :as store]
|
[arthur.footage.store :as store]
|
||||||
[arthur.ui.timeline :as timeline]))
|
[arthur.ui.timeline :as timeline]))
|
||||||
|
|
||||||
(deftest one-row-projects-all-occurrences-and-keeps-selection-addresses
|
(deftest one-row-projects-all-cels-and-keeps-selection-addresses
|
||||||
(let [doc (fixture/document)
|
(let [doc (fixture/document)
|
||||||
rows (timeline/rows doc :main #{})
|
rows (timeline/rows doc :main #{})
|
||||||
lane (first (filter :cels rows))]
|
lane (first (filter :cels rows))]
|
||||||
|
|
@ -24,15 +24,15 @@
|
||||||
id (store/install! {:clip doc :store {}} "sequence-test")
|
id (store/install! {:clip doc :store {}} "sequence-test")
|
||||||
db {:clip/current id :paint/revision 0
|
db {:clip/current id :paint/revision 0
|
||||||
:ui {:open :main :selection [:node :main :a [:a]]}}
|
:ui {:open :main :selection [:node :main :a [:a]]}}
|
||||||
refused (ui/apply-sequence-command db :main
|
refused (ui/apply-lane-command db :main
|
||||||
(sequence/extend-hold doc :main :a 1 {}) [:retry])]
|
(lane/extend-hold doc :main :a 1 {}) [:retry])]
|
||||||
(is (= doc (:clip (store/entry id))))
|
(is (= doc (:clip (store/entry id))))
|
||||||
(is (nil? (:history (store/entry id))))
|
(is (nil? (:history (store/entry id))))
|
||||||
(is (= [:retry] (get-in refused [:ui :sequence-retry])))
|
(is (= [:retry] (get-in refused [:ui :lane-retry])))
|
||||||
(let [r1 (sequence/extend-hold doc :main :a 1 {:extent :grow-symbol})
|
(let [r1 (lane/extend-hold doc :main :a 1 {:extent :grow-symbol})
|
||||||
db1 (ui/apply-sequence-command db :main r1 nil)
|
db1 (ui/apply-lane-command db :main r1 nil)
|
||||||
r2 (sequence/extend-hold (:clip r1) :main :a 1 {:extent :grow-symbol})
|
r2 (lane/extend-hold (:clip r1) :main :a 1 {:extent :grow-symbol})
|
||||||
db2 (ui/apply-sequence-command db1 :main r2 nil)
|
db2 (ui/apply-lane-command db1 :main r2 nil)
|
||||||
h (:history (store/entry id))
|
h (:history (store/entry id))
|
||||||
undo (history/undo h (leaf/leaves "u" (:clip r2)))
|
undo (history/undo h (leaf/leaves "u" (:clip r2)))
|
||||||
undo2 (history/undo (:history undo) (:leaves undo))]
|
undo2 (history/undo (:history undo) (:leaves undo))]
|
||||||
|
|
@ -59,7 +59,7 @@ try {
|
||||||
await evaluate(`(() => {
|
await evaluate(`(() => {
|
||||||
const k = cljs.core.keyword;
|
const k = cljs.core.keyword;
|
||||||
cljs.core.swap_BANG_(re_frame.db.app_db, db => cljs.core.assoc(db, k('route'), k('local-test')));
|
cljs.core.swap_BANG_(re_frame.db.app_db, db => cljs.core.assoc(db, k('route'), k('local-test')));
|
||||||
window.sequenceSnapshot = () => {
|
window.laneSnapshot = () => {
|
||||||
const db = cljs.core.deref(re_frame.db.app_db);
|
const db = cljs.core.deref(re_frame.db.app_db);
|
||||||
const entry = arthur.footage.store.entry(cljs.core.get(db, k('clip/current')));
|
const entry = arthur.footage.store.entry(cljs.core.get(db, k('clip/current')));
|
||||||
return cljs.core.clj__GT_js(entry);
|
return cljs.core.clj__GT_js(entry);
|
||||||
|
|
@ -75,7 +75,7 @@ try {
|
||||||
})()`), `enabled button: ${label}`);
|
})()`), `enabled button: ${label}`);
|
||||||
await sleep(180);
|
await sleep(180);
|
||||||
};
|
};
|
||||||
const shot = async () => (await evaluate('sequenceSnapshot()'));
|
const shot = async () => (await evaluate('laneSnapshot()'));
|
||||||
const instances = s => Object.values(s.clip.symbols.main.nodes).filter(n => n.kind === 'instance')
|
const instances = s => Object.values(s.clip.symbols.main.nodes).filter(n => n.kind === 'instance')
|
||||||
.sort((a, b) => a.time.at - b.time.at);
|
.sort((a, b) => a.time.at - b.time.at);
|
||||||
await click('+ lane');
|
await click('+ lane');
|
||||||
|
|
@ -90,7 +90,7 @@ try {
|
||||||
assert.equal(await evaluate('document.querySelectorAll(".tl-label:not(.tl-corner)").length'), 1);
|
assert.equal(await evaluate('document.querySelectorAll(".tl-label:not(.tl-corner)").length'), 1);
|
||||||
assert.equal(await evaluate(`cljs.core.get_in(cljs.core.deref(re_frame.db.app_db),
|
assert.equal(await evaluate(`cljs.core.get_in(cljs.core.deref(re_frame.db.app_db),
|
||||||
cljs.core.vector(cljs.core.keyword('playback'), cljs.core.keyword('frame')))`), 4,
|
cljs.core.vector(cljs.core.keyword('playback'), cljs.core.keyword('frame')))`), 4,
|
||||||
'new drawing seeks to its occurrence');
|
'new drawing seeks to its cel');
|
||||||
|
|
||||||
// Shorten this test shot to the occupied extent, purely in memory.
|
// Shorten this test shot to the occupied extent, purely in memory.
|
||||||
await evaluate(`(() => {
|
await evaluate(`(() => {
|
||||||
|
|
@ -112,9 +112,9 @@ try {
|
||||||
assert.equal(s.history.done.length, before.history.done.length + 1);
|
assert.equal(s.history.done.length, before.history.done.length + 1);
|
||||||
await evaluate(`document.dispatchEvent(new KeyboardEvent('keydown', {key:'z', ctrlKey:true, bubbles:true}))`);
|
await evaluate(`document.dispatchEvent(new KeyboardEvent('keydown', {key:'z', ctrlKey:true, bubbles:true}))`);
|
||||||
await sleep(250);
|
await sleep(250);
|
||||||
assert.deepEqual((await shot()).clip, before.clip, 'one undo restores exposure, ripple, and shot length');
|
assert.deepEqual((await shot()).clip, before.clip, 'one undo restores cel, ripple, and shot length');
|
||||||
|
|
||||||
// Sharing: one drawing exposed twice, then one exposure decoupled. Room is
|
// Sharing: one drawing exposed twice, then one cel decoupled. Room is
|
||||||
// made first so these assertions are about content and not about overflow.
|
// made first so these assertions are about content and not about overflow.
|
||||||
await evaluate(`(() => {
|
await evaluate(`(() => {
|
||||||
const k = cljs.core.keyword, db = cljs.core.deref(re_frame.db.app_db);
|
const k = cljs.core.keyword, db = cljs.core.deref(re_frame.db.app_db);
|
||||||
|
|
@ -135,19 +135,19 @@ try {
|
||||||
assert.equal(cels[2].source.symbol, cels[0].source.symbol, 'reuse exposes the same drawing');
|
assert.equal(cels[2].source.symbol, cels[0].source.symbol, 'reuse exposes the same drawing');
|
||||||
assert.equal(await evaluate('document.querySelectorAll(".tl-cel").length'), 3);
|
assert.equal(await evaluate('document.querySelectorAll(".tl-cel").length'), 3);
|
||||||
assert.equal(await evaluate('document.querySelectorAll(".tl-label:not(.tl-corner)").length'), 1,
|
assert.equal(await evaluate('document.querySelectorAll(".tl-label:not(.tl-corner)").length'), 1,
|
||||||
'three exposures, still one row');
|
'three cels, still one row');
|
||||||
assert.equal(await enabled('make unique'), true);
|
assert.equal(await enabled('make unique'), true);
|
||||||
await click('make unique');
|
await click('make unique');
|
||||||
s = await shot();
|
s = await shot();
|
||||||
cels = instances(s);
|
cels = instances(s);
|
||||||
assert.notEqual(cels[2].source.symbol, cels[0].source.symbol, 'that exposure has its own drawing');
|
assert.notEqual(cels[2].source.symbol, cels[0].source.symbol, 'that cel has its own drawing');
|
||||||
assert.equal(await enabled('make unique'), false, 'and is not shared any more');
|
assert.equal(await enabled('make unique'), false, 'and is not shared any more');
|
||||||
await click('duplicate');
|
await click('duplicate');
|
||||||
s = await shot();
|
s = await shot();
|
||||||
cels = instances(s);
|
cels = instances(s);
|
||||||
assert.equal(cels.length, 4);
|
assert.equal(cels.length, 4);
|
||||||
assert.equal(new Set(cels.map(n => n.source.symbol)).size, 4,
|
assert.equal(new Set(cels.map(n => n.source.symbol)).size, 4,
|
||||||
'four exposures of four drawings: nothing is shared once every copy is made');
|
'four cels of four drawings: nothing is shared once every copy is made');
|
||||||
assert.equal(s.history.done.length, before.history.done.length + 3, 'three more commands, three more steps');
|
assert.equal(s.history.done.length, before.history.done.length + 3, 'three more commands, three more steps');
|
||||||
|
|
||||||
// A drawing into the middle of a hold: split, then insert. Both act at the
|
// A drawing into the middle of a hold: split, then insert. Both act at the
|
||||||
|
|
@ -158,25 +158,25 @@ try {
|
||||||
assert.deepEqual(placed(s), [[0, 4], [4, 5], [5, 6], [6, 7]]);
|
assert.deepEqual(placed(s), [[0, 4], [4, 5], [5, 6], [6, 7]]);
|
||||||
await evaluate(`document.querySelector('.tl-cel').click()`);
|
await evaluate(`document.querySelector('.tl-cel').click()`);
|
||||||
await sleep(200);
|
await sleep(200);
|
||||||
assert.equal(await enabled('split'), false, 'the start of an exposure is not inside it');
|
assert.equal(await enabled('split'), false, 'the start of a cel is not inside it');
|
||||||
await click('+1');
|
await click('+1');
|
||||||
await click('+1');
|
await click('+1');
|
||||||
assert.equal(await enabled('split'), true);
|
assert.equal(await enabled('split'), true);
|
||||||
await click('split');
|
await click('split');
|
||||||
s = await shot();
|
s = await shot();
|
||||||
assert.deepEqual(placed(s), [[0, 2], [2, 4], [4, 5], [5, 6], [6, 7]],
|
assert.deepEqual(placed(s), [[0, 2], [2, 4], [4, 5], [5, 6], [6, 7]],
|
||||||
'one exposure became two, over the frames it had');
|
'one cel became two, over the frames it had');
|
||||||
await click('insert');
|
await click('insert');
|
||||||
s = await shot();
|
s = await shot();
|
||||||
assert.deepEqual(placed(s), [[0, 2], [2, 3], [3, 5], [5, 6], [6, 7], [7, 8]],
|
assert.deepEqual(placed(s), [[0, 2], [2, 3], [3, 5], [5, 6], [6, 7], [7, 8]],
|
||||||
'the new drawing took frame 2 and everything from there rippled later');
|
'the new drawing took frame 2 and everything from there rippled later');
|
||||||
assert.equal(await evaluate('document.querySelectorAll(".tl-cel").length'), 6);
|
assert.equal(await evaluate('document.querySelectorAll(".tl-cel").length'), 6);
|
||||||
assert.equal(await evaluate('document.querySelectorAll(".tl-label:not(.tl-corner)").length'), 1,
|
assert.equal(await evaluate('document.querySelectorAll(".tl-label:not(.tl-corner)").length'), 1,
|
||||||
'six exposures, still one row');
|
'six cels, still one row');
|
||||||
assert.equal(s.history.done.length, before.history.done.length + 5);
|
assert.equal(s.history.done.length, before.history.done.length + 5);
|
||||||
|
|
||||||
// Trim, move and blank: three gestures that move nothing but their own
|
// Trim, move and blank: three gestures that move nothing but their own
|
||||||
// exposure, and a shot whose length does not follow what is in it.
|
// cel, and a shot whose length does not follow what is in it.
|
||||||
await evaluate(`[...document.querySelectorAll('.tl-cel')][2].click()`);
|
await evaluate(`[...document.querySelectorAll('.tl-cel')][2].click()`);
|
||||||
await sleep(200);
|
await sleep(200);
|
||||||
assert.deepEqual(placed(await shot()).slice(2, 4), [[3, 5], [5, 6]]);
|
assert.deepEqual(placed(await shot()).slice(2, 4), [[3, 5], [5, 6]]);
|
||||||
|
|
@ -185,7 +185,7 @@ try {
|
||||||
await click('trim out');
|
await click('trim out');
|
||||||
s = await shot();
|
s = await shot();
|
||||||
assert.deepEqual(placed(s), [[0, 2], [2, 3], [3, 4], [5, 6], [6, 7], [7, 8]],
|
assert.deepEqual(placed(s), [[0, 2], [2, 3], [3, 4], [5, 6], [6, 7], [7, 8]],
|
||||||
'it ends at the playhead and every other exposure stayed');
|
'it ends at the playhead and every other cel stayed');
|
||||||
assert.equal(await enabled('move here'), true);
|
assert.equal(await enabled('move here'), true);
|
||||||
await click('move here');
|
await click('move here');
|
||||||
s = await shot();
|
s = await shot();
|
||||||
Loading…
Add table
Add a link
Reference in a new issue