Audio Balance and avoiding clipping and other distortion

Davidelvig

Well-known member
I have this design now, and it's sounding great, through headphones, line-out and USB audio out.
I've been winging it in terms of setting levels throughout the chain, trying to intuitively stay below 1.0 at any node.
I've have some outlier conditions where I get a hiss.

I'd like to rigorously walk through the code and "measure" the levels at each important point.
Can someone advise me on:
- where are the important points to measure?
- what's the best way to measure values?
- where should I be setting the amplitude values for best control
(e.g. waveForm0.amplitude() vs mixer_Waves.gain(0, x) vs something added, like an AudioAmplifier object?

Thanks!
1738722759299.png
 

Attachments

  • 1738722733944.png
    1738722733944.png
    431.1 KB · Views: 6
@Davidelvig:

In my TeensyMIDIPolySynth (TMPS) project, I use the AudioAnalyzePeak (for example, naming it peak1) audio object, calling peak1.read() every 100ms to get the value. I know that I'm wasting some headroom by doing so, but I try to keep my sampled values below 0.9, just to improve the likelihood that brief & instantaneous peaks will go thru without causing distortion. Doing my best to ensure that the output from my TMPS does not include any unintentional distortion (there are some functions which introduce intentional distortion . . . I'm talking about everything else except those) is very important to me, so that's the threshold that I've chosen, and it has worked very well so far.

I also include an AudioAnalyzePeak object monitoring the final audio output, and I use that to drive a "CLIPPING" indicator on my control touchscreen.

Hope that helps . . .

Mark J Culross
KD5RXT
 
Thanks, @kd5rxt-mark , that’s a big help!
Since I’m blending a few sources, I imagine I’ll connect and monitor a few points along the chain.
And the goal is to avoid any point in the chain hitting 1.0, right?
 
I do something very similar to Mark and sprinkle AudioAnalyzePeak around the key points of the design.

I run this routine on a slow update (10 hz) to collect these together and use the global variable, max_vu_level to set a led showing any potetnail clipping.

Code:
void analyseGain() {
  BCRMB1;
  // update peak gains
  if (peakIn.available()) gainStage[0] = peakIn.read();
  if (peakEffectMixOut.available()) gainStage[1] = peakEffectMixOut.read();
  if (peakLowPassFilter.available()) gainStage[2] = peakLowPassFilter.read();
  if (peakSdFilterAmp.available()) gainStage[3] = peakSdFilterAmp.read();
  if (peakShelfFilterOut.available()) gainStage[4] = peakShelfFilterOut.read();
  if (peakFeedbackEcho.available()) gainStage[5] = peakFeedbackEcho.read();
  if (peakFeedbackReverb.available()) gainStage[6] = peakFeedbackReverb.read();
  if (peakLeftOutput.available()) gainStage[7] = peakLeftOutput.read();
  if (peakRightOutput.available()) gainStage[8] = peakRightOutput.read();
  if (peakLeftFinalOutput.available()) gainStage[9] = peakLeftFinalOutput.read();
  if (peakRightFinalOutput.available()) gainStage[10] = peakRightFinalOutput.read();

  // find max vu
  max_vu_level = 0.0f;
  for (int a = 0; a < numberOfStages; a++) {
    if (gainStage[a] > max_vu_level) max_vu_level = gainStage[a];
  }
  if (data_display_type == DATA_DISPLAY_VU) data_display_value = int(max_vu_level * 8.0);  // map the max_vu (0.0-1.0) to 8 leds
  if (verboseGainStages) dumpGainAveragesToSerial(true);
}

Cheers, Paul
 
Thanks, @houtson ! Just what I needed!
And the red clipping indicator is shown when you find any peak...read() over 1.0, or 0.9 or some other threshhold?
Is that it or is it fuzzier logic?
 
I use a RGB led and x10 max_vu_level to pick a colour from
Code:
// VU Meter display colours
int VU_display_colour[11] = {0xE4E4E4, 0x008000, 0x002600, 0x00FF00, 0x00FF00, 0xFF4B00,
                             0xFF4B00, 0xFF4B00, 0xFF4B00, 0xFF4B00, 0xFF0000};  // gamma corrected

During development I also dump averages out to serial (verboseGainStages) every so often to help tune the gains at each stage to reasonable values.

Code:
void dumpGainAveragesToSerial(boolean add_to_average) {
  BCRMB1;
  static int count_of_updates = 0;
  static float gainStageAverage[numberOfStages] = {0.0};
  if (add_to_average) {
    // update averages
    count_of_updates++;
    for (int a = 0; a < numberOfStages; a++) {
      gainStageAverage[a] += gainStage[a];
    }
  } else {
    Serial.printf("%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f\n", gainStageAverage[0] / (float)count_of_updates,
                  gainStageAverage[1] / count_of_updates, gainStageAverage[2] / count_of_updates, gainStageAverage[3] / count_of_updates,
                  gainStageAverage[4] / count_of_updates, gainStageAverage[5] / count_of_updates, gainStageAverage[6] / count_of_updates,
                  gainStageAverage[7] / count_of_updates, gainStageAverage[8] / count_of_updates, gainStageAverage[9] / count_of_updates,
                  gainStageAverage[10] / count_of_updates);
    // reset averages
    count_of_updates = 0;
    for (int a = 0; a < numberOfStages; a++) {
      gainStageAverage[a] = 0.0;
    }
  }
}

Cheers, Paul
 
Got it. And nothing magic about 1.000, but rather, getting aware of what measured levels sound good, and what sounds overblown, and adjusting to that?
 
1.0f will likely be hard clipping so unless you like it that way I'd aim to keep it well under 1.0f at each stage - Mark's 0.9 is a good working max.

Cheers Paul
 
Back
Top