aax
無料Optional AAX support for Pulp, including developer-supplied Avid SDK setup, CMake enablement, DigiShell/AAX Validator workflows, and local AAX builds on macOS or Windows.
日本語の概要は準備中です。原文の説明を表示しています。
Building and shipping a Forge app (Modular, Instrument, MIDI, FX, and the sequencer work to come) as a signed installer somebody else can actually use. Covers the seam between the Forge and Pulp repos, what a green signal does and does not prove, shipping the runtime rather than only the binary, and the wiring gaps that make a finished feature behave like a missing one.
インストール方法を見るインストールする前に、エージェントに与えられる指示の中身を確認できます。
A Forge app is not one binary. It is a shell compiled in a different repository, a Python generator, a Rack plug-in, three plug-in formats, an uninstaller and a toolchain the user's machine fetches for itself. Every delivery failure recorded here came from one of those parts being absent, stale, or present but unreachable while every check reported success.
Read this before packaging anything, and before concluding that a feature "works".
Verify identity, not size, and never a signature. In a single day this project shipped:
ditto copies)Contents/MacOS at configure time, so -e is true for
an empty husk)The third is the important one. Size caught the first two and was useless for the third, because the payloads were the right size. Only identity distinguishes a correct build from a plausible one. Pick a string that exists in the current build and cannot exist in a stale or wrong one, and assert it in the packaging script:
hits=$(strings "$binary" 2>/dev/null | grep -cF "$SHELL_MARKER" || true)
Then expand the finished package and check the payload, rather than trusting the script that made it:
pkgutil --expand "Foo.pkg" out # --payload-files does NOT recurse into
# nested component payloads
core/simd/includeexamples/forge-modular/package.sh refuses to pack when any consumed input
tree is dirty, and that list now includes core/simd/include because signal
headers include <pulp/simd/simd.hpp>. A local edit under core/simd blocks
a pack exactly like an edit under core/signal.
Forge app shells live in forge-seam/, are copied into a throwaway Forge
worktree by forge-seam/populate.sh (/tmp/forge-cur), compiled there, and
copied back by forge-seam/sync.sh.
Consequences that have each cost a cycle:
/tmp is cleared. Run forge-seam/sync.sh before finishing any session
that touched a shell source, or the work is gone.forge-seam/patches/ or they
are lost on the next repopulate.forge-seam/*.cpp are noise. Those files only
resolve their includes inside the Forge worktree. A wall of "no type named
string in namespace std" means you are reading the file outside its build,
not that the file is broken.examples/<app>/app/src/shell.cpp.
A packaging script that hardcodes one path cannot be pointed at the other by
any argument. Print which artifact you selected — a silent choice between
two same-named apps is how the wrong one shipped.Forge Modular, Sequencer, Instrument, MIDI, and FX share Pulp infrastructure but do not share one product path. A fix discovered in one app therefore starts with two questions: can the capability or invariant live in Pulp, and which Forge descendants consume the affected seam? Put reusable behavior in Pulp or the common Forge layer rather than copying it between apps. Then record every descendant as one of:
Never infer product acceptance from common ancestry. Modular still needs its module/Rack/standalone/DAW ladder; Sequencer needs its own timeline, MIDI, transport, persistence, and control-surface ladder. The durable win is one shared repair plus narrow product-specific proofs, not identical code or a single sibling's green build standing in for the rest.
The supported Modular-only signed/notarized installer entry point is
examples/forge-modular/release-package.sh. Its product binaries come from a
clean detached current Forge checkout and its Release build; do not reconstruct
them from forge-seam/patches/BASE, which records the historical patch seam and
is not release source authority.
The release driver deliberately passes exactly Forge Modular AU, VST3, CLAP,
and Standalone to the shared build_combined_installer.sh primitive. It rejects
different Forge source snapshots, Pulp SDK refs, toolchain stamps or toolchain
content across those four bundles, requires the runtime-resolved Rack
saved-patch decoder and checks both bundled helper architectures, then expands
the finished PKG and refuses
any FX, Instrument, MIDI, Sequencer, or other product bundle. Notarization is
not optional on this path. Local packaging experiments that need an unsigned
artifact continue to use the older development package.sh; they are not a
release claim.
A Forge app runs a Python generator. If the installer carries the app and not the generator, it installs and does nothing.
This shipped. The package had the app, three plug-in formats and the Rack modules,
and zero Python. It went unnoticed because the build machine had the tools
seeded into ~/Library/Application Support/<app>/tools/ by an earlier manual
step, and that path wins the lookup.
Two halves are required and either alone still ships a dead app:
tools_dir() looks inside its own bundle (walk up from
_NSGetExecutablePath to Contents, then Resources/tools/…), after the
Application Support copy so a user-replaceable copy still wins.The same boundary applies to native helper executables shipped inside that toolchain. Build them at package time for the package's declared architecture, not at end-user install time and not implicitly for the packaging host. A user may have no compiler, and an arm64 packaging host may be producing an x86_64 artifact. Verify the helper's file identity, requested Mach-O slice, mode and system-only linkage before signing. Execute it only when its architecture is native to the verification host; requiring a foreign slice to run silently turns Rosetta into a packaging dependency. Cross-architecture verification must remain structural, while a native package lane exercises the helper against a real fixture.
Signed installer staging also has a hard precondition: Pulp's unattended
signing doctor must pass before the first production codesign. A missing or
failed preflight terminates packaging; never skip it or turn it into a warning,
because login-keychain fallback can open a GUI password prompt and wedge an
agent/SSH/CI session. The shared doctor owns dedicated-keychain repair, full
partition authorization, identity-hash selection, and the real timestamped
probe.
Test from a clean state. Move the seeded directory aside before testing, or you are exercising the fallback that hides the bug:
mv ~/Library/Application\ Support/<app>/tools{,.bak}
The app's working copy of the generator lives in Application Support and the shipped one lives in the bundle. Preferring Application Support unconditionally means a toolchain written by an older release shadows every fix a newer one ships — and it fails silently, because the shadowed script is old enough to reject a subcommand the new app calls.
That is exactly what happened: library_catalog.py index did not exist in the
August 1st copy, so the app asked for a library index on every launch, the
script printed its usage, exited 2, and four days passed with a 200-plugin
index nobody could explain.
package.sh writes
Contents/Resources/tools/rack/VERSION (version, then packaged-at);
choose_toolchain() prefers the bundle only when it is strictly newer, so
an equal stamp leaves the installed copy in charge and hand-editing it still
works for development. An unstamped directory is the oldest thing there
is, which is what every pre-stamp machine looks like.install_toolchain.sh must not strip that stamp. It rsync --deletes
from its source, and a source checkout has no VERSION — so a developer's
install would make the destination look older than the release and lose to it
forever. Exclude VERSION when the source lacks one.library_index_command() writes runs/library-status; the
settings row turns that into "the refresh failed (exit 2), see …". A log
nobody opens is the same as no report at all.package.sh --version named the .pkg and nothing else, so an installed 0.12.7
answered CFBundleShortVersionString 0.11.0 and 12.6 was indistinguishable
from 12.7 on the machine. Stamp the version into the app and all three
plug-in bundles (staged copies, before signing, so the signature covers it),
then read it back out of the expanded package and refuse the release when
it disagrees.
Ship a details surface with it. The field that matters most is the live
toolchain path — had it been visible, a day of shadowed fixes would have been
obvious in seconds. Version, packaged date, that path and its stamp, index
count and age, Rack SDK location, and whether a VCV sign-in was found — never
the token. A Label cannot be selected with a mouse, so a Copy button is the
affordance; promising selectable text you do not have is the same kind of claim
as an installer promising modules it lacks.
Forge's settings card is a fixed 660 tall and the Permissions pane was not a scroll view. A pane taller than the card therefore did not clip — flex shrank whatever it could, captions collapsed to zero height, and every row drew on top of the one below it. Adding one product row made it unmistakable; it was already true of the built-in ones.
There is a second, independent cause worth knowing anywhere in Pulp: Yoga's
measure callback asks a Label for its INTRINSIC width first, and a
paragraph's intrinsic width is the whole thing on one line — so the height it
measures is one line however narrow the label is bounded to. Reserve the real
height explicitly with label.measured_height(bound) when you bound a
multi-line label's width.
Every one of these was a finished feature that behaved exactly like a missing one, and each was found by a user rather than a test:
| Symptom | Cause |
|---|---|
| Auto-download never worked | token sent as a query parameter; the API wants a cookie, so it had returned 403 for every plugin since it was written |
| Patches used self-built lookalikes of famous free modules | the model was never given an inventory, so it could not know they existed |
@-mention told the user to go install it themselves | the download function existed and that path did not call it |
| A setting had no effect | it was added to the defaults and read by nothing |
| A setting could not be changed | it existed only in a JSON file with no UI |
| A shipped fetch script did nothing | nothing invoked it, and it installed to a third path neither reader used |
Before claiming a capability works, trace it end to end from the surface the user touches. "The function exists" is not the claim being made.
The most recent entry is the subtlest, because every part of it behaved as specified: naming something did not fetch it. A prompt that named a maker expanded correctly into the model's brief, the model reached for that maker four times, was told each time that the plugin was not installed, substituted something else and said so honestly, and the count at the end read "0 module(s) drawn from this maker". The download machinery worked and had one trigger — a missing-CAPABILITY gap in preflight. A mention was not a trigger at all. If a surface lets somebody name a thing, naming it has to guarantee it is there before the thing that consumes the name runs. Bound what a category fetches (a maker is a preference, so rank by the request and cap it) and keep the exactness for what was named outright.
Two design rules fell out of this:
premium put everything
free ahead of the 70 premium plugins the user had bought. Sort by what it
takes to obtain a thing (installed → owned → free → unavailable), and never
conflate "premium" with "not owned". Downloading something already paid for is
not a purchase.The generator's working copy is laid down by install_toolchain.sh from
Contents/Resources, and an installed bundle is root-owned and sealed. Two
consequences, both of which stopped the first build on a genuinely clean
machine dead:
rsync -a reproduces the source's modes. The module pack arrived
r--r--r-- and the panel emitter died on PermissionError: .../res/ATT.svg
— after installing everything and verifying nothing. A copy that is going to
be rewritten must be made writable explicitly.rsync is openrsync, which ACCEPTS --chmod and ignores it. No
error, no warning, not one mode bit changed. Set the modes afterwards with
chmod -R u+rwX, by a tool that does what it says.chmod -R u+rwX "$DEST") before
copying.Simulate this by making the staged bundle read-only (chmod -R a-w) before
testing the install path. A writable copy of the app tests nothing.
examples/forge-modular/src/ holds the modules AND test_portmap_merge.cpp,
which has its own main. It compiled into the plugin dylib harmlessly for
months. The behavioural gate links those same objects beside its main, so
every module build ended in duplicate symbol '_main' — three attempts, three
model calls, several minutes — and the gate had therefore never passed for any
generated module.
Two rules fall out of it:
generate.py's sources() and the CMake glob both skip _* and test_*.error:. The
only error: line a linker emits is "linker command failed", which says
nothing; the symbol is in the lines above it. The message read as a blank
refusal for exactly as long as that filter existed.until kill -0 "$PID" 2>/dev/null; do sleep 10; done after
capturing PID=$!.pgrep -f "<cmd>" is a completion signal only for a command that never
execs. The packaging wrappers this skill documents all end with
exec "$ROOT/tools/scripts/build_combined_installer.sh" ..., which replaces
the process image — so pgrep -f package.sh returns 0 while the same PID
is still running, for the entire signing + packaging + notarization phase.
Two live release pipelines were declared dead that way in one evening, and
each was "recovered" by a hand-written script that stapled a .pkg the
running pipeline had already finished. For a packaging run specifically, the
truthful signal is the recipe's own heartbeat
([heartbeat] notarization in progress (Ns elapsed)); see the ship skill.grep -qF under set -o pipefail exits on first match, SIGPIPEs the
upstream command, and fails the pipeline — so a binary that does contain the
marker is rejected for containing it. Count instead; it drains the stream.find … | head -1 in Contents/MacOS returns libwgpu_native.dylib,
which is copied in beside the executable and sorts first. Prefer the file named
after the bundle.ls a/*.x b/*.x reports nothing found even when a/ has matches. That
produced a false "the Rack modules are missing" conclusion.REQUIRE(cache.stats().builds > 0)) before asserting it was cheap.sample and other profilers fail silently over SSH (TCC is per-process).returncode != 0 was taken to mean "this
patch makes no sound", so six generations in a row ended "gave up after 3
attempts" with an empty explanation and nothing anywhere saying a process had
died. A negative return code is a signal, not an answer: check
returncode < 0 separately and say which signal and what it was loading.
The same shape applies to any gate that shells out.
Naming it is only half. The retry context still said "structurally valid
but SILENT when run" whatever had happened, so the model was sent to fix a
fault nobody had measured and the patch was discarded at the end anyway. A
check that could not run must not feed the verdict path at all: keep the
artifact, say the doubt out loud.~/Library/Logs/DiagnosticReports/*.ips, or lldb with
settings set target.env-vars DYLD_LIBRARY_PATH=… — the env var is stripped
from a debugged process, so a run under lldb otherwise dies in dyld and looks
like a different bug):
EXC_BAD_ACCESS at 0x10 in a module's constructor. APP is
rack::contextGet() and is null until something calls contextSet();
Context::engine sits at offset 0x10, so any module reading the sample rate
while being built dies. Bogaudio's base module does it for all 111 models.EXC_BAD_ACCESS at 0x0 in a module's process(). A constructed module is
not a running one. Rack sends onSampleRateChange then onAdd before it
ever calls process(), and modules allocate their DSP buffers there — CV
funk's Alloy sizes a delay line in it, so the harness read through a null
pointer with a zero ring mask.
Fixing the first uncovered the second, and each survives the other's fix, so
one plugin is not a proxy for the other in a regression test.chmod -R a-w (above) leaves --out unwritable, and the next
package.sh run dies in a wall of rm: Permission denied that reads like a
packaging bug. chmod -R u+rwX "$OUT_DIR" before removing it.Every test ships broken-on-purpose once. A test that has never failed has not been tested. State the mutation and its result when reporting.
/Applications/Forge Modular.app/... unquoted is read as far as Forge and
reports "no such file or directory" — which reads as a missing uninstaller
rather than a quoting mistake. Show the quoted form and say why.fetch_sdk.ensure() says what it is about to do and then does it, and
nobody is asked. The sentence now describes that. Softening copy is only
half the fix, because it is correct only while the code stays
announce-and-fetch: check_installer_promises() in tools/rack/test_patch.py
asserts BOTH halves, driving ensure() with stubs to prove the order is
announce-then-fetch and then scanning the pane for consent wording. Build the
prompt later and that check is what says the pane has to be rewritten with it.
Note the setting that governs it, auto_fetch_sdk, has no control in
Settings (settings_choices() exposes module source, downloads, the time
limit, the index refresh and the about pane, and not this one) — so do not
write copy telling anybody they can switch it off there.required (enabled="false" selected="true"). For Forge Modular that is the app, because the Rack modules
and the uninstaller live inside its bundle..vcvplugin is the only artifact linking the SDK. Keeping it a
separate payload from the app preserves that boundary; merging them blurs it.Two per-frame walks in Pulp were written for a plugin editor's tens of views and
each asked libc++abi a question per node: needs_continuous_frames tried six
dynamic_casts (three of them through multiple inheritance, so each miss walked
__vmi_class_type_info), and the host-parameter pump asked "is this a
DesignFrameView". Sampled on an idle Forge Modular window on an M3 Ultra those
two were ~29% of the process — more than the Skia drawing and the Yoga layout
they were gating. Both are now a virtual call and a bool.
The general rule: anything that runs over the whole view tree every frame must not use RTTI. A shell's tree is thousands of views, and 120 Hz multiplies whatever a node costs by four or five orders of magnitude.
Measuring it: no PULP_TRACING in these builds, so sample <pid> 10 -f out.txt.
It fails silently over SSH (TCC is per-process), so run it from a window on the
machine. Read it by attributing each libc++abi run to its nearest non-libc++abi
ancestor, or the cost hides inside dyn_cast_slow where no Pulp symbol appears.
Thread count is not a smell by itself: an idle window here was 20 threads, all
accounted — main, CVDisplayLink, NSEventThread, four CoreAudio (caulk* +
IOThread.client, because the standalone opens a device), eight Dawn
AsyncWorkerThreadPool workers, one BackgroundJobService, four libdispatch
workers serving Metal/CoreAnimation queues.
Sequencer-specific notes go here when that build starts. The parts above are app-agnostic: the seam, the staging, the identity check and the wiring-gap class apply to any Forge shell. What tends to differ per app is the generator's external toolchain (Forge Modular needs the Rack SDK and a C++ compiler) and what "the runtime" means for it — enumerate that first, because it is the thing most likely to be left out of the installer.
The A4 DPR matrix requires the exact Forge Modular native shell, not a Pulp example standing in for it. Use a fresh Forge worktree, record both Forge and Pulp SHAs, and preserve binary identity with the result artifacts. Exercise the same logical fixture at each requested DPR and mode; a rebuilt or substituted payload is a different trial. Do not publish a scale-policy conclusion from a planned/synthetic run or before the A2T trace and A3 budget dependencies exist.
Register each real Forge shell with tools/scripts/gpu_dpr_runner.py as its
scenario-specific executable adapter. The adapter receipt must bind the exact
Forge/Pulp SHAs and binary; missing native, DAW, or browser legs stay explicit
dependencies and must not be replaced with a Pulp example.
For terminal v2, initialize only after the fixed A2T, A3 product-policy, and A3
runtime receipts are terminal on protected Pulp main. The runner snapshots the
actual Forge adapter, exact executable product, producer/product/fresh-process
identities, and all eight artifacts for each original/repeat cell. Do not pass
a Forge-authored manifest, draft result, or disposition to finalize-v2; it
derives them from the terminal dependencies and 168 accepted nonce receipts.
Protected publication is a separate fixed-path Git-blob verification step.
The Forge producer must also use the shared instrument-validity contract: each
metric declares measured/derived/unavailable provenance; timer samples carry a
detectable known-extra-work calibration; the expected logical point/target
comes from the frozen scenario while Forge reports the actual event and hit;
and two same-content captures supply numeric similarity, text, and stroke
oracles. Preserve any older self-satisfying or constant-valued receipts as
SUPERSEDED/NONCOUNTED, not as partial terminal cells.
Forge shells may reuse ControlGpuHealthViewAdapter by supplying their real
back-buffer capture and GpuSurface callbacks. The provider is Pulp-owned and
bounded, but a Forge canary still needs exact Forge/Pulp binary identity and a
correlated trace. Forge is a visible role: declare
native-compositor-presentation and supply an independent compositor timestamp;
capture completion is only an upper bound. Missing causal instrumentation stays
nullable and named rather than becoming fabricated Vellum evidence. Leave an
unratified startup budget unverified; never substitute a Pulp example
for the requested Forge product. Exercise the receipt-producing seeded blank
control documented in docs/validation/gpu-first-visible-a3-acceptance.md
before accepting a real-machine result.
For the A3 terminal campaign, invoke the exact Forge-shell adapter through
gpu_first_visible_a3_campaign.py run-role --role forge. Its identity JSON must
use plugin_format: standalone and bind exact Pulp and Forge revisions; a DAW
plugin format or Pulp example is rejected even if its pixels match. The adapter
owns the real 10-cold/10-warm shell lifecycle and native presentation source.
The runner owns the immutable adapter/budget snapshot and closed artifact
validation. Preserve timeout, unavailable shell delivery, and missing
post-Vellum instrumentation as explicit dependencies rather than substituting
another executable.
For the executable handoff, use
gpu_first_visible_a3_external_adapter.py with the checked-in
gpu_first_visible_a3_forge_producer.py. Configure clean exact-revision Pulp
and Forge roots, the exact app executable for both product and host identity,
the product-specific lifecycle driver, checked-in source-bound trace analyzer,
embedded-build verifier, and the digest-bound Forge build
attestation/receipt. Configure a reviewed Pulp- or Forge-owned source-build
driver as well; it must reproduce the executable and complete .app tree from
clean exact-revision sources without receiving the measured path. Both trees
must bind CFBundleExecutable, CFBundleIdentifier, and CFBundleName to the
request. The producer pins and rehashes
those inputs; the driver must resolve to reviewed source at the declared clean
Pulp or Forge revision. It requires 10+10 reopen/reset rows, producer-observed
live executable/start identities, independent native presentation, terminated
owned Forge PIDs, and named replay on the challenged trace-host PID. A missing driver,
provenance, dirty/wrong source head, different Forge build, or Pulp demo stays
nonterminal or fails closed.
The exact Forge executable must carry the canonical embedded build marker. Its
role campaign cost includes active product spans, but terminal A3 also requires
the separate four-state pre-change/compile-out/compiled-in-idle/active product
overhead receipt with zero xruns and audio-thread trace events. Keep the
analyzer's structural unverified result distinct from the campaign budget
verdict; neither an offline A2T classification nor a passing Forge role waives
the overhead control.
Run that control through gpu_first_visible_a3_trace_producer_overhead.py collect-state with the exact Forge executable and reviewed Forge lifecycle
driver. Each state requires 55 live challenges; the source-built .app and
measured executable identities remain exact, and idle/active reuse identical
bytes. Active binary Perfetto replay covers both the health producer and the
complete b4ba exact 20-signature state/render/js inventory from the A3
guide. Acquire/submit/present are mandatory; all other signatures are counted
and an unobserved one stays not-covered, not zero-cost. Require zero
xruns/audio-thread producer events. A direct driver result or Pulp example is
nonterminal.
Also pin the candidate-relative state_build_driver. The collector exports the
exact source row and default-deny rebuilds it without the measured .app,
ambient build output, or network, then byte-compares the rebuilt executable and
checks its tracing sentinel. Retain source archive, closed build
request/receipt, product, logs, and toolchain snapshots; a pre-existing Forge
bundle cannot satisfy this proof.
Forge A3 v2 campaigns must retain producer/sample provenance and exact trace/analyzer artifacts. Terminal validation reruns the prepared analyzer and binds the result to the Forge revision, role, host process, evidence cohort, categories, and capture completeness.
まだレビューはありません。使ってみた感想をお寄せください。
概要と使いどころ
Optional AAX support for Pulp, including developer-supplied Avid SDK setup, CMake enablement, DigiShell/AAX Validator workflows, and local AAX builds on macOS or Windows.
日本語の概要は準備中です。原文の説明を表示しています。
Configure, implement, and test Pulp's optional desktop Ableton Link tempo-sync adapter while preserving the developer-supplied SDK, licensing, realtime, latency-compensation, and no-install boundaries.
日本語の概要は準備中です。原文の説明を表示しています。
Maintain Pulp's installed design-time agent capability manifest and public-surface ledger. Use when adding, removing, renaming, or materially changing public audio, MIDI, signal, timebase, or sequence APIs; registering a new algorithm for generators; changing capability support or deprecation state; or repairing agent-capabilities freshness, schema, fingerprint, tombstone, or installed-SDK tests.
日本語の概要は準備中です。原文の説明を表示しています。
Android platform development for Pulp — NDK cross-compilation, Oboe audio, Dawn/Skia GPU rendering, JNI bridge, touch interaction, emulator workflows, and end-to-end smoke validation. Covers build, deploy, debug, and the gotchas discovered during bringup.
日本語の概要は準備中です。原文の説明を表示しています。
Optional ARA support for Pulp, including developer-supplied ARA SDK setup, CMake enablement, adapter companion APIs, validation, and ARA-aware plugin implementation guidance.
日本語の概要は準備中です。原文の説明を表示しています。
The measurement surface for ALL Pulp DSP and audio-pipeline work — read it BEFORE writing or gating DSP, not only when something already sounds wrong. Covers the C++ harness (signal generators, metrics, assertions, RenderScenario, contracts), the offline Audio Doctor (magnitude/frequency response, THD/THD+N, phase/group delay), and their Python sibling the Audio Quality Lab (tools/audio/quality-lab — null residual + alignment, LTAS log-spectral distance, spectral flux/centroid, HNR, Theil-Sen drift slope, Kaiser-sinc resampling, license-guarded corpus, regression-net ratchet). TRIGGER on AUTHORING work — "build/design an oscillator/filter/synth/effect", "add a DSP module", "what should the acceptance gate be", "how do I measure aliasing / anti-aliasing / alias floor", "null against a reference", "is this DSP correct", "choose a tolerance", "golden/regression corpus for audio", "measure drift or jitter", "A/B two renders" — AND on DEBUGGING work — "is there sound / no audio / I hear nothing", "does this filter/compressor/synth/delay produce the right signal", "prove the DSP / prove the contract", "measure the frequency response", "what's the THD / is it distorting", "what's the group delay / phase response / measured latency", "magnitude response curve", "render a test tone and assert", "audio regression", "64-frame works but 128 is silent", "sample-rate change pitch-shifted it", "describe what's in this buffer", "audio doctor", "compare before/after a DSP refactor". Reach for this BEFORE hand-rolling any FFT, null test, alias measurement, pitch tracker, or golden-render script — most of it already exists in one of the two lanes. Test/tool layer over HeadlessHost — deterministic, no audio device, no speakers. Off the realtime thread entirely.
日本語の概要は準備中です。原文の説明を表示しています。