Crossfading "Squeak"

I’m developing an multi-channel output (send&return) sequencer and was noticing a “pop” when the channel switching would occur. I determined that this pop was from discontinuities in the signal created from the slight delays from effects between the Send & Return paths.
I started using the Crossfade module of the DaisySP to resolve this- fading from the Returns path (channel A) to the original Input back to the Returns path (now the return of channel B) during the channel switching.
This eliminated the “pop” but has created a slight “squeak” sound during the middle of the fade. Lengthening the duration of the crossfade (originally 48 samples) makes this reduces the volume of the squeak, but I get an ever so slight delay in the channel switching. (The longer the crossfade, the quieter the squeak but the larger the delay). (I am currently using a crossfade length of 348 samples.)
I am wondering if I am missing anything that is creating this squeak or if I need to just use a longer crossfade duration.
What would be the typical method for switching between discontinuous signals without creating an audible “pop”?

I’d say 48 is good enough. But have you tried using contant power crossfade function instead of linear? If your audio is uncorrelated, that’s what you need to avoid a dip in the middle of crossfade .

I am already using the constant power crossfade function because the audio is uncorrelated. I am not experiencing a dip in the crossfade, it’s an audible “squeak”, a short high pitched noise.
I have tried it with constant power and linear at 48 and they both produce this squeak.

hmm it’s hard to know why without reading your code. Would you mind posting it?

I’d say you should try capturing those signals to see what exactly are you mixing. I.e. send copies to spare outputs before the mixing, record them and convert to graphics.

Looking at code like Ben suggested would help too in case if you’ve made some mistake, even though it sounds like you know what you’re doing.

not at all!
(I am working in Arduino)

#include <CJMnotebook.h>
#include <DaisyDuino.h>

String ver = "oprtr_beta02_2ch_mockup";
String notes = "..." 
               "ch1: pin 22 transistor and the Blue led are turned on"
               "ch2: pin 23 transistor and the Green led are turned on"
               "It takes AudioIn2(Returns) and writes to AudioOut1(mainOut),"
               "crossfading between Rtrns and mainIn to smooth transistion.";

DaisyHardware hw;

#define ledB D22 // blue led. pin 29, referenced to Daisy as D22, see Pinout
#define ledG D21 // green led, pin 28
#define ledR D19 // red led, pin 26

#define ch1 D15 // send 1, opens Channel 1. pin 22
#define ch2 D16 // pin 23
#define fsw D25 // pin 32, bypass

int byp = LOW; // LOW: running, HIGH: bypassed

long crntTime; // current Time
long prevTime; // previous Time
bool chSwitch = false; // switch between ch1 and ch2
long waitTime; // wait this long (milliseconds) until switching output

float pos; // Crossfade position
float cfMix;
float input;
float rtrns;
bool chngSoon; // channel change soon
float chngCount; // counts up to channel change
float cfLength = 384.0; // crossfade length
float cfHlfL = cfLength/2.0; // half crossfade length
float a = -0.000027; // rate of crossfade parabola
// cfLength:a   48.0:-0.00173611; 96.0:-0.00043; 192.0:-0.000108; ... 288:-0.0000482...; 384:-0.000027
// !!! Experiment with different crossfade lengths !!!
/*
 *Tradeoffs between lower "squeak" volume and slight delay in channel switching
 */

static CrossFade cfade;

CJMnotebook nb(ver, notes); // for printing version and notes
size_t num_channels;

void bypass(){
    byp = !byp;
}

void chTimer(){
    crntTime = millis();

    if (chngSoon == false && (crntTime - prevTime > waitTime - cfHlfL)){
        chngSoon = true;
        chngCount = 0;
        // Serial.print("Start Crossfade:");
    }
    if (chngSoon == true) chngCount += 1.0;
    if(chngCount > cfLength){
      chngSoon = false;
      chngCount = 0.0;
    //   Serial.print("End CF:");
    }
    
    if (crntTime - prevTime > waitTime){
        prevTime = crntTime;
        if (chSwitch == false) chSwitch = true;
        else chSwitch = false;
        // Serial.print("Channel Switch:");
    }
}

void startAnimation(){
    digitalWrite(ledB, HIGH);
    delay(250);
    digitalWrite(ledG, HIGH);
    delay(400);
    digitalWrite(ledR, HIGH);
    digitalWrite(ledB, LOW);
    delay(500);
    digitalWrite(ledR, LOW);
    digitalWrite(ledG, LOW);

    nb.notePrint();
    // maybe delay(500);  here
}

void myCallback(float **in, float **out, size_t size){
    hw.DebounceControls();
    if (byp == HIGH){
        digitalWrite(ledB, LOW);
        digitalWrite(ledG, LOW);
        digitalWrite(ledR, HIGH);
        for (size_t i = 0; i < size; i++){
                out[0][i] = in[0][i]; // write AudioIn1 (mainIn) to AudioOut1 (mainOut)
        }
    }

    if (byp == LOW){
        if (chSwitch == false){ // ch1, blue
            digitalWrite(ledB, HIGH);
            digitalWrite(ledG, LOW);
            digitalWrite(ledR, LOW);
            digitalWrite(ch1, HIGH);
            digitalWrite(ch2, LOW);
        }
        else if (chSwitch == true){ // ch2, green
            digitalWrite(ledB, LOW);
            digitalWrite(ledG, HIGH);
            digitalWrite(ledR, LOW);
            digitalWrite(ch1, LOW);
            digitalWrite(ch2, HIGH);
        }

        for (size_t i = 0; i < size; i++){
            out[1][i] = in[0][i];
            // writes AudioOut2 (Sends path) from AudioIn1 (mainIn)
                
            if (chngSoon == false){
                out[0][i] = in[1][i];
                // writes AudioOut1 (mainOut) from AudioIn2 (Rtrns path)
            }
            if (chngSoon == true){
                input = in[0][i];
                rtrns = in[1][i];
                
                pos = ((a * sq(chngCount - cfHlfL)) + 1); // calculates the pos w/ crossfade parabola
                cfade.SetPos(pos);
                cfMix = cfade.Process(rtrns, input);
                out[0][i] = cfMix; // write AudioOut1 (mainOut) from Crossfade result

            }
        }
    }
    chTimer();
    // if(chngSoon == true) Serial.println(pos);
}
 


void setup() {
    float samplerate;
    pinMode(ledB, OUTPUT);
    pinMode(ledG, OUTPUT);
    pinMode(ledR, OUTPUT);
    pinMode(ch1, OUTPUT);
    pinMode(ch2, OUTPUT);
    attachInterrupt(digitalPinToInterrupt(fsw), bypass, RISING);
    hw = DAISY.init(DAISY_SEED, AUDIO_SR_48K);
    num_channels = hw.num_channels;
    samplerate = DAISY.get_samplerate();
    cfade.Init();
    cfade.SetCurve(CROSSFADE_CPOW); // _LIN or _CPOW

    prevTime = 0;
    waitTime = 1250;

    startAnimation();
    DAISY.begin(myCallback);
}

void loop() {

}

I will try to record the signals independently and then take a look at them, maybe in a spectogram

Actually spectrogram won’t help much, the issue is happening in time domain.

Looking at code snippe and I don’t quite follow what pos = ((a * sq(chngCount - cfHlfL)) + 1); is supposed to do here. The position must change linearly during crossfade, not with a parabola. If you’re applying parabolic distortion to crossfade position, you end up with invalid curve.

re: spectogram. Aah ok, thank you.

The parabola is intended to “automate” the crossfade position from 0.0 to 1.0 back to 0.0 for the Return, Input, Return fade.
Would this create in an invalid curve that results in errors?

I tried out using a linear equation to automate the pos for crossfade and I’m still getting the squeak on channel switching, now even when there’s not incoming audio.
Am I misunderstanding what you mean by “the position must change linearly during crossfade”?