Creating Audio on the Teensy 3.6

Two playSDWav blocks, two mixer blocks, and one i2s output block.

The very first thing I decided to tackle for custom pinball project was getting audio working. I wanted to work on this first because I figured it would be one of the most processor intensive tasks. It’s also something I’ve never worked with before in my life. It has turned out to be one of the easiest things I’ve ever done on a microprocessor as well.

The key to getting it up and running so easily is really the Audio library that was developed for the Teensy 3.6. The first step is to use the GUI over at the PJRC website that gives you a visual block diagram way of creating your audio system.

Teensy Audio System Design Tool
Teensy Audio System Design Tool

With this tool, you just drag and drop the blocks you need onto the board, then connect them up. For this pinball project, I have two audio sources, both read from the SD card. One to play background music, called BGM, and the second for sound effects, called SFX. Each audio source has two channels, one for the left and one for the right. The output format I’m using is the I2S standard, so I drag that block out. This takes two inputs, one for the left channel and the other for the right channel. So how do you connect the four output channels from the two SD blocks to the two input channels on the I2S block? You use mixers, one for the left channel and one for the right channel. I renamed the blocks to be more descriptive for my application and to make the code more readable and intuitive.

Two playSDWav blocks, two mixer blocks, and one i2s output block.
Two playSDWav blocks, two mixer blocks, and one i2s output block.

When finished, you click the “Export” button at the top of the page, and it generates the code you’ll need to copy & paste into your Arduino Sketch to implement the system you just designed. It’s really that easy.

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

// GUItool: begin automatically generated code
AudioPlaySdWav           BGM;     //xy=359,364
AudioPlaySdWav           SFX;     //xy=359,459
AudioMixer4              rightMixer;         //xy=586,453
AudioMixer4              leftMixer;         //xy=587,357
AudioOutputI2S           i2sOut;           //xy=785,406
AudioConnection          patchCord1(BGM, 0, leftMixer, 0);
AudioConnection          patchCord2(BGM, 1, rightMixer, 0);
AudioConnection          patchCord3(SFX, 0, leftMixer, 1);
AudioConnection          patchCord4(SFX, 1, rightMixer, 1);
AudioConnection          patchCord5(rightMixer, 0, i2sOut, 1);
AudioConnection          patchCord6(leftMixer, 0, i2sOut, 0);
// GUItool: end automatically generated code

In this code snippet, you can see it’s just a series of library includes (the *.h files) and then a series of object calls to create the system you created in the visual GUI. The xy coordinates in comments are there so that you can re-import your code into the GUI and it will place the blocks in the same location as when you created it.

Now, to get audio working you’ll need a few more #define statements.

#define SDCARD_CS_PIN    BUILTIN_SDCARD
#define SDCARD_MOSI_PIN  11  // not actually used
#define SDCARD_SCK_PIN   13  // not actually used
#define BGM_button 29
#define SFX_button 25

The first three tell the SD library what pins to use for your SD card. With the Teensy 3.6, this is the built-in card reader. In addition, the SD library is the one provided by the Teensy rather than the one that comes with Arduino. This is important because the regular Arduino library uses an SPI interface while the Teensy one uses SDIO which is much faster. The last two defines are just for a couple of buttons I used to play WAV files when pressed.

void setup() {
  // Start serial port
  Serial.begin(115200);

  // Configure I/O
  pinMode(BGM_button, INPUT);
  pinMode(SFX_button, INPUT);
  
  // Configure Audio
  AudioMemory(8);
  rightMixer.gain(0,0.25);
  leftMixer.gain(0,0.25);

  // Configure SD Card
  SPI.setMOSI(SDCARD_MOSI_PIN);
  SPI.setSCK(SDCARD_SCK_PIN);
  if (!(SD.begin(SDCARD_CS_PIN))) {
    // stop here, but print a message repetitively
    while (1) {
      Serial.println("Unable to access the SD card");
      delay(500);
    }
  }
}

The void setup() function is only run once at startup. It is used to configure your peripherals and program. In this snippet, it opens a serial port which can be used to output debug messages onto the USB port. It then configures my two buttons as inputs. For the audio configuration, you only need to provide the AudioMemory() function to allocate some memory. In my testing, I’ve only needed 2 blocks of memory but I left it at 8 from the example. The next two lines set the gain on the BGM channel to 25%. I did this to reduce the volume of the music so that the sound effects could be heard more easily. Tweak this to your liking. The remaining code configures the SD card, if a problem occurs it will go into an infinite loop and spit out that error message every half second.

void loop() {
  // put your main code here, to run repeatedly:

  if(digitalRead(BGM_button) == LOW){
    if(!BGM.isPlaying()) {
      BGM.play("BGM1.WAV");
      delay(5);
      Serial.println("Playing BGM");
    }
  }
  if(digitalRead(SFX_button) == LOW){
    if(!SFX.isPlaying()) {
      SFX.play("SFX1.WAV");
      delay(5);
      Serial.println("Playing SFX");
    }
  }
}

The last bit of code is the void loop() code, which is what will run continuously. This simply checks my buttons, and when one is pressed it will play a WAV file if it isn’t already playing. That’s all there is to it for getting audio to play.

Leave a Reply

Your email address will not be published. Required fields are marked *