Building a 2 channel DJ mixer

Rezo

Well-known member
After my first endeavor with Audio related projects on the teensy with my Teensy based CDJ1000, I am now starting another project in parallel - building a 2 channel DJ mixer
Not doing this for commercial use or anything alike, just for personal use and the fun of it.

My requirements are for the mixer are:
  • Two stereo inputs - one per channel
  • For each channel:
    • Trim (gain control) range -infinity to +6dB
    • 3 band EQ with range of -infinity to +6dB
    • Channel fader
    • LED VU indicator
  • For the master control:
    • Cross fader between the two channels
    • LED VU indicator
    • Master volume
    • Effects (Reverb, delay, flange etc) - not included for this phase
Using the audio library design tool, I came up with this setup
1725267139675.png
First stage is quad input for the two channels
Each set of inputs is fed to an amp object to provide gain via the Trim pot
A 3-band EQ using a biquad filter
An amp to attune the signal without adding gain for the channel fader
Two mixers to sum signals into a peak object for the VU meter
Two mixers for the crossfader to mix the two channels
Two amps for the master volume to attune (no added gain)
And then out to the i2s outout object + two peak objects for the master VU LEDs

Does this setup seem okay? will the biquad filter be sufficient for 3 bands at 100Hz, 1000Hz and 10000Hz?

I'd like to run at 96Khz sample rate - what would be some affordable ADCs to use for the inputs, as well as a DAC for the output?

Thanks,
David
 
Regarding the 3-band EQ and biquad filter - as I have zero experience with this, I consulted with chatGPT on how to setup such an EQ and this is what it suggested:
C++:
AudioFilterBiquad       bassFilter;      // Biquad filter for bass
AudioFilterBiquad       midFilter;       // Biquad filter for mid
AudioFilterBiquad       trebleFilter;    // Biquad filter for treble


void configureEQ() {
  // Set frequencies for bass, mid, and treble bands
  float bassFreq = 100.0;      // Bass at 100 Hz
  float midFreq = 1000.0;      // Midrange at 1000 Hz
  float trebleFreq = 10000.0;  // Treble at 10 kHz

  // Gain values for each band (set initial values)
  float bassGain = 0.0;   // In dB, range: -24 to +6
  float midGain = 0.0;    // In dB, range: -24 to +6
  float trebleGain = 0.0; // In dB, range: -24 to +6

  // Set the biquad filters
  bassFilter.setLowShelf(0, bassFreq, 0.707, pow(10, bassGain / 20));
  midFilter.setPeak(0, midFreq, 1.0, pow(10, midGain / 20));
  trebleFilter.setHighShelf(0, trebleFreq, 0.707, pow(10, trebleGain / 20));
}

void setBassGain(float gain) {
  float bassFreq = 100.0;
  if (gain <= -24.0) {
    bassFilter.setLowShelf(0, bassFreq, 0.707, 0.0); // Mute bass band
  } else {
    bassFilter.setLowShelf(0, bassFreq, 0.707, pow(10, gain / 20));
  }
}

void setMidGain(float gain) {
  float midFreq = 1000.0;
  if (gain <= -24.0) {
    midFilter.setPeak(0, midFreq, 1.0, 0.0); // Mute mid band
  } else {
    midFilter.setPeak(0, midFreq, 1.0, pow(10, gain / 20));
  }
}

void setTrebleGain(float gain) {
  float trebleFreq = 10000.0;
  if (gain <= -24.0) {
    trebleFilter.setHighShelf(0, trebleFreq, 0.707, 0.0); // Mute treble band
  } else {
    trebleFilter.setHighShelf(0, trebleFreq, 0.707, pow(10, gain / 20));
  }
}

Would it be as simple as this?
 
I am not sure if it is possible to change the Biquad filter without getting audible artifacts. You can simply try that out and see if it is working for you. In the documentation there is a note about low corner frequencies which could be also relevant for you.

The state variable filter would be another option where I can say from my experience that changing the filter frequencies is working fine. You can also combine several filters to get a steeper slope.

Take also care of the note that the signal through the filters must not exceed 1.0. This means if you want to have a range up to +6dB you have to attenuate before the filter and amplify after it (or somehow make sure that the signal stays below 1.0). At all points you have to take care that there is no clipping.
 
@TomChiron thank you for your detailed comment and insights!
Would you be able to perhaps show me how to setup the 3 band EQ using the state variable filter? I had a look at the object in the design tool but couldn’t figure out how to actually use it or configure it
 
So I made two simple sketches to play a file from SD and then implement a 3 band eq (mono for now), one using the biquad filter for all three bands, and another using the state variable filter for all three bands.

In general, it works. The music sounds "warmer" using the state variable filters, but on both types I get very faint clicking noise when attuning or boosting the lowpass filter - any idea what would be causing that and how I can eliminate it?
Im using an audio shield with Bose QC35 headphones (not powered for the tests)

Attached are the two sketches
 

Attachments

  • sdWav_3b_EQ_biquad.ino
    3.9 KB · Views: 32
  • sdWav_3b_EQ_fsv.ino
    3.3 KB · Views: 32
This is probably "zipper noise" from the mixer, as any gain changes are sudden. One way around it is to use DC objects and multipliers, thus:
1725396606577.png

C++:
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>

// GUItool: begin automatically generated code
AudioPlaySdWav           playSdWav1;     //xy=97,241
AudioFilterBiquad        highBand;       //xy=386,184
AudioFilterBiquad        midBand;        //xy=386,226
AudioFilterBiquad        lowBand;        //xy=387,268
AudioSynthWaveformDc     gainH;            //xy=410,52
AudioSynthWaveformDc     gainM; //xy=416,97
AudioSynthWaveformDc     gainL; //xy=420,138
AudioEffectMultiply      multH;      //xy=619,113
AudioEffectMultiply      multM; //xy=621,152
AudioEffectMultiply      multL; //xy=623,190
AudioMixer4              mixer;          //xy=831,158
AudioOutputI2S           audioOutput;    //xy=1012,158

AudioConnection          patchCord1(playSdWav1, 0, highBand, 0);
AudioConnection          patchCord2(playSdWav1, 0, midBand, 0);
AudioConnection          patchCord3(playSdWav1, 0, lowBand, 0);
AudioConnection          patchCord4(highBand, 0, multH, 1);
AudioConnection          patchCord5(midBand, 0, multM, 1);
AudioConnection          patchCord6(lowBand, 0, multL, 1);
AudioConnection          patchCord7(gainH, 0, multH, 0);
AudioConnection          patchCord8(gainM, 0, multM, 0);
AudioConnection          patchCord9(gainL, 0, multL, 0);
AudioConnection          patchCord10(multH, 0, mixer, 2);
AudioConnection          patchCord11(multM, 0, mixer, 1);
AudioConnection          patchCord12(multL, 0, mixer, 0);
AudioConnection          patchCord13(mixer, 0, audioOutput, 0);
AudioConnection          patchCord14(mixer, 0, audioOutput, 1);

AudioControlSGTL5000     sgtl5000_1; //xy=1030,215
// GUItool: end automatically generated code



// Constants for band indices
#define LOW_BAND  0
#define MID_BAND  1
#define HIGH_BAND 2

// Define pin numbers
const int potPin = A0;        // Potentiometer pin
const int buttonPin = 2;      // Push button pin

int currentBand = LOW_BAND;   // Start with adjusting the low band
bool lastButtonState = LOW;   // Last state of the button
bool buttonPressed = false;   // Flag for button press



void setup() {
  // Initialize serial communication for debugging (optional)
  Serial.begin(9600);
  while (!Serial); // Wait for Serial monitor to open
 
  pinMode(buttonPin, INPUT_PULLUP); // Set button pin as input with pullup
  // Allocate memory for audio processing
  AudioMemory(12);
 
  // Enable and configure audio codec
  sgtl5000_1.enable();
  sgtl5000_1.volume(0.5);

  if (!(SD.begin(BUILTIN_SDCARD))) {
    // stop here, but print a message repetitively
    while (1) {
      Serial.println("Unable to access the SD card");
      delay(500);
    }
  }

  // Setup EQ filters
  setupEQBands();

  // Set initial gains for each band (in dB)
  setBandGain(LOW_BAND, 0.0);   // 0 dB
  setBandGain(MID_BAND, 0.0);   // 0 dB
  setBandGain(HIGH_BAND, 0.0);  // 0 dB
 
  mixer.gain(0, 2.0f);
  mixer.gain(1, 2.0f); // add 6dB of gain that multiplier can't do
  mixer.gain(2, 2.0f);

  const char * filename = "ATMOSPHERE.wav";
  playSdWav1.play(filename);
 

}

void loop() {
  static int lastPotValue = 0;
  int potValue = analogRead(potPin);
  if (potValue != lastPotValue){
    float gainValue = map(potValue, 0, 1023, -26.1, 6.0);
    Serial.printf("db: %f \n", gainValue);
 
    setBandGain(currentBand, gainValue);
    lastPotValue = potValue;
  }
  // Check if the button is pressed
  bool buttonState = digitalRead(buttonPin);
  if (buttonState == LOW && lastButtonState == HIGH && !buttonPressed) {
    // Button press detected, toggle the band
    currentBand = (currentBand + 1) % 3; // Cycle through 0, 1, 2
    buttonPressed = true; // Set flag to avoid multiple toggles
    Serial.print("Switched to band: ");
    Serial.println(currentBand);
  }
 
  // Reset button pressed flag when button is released
  if (buttonState == HIGH && lastButtonState == LOW) {
    buttonPressed = false;
  }
 
  // Save the current button state for the next loop
  lastButtonState = buttonState;
 
  delay(100); // Adjust as needed
}

void setupEQBands() {
  // Configure low band
  lowBand.setLowpass(0, 100, 0.707); // Butterworth filter

  // Configure mid band
  midBand.setBandpass(0, 1000, 1.0); // Q=1.0 covers approx. 1 octave

  // Configure high band
  highBand.setHighpass(0, 13000, 0.707); // Butterworth filter
}

void setBandGain(uint8_t band, float dB) {
  float transTime = 10.0f; // gain transition time in milliseconds
 
  // Constrain dB value between -infinity (-60 dB for practical purposes) and +6 dB
  if (dB < -26.0) dB = -26.0; // no point, this is caught later!
  if (dB > 6.0) dB = 6.0;
 
  // Convert dB to linear gain
  float linearGain;
  if (dB <= -26.0) {
    linearGain = 0.0; // Practically silence
  } else {
    linearGain = pow(10.0, dB / 20.0);
  }
  linearGain /= 2; // mixer is set to gain of 2.0 (linear)
 
  // Set gain on mixer
  switch (band)
  {
    case 0: gainL.amplitude(linearGain,transTime); break;
    case 1: gainM.amplitude(linearGain,transTime); break;
    case 2: gainH.amplitude(linearGain,transTime); break;
  }
}
Unlike the mixer object, DC+multiplier can't give you a gain greater than 0dB, so I've made the mixer have a fixed gain of 6dB (well, x2, near enough...), and adjusted the linearGain value to suit. This loses the LSbit of the signal, but it's only test code, right?
 
@h4yn0nnym0u5e thanks for putting that together - just flashed it and now there is no more noise when attuning or booting!
For a steeper rolloff do I just add more filters in series?

Also, why the biquad over the state variable filter? My low band is set to a frequency of 100Hz, while the object notes state that anything below 400Hz could lead to poor performance with limited precision.
Would it be worth swapping out the LOW biquad filter with a state variable, and add an amp after it to attune/boost? or am I over complicating things?
 
I've not played with the filters much, but I'd certainly expect more stages = steeper rolloff. Note that the biquads each contain 4 stages, of which you've so far only used one.

I just picked one of your examples, since they were basically identical apart from the choice of filter.

You should probably play around with your filter choices until you're happy with the result - that's the true measure of "correct"! Of course, given the warning about using the biquads at low frequency it's certainly worth considering the SVF for the low end.
 
Swapped out the lowBand biquad for a svf and lows sound much better!
Can you please share your project? I'm struggling to find a good example for a simple tone control and/or eq for the audio library. I just don't understand digital filters yet. All I want is 3 or maybe 5 "knobs" like on a mixing console.
 
Back
Top