34 Interactive Audio
Phil Schatzmann edited this page 2024-03-28 03:17:48 +01:00

By having read the Introduction, you should now be able to follow this chapter w/o any problems.

It is quite easy to build some simple interactive audio: In the next example I will use a sine generator as audio source together with some buttons. When the button is pressed you should get some audio, when it is released the tone should stop.

You could write your own button control, but to make things easy and concise, I have added the AudioActions class that can detect pin status changes and act on them.

The Objects

We define the global objects.

#include "AudioTools.h"

I2SStream i2s;
SineWaveGenerator<int16_t> sine;
GeneratedSoundStream<int16_t> in(sine); 
StreamCopy copier(i2s, in); 
AudioActions action;

Nothing fancy so far: we just copy the sine tone to the I2S output. The AudioActions is the new thing here. Check out the documentation: it is possible to add only one action method or two.

The Actions

We want to play a tone when we press a key and stop when we release it. So we define a separate method for each. Please note that the method signature is fixed and we have an active flag, the pin number and a pointer to any additional information as parameters:

void actionKeyOn(bool active, int pin, void* ptr){
  float freq = *((float*)ptr);
  sine.setFrequency(freq);
  in.begin();
}

void actionKeyOff(bool active, int pin, void* ptr){
  in.end();
}

On the Key On action, we just define the frequency and start the sound stream. To stop the output of sound we just call end or we could set the frequency to 0.0f.

The frequency we get via the additional flexible void pointer parameter.

Setting up the Actions

Next we define the actions that should be executed for each pin: as additional parameter we can just provide a pointer to a frequency value that we store in an static array. We could also move this array to our global objects: but you must not use any local variable that is stored on the stack!

// We want to play some notes on the Audioi2s keys 
void setupActions(){
  // assign buttons to notes
  auto act_low = AudioActions::ActiveLow;
  static float note[] = {N_C3, N_D3, N_E3, N_F3, N_G3, N_A3}; // frequencies
  action.add(6, actionKeyOn, actionKeyOff, AudioActions::ActiveLow, &(note[0])); // C3
  action.add(7, actionKeyOn, actionKeyOff, AudioActions::ActiveLow, &(note[1])); // D3
  action.add(8, actionKeyOn, actionKeyOff, AudioActions::ActiveLow, &(note[2])); // E3
  action.add(9, actionKeyOn, actionKeyOff, AudioActions::ActiveLow, &(note[3])); // F3
  action.add(10, actionKeyOn, actionKeyOff, AudioActions::ActiveLow, &(note[4])); // G3
  action.add(11, actionKeyOn, actionKeyOff, AudioActions::ActiveLow, &(note[5])); // A3
}
We just define 6 actions for the GPIO 6 to GPIO 11.

The Arduino Setup

No surprises here as well, we just setup I2S and make sure that the sound generation uses the same sample rate, channels and bits_per_sample like i2s. As a last thing we just call the setupActions() that has been explained in the chapter above.

void setup() {
  Serial.begin(115200);
  AudioLogger::instance().begin(Serial,AudioLogger::Info);

  // Setup output
  auto cfg = i2s.defaultConfig(TX_MODE);
  // add additional settings e.g. to define your pins or a different sample rate
  i2s.begin(cfg);

  // Setup sound generation based on Audioi2s settings
  in.begin(cfg);

  // activate keys
  setupActions();

}

The Arduino Loop

In the loop we just copy the audio source to the audio sink. The critical thing here is that we need to call processActions() which handles the magic of calling our key press and key release methods.

// copy the data
void loop() {
  copier.copy();
  action.processActions();
}

Summary

This is a simple example on how to create interactive sound. I was just picking a sine generator as source, but you can replace it with any other audio source: e.g. a MemoryStream. Just set it to loop mode by calling setLoop(true) to generate an endless signal or a FileLoop which wraps a File.

This sketch has one weak point: when you press or release a button there might be some clicking or popping sound. This is happening because the signal is changing abruptly and if the sine wave is around a peak this generates the artifact. The solution to prevent this is to use some Fade In and Fade Out.

Fade: the Objects

In order to prepare the fade operation we add a FadeStream to our processing chain. In the example below we apply it to the input side, but it would also work on the output side by wrapping the I2SStrem.

#include "AudioTools.h"

I2SStream i2s;
SineWaveGenerator<int16_t> sine;
GeneratedSoundStream<int16_t> in(sine);
FadeStream fade(in); 
StreamCopy copier(i2s, fade); 
AudioActions action;

Extending the Actions

Next we need to trigger the fade in when the key is pressed and fade out when the key is released.

void actionKeyOn(bool active, int pin, void* ptr){
  int freq = *((float*)ptr);
  sine.setFrequency(freq);
  fade.setFadeInActive(true);
  in.begin();
}

void actionKeyOff(bool active, int pin, void* ptr){
  fade.setFadeOutActive(true);
  copier.copy();
  in.end();
}

There is on small complication on the fade out: we need to provide some samples to apply the fade out operation. This can be done easily by calling copier.copy() before switching off the output.

Extending the Setup

Since we have a new object, we must not forget to configure and start it.

  // Setup fade
  fade.begin(cfg);

With these changes the clicks and pops should not happen any more.

Further Information

Fading is a primitive method to shape the sound. A much more sophisticated method is ADSR which is available using Effects.