Commit Graph
3365 Commits
Author SHA1 Message Date
tylerjenningsw 40a9173f17 Merge main into fix/expression-value-color 2026-10-25 14:26:30 -04:00
tylerjenningsw 5808550c5a Merge main into fix/particle-birth-history-ps2-mercury 2026-10-25 14:26:29 -04:00
tylerjenningsw 20d8501ea2 Merge main into fix/particle-birth-history 2026-10-25 14:26:29 -04:00
Dylan M. Taylor 1be30a1607 refactor(updates): drive the flow with craft-update's Updater
The check at start is opt-in for the desktop app, a successful update closes through the app's own close path, and no update starts over unsaved work.
2026-10-25 14:23:52 -04:00
aiongg 58f4a4e36a Merge upstream main into craft-ui-docking 2026-10-25 18:16:57 +00:00
Davit MatchakhelidzeandClaude Opus 5.5 c6376249dd i18n(ka): shorter wording for labels that were cut off
Decided in the native-speaker review of the screens (all-t01).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YMTAbGNRxnMEzRvYnQGphF
2026-10-25 22:13:46 +04:00
tylerjenningsw 86907e9a3a Merge main into enhance/keyframe-text-clipboard 2026-10-25 14:09:38 -04:00
aiongg 26dd2ab814 Merge #2367 (egui 0.37) into craft-ui-docking
# Conflicts:
#	Cargo.lock
#	Cargo.toml
#	apps/effectcraft/src/main.rs
#	crates/ui-egui/src/control.rs
#	crates/ui-egui/src/dock.rs
#	crates/ui-egui/src/dock_ui.rs
#	crates/ui-egui/src/lib.rs
#	crates/ui-egui/tests/it/shared_docking_ui.rs
#	crates/ui-egui/tests/it/ui_dock_resize.rs
#	crates/ui-egui/tests/it/ui_panel_windows.rs
#	crates/ui-egui/tests/it/ui_workspace_focus.rs
2026-10-25 18:06:51 +00:00
yanhenrique-dev 49e8fa654e docs: recount the other panel catalogs the batch merges left stale
The batch merges grew Japanese 410 → 693, both Chinese catalogs 506 → 695,
Ukrainian 505 → 524, Czech 501 → 518 and Spanish 513 rows (its #996 landed)
without touching the doc's counts or shares; the overall figure was still the
10-October measurement. pt-br was the last count to update, so the row now
mirrors the code at this tree.
2026-10-25 15:01:10 -03:00
yanhenrique-dev 0337d5d815 i18n: complete the Brazilian Portuguese (pt-br) panel catalog
278 new rows (417 → 695), the Chinese catalogs' source list, keyed by the English
source string: panels, dialogs, tooltips and the Settings pages stop falling back
to English for every string the zh-hans catalog covers, including the six keys
the batch merges added on top. Terminology follows After Effects' Brazilian
Portuguese conventions; brand names stay English.

AI-assisted translation (OpenCode agents, GLM); not yet native-speaker proofread.
2026-10-25 15:00:23 -03:00
Dylan M. Taylor 6f813b74e9 refactor(updates): drive the flow with craft-update's Updater
Startup check is opt-in for the desktop app, the Windows update helper is dispatched before argument parsing, and a successful update closes through the app's own close path.
2026-10-25 13:58:39 -04:00
Dylan M. Taylor 9932ca0cf2 build: pin craft-update to the dirs-free, zip 9 build
Satisfies cargo-deny: no MPL-2.0 option-ext, no zip 8 advisories, and a versioned git dependency instead of a wildcard.
2026-10-25 13:58:38 -04:00
Dylan M. Taylor 89a0313ab0 fix(updates): report self-update results to the prompt and finish through the app's close path
Keeps the worker's failure visible instead of leaving the prompt installing, repaints while a check runs, and no longer calls process::exit past the unsaved-work prompt.
2026-10-25 13:58:38 -04:00
Dylan M. Taylor adcc3b916b feat: startup update prompt modal and direct self-updating
Integrate `craft-update` pinned to storytold/craft-libs fe61243 to check for
updates on startup, display the standard suite update modal with changelog notes,
and support one-click in-app updating and restart.

Ref: storytold/craft-libs#319
2026-10-25 13:58:38 -04:00
Dylan M. Taylor 76402b1104 Merge pull request #2367 from storytold/codex/egui-037-effectcraft-20261023
Migrate EffectCraft to egui 0.37
2026-10-25 13:57:44 -04:00
zitann2020andDeepSeek V4.1 Flash 6938cf42da i18n: say in the test that A stands in for the row's baseline
epaint exposes no baseline, but a capital without a descender has its ink bottom on
it, so the Latin half of the pair is load-bearing. Suggested by FeiLiuEM in #1309.

Co-Authored-By: DeepSeek V4.1 Flash <noreply@deepseek.com>
2026-10-26 02:39:01 +09:00
zitann2020andDeepSeek V4.1 Flash a50a182a5f i18n: the baseline test reads the sign, not a fixed 1.5 pt
Measured on a second machine (Noto Sans CJK JP), the ink-bottom gap after the
correction is 1.125 / 1.9375 / 1.71875 pt at 12 / 15 / 18 pt, so the old bound
failed at 15 and 18 where the interface is right. Without the correction the kana
sits 3-5 pt above the Latin ink, so the sign separates the two and the magnitude
only has to allow the face's own kana overshoot.

Co-Authored-By: DeepSeek V4.1 Flash <noreply@deepseek.com>
2026-10-26 02:39:01 +09:00
zitann2020andDeepSeek V4.1 Flash 153fca19bc i18n: the CJK fallback sits on the Latin baseline (#1309)
Co-Authored-By: DeepSeek V4.1 Flash <noreply@deepseek.com>
2026-10-26 02:39:01 +09:00
Dylan M. Taylor e88feff569 engine.batch: drop the events a rolled-back atomic batch queued
A failing atomic batch restored the project, history and state but left the
events its steps had already published, so the UI could open the comp the
rollback had just undone. Snapshot the event count with the other snapshots and
truncate before the rollback bumps.

🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>
2026-10-25 13:14:28 -04:00
aiongg e5bb52cc35 craft-libs: pin every crate to 5bbe9f62 (craft-libs main with every shared docking fix) 2026-10-25 16:59:58 +00:00
aiongg 2c7cd6202b Merge upstream main into craft-ui-docking 2026-10-25 16:26:06 +00:00
Davit MatchakhelidzeandClaude Opus 5.5 ab7b3588ba i18n(ka): shorter Open Demo Project tile label, so it isn't clipped
The Home quick-start tile clips its label to the tile; "სადემონსტრაციო
პროექტის გახსნა" was cut off after "პროექტის". "დემო-პროექტის გახსნა"
fits, like the Import Footage tile; the File menu item (file.openDemoProject)
keeps the full wording.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 18:41:41 +04:00
Davit MatchakhelidzeandClaude Opus 5.5 939ead1f74 i18n(ka): shorter Discord button label, so it fits the Home sidebar button
"შემოუერთდით ArtCraft-ის Discord-ს" ran past the right edge of the
fixed-width Discord button at the foot of the Home sidebar (v0.17.0,
macOS). "შემოგვიერთდით Discord-ზე" ("Join us on Discord") fits; the Help
menu item keeps its full wording.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 18:31:59 +04:00
Kushagra Kumar 59b564326e fix(engine): default transparent to true in queue_frame 2026-10-25 19:51:06 +05:30
aiongg 6ffb7778e9 craft-libs: pin every crate to d24e4bcc (Mac round 3: docking fixes, drop previews, native floats stay visible while inactive) 2026-10-25 14:02:31 +00:00
aiongg fdc22548d1 D2: file dialog sheets go on the main window; floating OS windows are blocked while one is up
On macOS rfd put an unparented sheet on AppKit's main window, which after a
click in a floating panel's OS window is that window. With the sheet holding
the keyboard no app window was focused, so craft-ui hid the floating window,
the sheet ended without answering, and later sheets queued behind it: Save As
flashed and closed, and Cmd-S did nothing after. The commands' dialogs now
open at the end of the frame with the main window as parent (macOS; Linux's
portal dialog stays unparented). While one is up the dock area blocks
floating OS windows, as for a modal dialog, and a dialog that ends without
answering never leaves them blocked.
2026-10-25 14:01:52 +00:00
Kushagra Kumar 5d25346547 fix(engine): propagate transparency setting to queued frame saves 2026-10-25 19:31:46 +05:30
Brandon ThomasandClaude Opus 5.5 362474a843 Release 0.17.0
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
v0.17.0
2026-10-25 09:31:11 -04:00
aiongg de86a67348 craft-libs: pin every crate to 27e60765 (a minimized floating OS window shrinks to its bar) 2026-10-25 13:29:54 +00:00
Minher0 b50e74f295 layer.create: every instance3d copy in one undo step
The op ran Session::edit once per selected layer, so each copy was a history
step of its own: undoing removed them one at a time. Collect the eligible
models first and create all the copies in a single edit — one undo (or redo)
removes (or restores) them all, and each copy keeps a name of its own.
2026-10-25 13:22:14 +00:00
Minher0 601a15bc1f render: a 3D layer read outside its in/out range is transparent
solo_3d passed the layer to the 3D pipelines whatever the comp time, so an
instance (CC Composite referencing a model layer) rendered before the source
layer's in-point or after its out-point. Like any other layer reference, there
is nothing to read outside the range: return the transparent canvas.
2026-10-25 13:21:57 +00:00
Kushagra Kumar 77e2cf0554 fix(engine): expose transparent param in comp.saveFrameAs schema and test execute_checked 2026-10-25 18:44:35 +05:30
Minher0 82cebba68f layer.create: the instance3d op - a 2D copy of a 3D model layer (#2100)
Layer > Create > Create 3D Layer Instance, as After Effects (public docs):
3D model layers take no masks or effects, so the command puts a comp-sized
solid at the top of the layer stack with CC Composite referencing the
selected model, and switches the model off (back on to keep its shadow) -
a 2D copy of its render, through the comp's camera, that 2D effects can be
applied to.

- CC Composite (ec.channel.cccomposite) gains an optional Layer parameter:
  with a layer chosen it takes the original's place; In Front replaces the
  solid's pixels, alpha included, the other Composite Original modes blend
  with them. The GPU compositor runs such an instance on the CPU.
- FxHost serves a referenced model or primitive layer as After Effects
  does - the layer rendered on its own through the comp's camera
  (Renderer::solo_3d); the owner keeps no cache entry for it (it moves with
  the camera and lights, without a trace in its own properties).
- layer.create {op: instance3d} skips layers that are not 3D model or
  primitive layers (the ones that take no effects); one undo step, the copy
  selected, the model's video off.

Tests: tests_instance3d.rs (behaviour, pixels, undo/redo, serde, 2D effects
on the copy), a CC Composite layer-parameter unit test; docs/effects.md
regenerated (3033 parameters).
2026-10-25 12:46:58 +00:00
vangel76andClaude Sonnet 5.5 c82633c8d7 av1enc: find the first frame's quantizer in fewer passes
Bitrate mode coded the first frame four times with a fixed gain. Now the
first pass still gets the old correction (30 quantizer steps per halving
of the size, which fits the starting range), and from the second
correction on the next quantizer is placed in the log domain of the
quantizer step, where the frame size is close to a line: false position
between the nearest passes that enclose the target, otherwise a secant
step with the measured slope. The search stops as soon as the size is
within 3 % of the target, when no untried quantizer is left between the
enclosing passes, or when it would repeat a quantizer (a target the
content cannot reach, where the old code coded the same frame at the
lowest quantizer up to four times). At most four passes remain.

The first frame is within 3 % of the target on the same cells as before
and nearer to it on most of the others; two cells where the size jumps
by a factor of 4 or more between neighbouring quantizers (noisy content)
land further away than with the old search. Total passes over the 28
test cells: 112 -> 70.

Part of #2390

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-25 13:38:38 +01:00
vangel76andClaude Sonnet 5.5 80af9b45e2 av1enc: fix the bitrate overshoot of the bitrate mode rate control
Bitrate mode gave key frames keyint-dependent extra bits (about 6.8 frame
budgets at the 60 frame default) but left the inter frames at the full
frame budget, so every group of frames overshot by (boost - 1) / keyint,
9.6 % at keyint 60 (up to +187 % at keyint 5 on some content). The
accumulated deviation was tracked against the boosted budget, so the
feedback never saw it.

Now a group of keyint frames adds up to keyint * frame budget: inter
frames get (keyint - boost) / (keyint - 1) of it. Further changes to the
frame level control:

- The quantizer follows the size error with a measured slope (quantizer
  steps per halving of the size, taken from consecutive inter frames)
  instead of a fixed 12 steps per doubling. On noisy content the curve is
  several times steeper than that and the old gain oscillated between
  1 kB and 42 kB frames.
- Key frames have their own quantizer offset, adapted from key frame
  sizes only, instead of feeding the key frame error into the inter
  quantizer. A key frame after the first that misses its budget by more
  than 19 % is coded again (at most twice more), with a corrected
  quantizer found in the log domain of the quantizer step.
- A deviation from the budget is paid back over 16 frames, bounded to
  0.35x..2x of the budget.

Constant quantizer mode is untouched (byte identical output). The
encoded stream in bitrate mode changes.

Adds a test that the rate stays within 5 % of the target over 180
frames for three 8 bit and one 10 bit setting.

Part of #2391

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-25 13:38:37 +01:00
skylar taylor-barrick dec327b98a egui037: Preserve text ownership across unrelated history changes 2026-10-25 13:03:14 +01:00
vangel76andClaude Sonnet 5.5 590fd32055 av1enc: reuse scratch buffers in the transform path, the coefficient writer and the level search
try_tx and reconstruct allocated six Vecs per candidate (residual, coefficients,
levels, dequantised coefficients, inverse residual, reconstruction), the
coefficient writer allocated its level map per transform block, and the loop
filter level search cloned the luma plane for every candidate level.

- Decider keeps a boxed TxScratch (32x32 arrays) that try_tx and reconstruct
  write into; the levels and the reconstruction of a candidate stay there and
  the caller copies out only what it keeps (the winning intra candidate, or the
  levels that go into the leaf). intra_leaf uses stack arrays for its
  predictions and a fixed array for the mode ranking and transform types.
- TileWriter reuses one level-map Vec, and the partition CDF at frame edges is
  read through a slice instead of a copy.
- The level search filters one working copy of the luma plane and restores it
  from the reconstruction before each candidate.

The encoded stream is unchanged.

Fixes #2374

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-25 12:26:27 +01:00
vangel76andClaude Sonnet 5.5 611ffcb841 opusenc: eight partial sums in the 48 kHz decimator
The decimator computed each output as one sequential f32 dot product over
167 taps, which is bound by the 4-cycle latency of the float addition. Use
eight independent partial sums (combined pairwise) so the compiler can
vectorise the loop.

This changes the input of the SILK encoder at rounding level, so the
bitstream differs from before; decoded quality and size are unchanged to
within noise (see the PR for SNR, segmental SNR and size at several
bitrates). A test checks the dot product against an f64 reference.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-25 12:24:45 +01:00
vangel76andClaude Sonnet 5.5 63c9ac8941 opusenc: vectorisable candidate filter for the greedy PVQ pulse search
pvq_search scored every coefficient in a serial compare-and-select chain per
pulse. Score all coefficients branch-free in 8-lane chunks instead (square
over energy, one division), take the maximum, and run the original exact
cross-multiplied test only on the entries within 1e-5 of it, which is
normally a single entry whose index comes from a lane-wise count. The chosen
pulse, and with it the bitstream, is unchanged; a test compares the result
with the old scalar search on 6000 random, sparse, tied and extreme-scale
vectors. The pre-projection also uses the truncating cast instead of a floor
libm call (the value is non-negative).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-25 12:18:35 +01:00
vangel76andClaude Sonnet 5.5 b4b7c2d339 av1enc: build the three half-sample reference planes in one pass
Decider::new built the half-sample planes (8,0), (0,8) and (8,8) with
three full 2-D 8-tap passes (subpel_plane), each with per-tap clamping.
half_planes computes them together: the horizontal half-sample pass is
shared by (8,0) and (8,8), the phase-0 kernels are exact shifts
(Round2(128*v, 11) = Round2(v, 4), Round2(16*s, 11) = Round2(s, 7)), and
interior pixels skip the clamps.

subpel_plane is kept as the test-only reference; a new unit test checks
half_planes against it bit for bit (including 1x1, 5x3 and 10 bit).
The only change in decide.rs is the call in Decider::new.

Output is bit-identical.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-25 12:15:23 +01:00
vangel76andClaude Sonnet 5.5 9c1f8bfa41 opusenc: radix-2/3/4 FFT kernels for the MDCT
Every FFT stage was a generic DFT butterfly: p * p complex multiplications
per butterfly (16 for radix 4) and a modulo on every twiddle index. Add
kiss_fft-style kernels for radix 2, 3 and 4 (three twiddle multiplications
and eight additions for radix 4, a sqrt(3)/2 constant for radix 3, +-j done
by swapping parts). The generic butterfly stays for radix 5, which occurs
once per transform, with its roots precomputed. The modulo is dropped: the
twiddle index q * k * tw_stride is always below n.

The results differ from before at f32 rounding level, so a few packets
change on speech and hybrid input (CELT music stays byte-identical). A new
test checks the FFT against a direct DFT for every radix combination up to
the 480-point frame size.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-25 12:14:10 +01:00
vangel76andClaude Sonnet 5.5 12af1c19cb av1enc: truncate instead of floor, log2 lookup table, skip division in the dead zone
The quantiser in try_tx called libm floorf for every coefficient, estimate_bits
and the motion-vector cost called f64::log2, and coefficients that quantise to
zero still went through an f32 division.

- (x / q + rnd).floor() as i32 becomes (x / q + rnd) as i32: the operand is
  never negative, so truncation equals floor.
- log2 of small integers comes from a table built with the same f64::log2
  call (estimate_bits and the MV cost in motion_search), so the values are
  bit-identical.
- Coefficients below an exact per-(q, rounding offset) threshold are zero
  without dividing. The threshold is found once per Decider by a binary
  search over f32 bit patterns of the monotone quantiser expression.

The encoded stream is unchanged. Unit tests compare the new quantiser with the
old expression at the threshold boundary and on random values for every
quantiser index, and the table with f64::log2.

Fixes #2373

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-25 12:13:44 +01:00
vangel76andClaude Sonnet 5.5 9a0e19aae7 opusenc: memoise the pitch contour correlations
The contour search tries 5 base lags times 34 codebook contours times 4
subframes, 680 normalised correlations, but only about 4 x 30 distinct
(subframe, lag) pairs occur. Cache them per call. The values are the same
f64 results as before, so the bitstream does not change (cmp on 24 SILK and
hybrid configurations of 90 s, mono and stereo, speech and music).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-25 12:05:19 +01:00
vangel76andClaude Sonnet 5.5 fd84350122 opusenc: halve the a2nlsf bisection steps
Each root was refined with 40 bisection steps, which shrinks the bracket to
about 3e-15 rad. A Q15 NLSF step is 9.6e-5 rad, so 20 steps (3e-9 rad) are
far more than the quantiser can see. The saved cos calls are the remaining
bulk of the root search after the grid table.

The coded packet streams are byte-identical to the 40-step search on 24
configurations of 90 s: speech-like and music-like input, mono and stereo,
SILK NB 8k, MB 12k, WB 16k and 32k, hybrid SWB 32k and FB 64k.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-25 12:04:03 +01:00
vangel76andClaude Sonnet 5.5 587d8bdf5b opusenc: tabulate the cosine grid of the a2nlsf root search
The LPC to LSF conversion scans a 1024-point grid for sign changes of two
cosine series and called libm cos for every series term, about 21,000 calls
per channel and frame (30-40 % of SILK and hybrid encode time). Compute
cos(m * w) for every grid point and multiple once, with the same expression
as before, so the values are bit-identical and the bitstream does not change.
Only the bisection steps still call cos.

A test compares the new root search against the previous one bit for bit on
random symmetric polynomials; a2nlsf rejects orders beyond the table.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-25 12:02:37 +01:00
vangel76andClaude Sonnet 5.5 18ce00da88 av1enc: specialise the inverse transform per size, size the forward tmp buffer
The inverse transform computed its bit-reversal permutations and loop
bounds at run time and went through one generic path for every size.
inverse_dct, the ADST permutations and inverse_2d are now const generic
over the size and inlined, so the permutations fold at compile time.

forward_2d zeroed a 4 KiB tmp buffer on every call, even for 4x4. It now
dispatches to a fixed-size version for 4, 8 and 16 (tmp is exactly n*n
floats); 32 keeps the run-time version, which measured faster than a
const 32 variant. The float operation order is unchanged.

Output is bit-identical.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-25 12:02:23 +01:00
vangel76andClaude Sonnet 5.5 e3593d608a opusenc: build the PVQ V table once for encode_pulses
encode_pulses rebuilt the full V(n, k) table with saturating u64 adds for
every coded band and summed the prefixes through a closure. Build the table
once (n <= 176, k <= 128, saturated at u32::MAX, 91 KB) and sum the prefixes
over a contiguous row slice. Shapes outside that range, which the band
layout never produces, fall back to a local table. The unused scratch Vec
is gone from BandEnc.

The bitstream is unchanged; a test keeps the old per-call encoder and
compares the coded bytes on random codewords over the whole (n, k) range.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-25 12:01:39 +01:00
skylar taylor-barrick 18206c1014 Fix text ownership after composition removal and simplify playback helpers 2026-10-25 11:40:05 +01:00
vangel76andClaude Sonnet 5.5 0de5887fe7 Format the merge
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-25 10:33:59 +01:00
vangel76andClaude Sonnet 5.5 4c8b89a6be Merge upstream/main into fix-1083-beat-marker-audio-pause
Conflicts in project/src/lib.rs and ui-egui/src/frames.rs: Comp::same_settings
takes with_markers and is shared by same_pixels_with and changed_span (#1641);
frames identity uses same_pixels_with/reads_markers and drops
same_pixels_ignoring_markers and project_has_expressions.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-25 10:33:46 +01:00