Gotta Go Faster, Part 4.


The second logical block - I2S input is done in two parts. Hardware, and in this case DSP, setup as outlined in my first post. And software, in this case using the Teensy Audio Libraries. The C++ struct SoundLevel used to capture levels for each channel might look a bit strange for the ones not used to wrap a method inside a struct but this is a kind of C++ after all. ;)

The code is once again quite straight forward and I hope you as a reader understand whats going on. Don't forget to call the AudioMemory function in the setup section. You can read about it in the Teensy Audio Library documentation (and how to measure the amount of memory needed to be passed as the argument). You also need to set the mixer gain per channel.

The ses function is a simple exponential smoothing filter. I use it to add a bit of analog feel to the response. Not correct from a pure accuracy standpoint but totally right from a visualisation standpoint.

While reading I2S levels a value of 0.0 represents no sound and 1.0 represents 0dBFS. I have selected values for peak hold, decay, silence and alpha value for smoothing filter based on my display routines. More about it in a future post about the music visualisation.

// Teensy
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>
// SND
const float     SILENCE       = 0.02;
const uint8_t   PEAK_HOLD     = 32;
const float     PEAK_DECAY    = 0.02;
const float     CHN_ALPHA     = 0.9;
// ETC
const uint32_t  SILENCE_MS    = 60000;
const uint8_t   UPDATE_FPS    = 60;
const uint8_t   UPDATE_MS     = 1000 / UPDATE_FPS;

#include "SupportClasses.h"

struct SoundLevel {
  float value   = 0;
  float peak    = 0;
  uint8_t hold  = 0;
  // update with new reading
  void update(float reading) {
    value = reading;
    if (value > peak) {
      peak = value;
      hold = PEAK_HOLD;
    } else if (hold > 0) {
      hold--;
    } else if (peak > 0) {
      peak -= PEAK_DECAY;
    }
  }
};

SoundLevel              leftCh;
SoundLevel              rightCh;
Timer                   silenceTimer;
Trigger                 updateTrigger;
// Teensy Audio
AudioInputI2Sslave      i2sSlave;
AudioMixer4             mixer;
AudioAnalyzePeak        peakLeft;
AudioAnalyzePeak        peakRight;
AudioConnection         patchCordPeakLeft(i2sSlave, 0, peakLeft, 0);
AudioConnection         patchCordMixerLeft(i2sSlave, 0, mixer, 0);
AudioConnection         patchCordPeakRight(i2sSlave, 1, peakRight, 0);
AudioConnection         patchCordMixerRight(i2sSlave, 1, mixer, 1);

// simple exponential smoothing, alpha is the smoothing factor [0,1]
// alpha closer to 1, less smoothing, greater weight to recent changes
// alpha closer to 0, more smoothing, less responsive to recent changes
float ses(float input, float average, float alpha) {
  return average + alpha * (input - average);
}

void processChannels() {
  if (peakLeft.available() && peakRight.available()) {
    leftCh.update(ses(peakLeft.read(), leftCh.value, CHN_ALPHA));
    rightCh.update(ses(peakRight.read(), rightCh.value, CHN_ALPHA));
    if (leftCh.value > SILENCE && rightCh.value > SILENCE) {
      silenceTimer.reset();
    }
  }
}

void updateCallback() {
  processChannels();
}

void setup() {
  // init sound system
  AudioMemory(15);
  mixer.gain(0, 0.5);
  mixer.gain(1, 0.5);
  // init timers and triggers
  silenceTimer.set(SILENCE_MS);
  updateTrigger.setup(UPDATE_MS, updateCallback);
}

void loop() {
  updateTrigger.update();
}