forked from Keelrleek/SoulStream
feat: crossfade preview suite, loudness normalization, debug overlay
Preview modal audio, five rounds: - True track-change crossfade: scheduleSettle no longer silences the outgoing clip at t=0; preview_load's equal-power fade owns both ramps, gated on the incoming voice's first Playing event (audible_marks), with failure/overstay fallbacks. CROSSFADE_MS = 340 — locked to the reel's card-slide so audio and visuals land together. - Two-neighbour lease prefetch + a 15-min on-disk clip byte-cache (recent_clips): steps in BOTH directions crossfade in ~15 ms; revisits never re-download. - Adaptive settle (isolated steps fire immediately, bursts coalesce) and anticipatory auto-advance (step at clip end minus 400 ms) so auto-play crossfades over the tail instead of gapping. - Session resume memory: revisited tracks continue where they left off (decoder-level start offset via play_local_from/resume_at); finished tracks restart from the top. - Open/close crossfades with the MAIN player: fade-pause with the desired-paused latch landing at ramp end, fade-resume from the paused position; MAIN_FADE_GEN guards; focus handshake relaxation documented in focusBridge. Volume normalization (Settings > Playback, default on): - Dependency-free BS.1770/EBU R128 meter in the player crate (K-weighting, 400 ms gated blocks, incremental) + analyzer that reads the same growing file playback streams from — first plays are measured at download speed. - Gain layered under user volume; audible start GATED on a preliminary measurement (10 s of audio or 800 ms budget) so the level is right from the first sample and never changes mid-track; prelim boosts capped 3 dB, peak-aware clipping guard, persistent loudness-cache.json. - Gapless boundaries get their own sample-accurate gain via SetNextNormGainDb, fed by preload analysis. Debug overlay (Ctrl+Shift+D): process-tree CPU / PDH GPU / working-set + private-commit RAM / network rates / FPS / current-track download progress read from the backend's own StreamProgress / live normalization state / preview session phase. Polls only while visible. 131 JS + player + app tests green; every path verified live over CDP.
This commit is contained in:
1 parent
5e877c0b0a
commit
793f78370e
27 files changed
+2834
-178
No files matched your search
@@ -38,6 +38,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function setVolumeNormalization(next) {
|
||||
if (next !== settings.volume_normalization) {
|
||||
await commit({ volumeNormalization: next });
|
||||
}
|
||||
}
|
||||
|
||||
async function chooseDownloadDir() {
|
||||
const dir = await api.pickFolder();
|
||||
if (dir) await commit({ downloadDir: dir });
|
||||
@@ -226,6 +232,21 @@
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="label">
|
||||
<div class="name">Normalize volume</div>
|
||||
<div class="hint">Plays every track at roughly the same loudness (−14 LUFS, like Spotify), so quiet and loud masters stop needing the volume knob. Streams are measured during their first play; boosts never clip.</div>
|
||||
</div>
|
||||
<select
|
||||
class="device-select"
|
||||
value={settings.volume_normalization ? "on" : "off"}
|
||||
disabled={saving}
|
||||
onchange={(e) => setVolumeNormalization(e.currentTarget.value === "on")}
|
||||
>
|
||||
<option value="on">On (recommended)</option>
|
||||
<option value="off">Off</option>
|
||||
</select>
|
||||
</div>
|
||||
<p class="muted small">Changes apply immediately and keep the current track near the same position.</p>
|
||||
</section>
|
||||
|
||||
|
||||
Reference in new issue
Block a user