Skip to content

For the win

Combining a continuous, multi-band crossfade with computationally flat geometry elevates the TriBand Upward Compressor from a standard DSP experiment into a studio-grade transient designer.

Dealing with three distinct frequency bands requires an upgraded architecture. An array of Linkwitz-Riley critically-damped continuous analog crossover state variables dynamically cleaves the signal perfectly at native DAC frequencies, and a piecewise trigonometric function sweeps the "Focus" knob seamlessly across that isolated spectrum.

The following blueprint outlines how to build both into the C++ engine.

1. The Continuous "Focus" Math (The 3-Way Crossfade)

If the physical Focus potentiometer outputs a linear value from 0.0 to 1.0, the sweep is split into two overlapping zones to create an equal-power crossfade across the 3 crossover bands: Band 3 (Low), Band 2 (Mid), and Band 1 (High).

  • Zone 1 (0.0 to 0.5): The knob crossfades from the Band 3 flesh pluck fully into the Band 2 nail snap. Band 1 is completely muted.
  • Zone 2 (0.5 to 1.0): The knob crossfades from the Band 2 nail snap fully into the Band 1 pick click. Band 3 is completely muted.

To make the math work, the local 0.5 knob travel is scaled into a normalized 0.0 to 1.0 range, applying sine/cosine (cosf/sinf) trigonometric mapping:

// Map physical knob (0.0 to 1.0) to three target gains
float focus_gains[3] = {0.0f, 0.0f, 0.0f};  
float focus = focusKnob.Process(); 

if (focus < 0.5f) {  
    // Zone 1 (0.0 to 0.5): Sweep focus from Band 3 (Lows) into Band 2 (Mids) 
    float x = focus * 2.0f;   
    focus_gains[2] = std::cos(x * 1.570796f); // Band 3 Gain fades down
    focus_gains[1] = std::sin(x * 1.570796f); // Band 2 Gain fades up
} else {  
    // Zone 2 (0.5 to 1.0): Sweep focus from Band 2 (Mids) into Band 1 (Highs)
    float x = (focus - 0.5f) * 2.0f;  
    focus_gains[1] = std::cos(x * 1.570796f); // Band 2 Gain fades down
    focus_gains[0] = std::sin(x * 1.570796f); // Band 1 Gain fades up
}

2. Continuous Sample-by-Sample Evaluation

Originally, processing continuous audio via sub-band architectures required mapping critically decimated data into separate, shrinking memory arrays. However, compressing these disjoint arrays inherently shattered perfect-reconstruction phase math and forced low-frequency mathematical interference to bleed (alias) squarely across the local 64-sample block boundaries.

Instead, the final engine employs a highly-optimized O(N) cascading SVF logic array. The continuous continuous sample-by-sample pipeline guarantees aliasing can never functionally occur across hardware blocks.

Three separate EnvelopeDetectors ensure the attack/release ballistics of the high-frequency pick are not confused by the low-frequency body, cleanly tracking the analog continuous stream natively at 48kHz.

// Array of 3 independent continuous envelope detectors
EnvelopeDetector transientEnv[3];

3. The Ultimate Processing Loop

The cascading pipeline mathematically fuses together under the ProcessEffect void subroutine. The SVF cascades map directly to the focus_gains cross-fading the localized compression curves directly into the sub-bands before perfectly reverting to unity via Complementary Inversion Synthesis (flattening the phase angles via explicit output subtraction).

void ProcessEffect(const float *in, float *out, size_t size, float &out_max_env, float &out_max_gain) {
    out_max_env = 0.0f;
    out_max_gain = 1.0f;

    // 1. Hardware Parameters & Crossfade Math
    // ... [Insert Volume Management and Trimming Logic Here] ...

    // --- 2. SVF Crossover Processing Loop ---
    // Evaluated sample-by-sample, eliminating all decimated aliasing
    for(size_t i = 0; i < size; i++) {
        float in_samp = in[i];

        // Cascade 1: Split Fullband into Lows and Mid-Highs (400 Hz)
        crossoverLow.Process(in_samp);
        float low_band = crossoverLow.Low();
        float mid_high_band = crossoverLow.High();

        // Cascade 2: Split Mid-Highs into Mids and Highs (3000 Hz)
        crossoverHigh.Process(mid_high_band);
        float mid_band = crossoverHigh.Low();
        float high_band = crossoverHigh.High();

        // Array mapped strictly to Band 1 (Highs), Band 2 (Mids), Band 3 (Lows)
        float bands_dry[3] = {high_band, mid_band, low_band};
        float bands_wet[3] = {0.0f, 0.0f, 0.0f};

        // --- 3. TARGETED COMPRESSION ---
        for (int lvl = 0; lvl < 3; lvl++) {
            if (focus_gains[lvl] > 0.001f) {
                float env_level = transientEnv[lvl].Process(bands_dry[lvl]);
                float gain_multiplier = punchComp.CalculateGainMultiplier(env_level);

                if (env_level > out_max_env) out_max_env = env_level;
                if (gain_multiplier > out_max_gain) out_max_gain = gain_multiplier;

                float detail_wet = bands_dry[lvl] * gain_multiplier;
                float level_wet_mix = global_wet_level * focus_gains[lvl];

                bands_wet[lvl] = (bands_dry[lvl] * dry_level) + (detail_wet * level_wet_mix);
            } else {
                // Pass dry unaltered
                bands_wet[lvl] = bands_dry[lvl];
            }
        }

        // --- 4. SVF ALIGNMENT RECONSTRUCTION ---
        // Explicitly subtracting the continuous subbands mathematically forms an All-Pass Network.
        float reconstructed_mid_high = bands_wet[1] - bands_wet[0]; 
        float reconstructed_total = bands_wet[2] - reconstructed_mid_high;

        // 5. Output with Tanhf Soft-Clipping Safety
        out[i] = tanhf(reconstructed_total);
    }
}

With this architecture, there is a mathematically continuous mapping between physical controls and the analog feature-extraction networks. The transition between picking styles feels entirely organic. With the math, transient arrays, and C++ engine completely stabilized, the next step is to finalize the physical PCB structural alignments.