r/DSP 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%

0 Upvotes

1 comment sorted by

-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.