Tips and tricks for processing audio in C

Audio can be used without hooking into the Lua runtime via playdate->sound->addSource(). There is also a version of this function that operates on channel objects, but I won't be covering it at this time. Refer to the Channels section of Inside Playdate with C for more information if you're curious. When not used in the context of a channel, the new source is associated with the "default channel", which is a resource managed by the audio runtime.

addSource() has the following arguments:

SoundSource* playdate->sound->addSource(
    AudioSourceFunction *callback,
    void *context,
    int stereo
);
  • callback is a pointer to a handler function that will be described in just a moment.
  • context is any pointer defined by the programmer. This same pointer will be used in calls to the handler callback.
  • stereo is a boolean value for the number of channels in the source: 0=mono, 1=stereo.
  • The return value is a pointer to the new SoundSource object.

AudioSourceFunction has the following arguments:

int AudioSourceFunction(
    void *context,
    int16_t *left,
    int16_t *right,
    int len
);
  • context is the same pointer that was used in the call to addSource(). Every time the handler is called, context will contain this pointer.
  • left and right are both pointers to a sample buffers to receive the output audio. They can be accessed like arrays, such as left[0], left[1] and so-on.
  • len is the number of audio frames to render. This many samples should be stored to left and (if stereo) right every time the handler is called.
  • The return value indicates whether there is any meaningful audio data: 0=all audio is silent, 1=data in left and right is meaningful.

Question: Is it true that mono sources only need to store to left? I feel like that's the case but I can't say with absolute certainty.

Once addSource() has been called, the runtime will automatically invoke the callback whenever it needs more samples, until playdate->sound->removeSource() has been called for that source object. This happens in a manner similar to the program's update callback, but in a distinct context. The pointer to the PlaydateAPI object is not passed to the audio handler, so if any runtime features (such as access to removeSource()) is needed inside the handler, the program will need to make use of the context argument.

Question: Is it safe to use removeSource() from inside the audio callback? I've been doing this without issue so far, but I don't know if it's guaranteed to continue working in future SDK versions.

The way I recommend to associate state with the context argument is to define a simple structure type. Consider the following:

// Context state for audio callback
typedef struct {
    PlaydateAPI *pd;
    SoundSource *source;
} AudioState;
// Create a new audio source with a state context
AudioState *state = pd->system.realloc(NULL, sizeof (AudioContext));
state->pd     = pd;
state->source = pd->sound->addSource(&AudioHandler, state, 1);
// Audio callback routine
int AudioHandler(void *context, int16_t *left, int16_t *right, int len) {

    // Resolve state fields as needed
    AudioState *state = (AudioState *) context;
    // Do not use the audio callback for system tasks:
    //   spend as little time here as possible

    // Process all requested samples
    for (int x = 0; x < len; x++) {
        left [x] = rand(); // White noise
        right[x] = rand(); // Narrowing conversion is automatic, so don't @ me
    }

    // Audio data is meaningful, so return 1
    return 1;
}

Here, a structure type AudioState is defined that points to both the PlaydateAPI object and a SoundSource object. This can be used to store any number of fields applicable to the program. For instance, if a sound effect is to be played and its length in samples is known, the state structure can store the number of remaining samples, allowing removeSource() to be called after the sound effect has finished playing.

It's important to remember that the audio callback is not intended for anything except delivering audio samples to the runtime. Don't use it for any other purpose: use the program's update callback for everything else.

5 Likes