Initial release

This commit is contained in:
civ
2026-08-16 18:24:52 +07:00
commit 876886a39a
13244 changed files with 2353959 additions and 0 deletions
+850
View File
@@ -0,0 +1,850 @@
--[[
md_audio_tiled_core.lua
MD Audio VAE Tiled Decoder — Core Math Engine
© 2026 Alexander Allan (MDMAchine) | A&E Concepts
GPL v3 — Public version.
Version: 3.0.1
PARITY: Algorithmic parity with md_audio_tiled_core.py v3.0.1
All coefficients, thresholds, and control flow identical.
Implements the host-side tiling arithmetic and DSP chain for Lua-based
VAE runtimes (HotStep, custom scripting environments).
What this file provides:
• Tile schedule builder (fixed and BPM-synced)
• Fade-in window generation (Hann/Cosine/Linear)
• Trapezoidal weight maps (dual-pass merge)
• Latent Spectral Suppressor (LSS)
• Biquad filter engine (peaking EQ + low shelf)
• Hum notch chain (bass shelf + surgical cuts)
• High-pass filter (Butterworth 2nd-order biquad)
• Soft clipper (tanh saturation)
• Stereo width (M/S)
• OLA write primitive with crossfade
• RMS leveling + absolute ceiling
• Dual-pass trapezoidal merge
What this file does NOT provide (requires your VAE runtime):
• vae.decode() — neural network inference
• STFT-domain ops (HPC spectral crossfade, SCE, Wiener)
• GPU tensor ops
TENSOR CONVENTION:
All audio/latent buffers are flat Lua tables indexed [1..N].
Layout: row-major [B, C, L] — B outermost, then C, then sample index.
index(b, c, i, C, L) = (b-1)*C*L + (c-1)*L + i (1-indexed)
IMPORTANT: This is a reference/scripting port. For production use in a
Lua JIT environment, profile biquad inner loops and cache
filter states across tiles.
--]]
local M = {}
-- Lua 5.4 removed math.tanh — polyfill via exp identity
local tanh = math.tanh or function(x)
if x > 20 then return 1.0 end
if x < -20 then return -1.0 end
local e2x = math.exp(2 * x)
return (e2x - 1) / (e2x + 1)
end
-- =============================================================================
-- CONSTANTS
-- =============================================================================
M.EPSILON = 1e-8
M.GAIN_CLAMP_BASE = 0.05 -- ±5% base RMS ride
M.GAIN_CLAMP_MAX = 0.20 -- ±20% max on entropy spikes
M.RMS_ABS_CEIL = 0.35 -- Hard per-tile RMS ceiling
M.DUAL_PASS_TAPER = 0.25 -- Trapezoidal edge ramp fraction
M.MIN_OLA_SAMPLES = 8
M.VAE_CONTEXT_FRAMES = 128 -- Oobleck causal warm-up prefix
-- =============================================================================
-- UTILITY
-- =============================================================================
local function clamp(v, lo, hi) return math.max(lo, math.min(hi, v)) end
local function sign(v) return v > 0 and 1 or (v < 0 and -1 or 0) end
-- 1-indexed flat buffer index: [B, C, L] row-major
local function idx(b, c, i, C, L) return (b-1)*C*L + (c-1)*L + i end
-- Allocate zeroed flat table of length N
local function zeros(N)
local t = {}
for i = 1, N do t[i] = 0.0 end
return t
end
local function copy(src, N)
local t = {}
for i = 1, N do t[i] = src[i] end
return t
end
-- =============================================================================
-- WINDOW GENERATION
-- =============================================================================
---Fade-in ramp [0 → 1], `length` samples.
---@param length integer
---@param mode string "Hann"|"Cosine"|"Linear"
---@return table
function M.make_fade_in(length, mode)
local w = {}
for i = 1, length do
local t = (i - 1) / math.max(1, length - 1)
if mode == "Hann" then
w[i] = 0.5 * (1 - math.cos(math.pi * t))
elseif mode == "Cosine" then
w[i] = math.sin(math.pi / 2 * t)
else -- Linear
w[i] = t
end
end
return w
end
---Trapezoidal weight: flat 1.0 centre, ramps from 0.5 at both edges.
---@param length integer
---@param edge_frac number fraction of length used for ramp (default 0.25)
---@return table
function M.make_trapezoid(length, edge_frac)
edge_frac = edge_frac or M.DUAL_PASS_TAPER
local taper = math.max(M.MIN_OLA_SAMPLES, math.floor(length * edge_frac))
taper = math.min(taper, math.floor(length / 2))
local w = {}
for i = 1, length do
if i <= taper then
w[i] = 0.5 + 0.5 * ((i - 1) / math.max(1, taper - 1))
elseif i > length - taper then
local j = length - i
w[i] = 0.5 + 0.5 * (j / math.max(1, taper - 1))
else
w[i] = 1.0
end
end
return w
end
-- =============================================================================
-- TILE SCHEDULE BUILDERS
-- =============================================================================
---Build a fixed (non-adaptive) tile schedule covering [1, W] in latent frames.
---Returns list of {start, end_, overlap} tables (1-indexed start/end).
---@param W integer total latent frames
---@param tile_size integer
---@param overlap integer
---@param start_offset integer 0-indexed start position (default 0)
---@return table[]
function M.build_fixed_schedule(W, tile_size, overlap, start_offset)
start_offset = start_offset or 0
local schedule = {}
local hop = tile_size - overlap
if hop <= 0 then hop = math.max(1, math.floor(tile_size / 2)) end
local cursor = start_offset
while cursor < W do
local e = math.min(W, cursor + tile_size)
table.insert(schedule, {start = cursor, end_ = e, overlap = overlap})
cursor = cursor + hop
end
return schedule
end
---BPM-synced overlap in latent frames.
---Downgrades bar count until overlap fits within tile_size / 2.
---@param bpm integer
---@param target_bars number e.g. 4.0 for "Max 4 Bars"
---@param tile_size integer
---@param latents_per_second number ACE-Step = 5.0
---@return integer overlap in latent frames
function M.bpm_sync_overlap(bpm, target_bars, tile_size, latents_per_second)
latents_per_second = latents_per_second or 5.0
local sec_per_bar = (60.0 / bpm) * 4.0
local frames_per_bar = sec_per_bar * latents_per_second
local bars = target_bars
local calc = math.floor(sec_per_bar * bars * latents_per_second + 0.5)
while calc > math.floor(tile_size / 2) and bars > 0.25 do
bars = bars / 2.0
calc = math.floor(sec_per_bar * bars * latents_per_second + 0.5)
end
-- Integer-multiple snap
if frames_per_bar >= 1.0 then
local n = math.floor(calc / frames_per_bar + 0.5)
calc = n * math.floor(frames_per_bar)
if calc < 8 then calc = 8 end
end
return calc
end
-- =============================================================================
-- LSS: LATENT SPECTRAL SUPPRESSOR
-- =============================================================================
---Suppress low-variance latent channels in-place.
---Modifies `latents` (flat [B, C_lat, T] table) in place.
---@param latents table flat [B, C, T] row-major
---@param B integer batch size
---@param C integer latent channels
---@param T integer latent time frames
---@param strength number suppression strength (01, gold standard 0.25)
---@param var_threshold number normalized variance threshold (gold standard 0.12)
---@param dc_remove boolean WARNING: causes metallic distortion — keep false
function M.apply_lss(latents, B, C, T, strength, var_threshold, dc_remove)
strength = strength or 0.25
var_threshold = var_threshold or 0.12
dc_remove = dc_remove or false
if strength < 1e-4 then return end
-- Per-channel variance averaged over batch
local ch_var = zeros(C)
for b = 1, B do
for c = 1, C do
local s, sq = 0.0, 0.0
for t = 1, T do
local v = latents[idx(b, c, t, C, T)]
s = s + v
sq = sq + v * v
end
local mean = s / T
ch_var[c] = ch_var[c] + (sq / T - mean * mean)
end
end
for c = 1, C do ch_var[c] = ch_var[c] / B end
local var_min = math.huge
local var_max = -math.huge
for c = 1, C do
if ch_var[c] < var_min then var_min = ch_var[c] end
if ch_var[c] > var_max then var_max = ch_var[c] end
end
local var_rng = (var_max - var_min) + M.EPSILON
-- Per-channel suppression gain
local gain = zeros(C)
for c = 1, C do
local var_norm = (ch_var[c] - var_min) / var_rng
if var_norm < var_threshold then
local smooth = (1 - strength) +
strength * (var_norm / (var_threshold + M.EPSILON))
smooth = clamp(smooth, 1 - strength, 1.0)
gain[c] = smooth
else
gain[c] = 1.0
end
end
-- Apply (with optional DC removal)
for b = 1, B do
for c = 1, C do
if dc_remove then
local s = 0.0
for t = 1, T do s = s + latents[idx(b, c, t, C, T)] end
local mean = s / T
for t = 1, T do
latents[idx(b, c, t, C, T)] = latents[idx(b, c, t, C, T)] - mean
end
end
local g = gain[c]
for t = 1, T do
latents[idx(b, c, t, C, T)] = latents[idx(b, c, t, C, T)] * g
end
end
end
end
-- =============================================================================
-- BIQUAD FILTER ENGINE
-- =============================================================================
---Compute peaking EQ biquad coefficients (normalized, a[1]=1).
---@return table b {b0,b1,b2}
---@return table a {1, a1, a2}
function M.peaking_biquad_coeffs(f0, gain_db, Q, sr)
local A = 10 ^ (gain_db / 40)
local w0 = 2 * math.pi * f0 / sr
local cos_w0 = math.cos(w0)
local sin_w0 = math.sin(w0)
local alpha = sin_w0 / (2 * Q)
local denom = 1 + alpha / A
return
{(1 + alpha * A) / denom, (-2 * cos_w0) / denom, (1 - alpha * A) / denom},
{1.0, (-2 * cos_w0) / denom, (1 - alpha / A) / denom}
end
---Compute low shelf biquad coefficients.
---@return table b, table a
function M.low_shelf_biquad_coeffs(f0, gain_db, slope, sr)
local A = 10 ^ (gain_db / 40)
local w0 = 2 * math.pi * f0 / sr
local cos_w0 = math.cos(w0)
local sin_w0 = math.sin(w0)
local alpha = sin_w0 / 2 * math.sqrt((A + 1/A) * (1/slope - 1) + 2)
local a0 = (A+1) + (A-1)*cos_w0 + 2*math.sqrt(A)*alpha
local b = {
A * ((A+1) - (A-1)*cos_w0 + 2*math.sqrt(A)*alpha) / a0,
2*A*((A-1) - (A+1)*cos_w0) / a0,
A * ((A+1) - (A-1)*cos_w0 - 2*math.sqrt(A)*alpha) / a0,
}
local a = {
1.0,
-2 * ((A-1) + (A+1)*cos_w0) / a0,
((A+1) + (A-1)*cos_w0 - 2*math.sqrt(A)*alpha) / a0,
}
return b, a
end
---Apply biquad IIR filter in-place to a single-channel flat buffer [1..L].
---Returns updated biquad state {x1,x2,y1,y2}.
---@param buf table [1..L] float samples
---@param L integer
---@param b table {b0,b1,b2}
---@param a table {1,a1,a2}
---@param state table {x1,x2,y1,y2} (pass {} to initialise fresh)
---@return table state
function M.biquad_filter_inplace(buf, L, b, a, state)
local x1 = state.x1 or 0
local x2 = state.x2 or 0
local y1 = state.y1 or 0
local y2 = state.y2 or 0
for i = 1, L do
local xn = buf[i]
local yn = b[1]*xn + b[2]*x1 + b[3]*x2 - a[2]*y1 - a[3]*y2
x2, x1 = x1, xn
y2, y1 = y1, yn
buf[i] = yn
end
return {x1=x1, x2=x2, y1=y1, y2=y2}
end
-- =============================================================================
-- HUM NOTCH CHAIN
-- =============================================================================
---Multi-band hum suppression. Modifies `audio` in place.
---@param audio table flat [B, C, L] row-major
---@param B integer
---@param C integer
---@param L integer
---@param params table TiledDecodeParams-compatible
function M.apply_hum_notch(audio, B, C, L, params)
if not params.hum_notch_enabled then return end
local sr = params.sample_rate or 48000
-- Build filter chain
local chain = {} -- {b, a} pairs
if params.hum_bass_shelf_enabled then
local b, a = M.low_shelf_biquad_coeffs(
params.hum_bass_shelf_hz or 120.0,
params.hum_bass_shelf_db or -2.0,
params.hum_bass_shelf_slope or 0.7,
sr)
table.insert(chain, {b=b, a=a})
end
local notches = {
{en="hum_74_enabled", hz="hum_74_hz", db="hum_74_db", q="hum_74_q"},
{en="hum_94_enabled", hz="hum_94_hz", db="hum_94_db", q="hum_94_q"},
{en="hum_656_enabled", hz="hum_656_hz", db="hum_656_db", q="hum_656_q"},
}
for _, n in ipairs(notches) do
if params[n.en] then
local b, a = M.peaking_biquad_coeffs(
params[n.hz], params[n.db], params[n.q], sr)
table.insert(chain, {b=b, a=a})
end
end
if #chain == 0 then return end
-- Apply each band to each channel independently
for b = 1, B do
for c = 1, C do
-- Extract channel slice into temp buffer
local ch = {}
local base = idx(b, c, 1, C, L)
for i = 1, L do ch[i] = audio[base + i - 1] end
-- Filter chain
for _, filt in ipairs(chain) do
M.biquad_filter_inplace(ch, L, filt.b, filt.a, {})
end
-- Write back
for i = 1, L do audio[base + i - 1] = ch[i] end
end
end
end
-- =============================================================================
-- HIGH-PASS FILTER
-- =============================================================================
---Butterworth 2nd-order high-pass filter (biquad). In-place.
---@param audio table flat [B, C, L]
---@param B integer
---@param C integer
---@param L integer
---@param cutoff_hz number
---@param sample_rate number
function M.apply_highpass(audio, B, C, L, cutoff_hz, sample_rate)
if cutoff_hz < 1.0 then return end
local w0 = 2 * math.pi * cutoff_hz / sample_rate
local cos_w0 = math.cos(w0)
local sin_w0 = math.sin(w0)
local alpha = sin_w0 / (2 * 0.7071) -- Q = 1/sqrt(2) Butterworth
local denom = 1 + alpha
local b = {
(1 + cos_w0) / (2 * denom),
-(1 + cos_w0) / denom,
(1 + cos_w0) / (2 * denom),
}
local a = {
1.0,
(-2 * cos_w0) / denom,
(1 - alpha) / denom,
}
for bi = 1, B do
for c = 1, C do
local base = idx(bi, c, 1, C, L)
local ch = {}
for i = 1, L do ch[i] = audio[base + i - 1] end
M.biquad_filter_inplace(ch, L, b, a, {})
for i = 1, L do audio[base + i - 1] = ch[i] end
end
end
end
-- =============================================================================
-- SOFT CLIPPER
-- =============================================================================
---tanh-based soft clipper. In-place.
---@param audio table flat [B, C, L]
---@param ceiling_db number e.g. -3.0 (must be < 0 to have effect)
function M.apply_soft_clip(audio, ceiling_db)
if ceiling_db >= 0 then return end
local ceiling_lin = 10 ^ (ceiling_db / 20)
local scale = ceiling_lin / tanh(1.0)
for i = 1, #audio do
audio[i] = scale * tanh(audio[i] / scale)
end
end
-- =============================================================================
-- PEAK NORMALIZE
-- =============================================================================
---Transparent peak normalization: scale entire audio so max |sample| = target.
---Pure gain reduction — no waveform distortion, no waveshaping.
---Only attenuates (never boosts). Skipped if peak is already below target.
---@param audio table flat buffer
---@param N integer total samples
---@param target_db number target peak in dBFS (e.g., -1.0)
function M.apply_peak_normalize(audio, N, target_db)
if not target_db or target_db >= 0 then return end
local target_lin = 10 ^ (target_db / 20)
local peak = 0.0
for i = 1, N do
local v = math.abs(audio[i])
if v > peak then peak = v end
end
if peak < 1e-8 then return end -- silence
if peak <= target_lin then return end -- already below target
local gain = target_lin / peak
for i = 1, N do
audio[i] = audio[i] * gain
end
end
-- =============================================================================
-- STEREO WIDTH (M/S)
-- =============================================================================
---M/S stereo width. Only operates when C == 2. In-place.
---@param audio table flat [B, 2, L]
---@param B integer
---@param L integer
---@param width number 1.0=unity, 0.0=mono, 2.0=doubled side
function M.apply_stereo_width(audio, B, L, width)
if math.abs(width - 1.0) < 1e-4 then return end
for b = 1, B do
local base_l = idx(b, 1, 1, 2, L)
local base_r = idx(b, 2, 1, 2, L)
for i = 0, L - 1 do
local l = audio[base_l + i]
local r = audio[base_r + i]
local mid = (l + r) * 0.5
local side = (l - r) * 0.5 * width
audio[base_l + i] = mid + side
audio[base_r + i] = mid - side
end
end
end
-- =============================================================================
-- RMS UTILITIES
-- =============================================================================
---Compute RMS of flat buffer.
function M.compute_rms(buf, N)
local s = 0.0
for i = 1, N do local v = buf[i]; s = s + v*v end
return math.sqrt(s / N + M.EPSILON)
end
---Downward-only absolute RMS ceiling. In-place.
---@param audio table flat buffer
---@param ceiling number default M.RMS_ABS_CEIL
function M.apply_rms_ceiling(audio, N, ceiling)
ceiling = ceiling or M.RMS_ABS_CEIL
local rms = M.compute_rms(audio, N)
if rms > ceiling then
local g = ceiling / rms
for i = 1, N do audio[i] = audio[i] * g end
end
end
-- =============================================================================
-- OLA WRITE PRIMITIVE
-- =============================================================================
---Write decoded_chunk into output_audio at out_start with OLA crossfade.
---All buffers flat [B, C, length] row-major (1-indexed).
---@param output_audio table flat [B, C, final_samples]
---@param B integer
---@param C integer
---@param final_samples integer
---@param decoded_chunk table flat [B, C, decoded_len]
---@param decoded_len integer
---@param out_start integer 0-indexed write position in audio samples
---@param overlap_audio integer audio-domain overlap samples
---@param blend_mode string "Hann"|"Cosine"|"Linear"
function M.ola_write(output_audio, B, C, final_samples,
decoded_chunk, decoded_len,
out_start, overlap_audio, blend_mode)
local valid_len = math.min(decoded_len, final_samples - out_start)
if valid_len <= 0 then return end
local ov = math.min(overlap_audio, math.floor(valid_len / 2))
if out_start > 0 and ov > 0 then
local fade_in = M.make_fade_in(ov, blend_mode)
-- Cosine equal-power fade-out: sqrt(1 - f^2)
-- Linear/Hann: simple 1-f
local use_eqp = (blend_mode == "Cosine")
for b = 1, B do
for c = 1, C do
local out_base = idx(b, c, 1, C, final_samples)
local in_base = idx(b, c, 1, C, decoded_len)
-- Crossfade
for i = 1, ov do
local fi = fade_in[i]
local fo = use_eqp
and math.sqrt(math.max(0, 1 - fi * fi))
or (1 - fi)
local out_i = out_start + i -- 1-indexed position in output
if out_i >= 1 and out_i <= final_samples then
output_audio[out_base + out_i - 1] =
output_audio[out_base + out_i - 1] * fo +
decoded_chunk[in_base + i - 1] * fi
end
end
-- Tail (straight copy after crossfade)
for i = ov + 1, valid_len do
local out_i = out_start + i
if out_i >= 1 and out_i <= final_samples then
output_audio[out_base + out_i - 1] =
decoded_chunk[in_base + i - 1]
end
end
end
end
else
-- First tile — straight write
for b = 1, B do
for c = 1, C do
local out_base = idx(b, c, 1, C, final_samples)
local in_base = idx(b, c, 1, C, decoded_len)
for i = 1, valid_len do
output_audio[out_base + out_start + i - 1] =
decoded_chunk[in_base + i - 1]
end
end
end
end
end
-- =============================================================================
-- DUAL-PASS WEIGHT ACCUMULATION
-- =============================================================================
---Build trapezoidal weight map for one pass's tile boundaries.
---@param weight_buf table [1..final_samples] float, modified in place
---@param final_samples integer
---@param schedule table[] list of {start, end_, overlap}
---@param boundaries table list of out_start integers (0-indexed audio positions)
---@param upscale_factor number
function M.fill_trapezoid_weights(weight_buf, final_samples,
schedule, boundaries, upscale_factor)
for i, bound in ipairs(boundaries) do
local tile = schedule[i]
if not tile then break end
local lat_len = tile.end_ - tile.start
local tile_len = math.floor(lat_len * upscale_factor + 0.5)
local out_s = bound + 1 -- convert to 1-indexed
local out_e = math.min(final_samples, out_s + tile_len - 1)
local L = out_e - out_s + 1
if L <= 0 then goto continue end
local trap = M.make_trapezoid(L, M.DUAL_PASS_TAPER)
for j = 1, L do
local pos = out_s + j - 1
if pos >= 1 and pos <= final_samples then
if trap[j] > weight_buf[pos] then weight_buf[pos] = trap[j] end
end
end
::continue::
end
end
-- =============================================================================
-- UPSCALE FACTOR SNAP
-- =============================================================================
---Snap upscale factor to nearest integer if within 0.5%.
---ACE-Step Oobleck always produces an exact integer ratio.
---Sub-sample error compounds across tiles → audible timing drift.
---@param raw number
---@return number
function M.snap_upscale_factor(raw)
local snapped = math.floor(raw + 0.5)
if math.abs(snapped - raw) / (raw + M.EPSILON) < 0.005 then
return snapped
end
return raw
end
-- =============================================================================
-- MASTER TILED DECODE ORCHESTRATOR
-- =============================================================================
---Master tiled VAE decode engine.
---
---@param vae_decode_fn function(latent_slice, lat_len) -> audio_chunk, actual_len
--- latent_slice: flat [B, C_lat, lat_len] table
--- Returns: flat [B, C_aud, actual_len] table, integer actual_len
---
---@param latents table flat [B, C_lat, W] latent buffer (may be modified by LSS)
---@param B integer batch size
---@param C_lat integer latent channels
---@param W integer latent frame count
---@param C_aud integer audio channels (typically 2)
---@param final_samples integer total output audio samples
---@param upscale_factor number audio samples per latent frame
---@param params table TiledDecodeParams-compatible:
--- tile_size, overlap, context_prefix, dual_pass, rms_leveling,
--- lss_enabled, lss_strength, lss_var_thresh, lss_dc_remove,
--- hum_notch_enabled (+ per-band params), highpass_hz, soft_clip_db,
--- stereo_width, sample_rate
---
---@return table flat [B, C_aud, final_samples] decoded audio
function M.execute_tiled_decode(vae_decode_fn, latents, B, C_lat, W,
C_aud, final_samples, upscale_factor, params)
params = params or {}
local tile_size = params.tile_size or 1024
local overlap = params.overlap or 64
local context_prefix = params.context_prefix or 512
local dual_pass = params.dual_pass ~= false -- default true
local rms_leveling = params.rms_leveling ~= false -- default true
local sample_rate = params.sample_rate or 48000
-- ── LSS ──────────────────────────────────────────────────────────────────
if params.lss_enabled ~= false then
M.apply_lss(latents, B, C_lat, W,
params.lss_strength or 0.25,
params.lss_var_thresh or 0.12,
params.lss_dc_remove or false)
end
-- ── Tile schedules ────────────────────────────────────────────────────────
local sched_a = M.build_fixed_schedule(W, tile_size, overlap, 0)
local sched_b = dual_pass
and M.build_fixed_schedule(W, tile_size, overlap,
math.floor(tile_size / 2))
or nil
-- ── Run one pass ──────────────────────────────────────────────────────────
local function run_pass(schedule)
local audio_out = zeros(B * C_aud * final_samples)
local boundaries = {}
local prev_rms = -1.0
for _, tile in ipairs(schedule) do
local ctx_start = math.max(0, tile.start - context_prefix)
local lat_len = tile.end_ - ctx_start
-- Extract latent slice
local lat_slice = zeros(B * C_lat * lat_len)
for b = 1, B do
for c = 1, C_lat do
local src_base = (b-1)*C_lat*W + (c-1)*W + ctx_start + 1
local dst_base = (b-1)*C_lat*lat_len + (c-1)*lat_len + 1
for t = 1, lat_len do
lat_slice[dst_base + t - 1] = latents[src_base + t - 1]
end
end
end
-- VAE decode
local chunk, actual_len = vae_decode_fn(lat_slice, lat_len)
if not chunk or actual_len <= 0 then goto next_tile end
-- ctx_skip: absorb VAE rounding into discarded context region
local expected_write = math.floor((tile.end_ - tile.start) * upscale_factor + 0.5)
local ctx_skip = math.max(0, actual_len - expected_write)
local write_len = actual_len - ctx_skip
if write_len <= 0 then goto next_tile end
-- Build write chunk (post ctx_skip)
local write_chunk = zeros(B * C_aud * write_len)
for b = 1, B do
for c = 1, C_aud do
local src_base = (b-1)*C_aud*actual_len + (c-1)*actual_len + ctx_skip + 1
local dst_base = (b-1)*C_aud*write_len + (c-1)*write_len + 1
for i = 1, write_len do
write_chunk[dst_base + i - 1] = chunk[src_base + i - 1]
end
end
end
-- RMS leveling
if rms_leveling and prev_rms > 0 then
local N = B * C_aud * write_len
local crms = M.compute_rms(write_chunk, N)
local gain = clamp(prev_rms / (crms + M.EPSILON),
1 - M.GAIN_CLAMP_BASE, 1 + M.GAIN_CLAMP_BASE)
for i = 1, N do write_chunk[i] = write_chunk[i] * gain end
end
-- Absolute RMS ceiling
M.apply_rms_ceiling(write_chunk, B * C_aud * write_len)
prev_rms = M.compute_rms(write_chunk, B * C_aud * write_len)
local out_start = math.floor(tile.start * upscale_factor + 0.5)
local overlap_aud = math.floor(tile.overlap * upscale_factor + 0.5)
table.insert(boundaries, out_start)
M.ola_write(audio_out, B, C_aud, final_samples,
write_chunk, write_len,
out_start, overlap_aud, "Cosine")
::next_tile::
end
return audio_out, boundaries
end
local audio_a, bounds_a = run_pass(sched_a)
local audio_b, bounds_b = nil, {}
if sched_b then
audio_b, bounds_b = run_pass(sched_b)
end
-- ── Dual-Pass Merge ───────────────────────────────────────────────────────
local output_audio
if dual_pass and audio_b then
local weight_a = zeros(final_samples)
local weight_b = zeros(final_samples)
M.fill_trapezoid_weights(weight_a, final_samples, sched_a, bounds_a, upscale_factor)
M.fill_trapezoid_weights(weight_b, final_samples, sched_b, bounds_b, upscale_factor)
output_audio = zeros(B * C_aud * final_samples)
for b = 1, B do
for c = 1, C_aud do
local base = (b-1)*C_aud*final_samples + (c-1)*final_samples
for i = 1, final_samples do
local wa = weight_a[i]
local wb = weight_b[i]
local total = wa + wb + M.EPSILON
output_audio[base + i] = audio_a[base + i] * (wa / total)
+ audio_b[base + i] * (wb / total)
end
end
end
else
output_audio = audio_a
end
-- ── Post-Decode DSP Chain ─────────────────────────────────────────────────
-- Order: hum notch → highpass → stereo width → soft clip
M.apply_hum_notch(output_audio, B, C_aud, final_samples, params)
M.apply_highpass(output_audio, B, C_aud, final_samples,
params.highpass_hz or 20.0, sample_rate)
if C_aud == 2 then
M.apply_stereo_width(output_audio, B, final_samples,
params.stereo_width or 0.8)
end
M.apply_peak_normalize(output_audio, B * C_aud * final_samples,
params.peak_normalize_db) -- nil = skip
M.apply_soft_clip(output_audio, params.soft_clip_db or -3.0)
return output_audio
end
-- =============================================================================
-- DEFAULT PARAMS
-- =============================================================================
---Gold-standard default parameters. Copy and override as needed.
M.DEFAULT_PARAMS = {
-- Tiling
tile_size = 1024,
overlap = 64,
context_prefix = 512,
dual_pass = true,
rms_leveling = true,
sample_rate = 48000,
-- LSS
lss_enabled = true,
lss_strength = 0.25,
lss_var_thresh = 0.12,
lss_dc_remove = false, -- WARNING: metallic distortion if true
-- DSP chain
highpass_hz = 20.0,
peak_normalize_db = nil, -- nil = disabled; e.g. -1.0 for -1dBFS peak target
soft_clip_db = -3.0,
stereo_width = 0.8,
-- Hum notch (gold standard: cuts only)
hum_notch_enabled = true,
hum_bass_shelf_enabled = true,
hum_bass_shelf_hz = 120.0,
hum_bass_shelf_db = -2.0,
hum_bass_shelf_slope = 0.7,
hum_74_enabled = true,
hum_74_hz = 74.4,
hum_74_db = -1.43,
hum_74_q = 6.27,
hum_94_enabled = true,
hum_94_hz = 94.0,
hum_94_db = -1.86,
hum_94_q = 7.08,
hum_656_enabled = true,
hum_656_hz = 654.0,
hum_656_db = -15.0,
hum_656_q = 6.0,
}
return M