Help with Stem Mixer project

acarson

Member
Hi Im still pretty new to the teensy audio world but I am trying to make a 4 track stem mixer with effects and I could use some help. The basic idea is that I would load 4 Stereo wav files of different song stems onto the teensy via sd card. Once the button is pressed those 4 wav files would play simultaneously. Those tracks would go through a mixer, then output with optional delay send. I then have 3 banks of 8 potentiometers to mix the stems. 4 pots for individual stem volume, 4 pots for individual stem panning, 4 pots for stem delay sends, and 4 pots for delay parameters such as time, feedback, etc. I have a rough sketch below but it feels incorrect. and I am unsure of how to assign the button for playback and the pots for their specific functions.

I am using Teensy 4.1 with the audio shield. Any help would be greatly appreciated!
 

Attachments

  • Screenshot 2026-03-08 at 10.12.54 PM.png
    Screenshot 2026-03-08 at 10.12.54 PM.png
    760.4 KB · Views: 25
You should probably start by looking at the tutorial - see https://www.pjrc.com/teensy/td_libs_Audio.html. That will give you an idea of what the Teensy Audio library can do for you.

What it doesn’t do is “assign” buttons and pots to control things; you have to write the code for that. More work, but very much more flexibility.

The SD playback in the stock audio library is far from ideal - you may be able to get a couple of files playing, but more can be problematic. This thread discusses some additional modules I wrote which work much better, and should easily give you 4 stereo tracks. But I’d strongly suggest you don’t dive into trying to use that until you’re a bit more up to speed with the basic library.

If you encounter a specific issue, do be prepared to post a minimal sketch which can be compiled and run using the Arduino IDE, on basic hardware, and demonstrates the problem. You can probably assume a Teensy and an audio adaptor, but anything else will severely reduce the number of people who are prepared to assemble the kit to reproduce the problem!
 
Thanks for the info! Ive gone through the tutorial video a few times and read through your buffered sd thread and have had a bit of luck playing back audio and adding a pot to control mixer gain, but im still trying to figure out a script for simultaneous playback of multiple sd files. I also got your buffered sd repo loaded into Arduino IDE. Is the "playSDwav" object in the online audio design tool obsolete? or does it just need to be used with "createBuffer()"?
 
Ah, OK, your OP suggested a bit less familiarity than that!

A near-example of what you want is AudioTestPlayMultiSD.ino. It uses two buttons and two SD cards, but can easily be tweaked to use one of each, and play four files rather than two.

As far as I’m concerned AudioPlaySdWav is only relevant to legacy projects. It doesn’t have a createBuffer method, plus it reads the SD card under interrupt which causes huge issues. And a bunch of other stuff.
 
I keep running into this issue when I play back multiple sd wavs that the audio gets really quiet and there's loud digital noise. Is this a common occurrence?
 
I keep running into this issue when I play back multiple sd wavs that the audio gets really quiet and there's loud digital noise. Is this a common occurrence?
If you encounter a specific issue, do be prepared to post a minimal sketch which can be compiled and run using the Arduino IDE, on basic hardware, and demonstrates the problem. You can probably assume a Teensy and an audio adaptor, but anything else will severely reduce the number of people who are prepared to assemble the kit to reproduce the problem!
 
oh yeah my bad! Got the code pasted below. Also my sd cars is a Sandisk Ultra Plus 128gb A1 V10 XC.



#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>

// GUItool: begin automatically generated code
AudioPlaySdWav playSdWav2; //xy=290,326
AudioPlaySdWav playSdWav3; //xy=300,414
AudioPlaySdWav playSdWav1; //xy=308,178
AudioPlaySdWav playSdWav4; //xy=327,497
AudioMixer4 mixer1; //xy=679,151
AudioMixer4 mixer2; //xy=730,310
AudioOutputI2S i2s1; //xy=1045,236
AudioConnection patchCord1(playSdWav2, 0, mixer1, 1);
AudioConnection patchCord2(playSdWav2, 1, mixer2, 1);
AudioConnection patchCord3(playSdWav3, 0, mixer1, 2);
AudioConnection patchCord4(playSdWav3, 1, mixer2, 2);
AudioConnection patchCord5(playSdWav1, 0, mixer1, 0);
AudioConnection patchCord6(playSdWav1, 1, mixer2, 0);
AudioConnection patchCord7(playSdWav4, 0, mixer1, 3);
AudioConnection patchCord8(playSdWav4, 1, mixer2, 3);
AudioConnection patchCord9(mixer1, 0, i2s1, 0);
AudioConnection patchCord10(mixer2, 0, i2s1, 1);
AudioControlSGTL5000 sgtl5000_1; //xy=529,657
// GUItool: end automatically generated code





// Use these with the Teensy 3.5 & 3.6 & 4.1 SD card
#define SDCARD_CS_PIN BUILTIN_SDCARD
#define SDCARD_MOSI_PIN 11 // not actually used
#define SDCARD_SCK_PIN 13 // not actually used

// Use these for the SD+Wiz820 or other adaptors
//#define SDCARD_CS_PIN 4
//#define SDCARD_MOSI_PIN 11
//#define SDCARD_SCK_PIN 13

void setup() {
Serial.begin(9600);
AudioMemory(200);
sgtl5000_1.enable();
sgtl5000_1.volume(0.8);
SPI.setMOSI(SDCARD_MOSI_PIN);
SPI.setSCK(SDCARD_SCK_PIN);
if (!(SD.begin(SDCARD_CS_PIN))) {
while (1) {
Serial.println("Unable to access the SD card");
delay(500);
}
}
mixer1.gain(0, 0.1);
mixer1.gain(1, 0.1);
mixer1.gain(2, 0.1);
mixer1.gain(3, 0.1);
mixer2.gain(0, 0.1);
mixer2.gain(1, 0.1);
mixer2.gain(2, 0.1);
mixer2.gain(3, 0.1);
delay(50);
}

void loop() {
if (playSdWav1.isPlaying() == false) {
Serial.println("Start playing 1");
playSdWav1.play("NUM1.WAV");
delay(10); // wait for library to parse WAV info
}
if (playSdWav2.isPlaying() == false) {
Serial.println("Start playing 2");
playSdWav2.play("NUM2.WAV");
delay(10); // wait for library to parse WAV info
}
if (playSdWav3.isPlaying() == false) {
Serial.println("Start playing 3");
playSdWav3.play("NUM3.WAV");
delay(10); // wait for library to parse WAV info
}
if (playSdWav4.isPlaying() == false) {
Serial.println("Start playing 4");
playSdWav4.play("NUM4.WAV");
delay(10); // wait for library to parse WAV info
}
int knob = analogRead(A10);
float gain1 = (float)knob / 1280.0;
mixer1.gain(0, gain1);
mixer2.gain(0, gain1);

int knob2 = analogRead(A11);
float gain2 = (float)knob2 / 1280.0;
mixer1.gain(1, gain2);
mixer2.gain(1, gain2);

int knob3 = analogRead(A12);
float gain3 = (float)knob3 / 1280.0;
mixer1.gain(2, gain3);
mixer2.gain(2, gain3);

int knob4 = analogRead(A13);
float gain4 = (float)knob4 / 1280.0;
mixer1.gain(3, gain4);
mixer2.gain(3, gain4);


}
 
For future reference, it works way better if you use the code tags button </> when you post a sketch. For the above, you get away with it, but the forum software is pretty dumb and can actually corrupt the text in certain circumstances.

As noted in my post at #2 above, AudioPlaySdWav is very much not good above a couple of files, and you're attempting four. That's almost certainly the cause of "digital noise". It might also be the cause of being "really quiet", though I don't see how - setting the mixer volumes to 0.1 seems a bit more likely, though anything above 0.25 does risk clipping, so 0.1 is actually a reasonable starting point.
 
Gotcha. I got the volume working properly now. I tried using the method you outlined in the AudioTestPlayMultiSD example but I couldn't get it to work, so I went with the simpler approach to start with. I appreciate your help. Im gonna give the createBuffer approach another stab.
 
Ok so I reworked the code a bit to change the AudioPlaySdWav to the AudioPlayWAVStereo etc. but it doesn't seem to like that. Any thoughts?
Code:
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>
#include <Bounce.h>

// GUItool: begin automatically generated code
AudioPlayWAVstereo           playRaw1;       //xy=302,157
AudioPlayWAVstereo           playRaw2;       //xy=302,157
AudioPlayWAVstereo           playRaw3;
AudioPlayWAVstereo           playRaw4;
AudioMixer4              mixer1;         //xy=679,151
AudioMixer4              mixer2;         //xy=730,310
AudioOutputI2S           i2s1;           //xy=1311,193
AudioConnection          patchCord1(playRaw2, 0, mixer1, 1);
AudioConnection          patchCord2(playRaw2, 1, mixer2, 1);
AudioConnection          patchCord3(playRaw3, 0, mixer1, 2);
AudioConnection          patchCord4(playRaw3, 1, mixer2, 2);
AudioConnection          patchCord5(playRaw1, 0, mixer1, 0);
AudioConnection          patchCord6(playRaw1, 1, mixer2, 0);
AudioConnection          patchCord7(playRaw4, 0, mixer1, 3);
AudioConnection          patchCord8(playRaw4, 1, mixer2, 3);
AudioConnection          patchCord9(mixer1, 0, i2s1, 0);
AudioConnection          patchCord10(mixer2, 0, i2s1, 1);
AudioControlSGTL5000     sgtl5000_1;     //xy=529,657
// GUItool: end automatically generated code





// Use these with the Teensy 3.5 & 3.6 & 4.1 SD card
#define SDCARD_CS_PIN BUILTIN_SDCARD
#define SDCARD_MOSI_PIN 11 // not actually used
#define SDCARD_SCK_PIN 13 // not actually used



void setup() {
 Serial.begin(9600);
 AudioMemory(200);
 sgtl5000_1.enable();
 sgtl5000_1.volume(0.8);
 SPI.setMOSI(SDCARD_MOSI_PIN);
 SPI.setSCK(SDCARD_SCK_PIN);
 if (!(SD.begin(SDCARD_CS_PIN))) {
 while (1) {
 Serial.println("Unable to access the SD card");
 delay(500);
  playRaw1.createBuffer(2048,AudioBuffer::inHeap);
  playRaw2.createBuffer(2048,AudioBuffer::inHeap);
  playRaw3.createBuffer(2048,AudioBuffer::inHeap);
  playRaw4.createBuffer(2048,AudioBuffer::inHeap);

 }
 mixer1.gain(0, 0.1);
 mixer1.gain(1, 0.1);
 mixer1.gain(2, 0.1);
 mixer1.gain(3, 0.1);
 mixer2.gain(0, 0.1);
 mixer2.gain(1, 0.1);
 mixer2.gain(2, 0.1);
 mixer2.gain(3, 0.1);

delay(50);

float knob5 = 0.9;

}
}

void loop() {
 if (playRaw1.isPlaying() == false) {
 Serial.println("Start playing 1");
 playRaw1.play("NUM1.WAV");
 delay(10); // wait for library to parse WAV info
 }
 if (playRaw2.isPlaying() == false) {
 Serial.println("Start playing 2");
 playRaw2.play("NUM2.WAV");
 delay(10); // wait for library to parse WAV info
 }
 if (playRaw3.isPlaying() == false) {
 Serial.println("Start playing 3");
 playRaw3.play("NUM3.WAV");
 delay(10); // wait for library to parse WAV info
 }
 if (playRaw4.isPlaying() == false) {
 Serial.println("Start playing 4");
 playRaw4.play("NUM4.WAV");
 delay(10); // wait for library to parse WAV info
 }
 int knob = analogRead(A10);
 float gain1 = (float)knob / 1280.0;
 mixer1.gain(0, gain1);
 mixer2.gain(0, gain1);

 int knob2 = analogRead(A11);
 float gain2 = (float)knob2 / 1280.0;
 mixer1.gain(1, gain2);
 mixer2.gain(1, gain2);

 int knob3 = analogRead(A12);
 float gain3 = (float)knob3 / 1280.0;
 mixer1.gain(2, gain3);
 mixer2.gain(2, gain3);

 int knob4 = analogRead(A13);
 float gain4 = (float)knob4 / 1280.0;
 mixer1.gain(3, gain4);
 mixer2.gain(3, gain4);





}
 
Got it thanks! So I got all the stems playing back just fine using createBuffer. But the digital noise is still very present. Im suspecting its not a hardware issue because its not there when I im only playing 1 audio track at a time. Thoughts?

Code:
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>
#include <Bounce.h>

// GUItool: begin automatically generated code
AudioPlayWAVstereo playRaw1;  //xy=302,157
AudioPlayWAVstereo playRaw2;  //xy=302,157
AudioPlayWAVstereo playRaw3;
AudioPlayWAVstereo playRaw4;
AudioMixer4 mixer1;   //xy=679,151
AudioMixer4 mixer2;   //xy=730,310
AudioOutputI2S i2s1;  //xy=1311,193
AudioConnection patchCord1(playRaw2, 0, mixer1, 1);
AudioConnection patchCord2(playRaw2, 1, mixer2, 1);
AudioConnection patchCord3(playRaw3, 0, mixer1, 2);
AudioConnection patchCord4(playRaw3, 1, mixer2, 2);
AudioConnection patchCord5(playRaw1, 0, mixer1, 0);
AudioConnection patchCord6(playRaw1, 1, mixer2, 0);
AudioConnection patchCord7(playRaw4, 0, mixer1, 3);
AudioConnection patchCord8(playRaw4, 1, mixer2, 3);
AudioConnection patchCord9(mixer1, 0, i2s1, 0);
AudioConnection patchCord10(mixer2, 0, i2s1, 1);
AudioControlSGTL5000 sgtl5000_1;  //xy=529,657
// GUItool: end automatically generated code





// Use these with the Teensy 3.5 & 3.6 & 4.1 SD card
#define SDCARD_CS_PIN BUILTIN_SDCARD
#define SDCARD_MOSI_PIN 11  // not actually used
#define SDCARD_SCK_PIN 13   // not actually used



void setup() {
  Serial.begin(9600);
  AudioMemory(200);
  sgtl5000_1.enable();
  sgtl5000_1.volume(0.8);
  SPI.setMOSI(SDCARD_MOSI_PIN);
  SPI.setSCK(SDCARD_SCK_PIN);
  if (!(SD.begin(SDCARD_CS_PIN))) {
    while (1) {
      Serial.println("Unable to access the SD card");
      delay(500);
    }
  }
  mixer1.gain(0, 0.1);
  mixer1.gain(1, 0.1);
  mixer1.gain(2, 0.1);
  mixer1.gain(3, 0.1);
  mixer2.gain(0, 0.1);
  mixer2.gain(1, 0.1);
  mixer2.gain(2, 0.1);
  mixer2.gain(3, 0.1);

 delay(50);
  playRaw1.createBuffer(2048, AudioBuffer::inHeap);
  playRaw2.createBuffer(2048, AudioBuffer::inHeap);
  playRaw3.createBuffer(2048, AudioBuffer::inHeap);
  playRaw4.createBuffer(2048, AudioBuffer::inHeap);




 float knob5 = 0.9;
 }


void loop() {
  if (playRaw1.isPlaying() == false) {
    Serial.println("Start playing 1");
    playRaw1.play("NUM1.WAV");
    delay(10);  // wait for library to parse WAV info
  }
  if (playRaw2.isPlaying() == false) {
    Serial.println("Start playing 2");
    playRaw2.play("NUM2.WAV");
    delay(10);  // wait for library to parse WAV info
  }
  if (playRaw3.isPlaying() == false) {
    Serial.println("Start playing 3");
    playRaw3.play("NUM3.WAV");
    delay(10);  // wait for library to parse WAV info
  }
  if (playRaw4.isPlaying() == false) {
    Serial.println("Start playing 4");
    playRaw4.play("NUM4.WAV");
    delay(10);  // wait for library to parse WAV info
  }
  int knob = analogRead(A10);
  float gain1 = (float)knob / 1280.0;
  mixer1.gain(0, gain1);
  mixer2.gain(0, gain1);

  int knob2 = analogRead(A11);
  float gain2 = (float)knob2 / 1280.0;
  mixer1.gain(1, gain2);
  mixer2.gain(1, gain2);

  int knob3 = analogRead(A12);
  float gain3 = (float)knob3 / 1280.0;
  mixer1.gain(2, gain3);
  mixer2.gain(2, gain3);

  int knob4 = analogRead(A13);
  float gain4 = (float)knob4 / 1280.0;
  mixer1.gain(3, gain4);
  mixer2.gain(3, gain4);
}
 
Probably an idea to try bigger buffers. The library tries to refill when they’re half empty, so every 1024 bytes consumed, as you currently have it. If the audio is stereo, that’s every 5.8ms or so, which is quite often, and you have 4 files playing. Bumping them to 10k would give a more comfortable refill interval of ~30ms; they need to be a multiple of 1k. Up to a point bigger is better, though bigger buffers will increase the time taken to start playing as the initial buffer fill takes a bit longer.

You don’t need the delay(10) any more - that‘s yet another misfeature I got rid of…
 
I went up to 15000 and the only difference I can tell is a change in timbre of the digital noise, other than that there's still about a -40db digital noise that's quite noticeable
 
I went up to 15000 and the only difference I can tell is a change in timbre of the digital noise, other than that there's still about a -40db digital noise that's quite noticeable
Is that the same noise as
there's loud digital noise
? I wouldn’t necessarily call -40dB loud … significant, for sure.

Consider for a moment
C++:
void loop () {
  ...
  int knob4 = analogRead(A13);
  float gain4 = (float)knob4 / 1280.0;
  mixer1.gain(3, gain4);
  mixer2.gain(3, gain4);
}
The analogue inputs default to 10-bit resolution, and will very likely have some noise on them, too. We can’t see your wiring, but I wouldn’t be surprised if at this stage of development it’s … sub-optimal? More opportunities for noise…

Note that the audio engine only updates every 128 samples, or about 2.9ms. At Teensy 4.x speeds, most of the gain changes your code makes are going to be overwritten. This also results in “zipper noise” if you turn the pot fast - a smooth turn becomes a series of steps at 2.9ms intervals. Look into using DC synth plus multiplier effect objects to get a smooth change; it gets messy, but there aren’t many options.

I hope you didn’t try 15000 samples for the buffers. Multiples of 1k, right? So, use 15360. SD cards present as 512-byte sectors, so reading in integer multiples of 512 bytes is way more efficient. The buffer refill is triggered when half empty, so a 15k buffer demands a 7.5k refill.
 
Back
Top