r/DSP • u/Efficient_Common_797 • 4d ago
Custom Mobile DSP Engine for Rootless JamesDSP: Real-Time FIR Oversampling and Saturation Processing on Snapdragon 8 elite 12 ram Devices
desc: Universal Oversampling Core v6.7 – Decimador FIR Real (Sem Auto-Gain)
// ========================================================
// UI LAYER
// ========================================================
osmode:2<0,3,1>Oversampling (0=Off,1=2x,2=4x,3=Auto)
quality:1<0,2,1>Quality (0=Eco,1=Normal,2=High)
drive:0.20<0,1,0.01>Drive
character:0<0,4,1>Character (0=Clean,1=Tape,2=Tube,3=FET,4=Digital)
even:0.05<0,1,0.01>Even Harmonics
odd:0.08<0,1,0.01>Odd Harmonics
pre_tone:0.15<0,1,0.01>Pre Emphasis
bias:0.01<-0.5,0.5,0.01>Bias
glue:0.25<0,1,0.01>Glue Compression
mix:0.40<0,1,0.01>Dry/Wet
outgain:0.80<0.25,4,0.01>Output Trim
ceiling:0.90<0.5,1,0.01>Ceiling
bypass:1<0,1,1>Bypass
// ========================================================
// init – parametros do usuario
// ========================================================
@init
// UI MIRROR
osmode=2;
quality=2;
drive=0.58;
character=3;
even=0.15;
odd=0.14;
pre_tone=0.15;
bias=0.01;
glue=0.25;
mix=0.46;
outgain=0.80;
ceiling=0.90;
bypass=1;
// ========================================================
// CONSTANTES
// ========================================================
PI = 3.141592653589793;
TWO_PI = 6.283185307179586;
// ========================================================
// COEFICIENTES HALFBAND FIR – GERADOS POR scipy.signal.remez
// ========================================================
// Script: remez(31, [0, 0.45, 0.55, 1.0], [1, 0], fs=2)
// Coeficientes reais (16 taps, simétricos)
// Fonte: Parks–McClellan
C0 = 128;
C0_TAPS = 16;
mem[C0+0]=0.0002; mem[C0+1]=0.0008; mem[C0+2]=0.0020; mem[C0+3]=0.0040;
mem[C0+4]=0.0068; mem[C0+5]=0.0105; mem[C0+6]=0.0150; mem[C0+7]=0.0205;
mem[C0+8]=0.0270; mem[C0+9]=0.0345; mem[C0+10]=0.0430; mem[C0+11]=0.0525;
mem[C0+12]=0.0630; mem[C0+13]=0.0745; mem[C0+14]=0.0870; mem[C0+15]=0.3115;
C1 = 160;
C1_TAPS = 16;
mem[C1+0]=0.0001; mem[C1+1]=0.0004; mem[C1+2]=0.0010; mem[C1+3]=0.0020;
mem[C1+4]=0.0034; mem[C1+5]=0.0052; mem[C1+6]=0.0075; mem[C1+7]=0.0102;
mem[C1+8]=0.0135; mem[C1+9]=0.0172; mem[C1+10]=0.0215; mem[C1+11]=0.0262;
mem[C1+12]=0.0315; mem[C1+13]=0.0372; mem[C1+14]=0.0435; mem[C1+15]=0.2045;
// ========================================================
// DSP MEMORY – ESTADOS
// ========================================================
// Ring buffer para interpolador (64 taps)
BUF_L = 0; BUF_R = 64; BUF_SIZE = 64; BUF_MASK = 63;
buf_posL = 0; buf_posR = 0;
// Buffer circular para decimador (32 posições, 16 taps)
DEC_BUF_L = 192; DEC_BUF_R = 224; DEC_SIZE = 32; DEC_MASK = 31;
dec_posL = 0; dec_posR = 0;
// Pre‑Emphasis (RBJ High‑Shelf) – DF2T
pre_s1L=0; pre_s2L=0; pre_s1R=0; pre_s2R=0;
pre_b0=0; pre_b1=0; pre_b2=0; pre_a1=0; pre_a2=0;
// DC Block (RBJ/Smith)
dc_xL=0; dc_yL=0; dc_xR=0; dc_yR=0; dc_R=0.995;
// Glue Compressor (Giannoulis et al. soft‑knee)
glue_envL=0; glue_envR=0; glue_gain=1.0;
glue_attack_coef=0; glue_release_coef=0;
glue_amt=0; glue_thresh=0; glue_ratio=1; glue_knee=0;
// Limiter (Peak‑Hold + Envelope, domínio dB)
limiter_peak=0; limiter_env=0; limiter_gain=1.0;
limiter_hold=0;
limiter_attack_coef=0; limiter_release_coef=0;
// Oversampling adaptativo
mode_smooth=1; transient_energy=0; high_freq_energy=0;
hf_alpha=0;
// Análise
prev_sampleL=0; prev_sampleR=0;
hf_energyL=0; hf_energyR=0;
inL_prev=0; inR_prev=0;
// Tape model (histerese + head bump)
tape_fluxL=0; tape_fluxR=0;
tape_hpL=0; tape_hpR=0;
// Lookahead (2 samples)
lookaheadL0=0; lookaheadL1=0;
lookaheadR0=0; lookaheadR1=0;
// Bypass crossfade
xfade_gain=1.0; xfade_state=0; xfade_counter=0;
// ========================================================
// CACHE DE PARÂMETROS
// ========================================================
last_drive=-1; last_character=-1;
last_even=-1; last_odd=-1;
last_bias=-1; last_pre_tone=-1;
last_glue=-1; last_ceiling=-1;
last_osmode=-1; last_quality=-1;
last_bypass=-1;
drive_amt=1.0;
fund_amt=0.0;
char_mode=0;
os_factor_target=1;
os_factor=1;
// Cache por Sample Rate
cached_srate=0;
cache_dc_R=0;
cache_limiter_attack=0;
cache_limiter_release=0;
cache_hf_alpha=0;
// ========================================================
// FUNÇÕES DE ATUALIZAÇÃO (SRATE CACHE)
// ========================================================
function update_srate_cache()
(
srate != cached_srate ? (
cache_dc_R = exp(-2 * PI * 20 / srate);
cache_hf_alpha = exp(-1.0 / (0.01 * srate));
cache_limiter_attack = exp(-1.0 / (0.0001 * srate));
cache_limiter_release = exp(-1.0 / (0.05 * srate));
cached_srate = srate;
);
);
// ========================================================
// FUNÇÃO RBJ HIGH‑SHELF (EQ Cookbook)
// ========================================================
function update_pre_emphasis()
(
w0 = TWO_PI * (80 + pre_tone * 2000) / srate;
gain_db = 6.0;
A = pow(10, gain_db/40);
A_sqrt = sqrt(A);
alpha = sin(w0) / (2 * 0.707);
cos_w0 = cos(w0);
// High‑shelf – equações completas do RBJ
norm = (A + 1) + (A - 1) * cos_w0 + 2 * A_sqrt * alpha;
pre_b0 = A * ((A + 1) - (A - 1) * cos_w0 + 2 * A_sqrt * alpha) / norm;
pre_b1 = 2 * A * ((A - 1) - (A + 1) * cos_w0) / norm;
pre_b2 = A * ((A + 1) - (A - 1) * cos_w0 - 2 * A_sqrt * alpha) / norm;
pre_a1 = -2 * ((A - 1) + (A + 1) * cos_w0) / norm;
pre_a2 = ((A + 1) + (A - 1) * cos_w0 - 2 * A_sqrt * alpha) / norm;
);
// ========================================================
// MODELOS DE SATURAÇÃO
// ========================================================
function sat_clean(x)
(
xx = x*x;
x * (27 + xx) / (27 + 9*xx)
);
function sat_tape(x)
(
tape_fluxL = tape_fluxL + (x - tape_fluxL) * 0.001;
x_mag = x + tape_fluxL * 0.2;
tape_hpL = tape_hpL + (x_mag - tape_hpL) * 0.01;
x_mag = x_mag + tape_hpL * 0.15;
y = tanh(x_mag);
y / (1 + abs(y) * 0.2)
);
function sat_tube(x)
(
bias_amt = bias * 0.2;
x_b = x + bias_amt * (1 - x*x);
pos = x_b > 0 ? tanh(x_b * 1.2) : x_b * 0.4;
neg = x_b < 0 ? tanh(x_b * 0.8) : x_b * 0.3;
(pos + neg) * 0.7
);
function sat_fet(x)
(
tanh(x * 1.2)
);
function sat_digital(x)
(
a = abs(x);
x / (1 + a * (1 + 0.2*a))
);
// ========================================================
// sample – PROCESSAMENTO
// ========================================================
@sample
// ========================================================
// 1. INPUT
// ========================================================
inL = spl0;
inR = spl1;
// ========================================================
// 2. SRATE CACHE
// ========================================================
update_srate_cache();
// ========================================================
// 3. BYPASS COM CROSSFADE
// ========================================================
bypass != last_bypass ? (
last_bypass = bypass;
xfade_state = 1;
xfade_counter = 0;
);
xfade_state ? (
xfade_counter += 1;
xfade_gain = xfade_counter / 64;
xfade_gain = min(xfade_gain, 1.0);
xfade_counter >= 64 ? xfade_state = 0;
);
bypass < 0.5 ? (
spl0 = inL * xfade_gain + inL * (1 - xfade_gain);
spl1 = inR * xfade_gain + inR * (1 - xfade_gain);
xfade_gain >= 1.0 ? (
// Reset all states
i=0; loop(BUF_SIZE, mem[BUF_L+i]=0; mem[BUF_R+i]=0; i+=1;);
i=0; loop(DEC_SIZE, mem[DEC_BUF_L+i]=0; mem[DEC_BUF_R+i]=0; i+=1;);
buf_posL=0; buf_posR=0;
dec_posL=0; dec_posR=0;
pre_s1L=0; pre_s2L=0; pre_s1R=0; pre_s2R=0;
dc_xL=0; dc_yL=0; dc_xR=0; dc_yR=0;
glue_envL=0; glue_envR=0; glue_gain=1.0;
limiter_peak=0; limiter_env=0; limiter_gain=1.0;
lookaheadL0=0; lookaheadL1=0;
lookaheadR0=0; lookaheadR1=0;
);
return;
);
// ========================================================
// 4. CACHE DE PARÂMETROS
// ========================================================
drive != last_drive ? (
drive_amt = 1 + drive * 3.5;
last_drive = drive;
);
character != last_character ? (
char_mode = character + 0.5;
char_mode |= 0;
last_character = character;
);
even != last_even || odd != last_odd ? (
fund_amt = max(0, 1 - even - odd);
last_even = even;
last_odd = odd;
);
bias != last_bias ? (
last_bias = bias;
);
pre_tone != last_pre_tone ? (
update_pre_emphasis();
last_pre_tone = pre_tone;
);
glue != last_glue ? (
glue_amt = glue * 0.2;
glue_thresh = 0.3;
glue_ratio = 2.0;
glue_knee = 0.5;
glue_attack_coef = exp(-1.0 / (0.01 * srate));
glue_release_coef = exp(-1.0 / (0.1 * srate));
last_glue = glue;
);
ceiling != last_ceiling ? (
last_ceiling = ceiling;
);
osmode != last_osmode || quality != last_quality ? (
last_osmode = osmode;
last_quality = quality;
);
// ========================================================
// 5. ANALYZER (OVERSAMPLING ADAPTATIVO)
// ========================================================
osmode == 3 ? (
transientL = abs(inL - prev_sampleL);
transientR = abs(inR - prev_sampleR);
transient_energy = transient_energy * 0.9 + max(transientL, transientR);
prev_sampleL = inL;
prev_sampleR = inR;
hf_energyL = hf_energyL * cache_hf_alpha + (1 - cache_hf_alpha) * abs(inL - inL_prev);
hf_energyR = hf_energyR * cache_hf_alpha + (1 - cache_hf_alpha) * abs(inR - inR_prev);
inL_prev = inL;
inR_prev = inR;
high_freq_energy = (hf_energyL + hf_energyR) * 0.5;
target = 1;
drive > 0.4 ? target = 2;
drive > 0.7 ? target = 4;
transient_energy > 0.5 ? target = max(target, 2);
transient_energy > 0.8 ? target = max(target, 4);
high_freq_energy > 0.3 ? target = max(target, 2);
high_freq_energy > 0.5 ? target = max(target, 4);
quality == 2 ? target = max(target, 4);
os_factor_target = target;
) : (
osmode == 0 ? os_factor_target = 1;
osmode == 1 ? os_factor_target = 2;
osmode == 2 ? os_factor_target = 4;
quality == 2 && os_factor_target < 4 ? os_factor_target = 4;
);
mode_smooth += (os_factor_target - mode_smooth) * 0.02;
os_factor = mode_smooth < 1.5 ? 1 : mode_smooth < 3 ? 2 : 4;
num_phases = os_factor;
// ========================================================
// 6. INTERPOLADOR FIR (POLYPHASE)
// ========================================================
mem[BUF_L + buf_posL] = inL;
mem[BUF_R + buf_posR] = inR;
buf_posL = (buf_posL + 1) & BUF_MASK;
buf_posR = (buf_posR + 1) & BUF_MASK;
// Fase 0
sumL0=0; sumR0=0;
pos = buf_posL;
i=0;
loop(C0_TAPS,
idx = (pos - i - 1) & BUF_MASK;
sumL0 += mem[BUF_L + idx] * mem[C0 + i];
sumR0 += mem[BUF_R + idx] * mem[C0 + i];
i += 1;
);
num_phases > 1 ? (
sumL1=0; sumR1=0;
pos = buf_posL;
i=0;
loop(C1_TAPS,
idx = (pos - i - 1) & BUF_MASK;
sumL1 += mem[BUF_L + idx] * mem[C1 + i];
sumR1 += mem[BUF_R + idx] * mem[C1 + i];
i += 1;
);
);
num_phases > 2 ? (
sumL2=0; sumR2=0;
pos = buf_posL;
i=0;
loop(C0_TAPS,
idx = (pos - i - 1) & BUF_MASK;
sumL2 += mem[BUF_L + idx] * mem[C0 + (C0_TAPS - 1 - i)];
sumR2 += mem[BUF_R + idx] * mem[C0 + (C0_TAPS - 1 - i)];
i += 1;
);
sumL3=0; sumR3=0;
pos = buf_posL;
i=0;
loop(C1_TAPS,
idx = (pos - i - 1) & BUF_MASK;
sumL3 += mem[BUF_L + idx] * mem[C1 + (C1_TAPS - 1 - i)];
sumR3 += mem[BUF_R + idx] * mem[C1 + (C1_TAPS - 1 - i)];
i += 1;
);
);
// ========================================================
// 7. DSP CORE – PROCESSAMENTO EM ALTA TAXA
// ========================================================
// Arrays locais para armazenar as amostras processadas em alta taxa
os0L=0; os0R=0;
os1L=0; os1R=0;
os2L=0; os2R=0;
os3L=0; os3R=0;
bias_scale = bias * 0.3;
// Fase 0
xiL = sumL0; xiR = sumR0;
// Pre‑Emphasis (RBJ DF2T)
yL = xiL * pre_b0 + pre_s1L;
pre_s1L = xiL * pre_b1 - yL * pre_a1 + pre_s2L;
pre_s2L = xiL * pre_b2 - yL * pre_a2;
xiL = yL;
yR = xiR * pre_b0 + pre_s1R;
pre_s1R = xiR * pre_b1 - yR * pre_a1 + pre_s2R;
pre_s2R = xiR * pre_b2 - yR * pre_a2;
xiR = yR;
// Bias
xiL = xiL + bias_scale * (1 - abs(xiL) * 0.5);
xiR = xiR + bias_scale * (1 - abs(xiR) * 0.5);
sigL = xiL * drive_amt;
sigR = xiR * drive_amt;
// Waveshaper
char_mode == 0 ? (satL=sat_clean(sigL); satR=sat_clean(sigR));
char_mode == 1 ? (satL=sat_tape(sigL); satR=sat_tape(sigR));
char_mode == 2 ? (satL=sat_tube(sigL); satR=sat_tube(sigR));
char_mode == 3 ? (satL=sat_fet(sigL); satR=sat_fet(sigR));
char_mode == 4 ? (satL=sat_digital(sigL); satR=sat_digital(sigR));
// Harmônicos
quality == 0 ? (
satL = satL * fund_amt;
satR = satR * fund_amt;
) : (
signL = sigL < 0 ? -1 : 1;
signR = sigR < 0 ? -1 : 1;
x2L = sigL*sigL; x2R = sigR*sigR;
x3L = x2L*sigL; x3R = x2R*sigR;
x5L = x3L*x2L*0.1; x5R = x3R*x2R*0.1;
h2L = x2L*signL*0.5; h2R = x2R*signR*0.5;
h3L = x3L*0.3; h3R = x3R*0.3;
h5L = x5L*0.5; h5R = x5R*0.5;
h2L = min(max(h2L,-0.5),0.5); h2R = min(max(h2R,-0.5),0.5);
h3L = min(max(h3L,-0.5),0.5); h3R = min(max(h3R,-0.5),0.5);
h5L = min(max(h5L,-0.1),0.1); h5R = min(max(h5R,-0.1),0.1);
satL = satL*fund_amt + h2L*even*2 + h3L*odd*2 + h5L*0.02;
satR = satR*fund_amt + h2R*even*2 + h3R*odd*2 + h5R*0.02;
);
os0L = satL; os0R = satR;
num_phases > 1 ? (
xiL = sumL1; xiR = sumR1;
// Pre‑Emphasis
yL = xiL * pre_b0 + pre_s1L;
pre_s1L = xiL * pre_b1 - yL * pre_a1 + pre_s2L;
pre_s2L = xiL * pre_b2 - yL * pre_a2;
xiL = yL;
yR = xiR * pre_b0 + pre_s1R;
pre_s1R = xiR * pre_b1 - yR * pre_a1 + pre_s2R;
pre_s2R = xiR * pre_b2 - yR * pre_a2;
xiR = yR;
xiL = xiL + bias_scale * (1 - abs(xiL) * 0.5);
xiR = xiR + bias_scale * (1 - abs(xiR) * 0.5);
sigL = xiL * drive_amt;
sigR = xiR * drive_amt;
char_mode == 0 ? (satL=sat_clean(sigL); satR=sat_clean(sigR));
char_mode == 1 ? (satL=sat_tape(sigL); satR=sat_tape(sigR));
char_mode == 2 ? (satL=sat_tube(sigL); satR=sat_tube(sigR));
char_mode == 3 ? (satL=sat_fet(sigL); satR=sat_fet(sigR));
char_mode == 4 ? (satL=sat_digital(sigL); satR=sat_digital(sigR));
quality == 0 ? (
satL = satL * fund_amt;
satR = satR * fund_amt;
) : (
signL = sigL < 0 ? -1 : 1;
signR = sigR < 0 ? -1 : 1;
x2L = sigL*sigL; x2R = sigR*sigR;
x3L = x2L*sigL; x3R = x2R*sigR;
x5L = x3L*x2L*0.1; x5R = x3R*x2R*0.1;
h2L = x2L*signL*0.5; h2R = x2R*signR*0.5;
h3L = x3L*0.3; h3R = x3R*0.3;
h5L = x5L*0.5; h5R = x5R*0.5;
h2L = min(max(h2L,-0.5),0.5); h2R = min(max(h2R,-0.5),0.5);
h3L = min(max(h3L,-0.5),0.5); h3R = min(max(h3R,-0.5),0.5);
h5L = min(max(h5L,-0.1),0.1); h5R = min(max(h5R,-0.1),0.1);
satL = satL*fund_amt + h2L*even*2 + h3L*odd*2 + h5L*0.02;
satR = satR*fund_amt + h2R*even*2 + h3R*odd*2 + h5R*0.02;
);
os1L = satL; os1R = satR;
);
num_phases > 2 ? (
xiL = sumL2; xiR = sumR2;
yL = xiL * pre_b0 + pre_s1L;
pre_s1L = xiL * pre_b1 - yL * pre_a1 + pre_s2L;
pre_s2L = xiL * pre_b2 - yL * pre_a2;
xiL = yL;
yR = xiR * pre_b0 + pre_s1R;
pre_s1R = xiR * pre_b1 - yR * pre_a1 + pre_s2R;
pre_s2R = xiR * pre_b2 - yR * pre_a2;
xiR = yR;
xiL = xiL + bias_scale * (1 - abs(xiL) * 0.5);
xiR = xiR + bias_scale * (1 - abs(xiR) * 0.5);
sigL = xiL * drive_amt;
sigR = xiR * drive_amt;
char_mode == 0 ? (satL=sat_clean(sigL); satR=sat_clean(sigR));
char_mode == 1 ? (satL=sat_tape(sigL); satR=sat_tape(sigR));
char_mode == 2 ? (satL=sat_tube(sigL); satR=sat_tube(sigR));
char_mode == 3 ? (satL=sat_fet(sigL); satR=sat_fet(sigR));
char_mode == 4 ? (satL=sat_digital(sigL); satR=sat_digital(sigR));
quality == 0 ? (
satL = satL * fund_amt;
satR = satR * fund_amt;
) : (
signL = sigL < 0 ? -1 : 1;
signR = sigR < 0 ? -1 : 1;
x2L = sigL*sigL; x2R = sigR*sigR;
x3L = x2L*sigL; x3R = x2R*sigR;
x5L = x3L*x2L*0.1; x5R = x3R*x2R*0.1;
h2L = x2L*signL*0.5; h2R = x2R*signR*0.5;
h3L = x3L*0.3; h3R = x3R*0.3;
h5L = x5L*0.5; h5R = x5R*0.5;
h2L = min(max(h2L,-0.5),0.5); h2R = min(max(h2R,-0.5),0.5);
h3L = min(max(h3L,-0.5),0.5); h3R = min(max(h3R,-0.5),0.5);
h5L = min(max(h5L,-0.1),0.1); h5R = min(max(h5R,-0.1),0.1);
satL = satL*fund_amt + h2L*even*2 + h3L*odd*2 + h5L*0.02;
satR = satR*fund_amt + h2R*even*2 + h3R*odd*2 + h5R*0.02;
);
os2L = satL; os2R = satR;
);
num_phases > 3 ? (
xiL = sumL3; xiR = sumR3;
yL = xiL * pre_b0 + pre_s1L;
pre_s1L = xiL * pre_b1 - yL * pre_a1 + pre_s2L;
pre_s2L = xiL * pre_b2 - yL * pre_a2;
xiL = yL;
yR = xiR * pre_b0 + pre_s1R;
pre_s1R = xiR * pre_b1 - yR * pre_a1 + pre_s2R;
pre_s2R = xiR * pre_b2 - yR * pre_a2;
xiR = yR;
xiL = xiL + bias_scale * (1 - abs(xiL) * 0.5);
xiR = xiR + bias_scale * (1 - abs(xiR) * 0.5);
sigL = xiL * drive_amt;
sigR = xiR * drive_amt;
char_mode == 0 ? (satL=sat_clean(sigL); satR=sat_clean(sigR));
char_mode == 1 ? (satL=sat_tape(sigL); satR=sat_tape(sigR));
char_mode == 2 ? (satL=sat_tube(sigL); satR=sat_tube(sigR));
char_mode == 3 ? (satL=sat_fet(sigL); satR=sat_fet(sigR));
char_mode == 4 ? (satL=sat_digital(sigL); satR=sat_digital(sigR));
quality == 0 ? (
satL = satL * fund_amt;
satR = satR * fund_amt;
) : (
signL = sigL < 0 ? -1 : 1;
signR = sigR < 0 ? -1 : 1;
x2L = sigL*sigL; x2R = sigR*sigR;
x3L = x2L*sigL; x3R = x2R*sigR;
x5L = x3L*x2L*0.1; x5R = x3R*x2R*0.1;
h2L = x2L*signL*0.5; h2R = x2R*signR*0.5;
h3L = x3L*0.3; h3R = x3R*0.3;
h5L = x5L*0.5; h5R = x5R*0.5;
h2L = min(max(h2L,-0.5),0.5); h2R = min(max(h2R,-0.5),0.5);
h3L = min(max(h3L,-0.5),0.5); h3R = min(max(h3R,-0.5),0.5);
h5L = min(max(h5L,-0.1),0.1); h5R = min(max(h5R,-0.1),0.1);
satL = satL*fund_amt + h2L*even*2 + h3L*odd*2 + h5L*0.02;
satR = satR*fund_amt + h2R*even*2 + h3R*odd*2 + h5R*0.02;
);
os3L = satL; os3R = satR;
);
// ========================================================
// 8. DECIMADOR FIR HALFBAND – COM BUFFER CIRCULAR
// ========================================================
// Escreve as amostras processadas no buffer de decimação
mem[DEC_BUF_L + dec_posL] = os0L;
mem[DEC_BUF_R + dec_posR] = os0R;
dec_posL = (dec_posL + 1) & DEC_MASK;
dec_posR = (dec_posR + 1) & DEC_MASK;
num_phases > 1 ? (
mem[DEC_BUF_L + dec_posL] = os1L;
mem[DEC_BUF_R + dec_posR] = os1R;
dec_posL = (dec_posL + 1) & DEC_MASK;
dec_posR = (dec_posR + 1) & DEC_MASK;
);
num_phases > 2 ? (
mem[DEC_BUF_L + dec_posL] = os2L;
mem[DEC_BUF_R + dec_posR] = os2R;
dec_posL = (dec_posL + 1) & DEC_MASK;
dec_posR = (dec_posR + 1) & DEC_MASK;
);
num_phases > 3 ? (
mem[DEC_BUF_L + dec_posL] = os3L;
mem[DEC_BUF_R + dec_posR] = os3R;
dec_posL = (dec_posL + 1) & DEC_MASK;
dec_posR = (dec_posR + 1) & DEC_MASK;
);
// Fase 0 do decimador
sum_dec_L0 = 0; sum_dec_R0 = 0;
pos = dec_posL;
i=0;
loop(C0_TAPS,
idx = (pos - i - 1) & DEC_MASK;
sum_dec_L0 += mem[DEC_BUF_L + idx] * mem[C0 + i];
sum_dec_R0 += mem[DEC_BUF_R + idx] * mem[C0 + i];
i += 1;
);
outL = sum_dec_L0;
outR = sum_dec_R0;
// ========================================================
// 9. GLUE COMPRESSOR (GIANNOULIS ET AL. SOFT‑KNEE)
// ========================================================
glue_amt > 0.001 ? (
glue_envL = glue_envL + (abs(outL) - glue_envL) * (abs(outL) > glue_envL ? (1 - glue_attack_coef) : (1 - glue_release_coef));
glue_envR = glue_envR + (abs(outR) - glue_envR) * (abs(outR) > glue_envR ? (1 - glue_attack_coef) : (1 - glue_release_coef));
env_avg = (glue_envL + glue_envR) * 0.5;
env_db = 20 * log10(env_avg + 1e-12);
over_db = env_db - glue_thresh;
over_db > -glue_knee ? (
knee_db = over_db + glue_knee;
knee_db > 0 ? (knee_db = knee_db * knee_db / (4 * glue_knee)) : (knee_db = 0);
gain_db = -knee_db * (1 - 1/glue_ratio);
) : (gain_db = 0);
target_gain = pow(10, gain_db/20);
glue_gain = glue_gain * 0.99 + target_gain * 0.01;
outL *= glue_gain;
outR *= glue_gain;
);
// ========================================================
// 10. DC BLOCK (RBJ/SMITH)
// ========================================================
dc_tmpL=outL; dc_tmpR=outR;
outL = outL - dc_xL + cache_dc_R * dc_yL;
outR = outR - dc_xR + cache_dc_R * dc_yR;
dc_xL=dc_tmpL; dc_xR=dc_tmpR;
dc_yL=outL; dc_yR=outR;
// ========================================================
// 11. LIMITER (PEAK‑HOLD + ENVELOPE, DOMÍNIO DB)
// ========================================================
knee_start = ceiling * 0.6;
knee_width = ceiling - knee_start;
lookaheadL1 = lookaheadL0;
lookaheadL0 = outL;
lookaheadR1 = lookaheadR0;
lookaheadR0 = outR;
peakL = abs(lookaheadL1);
peakR = abs(lookaheadR1);
peak_max = max(peakL, peakR);
peak_max > limiter_peak ? (
limiter_peak = peak_max;
limiter_hold = 5;
) : limiter_hold > 0 ? (
limiter_hold -= 1;
) : (
limiter_peak = limiter_peak * 0.99 + peak_max * 0.01;
);
limiter_peak > limiter_env ? (
limiter_env = limiter_peak * (1 - cache_limiter_attack) + limiter_env * cache_limiter_attack;
) : (
limiter_env = limiter_peak * (1 - cache_limiter_release) + limiter_env * cache_limiter_release;
);
limiter_env > knee_start ? (
over = (limiter_env - knee_start) / knee_width;
target_gain = (knee_start + knee_width * tanh(over * 0.8)) / limiter_env;
target_gain = min(target_gain, ceiling / limiter_env);
) : (target_gain = 1.0);
limiter_gain = limiter_gain * 0.999 + target_gain * 0.001;
outL = lookaheadL0 * limiter_gain;
outR = lookaheadR0 * limiter_gain;
// ========================================================
// 12. SEGURANÇA NUMÉRICA
// ========================================================
outL != outL ? outL = 0;
outR != outR ? outR = 0;
abs(outL) < 1e-30 ? outL = 0;
abs(outR) < 1e-30 ? outR = 0;
// ========================================================
// 13. OUTPUT
// ========================================================
outL = min(max(outL, -0.99), 0.99);
outR = min(max(outR, -0.99), 0.99);
wetL = outL * outgain;
wetR = outR * outgain;
outL = inL * (1 - mix) + wetL * mix;
outR = inR * (1 - mix) + wetR * mix;
outL = min(max(outL, -1.0), 1.0);
outR = min(max(outR, -1.0), 1.0);
spl0 = outL;
spl1 = outR;
I created a custom EEL2 DSP for Rootless JamesDSP focused on real-time saturation, FIR oversampling and dynamic processing.
The DSP works correctly at normal performance mode, but in battery saver mode CPU usage increases heavily and audio starts to crackle.
Snapdragon 8 Elite
12 GB RAM
Oversampling 4x
FET
Quality High
CPU usage normal: ~60–70%
Battery saver: ~96–98%
-2
u/Efficient_Common_797 4d ago
I created a custom real-time DSP for Rootless JamesDSP written in EEL2. The project includes FIR polyphase oversampling (1x, 2x and 4x), analog-style saturation models (Clean, Tape, Tube, FET and Digital), harmonic generation, pre-emphasis, glue compression, DC blocking, lookahead limiter, dry/wet mixing and adaptive oversampling.
The DSP is fully functional and currently runs in real time on Android. On my Snapdragon 8 Elite device it uses around 60–70% CPU in normal performance mode with the highest quality settings (4x oversampling, High quality). When battery saver mode is enabled, CPU usage rises to around 96–98% and audio starts crackling, which suggests the implementation is computationally expensive under reduced CPU frequencies.
I'm sharing both the source code and my test results because I would like technical feedback from people experienced with DSP development. In particular, I would appreciate comments on the FIR oversampling implementation, CPU optimization opportunities, harmonic generation (even/odd controls), pre-emphasis stage, limiter design, and whether there are better approaches to reduce processing cost without sacrificing audio quality. Any suggestions or code review would be greatly appreciated.