Skip to content

Commit

Permalink
Add an example for high- and low-level interop.
Browse files Browse the repository at this point in the history
This example captures data from the microphone using the low-level API
and then plays back the data through the engine. The intermediary data
source is a ring buffer.
  • Loading branch information
mackron committed Mar 25, 2023
1 parent 8ce4857 commit 4cd0668
Showing 1 changed file with 148 additions and 0 deletions.
148 changes: 148 additions & 0 deletions examples/hilo_interop.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
/*
Demonstrates interop between the high-level and the low-level API.
In this example we are using `ma_device` (the low-level API) to capture data from the microphone
which we then play back through the engine as a sound. We use a ring buffer to act as the data
source for the sound.
This is just a very basic example to show the general idea on how this might be achieved. In
this example a ring buffer is being used as the intermediary data source, but you can use anything
that works best for your situation. So long as the data is captured from the microphone, and then
delivered to the sound (via a data source), you should be good to go.
A more robust example would probably not want to use a ring buffer directly as the data source.
Instead you would probably want to do a custom data source that handles underruns and overruns of
the ring buffer and deals with desyncs between capture and playback. In the future this example
may be updated to make use of a more advanced data source that handles all of this.
*/
#define MINIAUDIO_IMPLEMENTATION
#include "../miniaudio.h"

static ma_pcm_rb rb;
static ma_device device;
static ma_engine engine;
static ma_sound sound; /* The sound will be the playback of the capture side. */

void capture_data_callback(ma_device* pDevice, void* pFramesOut, const void* pFramesIn, ma_uint32 frameCount)
{
ma_result result;
ma_uint32 framesWritten;

/* We need to write to the ring buffer. Need to do this in a loop. */
framesWritten = 0;
while (framesWritten < frameCount) {
void* pMappedBuffer;
ma_uint32 framesToWrite = frameCount - framesWritten;

result = ma_pcm_rb_acquire_write(&rb, &framesToWrite, &pMappedBuffer);
if (result != MA_SUCCESS) {
break;
}

if (framesToWrite == 0) {
break;
}

/* Copy the data from the capture buffer to the ring buffer. */
ma_copy_pcm_frames(pMappedBuffer, ma_offset_pcm_frames_const_ptr_f32(pFramesIn, framesWritten, pDevice->capture.channels), framesToWrite, pDevice->capture.format, pDevice->capture.channels);

result = ma_pcm_rb_commit_write(&rb, framesToWrite);
if (result != MA_SUCCESS) {
break;
}

framesWritten += framesToWrite;
}
}

int main(int argc, char** argv)
{
ma_result result;
ma_device_config deviceConfig;

/*
The first thing we'll do is set up the capture side. There are two parts to this. The first is
the device itself, and the other is the ring buffer. It doesn't matter what order we initialize
these in, so long as the ring buffer is created before the device is started so that the
callback can be guaranteed to have a valid destination. We'll initialize the device first, and
then use the format, channels and sample rate to initialize the ring buffer.
It's important that the sample format of the device is set to f32 because that's what the engine
uses internally.
*/

/* Initialize the capture device. */
deviceConfig = ma_device_config_init(ma_device_type_capture);
deviceConfig.capture.format = ma_format_f32;
deviceConfig.dataCallback = capture_data_callback;

result = ma_device_init(NULL, &deviceConfig, &device);
if (result != MA_SUCCESS) {
printf("Failed to initialize capture device.");
return -1;
}

/* Initialize the ring buffer. */
result = ma_pcm_rb_init(device.capture.format, device.capture.channels, device.capture.internalPeriodSizeInFrames * 5, NULL, NULL, &rb);
if (result != MA_SUCCESS) {
printf("Failed to initialize the ring buffer.");
return -1;
}

/*
Ring buffers don't require a sample rate for their normal operation, but we can associate it
with a sample rate. We'll want to do this so the engine can resample if necessary.
*/
ma_pcm_rb_set_sample_rate(&rb, device.sampleRate);



/*
At this point the capture side is set up and we can now set up the playback side. Here we are
using `ma_engine` and linking the captured data to a sound so it can be manipulated just like
any other sound in the world.
Note that we have not yet started the capture device. Since the captured data is tied to a
sound, we'll link the starting and stopping of the capture device to the starting and stopping
of the sound.
*/

/* We'll get the engine up and running before we start the capture device. */
result = ma_engine_init(NULL, &engine);
if (result != MA_SUCCESS) {
printf("Failed to initialize the engine.");
return -1;
}

/*
We can now create our sound. This is created from a data source, which in this example is a
ring buffer. The capture side will be writing data into the ring buffer, whereas the sound
will be reading from it.
*/
result = ma_sound_init_from_data_source(&engine, &rb, 0, NULL, &sound);
if (result != MA_SUCCESS) {
printf("Failed to initialize the sound.");
return -1;
}

/* Make sure the sound is set to looping or else it'll stop if the ring buffer runs out of data. */
ma_sound_set_looping(&sound, MA_TRUE);

/* Link the starting of the device and sound together. */
ma_device_start(&device);
ma_sound_start(&sound);


printf("Press Enter to quit...\n");
getchar();

ma_sound_uninit(&sound);
ma_engine_uninit(&engine);
ma_device_uninit(&device);
ma_pcm_rb_uninit(&rb);


(void)argc;
(void)argv;
return 0;
}

0 comments on commit 4cd0668

Please sign in to comment.