Demonstrates one way to chain together a number of data sources so they play back seamlessly
without gaps.
diff --git a/docs/examples/duplex_effect.html b/docs/examples/duplex_effect.html
index 494257f1..a48a7bbb 100644
--- a/docs/examples/duplex_effect.html
+++ b/docs/examples/duplex_effect.html
@@ -245,7 +245,7 @@ a.doc-navigation-l4 {
+Shows how to use the high level engine API with SDL.
+
+
+
+By default, miniaudio's engine API will initialize a device internally for audio output. You can
+instead use the engine independently of a device. To show this off, this example will use SDL for
+audio output instead of miniaudio.
+
+
+
+This example will load the sound specified on the command line and rotate it around the listener's
+head.
+
+#define MA_NO_DEVICE_IO /* <-- Disables the ma_device API. We don't need that in this example since SDL will be doing that part for us. */
+#define MINIAUDIO_IMPLEMENTATION
+#include"../miniaudio.h"
+
+#define SDL_MAIN_HANDLED
+#include<SDL.h>/* Change this to your include location. Might be <SDL2/SDL.h>. */
+
+#define CHANNELS 2 /* Must be stereo for this example. */
+#define SAMPLE_RATE 48000
+
+static ma_engine g_engine;
+static ma_sound g_sound; /* This example will play only a single sound at once, so we only need one ma_sound object. */
+
+void data_callback(void* pUserData, ma_uint8* pBuffer, int bufferSizeInBytes)
+{
+ /* Reading is just a matter of reading straight from the engine. */
+ ma_uint32 bufferSizeInFrames = (ma_uint32)bufferSizeInBytes / ma_get_bytes_per_frame(ma_format_f32, ma_engine_get_channels(&g_engine));
+ ma_engine_read_pcm_frames(&g_engine, pBuffer, bufferSizeInFrames, NULL);
+}
+
+int main(int argc, char** argv)
+{
+ ma_result result;
+ ma_engine_config engineConfig;
+ SDL_AudioSpec desiredSpec;
+ SDL_AudioSpec obtainedSpec;
+ SDL_AudioDeviceID deviceID;
+
+ if (argc < 2) {
+ printf("No input file.");
+ return -1;
+ }
+
+ /*
+ We'll initialize the engine first for the purpose of the example, but since the engine and SDL
+ are independent of each other you can initialize them in any order. You need only make sure the
+ channel count and sample rates are consistent between the two.
+
+ When initializing the engine it's important to make sure we don't initialize a device
+ internally because we want SDL to be dealing with that for us instead.
+ */
+ engineConfig = ma_engine_config_init();
+ engineConfig.noDevice = MA_TRUE; /* <-- Make sure this is set so that no device is created (we'll deal with that ourselves). */
+ engineConfig.channels = CHANNELS;
+ engineConfig.sampleRate = SAMPLE_RATE;
+
+ result = ma_engine_init(&engineConfig, &g_engine);
+ if (result != MA_SUCCESS) {
+ printf("Failed to initialize audio engine.");
+ return -1;
+ }
+
+ /* Now load our sound. */
+ result = ma_sound_init_from_file(&g_engine, argv[1], 0, NULL, NULL, &g_sound);
+ if (result != MA_SUCCESS) {
+ printf("Failed to initialize sound.");
+ return -1;
+ }
+
+ /* Loop the sound so we can continuously hear it. */
+ ma_sound_set_looping(&g_sound, MA_TRUE);
+
+ /*
+ The sound will not be started by default, so start it now. We won't hear anything until the SDL
+ audio device has been opened and started.
+ */
+ ma_sound_start(&g_sound);
+
+
+ /*
+ Now that we have the engine and sound we can initialize SDL. This could have also been done
+ first before the engine and sound.
+ */
+ if (SDL_InitSubSystem(SDL_INIT_AUDIO) != 0) {
+ printf("Failed to initialize SDL sub-system.");
+ return -1;
+ }
+
+ MA_ZERO_OBJECT(&desiredSpec);
+ desiredSpec.freq = ma_engine_get_sample_rate(&g_engine);
+ desiredSpec.format = AUDIO_F32;
+ desiredSpec.channels = ma_engine_get_channels(&g_engine);
+ desiredSpec.samples = 512;
+ desiredSpec.callback = data_callback;
+ desiredSpec.userdata = NULL;
+
+ deviceID = SDL_OpenAudioDevice(NULL, 0, &desiredSpec, &obtainedSpec, SDL_AUDIO_ALLOW_ANY_CHANGE);
+ if (deviceID == 0) {
+ printf("Failed to open SDL audio device.");
+ return -1;
+ }
+
+ /* Start playback. */
+ SDL_PauseAudioDevice(deviceID, 0);
+
+#if 1
+ {
+ /* We'll move the sound around the listener which we'll leave at the origin. */
+ float stepAngle = 0.002f;
+ float angle = 0;
+ float distance = 2;
+
+ for (;;) {
+ double x = ma_cosd(angle) - ma_sind(angle);
+ double y = ma_sind(angle) + ma_cosd(angle);
+
+ ma_sound_set_position(&g_sound, (float)x * distance, 0, (float)y * distance);
+
+ angle += stepAngle;
+ ma_sleep(1);
+ }
+ }
+#else
+ printf("Press Enter to quit...");
+ getchar();
+#endif
+
+ ma_sound_uninit(&g_sound);
+ ma_engine_uninit(&g_engine);
+ SDL_CloseAudioDevice(deviceID);
+ SDL_QuitSubSystem(SDL_INIT_AUDIO);
+
+ return 0;
+}
Demonstrates integration of Steam Audio with miniaudio's engine API.
-In this example we'll apply a HRTF effect from Steam Audio. To do this a custom node will be
+In this example a HRTF effect from Steam Audio will be applied. To do this a custom node will be
implemented which uses Steam Audio's IPLBinauralEffect and IPLHRTF objects.
By implementing this as a node, it can be plugged into any position within the graph. The output
-channel count of this node is always stereo.
+channel count of this node is always stereo.
+
+
+
+Steam Audio requires fixed sized processing, the size of which must be specified at initialization
+time of the IPLBinauralEffect and IPLHRTF objects. This creates a problem because the node graph
+will at times need to break down processing into smaller chunks for it's internal processing. The
+node graph internally will read into a temporary buffer which is then mixed into the final output
+buffer. This temporary buffer is allocated on the stack and is a fixed size. However, variability
+comes into play because the channel count of the node is variable. It's not safe to just blindly
+process the effect with the frame count specified in miniaudio's node processing callback. Doing so
+results in glitching. To work around this, this example is just setting the update size to a known
+value that works (256). If it's set to something too big it'll exceed miniaudio's processing size
+used by the node graph. Alternatively you could use some kind of intermediary cache which
+accumulates input data until enough is available and then do the processing. Ideally, Steam Audio
+would support variable sized updates which would avoid this whole mess entirely.
#define MINIAUDIO_IMPLEMENTATION
#include"../miniaudio.h"#include<phonon.h>/* Steam Audio */
-#include<stdint.h>/* Required for uint32_t which is used by STEAMAUDIO_VERSION. */
+#include<stdint.h>/* Required for uint32_t which is used by STEAMAUDIO_VERSION. That dependency needs to be removed from Steam Audio - use IPLuint32 or "unsigned int" instead! */#define FORMAT ma_format_f32 /* Must be floating point. */#define CHANNELS 2 /* Must be stereo for this example. */
@@ -439,17 +454,15 @@ MA_API ma_result ma_steamaudio_binaural_node_
return result;
}
+ heapSizeInBytes = 0;
+
/*
Unfortunately Steam Audio uses deinterleaved buffers for everything so we'll need to use some
intermediary buffers. We'll allocate one big buffer on the heap and then use offsets. We'll
use the frame size from the IPLAudioSettings structure as a basis for the size of the buffer.
*/
- heapSizeInBytes = sizeof(float) * channelsOut * pBinauralNode->iplAudioSettings.frameSize; /* Output buffer. */
-
- /* Only need input buffers if we're not using mono input. */
- if (channelsIn > 1) {
- heapSizeInBytes += sizeof(float) * channelsIn * pBinauralNode->iplAudioSettings.frameSize;
- }
+ heapSizeInBytes += sizeof(float) * channelsOut * pBinauralNode->iplAudioSettings.frameSize; /* Output buffer. */
+ heapSizeInBytes += sizeof(float) * channelsIn * pBinauralNode->iplAudioSettings.frameSize; /* Input buffer. */
pBinauralNode->_pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks);
if (pBinauralNode->_pHeap == NULL) {
@@ -461,7 +474,7 @@ MA_API ma_result ma_steamaudio_binaural_node_
pBinauralNode->ppBuffersOut[0] = (float*)pBinauralNode->_pHeap;
pBinauralNode->ppBuffersOut[1] = (float*)ma_offset_ptr(pBinauralNode->_pHeap, sizeof(float) * pBinauralNode->iplAudioSettings.frameSize);
- if (channelsIn > 1) {
+ {
ma_uint32 iChannelIn;
for (iChannelIn = 0; iChannelIn < channelsIn; iChannelIn += 1) {
pBinauralNode->ppBuffersIn[iChannelIn] = (float*)ma_offset_ptr(pBinauralNode->_pHeap, sizeof(float) * pBinauralNode->iplAudioSettings.frameSize * (channelsOut + iChannelIn));
@@ -547,7 +560,7 @@ MA_API ma_result ma_steamaudio_binaural_node_
need not be exposed to the public API. There should be no need for the public API to require a
fixed sized update.
*/
- iplAudioSettings.frameSize = g_engine.pDevice->playback.internalPeriodSizeInFrames;
+ iplAudioSettings.frameSize = engineConfig.periodSizeInFrames;
/* IPLContext */
diff --git a/docs/examples/fixed_size_callback.html b/docs/examples/fixed_size_callback.html
deleted file mode 100644
index a46a32be..00000000
--- a/docs/examples/fixed_size_callback.html
+++ /dev/null
@@ -1,414 +0,0 @@
-
-
-
- miniaudio - A single file audio playback and capture library.
-
-
-
-
-
-
-
-
-
-
-
-
-
-Shows one way to implement a data callback that is called with a fixed frame count.
-
-
-
-miniaudio does not have built-in support for firing the data callback with fixed sized buffers. In order to support
-this you need to implement a layer that sits on top of the normal data callback. This example demonstrates one way of
-doing this.
-
-
-
-This example uses a ring buffer to act as the intermediary buffer between the low-level device callback and the fixed
-sized callback. You do not need to use a ring buffer here, but it's a good opportunity to demonstrate how to use
-miniaudio's ring buffer API. The ring buffer in this example is in global scope for simplicity, but you can pass it
-around as user data for the device (device.pUserData).
-
-
-
-This example only works for output devices, but can be implemented for input devices by simply swapping the direction
-of data movement.
-
-#define MINIAUDIO_IMPLEMENTATION
-#include"../miniaudio.h"
-
-#include<stdio.h>
-
-#define DEVICE_FORMAT ma_format_f32
-#define DEVICE_CHANNELS 1
-#define DEVICE_SAMPLE_RATE 48000
-
-#define PCM_FRAME_CHUNK_SIZE 1234 /* <-- Play around with this to control your fixed sized buffer. */
-
-ma_waveform g_sineWave;
-ma_pcm_rb g_rb; /* The ring buffer. */
-
-void data_callback_fixed(ma_device* pDevice, void* pOutput, constvoid* pInput, ma_uint32 frameCount)
-{
- /*
- This callback will have a guaranteed and consistent size for frameCount. In this example we just fill the output buffer with a sine wave. This
- is where you would handle the callback just like normal, only now you can assume frameCount is a fixed size.
- */
- printf("frameCount=%d\n", frameCount);
-
- ma_waveform_read_pcm_frames(&g_sineWave, pOutput, frameCount, NULL);
-
- /* Unused in this example. */
- (void)pDevice;
- (void)pInput;
-}
-
-void data_callback(ma_device* pDevice, void* pOutput, constvoid* pInput, ma_uint32 frameCount)
-{
- /*
- This is the device's main data callback. This will handle all of the fixed sized buffer management for you and will call data_callback_fixed()
- for you. You should do all of your normal callback stuff in data_callback_fixed().
- */
- ma_uint32 pcmFramesAvailableInRB;
- ma_uint32 pcmFramesProcessed = 0;
- ma_uint8* pRunningOutput = (ma_uint8*)pOutput;
-
- MA_ASSERT(pDevice->playback.channels == DEVICE_CHANNELS);
-
- /*
- The first thing to do is check if there's enough data available in the ring buffer. If so we can read from it. Otherwise we need to keep filling
- the ring buffer until there's enough, making sure we only fill the ring buffer in chunks of PCM_FRAME_CHUNK_SIZE.
- */
- while (pcmFramesProcessed < frameCount) { /* Keep going until we've filled the output buffer. */
- ma_uint32 framesRemaining = frameCount - pcmFramesProcessed;
-
- pcmFramesAvailableInRB = ma_pcm_rb_available_read(&g_rb);
- if (pcmFramesAvailableInRB > 0) {
- ma_uint32 framesToRead = (framesRemaining < pcmFramesAvailableInRB) ? framesRemaining : pcmFramesAvailableInRB;
- void* pReadBuffer;
-
- ma_pcm_rb_acquire_read(&g_rb, &framesToRead, &pReadBuffer);
- {
- memcpy(pRunningOutput, pReadBuffer, framesToRead * ma_get_bytes_per_frame(pDevice->playback.format, pDevice->playback.channels));
- }
- ma_pcm_rb_commit_read(&g_rb, framesToRead);
-
- pRunningOutput += framesToRead * ma_get_bytes_per_frame(pDevice->playback.format, pDevice->playback.channels);
- pcmFramesProcessed += framesToRead;
- } else {
- /*
- There's nothing in the buffer. Fill it with more data from the callback. We reset the buffer first so that the read and write pointers
- are reset back to the start so we can fill the ring buffer in chunks of PCM_FRAME_CHUNK_SIZE which is what we initialized it with. Note
- that this is not how you would want to do it in a multi-threaded environment. In this case you would want to seek the write pointer
- forward via the producer thread and the read pointer forward via the consumer thread (this thread).
- */
- ma_uint32 framesToWrite = PCM_FRAME_CHUNK_SIZE;
- void* pWriteBuffer;
-
- ma_pcm_rb_reset(&g_rb);
- ma_pcm_rb_acquire_write(&g_rb, &framesToWrite, &pWriteBuffer);
- {
- MA_ASSERT(framesToWrite == PCM_FRAME_CHUNK_SIZE); /* <-- This should always work in this example because we just reset the ring buffer. */
- data_callback_fixed(pDevice, pWriteBuffer, NULL, framesToWrite);
- }
- ma_pcm_rb_commit_write(&g_rb, framesToWrite);
- }
- }
-
- /* Unused in this example. */
- (void)pInput;
-}
-
-int main(int argc, char** argv)
-{
- ma_waveform_config waveformConfig;
- ma_device_config deviceConfig;
- ma_device device;
-
- waveformConfig = ma_waveform_config_init(DEVICE_FORMAT, DEVICE_CHANNELS, DEVICE_SAMPLE_RATE, ma_waveform_type_sine, 0.1, 220);
- ma_waveform_init(&waveformConfig, &g_sineWave);
-
- ma_pcm_rb_init(DEVICE_FORMAT, DEVICE_CHANNELS, PCM_FRAME_CHUNK_SIZE, NULL, NULL, &g_rb);
-
- deviceConfig = ma_device_config_init(ma_device_type_playback);
- deviceConfig.playback.format = DEVICE_FORMAT;
- deviceConfig.playback.channels = DEVICE_CHANNELS;
- deviceConfig.sampleRate = DEVICE_SAMPLE_RATE;
- deviceConfig.dataCallback = data_callback;
- deviceConfig.pUserData = NULL; /* <-- Set this to a pointer to the ring buffer if you don't want it in global scope. */
-
- if (ma_device_init(NULL, &deviceConfig, &device) != MA_SUCCESS) {
- printf("Failed to open playback device.\n");
- ma_pcm_rb_uninit(&g_rb);
- return -4;
- }
-
- printf("Device Name: %s\n", device.playback.name);
-
- if (ma_device_start(&device) != MA_SUCCESS) {
- printf("Failed to start playback device.\n");
- ma_pcm_rb_uninit(&g_rb);
- ma_device_uninit(&device);
- return -5;
- }
-
- printf("Press Enter to quit...\n");
- getchar();
-
- ma_device_uninit(&device);
- ma_pcm_rb_uninit(&g_rb);
-
- (void)argc;
- (void)argv;
- return 0;
-}
-
diff --git a/docs/manual/index.html b/docs/manual/index.html
index 404c0bac..3219ce69 100644
--- a/docs/manual/index.html
+++ b/docs/manual/index.html
@@ -1455,12 +1455,10 @@ runtime linking via dlopen().
-MA_DEBUG_OUTPUT
-
+MA_DEBUG_OUTPUT
-Enable processing of MA_LOG_LEVEL_DEBUG messages and printf()
-output.
+Enable printf() output of debug logs (MA_LOG_LEVEL_DEBUG).
@@ -3599,6 +3597,34 @@ should set this for any nodes that perform
resampling.
+
+
+MA_NODE_FLAG_SILENT_OUTPUT
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Used to tell miniaudio that a node produces only
+silent output. This is useful for nodes where you
+don't want the output to contribute to the final
+mix. An example might be if you want split your
+stream and have one branch be output to a file.
+When using this flag, you should avoid writing to
+the output buffer of the node's processing
+callback because miniaudio will ignore it anyway.
+
+
If you need to make a copy of an audio stream for effect processing you can use a splitter node