Sound Tools - Noise Gate

I’ve been having such fun playing with @jeff’s music sequencer for sound reactive patterns, I thought I’d contribute some of my own sound toolkit. A few weeks ago, I mentioned noise gates as a way to keep from amplifying static or noise when you’re using automatic gain control and there’s no music playing. Here’s an overview if you haven’t encountered one of these things before.

Anyway, I’ve made an example for Pixelblaze. This gate, called from beforeRender(), opens pretty much instantly when it detects sound above the threshold, stays open for at least the hold time, then closes progressively over the release interval when the audio level drops below the threshold.

The display is 2D, but you can take the “Audio Tools Block” with the gate and it’s variables and plug it into pretty much any pattern. Without further ado, here’s the code:

// Audio noise gate demo, with super simple energy averaging 
// spectrum analyzer
//
// shows "normalized" frequency data, with a single LED in the 
// upper corner showing gate state - green for open, red for closed.
//
// See comments below for details on the how the gate works, and 
// please take the audioGate() function and use it in your own code!
//
// 11/11/21 ZRanger1

export var frequencyData
export var energyAverage
export var maxFrequencyMagnitude
export var maxFrequency

// UI Variables
export var silenceThreshold = 0.08 // close gate if level drops below this threshold
export var holdTime = 500;         // keep gate open for at least this long (milliseconds)
export var releaseTime = 300;      // how long does it take the gate to close (milliseconds)

// spectrum analyzer variables
var MAXWINDOWSIZE =  16
var windowSize = 3;
var bandHistory = array(32);
var bandAverage = array(32);
var avgIndex = 0;

// UI functions

// set threshold for noise gate
export function sliderThreshold(v) { 
  silenceThreshold = v*v;
}

export function sliderHold(v) {
  holdTime = floor(1000 * v * v);
}

export function sliderRelease(v) {
  releaseTime = floor(2000 * v * v);
}

//// START OF AUDIO TOOLS BLOCK //////////////////////////////////////////////////////////////////////////

// highest value seen for an input line signal at +8db on analog vu meter
var EAMAX = 0.25  

// various internal variables used by AGC
var gateTime = 0;
var gateState = 0;

// audioGate
// Called from beforeRender(), returns a value between 1 and 0 representing the state
// of the noise gate.  Fully closed == 0, fully open == 1, anything between is the release
// slope as the gate closes. 
//
// Parameters are:
//   threshold if the signal level drops below this for longer than the hold time, the gate will close
//   hold - minimum amount of time the gate will stay open once triggered
//   release - number of milliseconds it takes the gate to close after detecting silence
//   delta - milliseconds elapsed since last frame.  Standard beforeRender(delta)
//
function audioGate(threshold,hold,release,delta) {
   
  // normalize level to emperical max input level. -- loud, highly compressed music
  // sent via line from mixing desk, showing max +8db on vu meter.
  // Mic may produce slightly lower levels -- I tested w/120w JBL studio monitors at
  // painful level with the sensor board right in front of the speaker, and it seemed
  // close enough though.
  // 
  lvl = energyAverage / EAMAX; 
  
  // update gate hold/release timer
  gateTime = min(gateTime+delta,release+hold);              
  
  // if below the threshold, start closing the gate.
  // It will stay fully open for at least <hold> milliseconds
  // after opening, regardless of the current level, then
  // will start on the downward release slope.
  if (lvl < threshold) {
    output = 1 - clamp((gateTime-hold) / release,0,1)
  }
  // otherwise, the gate is fully open
  else {
    output = 1;
    gateTime = 0;
  }
  
  return output;  
}

//// END OF AUDIO TOOLS BLOCK ///////////////////////////////////////////////////////////////////

// matrix for energy history
for (var i = 0; i < 32; i++) {
  bandHistory[i] = array(MAXWINDOWSIZE);
}

var freqTime = 0;
export var displayLevel;
function calcAvgBandEnergy(delta,gateLevel) {
  freqTime += delta;
  displayLevel = gateLevel;
  
  if (freqTime >= 25) {
    for (i = 0; i < 32; i++) {
      bandHistory[i][avgIndex] = frequencyData[i]/maxFrequencyMagnitude ;
      var avg = arrayReduce(bandHistory[i], (acc, v)=> acc + v, 0) / windowSize
      bandAverage[i] = avg * gateLevel; 
    }
    avgIndex = (avgIndex + 1) % windowSize;
    freqTime = 0;
  }
}

export function beforeRender(delta) {
  calcAvgBandEnergy(delta,audioGate(silenceThreshold,holdTime,releaseTime,delta));
}

export function render2D(index,x,y) {
  y = 1-y;  // flip y  axis if necessary  

  // show "normalized" spectrum
  if (bandAverage[(1-x) * 31] >= y) {
    hsv(x,1,1);
  }
  else {
    rgb(0,0,0);
  }
  
  if (x+y >= 1.9999) {
    col = (displayLevel == 1) ? 0.333 : 0;
    hsv(col,1,1);
  }
}
5 Likes

Can’t wait to try this! Thank you