Skip to content

Playing Multiple Files in Sequence

Phil Schatzmann edited this page Feb 19, 2025 · 10 revisions

With this library you have usually many ways to provide the same functionality. This chapter gives a short overview of the alternatives how you can multiple files (or streams) in a defined sequence:

Define File Names in an Array

You can just store the file names in an array and play them one after the other:

#include <SPI.h>
#include <SD.h>
#include "AudioTools.h"
#include "AudioTools/AudioCodecs/CodecAACHelix.h"

// files from https://filesamples.com/formats/aac
const char* filesNames[] = {
  "/aac/sample1.aac",
  "/aac/sample2.aac",
  "/aac/sample3.aac",
  "/aac/sample4.aac",
  nullptr,
};
int fileIdx = 0; 

const int chipSelect = PIN_AUDIO_KIT_SD_CARD_CS;
I2SStream i2s;                                            // final output of decoded stream
EncodedAudioStream decoder(&i2s, new AACDecoderHelix());  // Decoding stream
File audioFile;
StreamCopy copier(decoder, audioFile);

void setup() {
  Serial.begin(115200);
  AudioToolsLogger.begin(Serial, AudioToolsLogLevel::Info);

  // setup i2s
  auto config = i2s.defaultConfig(TX_MODE);
  config.sd_active = true;
  i2s.begin(config);

  // setup file
  SD.begin(chipSelect);
  audioFile = SD.open(filesNames[fileIdx++]);

  if (!audioFile){
    Serial.println("file does not exist");
    stop();
  }

  // setup I2S based on sampling rate provided by decoder
  decoder.begin();

}

void loop() {
  if (!copier.copy()) {
    // move to beginning on end
    if (filesNames[fileIdx]==nullptr) fileIdx = 0;
    // move to next file
    audioFile = SD.open(filesNames[fileIdx++]);
  }
}

We just manage the fileIdx variable to select the file to be played next.

Play Concatenated Files

If you use an audio format that can be concatenated (e.g. aac or mp3) you can use a CatStream to concatenate them. If you use a WAV file, you need to make sure that all have the same format and you need to set the initial file position past the header (e.g. with file.seek(44)); except for the first file.

#include <SPI.h>
#include <SD.h>
#include "AudioTools.h"
#include "AudioTools/AudioCodecs/CodecAACHelix.h"

const int chipSelect = PIN_AUDIO_KIT_SD_CARD_CS;
I2SStream i2s;                                            // final output of decoded stream
EncodedAudioStream decoder(&i2s, new AACDecoderHelix());  // Decoding stream
File file[4];
CatStream cat;
StreamCopy copier(decoder, cat);

void setup() {
  Serial.begin(115200);
  AudioToolsLogger.begin(Serial, AudioToolsLogLevel::Info);

  // setup i2s
  auto config = i2s.defaultConfig(TX_MODE);
  config.sd_active = true;
  i2s.begin(config);

  // setup file
  SD.begin(chipSelect);

  // define concatenated files
  file[0] = SD.open("/aac/sample1.aac");
  file[1] = SD.open("/aac/sample2.aac");
  file[2] = SD.open("/aac/sample3.aac");
  file[3] = SD.open("/aac/sample4.aac");
  for (int j=0;j < 4; j++){
     cat.add(file[j]);
  }

  // start decoder
  decoder.begin();

}

void loop() {
  if (!copier.copy()) {
     stop();
  }
}

AudioPlayer with an File Index based AudioSource

If you select an index based AudioSource, you can define (or edit) the index file to define the files that should be played in sequence

Further information can be found in the AudioPlayer Wiki.

AudioPlayer with your custom Callback AudioSource

You can just implement an AudioSourceCallback that returns the files (or streams) in your desired sequence:

#include "Arduino.h"
#include "AudioTools.h"
#include "AudioTools/AudioCodecs/CodecMP3Helix.h"
#include <SPI.h>
#include <SD.h>

// files
const char* files[]{
  "/file1.mp3",
  "/file2.mp3"
};
int fileIndex = 0;
File actualFile;

// forward declarations
void callbackInit();
Stream* callbackNextStream(int offset);

// data
const int chipSelect=PIN_CS;
AudioSourceCallback source(callbackNextStream, callbackInit);
I2SStream i2s;
MP3DecoderHelix decoder;
AudioPlayer player(source, i2s, decoder);

void callbackInit() {
  fileIndex = 0;
}

Stream* callbackNextStream(int offset) {
  if (fileIndex >= sizeof(files)) return nullptr;
  actualFile = SD.open(files[fileIndex]);
  fileIndex += offset;
  return &actualFile;
}

void setup() {
  Serial.begin(115200);
  AudioToolsLogger.begin(Serial, AudioToolsLogLevel::Info);

  // setup SD
  SD.begin(chipSelect);

  // setup output
  auto cfg = i2s.defaultConfig(TX_MODE);
  i2s.begin(cfg);

  // setup player
  player.begin();
}

void loop() {
  player.copy();
}

A cleaner approach would be to use your custom subclass of AudioSource where you can store all related data in the class instead of using global data and callback methods in your sketch.

Clone this wiki locally