Tutorial 28. Audio Passthrough and Filtering

Using MEAP's line input and applying a basic filter.

In addition to MEAP's audio output, we also have a few input options. You could solder up a condenser capsule to the mic input pad, but the easier way to get audio into MEAP is through the line input jack, which is the black jack located at the top left of your MEAP.

Behind the scenes, MEAP is already reading data from the line input, so all we need to do is grab that data. Both the left and right samples are stored in the meap_input_frame[2] array at indices 0 and 1 respectively.

Most of what we have done in MEAP so far has been mono, but since we have a stereo line input, we may want do deal with it as stereo instead of mixing down to mono. In this example we will be playing audio into MEAP from a phone (or computer etc.) through a stereo 3.5mm cable and then passing it through a lowpass filter.


First, lets create two filters in the global variable section. We'll control both of these filters together but we do need a separate one for the left and right channel. Once we get our line in signal into MEAP, we can process it the same way we do with any other internal signal using filters etc.

MultiResonantFilter l_filter;
MultiResonantFilter r_filter;

And in updateControl() we'll set the filters' cutoff and resonance using pots 0 and 1 respectively.

int cutoff = map(meap.pot_vals[0], 0, 4095, 0, 255);
int resonance = map(meap.pot_vals[1], 0, 4095, 0, 255);
l_filter.setCutoffFreqAndResonance(cutoff, resonance);
r_filter.setCutoffFreqAndResonance(cutoff, resonance);

In updateAudio() we grab both input samples and place them in variables that we can work with.

int64_t l_sample = meap_input_frame[0];
int64_t r_sample = meap_input_frame[1];

Then we send them through our filters.

l_filter.next(l_sample);
r_filter.next(r_sample);
l_sample = l_filter.low();
r_sample = r_filter.low();

Finally, in our last line, instead of sending out_sample to both outputs as normal, we will send l_sample to the left output and r_sample to the right output. We'll also give one bit of headroom to account for increased volume if we turn up the filter's resonance.

return StereoOutput::fromNBit(17, (l_sample * meap.volume_val) >> 12, (r_sample * meap.volume_val) >> 12);

Upload the code and connect an audio source to your line in jack. If you are playing audio from a phone or mp3 player be sure to turn the volume up all the way. It will still be slightly quieter than a true line level signal but should be loud enough to hear.

FULL CODE BELOW


/*
  Basic template for working with a stock MEAP board.
 */

#define CONTROL_RATE 128  // Hz, powers of 2 are most reliable
#include <Meap.h>         // MEAP library, includes all dependent libraries, including all Mozzi modules

Meap meap;                                            // creates MEAP object to handle inputs and other MEAP library functions
MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, MIDI);  // defines MIDI in/out ports

// ---------- YOUR GLOBAL VARIABLES BELOW ----------
MultiResonantFilter l_filter;
MultiResonantFilter r_filter;

void setup() {
  Serial.begin(115200);                      // begins Serial communication with computer
  Serial1.begin(31250, SERIAL_8N1, 43, 44);  // sets up MIDI: baud rate, serial mode, rx pin, tx pin
  startMozzi(CONTROL_RATE);                  // starts Mozzi engine with control rate defined above
  meap.begin();                              // sets up MEAP object

  // ---------- YOUR SETUP CODE BELOW ----------
}


void loop() {
  audioHook();  // handles Mozzi audio generation behind the scenes
}


/** Called automatically at rate specified by CONTROL_RATE macro, most of your mode should live in here
	*/
void updateControl() {
  meap.readInputs();
  // ---------- YOUR updateControl CODE BELOW ----------
  int cutoff = map(meap.pot_vals[0], 0, 4095, 0, 255);
  int resonance = map(meap.pot_vals[1], 0, 4095, 0, 255);
  l_filter.setCutoffFreqAndResonance(cutoff, resonance);
  r_filter.setCutoffFreqAndResonance(cutoff, resonance);
}

/** Called automatically at rate specified by AUDIO_RATE macro, for calculating samples sent to DAC, too much code in here can disrupt your output
	*/
AudioOutput_t updateAudio() {
  int64_t l_sample = meap_input_frame[0];
  int64_t r_sample = meap_input_frame[1];
  l_filter.next(l_sample);
  r_filter.next(r_sample);
  l_sample = l_filter.low();
  r_sample = r_filter.low();

  return StereoOutput::fromNBit(17, (l_sample * meap.volume_val) >> 12, (r_sample * meap.volume_val) >> 12);
}

/**
   * Runs whenever a touch pad is pressed or released
   *
   * int number: the number (0-7) of the pad that was pressed
   * bool pressed: true indicates pad was pressed, false indicates it was released
   */
void updateTouch(int number, bool pressed) {
  if (pressed) {  // Any pad pressed

  } else {  // Any pad released
  }
  switch (number) {
    case 0:
      if (pressed) {  // Pad 0 pressed
        Serial.println("t0 pressed ");
      } else {  // Pad 0 released
        Serial.println("t0 released");
      }
      break;
    case 1:
      if (pressed) {  // Pad 1 pressed
        Serial.println("t1 pressed");
      } else {  // Pad 1 released
        Serial.println("t1 released");
      }
      break;
    case 2:
      if (pressed) {  // Pad 2 pressed
        Serial.println("t2 pressed");
      } else {  // Pad 2 released
        Serial.println("t2 released");
      }
      break;
    case 3:
      if (pressed) {  // Pad 3 pressed
        Serial.println("t3 pressed");
      } else {  // Pad 3 released
        Serial.println("t3 released");
      }
      break;
    case 4:
      if (pressed) {  // Pad 4 pressed
        Serial.println("t4 pressed");
      } else {  // Pad 4 released
        Serial.println("t4 released");
      }
      break;
    case 5:
      if (pressed) {  // Pad 5 pressed
        Serial.println("t5 pressed");
      } else {  // Pad 5 released
        Serial.println("t5 released");
      }
      break;
    case 6:
      if (pressed) {  // Pad 6 pressed
        Serial.println("t6 pressed");
      } else {  // Pad 6 released
        Serial.println("t6 released");
      }
      break;
    case 7:
      if (pressed) {  // Pad 7 pressed
        Serial.println("t7 pressed");
      } else {  // Pad 7 released
        Serial.println("t7 released");
      }
      break;
  }
}

/**
   * Runs whenever a DIP switch is toggled
   *
   * int number: the number (0-7) of the switch that was toggled
   * bool up: true indicated switch was toggled up, false indicates switch was toggled
   */
void updateDip(int number, bool up) {
  if (up) {  // Any DIP toggled up

  } else {  //Any DIP toggled down
  }
  switch (number) {
    case 0:
      if (up) {  // DIP 0 up
        Serial.println("d0 up");
      } else {  // DIP 0 down
        Serial.println("d0 down");
      }
      break;
    case 1:
      if (up) {  // DIP 1 up
        Serial.println("d1 up");
      } else {  // DIP 1 down
        Serial.println("d1 down");
      }
      break;
    case 2:
      if (up) {  // DIP 2 up
        Serial.println("d2 up");
      } else {  // DIP 2 down
        Serial.println("d2 down");
      }
      break;
    case 3:
      if (up) {  // DIP 3 up
        Serial.println("d3 up");
      } else {  // DIP 3 down
        Serial.println("d3 down");
      }
      break;
    case 4:
      if (up) {  // DIP 4 up
        Serial.println("d4 up");
      } else {  // DIP 4 down
        Serial.println("d4 down");
      }
      break;
    case 5:
      if (up) {  // DIP 5 up
        Serial.println("d5 up");
      } else {  // DIP 5 down
        Serial.println("d5 down");
      }
      break;
    case 6:
      if (up) {  // DIP 6 up
        Serial.println("d6 up");
      } else {  // DIP 6 down
        Serial.println("d6 down");
      }
      break;
    case 7:
      if (up) {  // DIP 7 up
        Serial.println("d7 up");
      } else {  // DIP 7 down
        Serial.println("d7 down");
      }
      break;
  }
}