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.
日本語の概要は準備中です。原文の説明を表示しています。
Offline time-stretch / pitch / varispeed — character modes, fine-tune presets, A/B toolkit, and the honest quality state, so an agent can pick a mode, dial it in, and ship a plugin with it.
インストール方法を見るインストールする前に、エージェントに与えられる指示の中身を確認できます。
pulp::signal::OfflineStretch is the non-realtime, max-quality stretch/pitch
engine. This skill is the agent-facing guide: which character mode to use,
how to fine-tune and share a preset, how to A/B measure, and what's
genuinely good vs. still cooking. Headers:
core/signal/include/pulp/signal/{offline_stretch,stretch_preset}.hpp. Dev CLI:
examples/offline-stretch/stretchcli. Eval toolkit: examples/offline-stretch/eval/.
Most DSP helpers now expose *64 aliases when the underlying class is templated,
including OfflineStretch64 and RealtimePitchTimeProcessor64. The offline
stretch public config remains control-plane double regardless of sample type;
only the sample buffers and internal DSP storage widen for the f64 aliases.
Keep quality comparisons explicit about which sample type is under test.
For clip-ready streaming, prepare RealtimePitchTimeProcessor in
PitchTimeMode::time_stretch and check its PitchTimePrepareStatus.
Non-positive sample rates, channel counts outside the prepared ceiling, and
non-positive max_block capacities are rejected before processor state changes.
Time-stretch sizing also requires a finite max_time_ratio >= 1; pitch sizing
requires a finite, non-negative semitone bound whose derived ratio is finite.
Preparation rejects any otherwise-valid bound whose synthesis hop, power-of-two
ring, or typed backing-buffer byte size is not representable in the target
address space; never rely on a later float-to-int conversion or allocation
failure to police capacity.
Optional FFT overrides must satisfy the spectral engine's 256–16384 power-of-two
window and analysis_hop <= fft_size/2 invariants or preparation rejects them
without changing prior state.
The realtime processor also stages its spectral-envelope analyzer before it
publishes any new configuration or retained processing state. Keep that prepare
transactional: an analyzer capacity failure must leave the previously prepared
processor usable rather than half-adopting the new geometry.
feed() is all-or-nothing: on
backpressure, drain available_stretched() with read_stretched() and retry
the identical input block. Never advance a decoder on a rejected feed.
At source EOF, call finalize() repeatedly, alternating with output reads,
until it returns complete. Finalization seals input, advances at most one
prepared block per call, preserves the overlap-add tail, and publishes the
frame-map-derived duration (exactly round(input_frames * ratio) for a constant
ratio). A later feed returns input_closed; reset starts a new stream.
input_priming_samples() is how much source must be queued before the first
final output becomes readable. It is not leading silence:
output_alignment_samples() is zero. output_free_space() is an advisory
scheduling value; the typed feed/finalize result remains authoritative. These
methods allocate nothing after prepare(). Timeline TimeConform::Stretch uses
this finite path only during program compilation. The compiler first
materializes the exact source slice at the compiled timeline sample rate,
updates ratios only at analysis boundaries from the authored tempo map, and
accepts only an exact target-frame result. It then publishes immutable audio
that the renderer reads 1:1. This offline path uses the scalar double finite
builder and a bounded final conversion to float so separate compiles remain
bit-identical within one build/platform. Compiler work-block size is
deliberately absent
from cache identity and must not change output. This is offline compilation,
not the separate live/realtime stretch lifecycle.
Higher engine layers use the public audio-domain boundaries
FiniteTimeStretchJob and RealtimeTimeStretchProcessor; they must not expose
pulp::signal types through playback headers. The realtime facade is prepared
on the control thread, reports its complete retained-state charge and fixed
causal delay, and is allocation-free after successful preparation. Timeline
live playback uses it only through a prepared RealtimeStretchProgramRuntime,
with one compensated latency shared by Stretch, parallel non-Stretch audio,
MIDI 1, and UMP output. Keep both audio implementation translation units in the
native no-exception mirror and the WAM/WebCLAP portable dependency inventories.
Source/filter analysis is a prepared control-thread facility. Reach it through
pulp/signal/source_filter_analysis.hpp (also exported by signal.hpp) for a
cepstral envelope, LPC coefficients, Schur stability, or an all-pole response;
formant extraction is deliberately not part of this contract. Preparation is
transactional and reports typed size, configuration, or allocation failures.
The analysis calls themselves use only retained scratch after a successful
prepare. SpectralEnvelopeShifterT likewise rejects a non-finite or negative
max_gain_db without changing its prior prepared state. Its runtime warp
must be finite and strictly positive: zero, negative, NaN, or infinity is a
fail-closed no-op on the frame, never a value to clamp or pass to an index
calculation. Preserve that guard before any scratch or frame mutation when
changing the stretch spectral path.
For reproducible offline artifacts, prefer FiniteStretchBuilder64 from
finite_stretch_builder.hpp over open-coding the stream loop. Keep the render
double-precision through completion, then seal to float once. This uses the
portable scalar FFT path; platform float FFTs are numerically equivalent but
do not promise bit-identical output across fresh instances. The typed capacity
checks charge sizeof(double) for every 64-bit caller-owned plane.
FiniteStretchBuilder remains available for float/RT-oriented uses. Its
caller-owned planar buffers must remain stable for the builder lifetime. One
step() does at most one bounded feed, drain, or finalize unit; rejected feeds
are retained and retried byte-for-byte after a drain. Success means the requested target frame
count was produced exactly; short and long natural renders are typed failures.
Output planes must be distinct and non-overlapping with every input plane,
because incremental writes otherwise corrupt unread source frames. Shared
read-only input planes are valid.
Long streams keep absolute synthesis position in a checked signed integer and
only the bounded fractional-hop residual in floating point. Do not restore an
ever-growing double accumulator: sub-sample hop fractions disappear around
large absolute positions even before integer spacing exceeds one.
The non-obvious ramp rule is that ratios belong to analysis-frame boundaries,
not decoder or host blocks. The builder's ratio_at_input_frame callback is
invoked at those boundaries even when max_block changes, and bounded
finalization preserves the same rule through EOF padding while holding the
endpoint ratio. Keep the callback noexcept and allocation-free. Do not replace
this with block-start ratio updates: that makes the rendered ramp depend on the
chosen work quantum.
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DPULP_ENABLE_GPU=OFF
tools/ci/governed-build.sh cmake --build build --target stretchcli
./build/examples/offline-stretch/stretchcli in.wav out.wav --ratio 2.0 --quality 2
--ratio = out_dur/in_dur (2.0 = half speed). Output length is exactly
round(in_frames*ratio) — loops stay bar-locked. --pitch S shifts S semitones
(duration preserved). --bpm-to T picks the ratio from detected BPM.
--character, or StretchCharacter)| Mode | What | Use for | State |
|---|---|---|---|
clean (default) | peak-lock phase vocoder + material-adaptive FFT; time ≠ pitch | tonal / melodic / sustained (bass, vocals, pads, mixes) | good, shipping |
varispeed | pitch+time LINKED (sinc resample) + speed-scaled tape head EQ + end-fade | tape slow/speed character; sidesteps the drum weakness | good, shipping |
phase_vocoder | reserved; renders as clean (the clean engine + --relocate is the punch path) | percussion punch | scaffold → renders clean; use --relocate for punch |
granular | grain/stutter texture | texture | scaffold → renders clean |
varispeed ≠ time-stretch: it changes pitch AND tempo together (like tape/vinyl
speed), so it has no phase-vocoder artifacts. Slowing down warms + dulls
(head-gap HF loss + low-mid bump, scaled by log2(ratio)); speeding up brightens.
Exact identity at ratio 1. Demo: --character varispeed vs --repitch (plain
resample, no EQ) vs --character clean (time-stretch).
varispeed is still an
option; full R3-beating drums would need a true transient-separated path.recommend_window): percussive→1024/128, bass/low→8192/512,
else 4096/512. Override with --fft/--hop.--stn to opt in for noisy textures. Two correctness
fixes landed on the OPT-IN path (default stays route_noise_stn=false, settled by
the broader A/B): a CAUSAL StnConfig (the morph split applied the mask to the
newest pushed frame, but the decomposer evaluated medians on the ring's CENTER
frame — lagging ~(time_median-1)/2 frames, so a transient's broadband energy was
misrouted into the random-phase noise path and decohered) and a √(8/3) Hann WOLA
synthesis-gain (random-phase frames overlap-add incoherently while WOLA normalizes
for coherent summation → ~4-5 dB loss). The morpher still dulls tonal/transient
material, so it stays off by default; these only improve the opt-in path.--transient-sens X raises the Röbel reset sensitivity (sharper attacks); off by
default — a fine-tune knob. (Measured to REGRESS at 2× on its own — a graft is
better; see relocation below.)TransientPhasePolicy::Config::refractory_frames,
default 3): the detector fires a phase reset per high-flux frame, so a drum hit's
DECAY/ring re-fires it on many consecutive frames — and each full-spectrum reset
discards the vocoder's accumulated synthesis-phase lead. Sustained re-firing
degenerates the PV toward raw OLA at the synthesis hop, which (a) pitches partials
DOWN by the stretch factor and (b) breaks phase coherence — audible as a
"blown-out / wobbly" sound on the harder, DEEPER hits (kicks). The gate fires once
at the onset then suppresses re-fires for N frames (≈8 ms at hop 128), keeping the
attack sharp while killing the over-fire. This is NOT detectable by peak/clip
metrics (the output never exceeds ~0.8 full-scale) — it's a perceptual transient
distortion; trust ears + a controlled FULL/HALF/OFF reset A/B over a metric here.StretchTransientMode::verbatim_relocate, or
--relocate): grafts each ORIGINAL attack back onto the PV output, PEAK-ALIGNED,
restoring the punch the PV smears (transient peak ~73% → ~97%+ of source across
0.25–4×; tonal/sine = perfect no-op; identity at ratio 1). On the tempo-only
spectral path. The three gotchas that make-or-break it (each cost a debugging
cycle): (1) the output transient is NOT at oi*ratio — tempo_stretch leaves
a ratio-dependent group-delay offset (~205 samples at 2×), so search |out| for
the real peak near the nominal position, don't graft at oi*ratio; (2) the
energy-window onset detector returns the window START, which LEADS the true
peak, so the input-peak search must go FORWARD from the onset (a back-search
grafts silence → makes it WORSE); (3) the output-peak search width scales with
the stretched onset spacing so it can't lock onto a neighbour at high
compression. Offline-only (allocates; runs on the render worker, never the audio
thread). Enabled by default in the PulpTempoSampler render path. Crest factor is
the WRONG success metric (restoring all peaks moves peak AND RMS together) — use
per-transient peak-vs-source + attack slope. (4) Graft only the HIGH band
(kReloHpHz, 300 Hz): the PV smears high-frequency attacks but reconstructs
sustained LOW frequencies cleanly + continuously. A full-band graft re-injects
low-frequency attack energy whose phase can't match the PV body across the short
(~1 ms) seam — and a deep kick's period (~15 ms) is longer than the whole graft
window — so the seam can't bridge it: a low-frequency discontinuity that "blows
out" deep hits, ONLY at stretch (no PV body to mismatch at ratio 1). The graft
high-passes both sides and swaps only the high band, leaving the kick body to the
PV. Crossover tuned by ear on a real break (180 Hz still blew out; 300 Hz clean +
punchy). This artifact is INVISIBLE to peak/clip/wobble metrics (output stays
~0.8 full-scale) — found only via a graft-on/off vs PV-only listening A/B.match_spectral_rms): a stretch reconstructs
BROADBAND material ~3-4 dB QUIET (the WOLA is unity for COHERENT overlap — proven
by the spectral-engine tests — but the incoherent broadband residual loses the
sqrt(8/3) Hann figure; tonal/peak-locked energy stays at level). Do NOT add a
constant at the normalization site (breaks the coherent-unity tests). Instead the
spectral paths (tempo / pitch / R+S; NOT identity / repitch / varispeed, which are
already level-correct) make up the interior RMS to the input (make-up only)
then soft-clip (transparent below 0.9, tanh shoulder to 0.999). The soft-clip
(not a whole-buffer peak-scale) is load-bearing: it also tames the verbatim graft's
ADDITIVE overshoot (raw peak ~1.2, previously hidden by the sampler's master
limiter) without attenuating the whole buffer — so the make-up survives and the
engine never emits |x|>1. Test: broadband RMS within ~0.02 dB of source across
0.5–2x, peak ≤ 1.0, a sine NOT inflated.restore_onset_head): the PV reconstructs a hard onset at
sample 0 from an EMPTY analysis history (the Hann edge is ~0), so the attack ramps
up over ~fft/2 samples (~10 ms percussive) — the first ~10 ms is too quiet. The
length-lock trim aligns input[0]→output[0] but does NOT remove the ramp, and
detect_onsets structurally misses a sample-0 attack (no flux RISE), so
relocate_transients can't fix it. Graft the input's leading attack over the head
(~10 ms, kHeadMs) with an equal-power crossfade (input[0] is the true sample — no
peak search needed). No-op when the input head is silent (a real fade-in,
kHeadEps) or the PV didn't lose the attack (kHeadRatio). Runs tempo_stretch →
relocate → restore_onset_head → match_spectral_rms (the make-up's interior-RMS
window excludes the head; its soft-clip bounds the grafted peak).
fft/2 — ~10 ms only at the 1024 percussive window, but ~42 ms at the
4096 default and ~85 ms at 8192 for sustained/tonal material. A fixed-10 ms head
hands the crossfade back to a still-ramping PV and scoops the envelope between the
head and the body, so head = min(out, in, max(kHeadMs·sr, engine_.fft_size()/2)).
match_spectral_rms floors its interior-RMS edge guard at that same head span
(head_guard), so the graft never skews the make-up gain on short (<~80 ms)
outputs. Both read engine_.fft_size() (==0 ⇒ engine default 4096).StretchPreset)Presets are a layer ON TOP of the core engine (they don't fork it). Tunable knobs:
character, fft/hop, transient sensitivity, STN, relocation. Tiny human-editable
key = value text spec.
# dial in, then save
stretchcli in.wav out.wav --character varispeed --transient-sens 1.5 --save-preset my.preset
# others load it (later flags still override)
stretchcli in.wav out.wav --ratio 1.5 --preset my.preset
Share my.preset (a few lines of text). API: apply_preset() / capture_preset()
in stretch_preset.hpp. Ratio/pitch are the caller's, never the preset's.
examples/offline-stretch/eval/pip install -r examples/offline-stretch/eval/requirements.txt # numpy + soundfile
python examples/offline-stretch/eval/ab_compare.py drum.wav \
--cli build/examples/offline-stretch/stretchcli --ratios 0.75,1.5,2.0 \
--configs "clean:--character clean" "tape:--character varispeed"
Metrics: centroid (brightness), onset (punch), peak_hz vs source (pitch fidelity),
wobble (pitch stability), spectral-L1 + band balance (EQ match). Metrics are
necessary, not sufficient — they repeatedly mislead on subtle artifacts; confirm
by ear. To compare against Rubber Band (GPL, NOT bundled), render with your own
rubberband and pass --reference <file>.
Subtle transient/phase artifacts (smear, "wobble", blown-out deep hits) are often INVISIBLE to peak/RMS/clip/AM metrics — output can sit at ~0.8 full-scale and read "clean" while clearly wrong by ear. Tune by ear, with discipline:
--relocate (verbatim
graft); a no---relocate A/B tests a DIFFERENT signal path and can read "great"
while the app blows out. Match the flags the product uses.RealtimePitchTimeProcessor sets
SpectralFrameEngineConfig::full_overlap_stream_start = false. Its stretched
frame map and input_priming_samples() assume analysis frame k covers input
[k * hop, k * hop + fft_size); the engine's default full-overlap grid starts
before the stream (first_frame_start() < 0) and would shift every frame
ordinal. Flipping it is a stream-contract change, not a one-line fix: the
frame map, finalize limits and the stream-contract tests all key on the
anchored grid, and its first fft_size - hop output samples keep the tapered
start.
Don't trust harmonic-clarity (peak/valley) or autocorr-f0-on-drums — both misled during tuning. Use centroid + peak-Hz-vs-source + wobble + the ear.
Keep integer types explicit around frame counts on Windows. MSVC's long is
32-bit, while std::llround returns long long; passing that directly into an
initializer-list such as std::min<long>({ ... }) can fail the Windows
release-path gate as narrowing. Cast or store the rounded value as long before
it enters std::min<long> / frame-count lists.
A faithful time-stretch must keep peak_hz IDENTICAL to the source at every
ratio. If it drifts, something's wrong (that's how the vertical-coherence and the
bass-FFT bugs were caught).
The test corpus sources hard-cut at full energy, so clean/time-stretch outputs ending abruptly is FAITHFUL, not a bug — don't add fades to the spectral path. (Varispeed is the exception: it fades, because tape doesn't hard-cut.)
まだレビューはありません。使ってみた感想をお寄せください。
概要と使いどころ
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.
日本語の概要は準備中です。原文の説明を表示しています。