Arthur Cabral
Arthur Cabral

Reputation: 177

Simple example of playing '.wav' audios simultaneously with alsa-lib in c++?

I am creating a piano with the computer keyboard and I need to play '.wav' audio simultaneously.

I managed to do this perfectly using the SFML/Audio.hpp and SDL2/SDL_mixer.h libs, but the PlayAudio :: play () function of the code below takes a few milliseconds to play the audio, the delay is almost imperceptible however, when the piano is played at high speed, I notice the small delay that exists.

Example:

#include <SFML / Audio.hpp>
#include <SDL2 / SDL_mixer.h>
#include "PlayAudio.h"

sf :: SoundBuffer buffer [10];
sf :: Sound pad [10];

void PlayAudio :: loadBank () {
  buffer [0] .loadFromFile ("src / audiosExa / a1.wav");
  pad [0] .setBuffer (buffer [0]);

  buffer [1] .loadFromFile ("src / audiosExa / a2.wav");
  pad [1] .setBuffer (buffer [1]);

  buffer [2] .loadFromFile ("src / audiosExa / a3.wav");
  pad [2] .setBuffer (buffer [2]);

   buffer [3] .loadFromFile ("src / audiosExa / a4.wav");
   pad [3] .setBuffer (buffer [3]);
};

void PlayAudio :: play (int i) {
  pad [i] .play ();
};

So I thought about doing the same process, but using the alsa-lib which seemed to be faster, but I managed to emit one sound at a time, and I can't emit audios simultaneously. I tried to use threads but a sound is only emitted after the end of another sound.

Upvotes: 0

Views: 1142

Answers (2)

Matt
Matt

Reputation: 607

You could take a few different approaches to get polyphonic sound in your application. Either you do your own mixing or get ALSA to do the mixing for you.

Probably the simplest approach (and the most resource heavy) would be to get ALSA to do the mixing for you. In this case, you open the ALSA dmix plugin once for each pad. You can create a class for each pad and thread them so they run independently. Each class loads the audio data to a buffer and when triggered plays it back to the ALSA dmix plugin. Here is an example (using gtkIOStream which provides C++ libsox audio file handling, threading and ALSA handling) :

#include <Sox.H>
#include <ALSA.H>
#include <Thread.H>
using namespace ALSA;

class Pad : public PlayBack, WaitingThread {
  Eigen::Array<int, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor> audio; // the Eigen array which holds the audio buffer

public:

  /// Constructor opens the audio plawyback device and reads in the audio file data
  Pad(char* devName, string audioFile) : PlayBack(devName) {
    Sox<int> sox; // instantiate libsox
    sox.openRead(audioFile); // open the audio file to read
    sox.read(audio); // read in the audio data
  }

  /// Configure the audio playback ([see here for example][2])
  configurePlayback(){
    // set channels, format, etc.
  }

  /// Playback the entire audio sound
  play(){
    *this<<audio; // stream the audio out through the PlayBack class
  }

  // In our threaded method, we wait to be signalled then we do playback, then wait again, [see here for examples on thread signalling][3]
  void *threadMain(void){
    while (continue) {
      cond.lock();
      cond.wait(); // wait till signalled
      play(); // playback the audio
      cond.unLock(); // unlock to be signalled again
    }
    return NULL;
  }
};

Pad pad1("dmix", "src/audiosExa/a1.wav"); // instantiate a pad
Pad pad2("dmix", "src/audiosExa/a2.wav"); // instantiate a pad
// in your UI thread, you need to run each of the Pad thread
pad1.run();
pad2.run();
// in your UI thread, you need to signal the waiting pad threads 
// Looks something like this for each pad
pad1.cond.lock(); pad1.cond.signal(); pad1.cond.unlock();

If you want to do your own mixing, then you would track which audio pad is being played out and where in the pad's buffer you are, so that each time you need to play out an audio buffer, you sum them together. In code (using gtkIOStream) it would look similar to this snippet taken from ALSAPlaybackTest.C :

const string deviceName="hw:0,0"; // could also be "default"
Playback playBack(deviceName.c_str()); // open the playback device
// configure the parameters of the playback device
Sox<short int> sox1, sox2; // instantiate two audio file readers (libsox)
int ret=sox1.openRead("src/audiosExa/a1.wav"); // load in their audio to an Eigen Array data buffer
Eigen::Array<int, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor> pad1, pad2;
sox1.read(pad1);
ret=sox2.openRead("src/audiosExa/a2.wav");
sox2.read(pad2);
// now loop to constantly output, using outBuf to sum all pads which require output
Eigen::Array<int, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor> outBuf;
while (continue){
  // construct your output buffer by adding portions of your pads here
  // something like : outBuf=pad1.block(...)+pad2.block(...)
  playBack<<outBuf; // play the audio data
}

Upvotes: 0

Arthur Cabral
Arthur Cabral

Reputation: 177

After a few months, it was possible to solve the big problem in a very easy and simple way ... In the code mentioned above I was using ONLY THE SFML LIB and unfortunately it has a very high latency as I already commented ... The lib SDL it was simply not being used, it was just being imported. I remade the code, this time using only the SDL lib, in addition to obtaining an excellent latency, I managed to make the audios play simultaneously. Follow the tutorial below: https://soundprogramming.net/programming/tutorial-using-sdl2-and-sdl_mixer-to-play-samples/

I made minor changes to the code to use only on Linux.

The use of the ALSA lib is quite complicated because the level is very low. If you do not need very fast audios, use the code above with lib SFML, as it is very easy and quick to implement, otherwise use the SDL and you will be successful.

Upvotes: 2

Related Questions