Tips and tricks for processing audio in C

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. :smiley:

// 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);

3 Likes