Here is what is meant to be a general example of processing the audio data stored in a Playdate AudioSample. And is ideally a good place to start if you want to mess around with Playdate audio data in C.
I will try to circle back and make this example even more general by showing how to handle stereo sound and ideally a little more compact.
Please let me know if you see any issues here and I'll fix it up.
// A few helpful macros and values
#define MIN(A, B) ((A) < (B) ? (A) : (B))
#define MAX(A, B) ((A) > (B) ? (A) : (B))
#define MAX_INT16_F 32767.0f
#define MIN_INT16_F -32767.0f
#define MAX_INT8_F 127.0f
#define MIN_INT8_F -127.0f
// In this case, I'm reading an AudioSample passed from the lua runtime.
AudioSample* sample = pd->lua->getArgObject(1, "playdate.sound.sample", NULL);
// Fetch a ptr to the sample's data, data length, format, and sample rate.
uint8_t* sound_data = NULL;
SoundFormat sound_format;
uint32_t sound_sample_rate;
uint32_t sound_data_length;
pd->sound->sample->getData(sample, &sound_data, &sound_format, &sound_sample_rate, &sound_data_length);
// Determine number of channels (stereo or mono).
const int sound_channels = SoundFormatIsStereo(sound_format) ? 2 : 1;
// For now this example only processes mono (8/16-bit) frames.
if(sound_channels > 1) {
return;
}
// Get length of each frame (if stereo, a frame includes a sample per channel).
const int bytes_per_frame = SoundFormat_bytesPerFrame(sound_format);
// Determine the number of samples in the AudioSample's data.
const int sample_count = sound_data_length / bytes_per_frame;
// Allocate an output buffer we'll use to store our processed audio.
uint8_t* output = malloc(sound_data_length);
// Now, you don't need to handle all sound formats if you know what data you're working with.
// However, if you want to create something more general, you could handle both 16 and 8 bit audio samples.
// In each of these cases we cast our output and input buffers to the correct.
// For now I am only handling mono here, but stereo samples sit side by side so you could have a subloop per sample to iterate over the number of channels.
if(!SoundFormatIs16bit(sound_format)) {
int16_t* output16 = (int16_t*)output;
int16_t* samples16 = (int16_t*)sound_data;
const int16_t clip_value = 5000;
for(int i = 0; i < sample_count; i++) {
const int16_t sample = samples16[i]; // Grab the current 16-bit sample.
const float sample_f = (float)sample / 32767.0f; // Not used but an example if you want to work with a normalized float (-1.0 – 1.0).
// Super simple processing of a hard clip 16 bit audio data.
output16[i] = MIN(clip_value, MAX(-clip_value, sample));
}
}
else {
int8_t* output8 = (int8_t*)output;
int8_t* samples8 = (int8_t*)sound_data;
const int8_t clip_value = 50;
for(int i = 0; i < sample_count; i++) {
const int8_t sample = samples8[i]; // Grab the current 8-bit sample.
const float sample_f = (float)sample / 127.0f; // Not used but as above just an example if you want to work with a normalized float (-1.0–1.0)
// Simple processing of a hard clip on 8 bit audio data.
output8[i] = MIN(clip_value, MAX(-clip_value, sample));
}
}
// To wrap our processed in an AudioSample again.
// The assumption here is that the sound format, sample rate, and data length haven't changed.
// If they have, you'll want to pass in those values.
AudioSample* new_sample = pd->sound->sample->newSampleFromData(output, sound_format, sound_sample_rate, sound_data_length);
// You do not need to free the output buffer as the AudioSample has now taken owership and will free it when it is freed.
// And if you wanted to push this new AudioSample back to the lua runtime.
pd->lua->pushObject(new_sample, "playdate.sound.sample", 0);