Commit 085728fd authored by David Reid's avatar David Reid

Remove ma_mixer.

parent 66a20f0a
...@@ -323,6 +323,9 @@ multiple threads comes into play when loading multiple sounds at the time time. ...@@ -323,6 +323,9 @@ multiple threads comes into play when loading multiple sounds at the time time.
extern "C" { extern "C" {
#endif #endif
MA_API size_t ma_get_accumulation_bytes_per_sample(ma_format format);
MA_API size_t ma_get_accumulation_bytes_per_frame(ma_format format, ma_uint32 channels);
/* /*
Routing Infrastructure Routing Infrastructure
...@@ -997,599 +1000,6 @@ MA_API ma_result ma_effect_get_input_data_format(ma_effect* pEffect, ma_format* ...@@ -997,599 +1000,6 @@ MA_API ma_result ma_effect_get_input_data_format(ma_effect* pEffect, ma_format*
/*
Mixing
======
Mixing is done via the ma_mixer API. You can use this if you want to mix multiple sources of audio together and play them all at the same time, layered on top
of each other. This is a mid-level procedural API. Do not confuse this with a high-level data-driven API. You do not "attach" and "detach" sounds, but instead
write raw audio data directly into an accumulation buffer procedurally. High-level data-driven APIs will be coming at a later date.
Below are the features of the ma_mixer API:
* Mixing to and from any data format with seamless conversion when necessary.
* Initialize the `ma_mixer` object using whatever format is convenient, and then mix audio in any other format with seamless data conversion.
* Submixing (mix one `ma_mixer` directly into another `ma_mixer`, with volume and effect control).
* Volume control.
* Effects (via the `ma_effect` API).
* Mix directly from raw audio data in addition to `ma_decoder`, `ma_waveform`, `ma_noise`, `ma_pcm_rb` and `ma_rb` objects.
Mixing sounds together is as simple as summing their samples. As samples are summed together they are stored in a buffer called the accumulation buffer. In
order to ensure there is enough precision to store the intermediary results, the accumulation buffer needs to be at a higher bit depth than the sample format
being mixed, with the exception of floating point. Below is a mapping of the sample format and the data type of the accumulation buffer:
+---------------+------------------------+
| Sample Format | Accumulation Data Type |
+---------------+------------------------+
| ma_format_u8 | ma_int16 |
| ma_format_s16 | ma_int32 |
| ma_format_s24 | ma_int64 |
| ma_format_s32 | ma_int64 |
| ma_format_f32 | float |
+---------------+------------------------+
The size of the accumulation buffer is fixed and must be specified at initialization time. When you initialize a mixer you need to also specify a sample format
which will be the format of the returned data after mixing. The format is also what's used to determine the bit depth to use for the accumulation buffer and
how to interpret the data contained within it. You must also specify a channel count in order to support interleaved multi-channel data. The sample rate is not
required by the mixer as it only cares about raw sample data.
The mixing process involves three main steps:
1) Clearing the accumulation buffer to zero
ma_mixer_begin()
2) Accumulating all audio sources
ma_mixer_mix_pcm_frames()
ma_mixer_mix_data_source()
ma_mixer_mix_rb()
ma_mixer_mix_pcm_rb()
3) Volume, clipping, effects and final output
ma_mixer_end()
At the beginning of mixing the accumulation buffer will be cleared to zero. When you begin mixing you need to specify the number of PCM frames you want to
output at the end of mixing. If the requested number of output frames exceeds the capacity of the internal accumulation buffer, it will be clamped and returned
back to the caller. An effect can be applied at the end of mixing (after volume and clipping). Effects can do resampling which means the number of input frames
required to generate the requested number of output frames may be different. Therefore, another parameter is required which will receive the input frame count.
When mixing audio sources, you must do so based on the input frame count, not the output frame count (usage examples are in the next section).
After the accumulation buffer has been cleared to zero (the first step), you can start mixing audio data. When you mix audio data you should do so based on the
required number of input frames returned by ma_mixer_begin() or ma_mixer_begin_submix(). You can specify audio data in any data format in which case the data
will be automatically converted to the format required by the accumulation buffer. Input data can be specified in multiple ways:
- A pointer to raw PCM data
- A data source (ma_data_source, ma_decoder, ma_audio_buffer, ma_waveform, ma_noise)
- A ring buffer (ma_rb, ma_pcm_rb)
Once you've finished accumulating all of your audio sources you need to perform a post process step which performs the final volume adjustment, clipping,
effects and copying to the specified output buffer in the format specified when the mixer was initialized. Volume is applied before clipping, which is applied
before the effect, which is done before final output. In between these steps is all of the necessary data conversion, so for performance it's important to be
mindful of where and when data will be converted.
The mixing API in miniaudio supports seamless data conversion at all stages of the mixing pipeline. If you're not mindful about the data formats used by each
of the different stages of the mixing pipeline you may introduce unnecessary inefficiency. For maximum performance you should use a consistent sample format,
channel count and sample rate for as much of the mixing pipeline as possible. As soon as you introduce a different format, the mixing pipeline will perform the
necessary data conversion.
Usage
-----
Initialize a mixer like the following:
```c
ma_mixer_config config = ma_mixer_config_init(ma_format_f32, 2, 1024, NULL);
ma_mixer mixer;
result = ma_mixer_init(&config, &mixer);
if (result != MA_SUCCESS) {
// An error occurred.
}
```
Before you can initialize a mixer you need to specify it's configuration via a `ma_mixer_config` object. This can be created with `ma_mixer_config_init()`
which requires the mixing format, channel count, size of the intermediary buffer in PCM frames and an optional pointer to a pre-allocated accumulation buffer.
Once you have the configuration set up, you can call `ma_mixer_init()` to initialize the mixer. If you passed in NULL for the pre-allocated accumulation buffer
this will allocate it on the stack for you, using custom allocation callbacks specified in the `allocationCallbacks` member of the mixer config.
Below is an example for mixing two decoders together:
```c
ma_uint64 frameCountIn;
ma_uint64 frameCountOut = desiredOutputFrameCount;
ma_mixer_begin(&mixer, NULL, &frameCountOut, &frameCountIn);
{
// At this point, frameCountIn contains the number of frames we should be mixing in this iteration, whereas frameCountOut contains the number of output
// frames we'll be outputting in ma_mixer_end().
ma_mixer_mix_data_source(&mixer, &decoder1, 0, frameCountIn, isLooping1);
ma_mixer_mix_data_source(&mixer, &decoder2, 0, frameCountIn, isLooping2);
}
ma_mixer_end(&mixer, NULL, pFinalMix, 0); // pFinalMix must be large enough to store frameCountOut frames in the mixer's format (specified at initialization time).
```
When you want to mix sounds together, you need to specify how many output frames you would like to end up with by the end. This depends on the size of the
accumulation buffer, however, which is of a fixed size. Therefore, the number of output frames you ask for is not necessarily what you'll get. In addition, an
effect can be applied at the end of mixing, and since that may perform resampling, the number of input frames required to generate the desired number of output
frames may differ which means you must also specify a pointer to a variable which will receive the required input frame count. In order to avoid glitching you
should write all of these input frames if they're available.
The ma_mixer API uses a sort of "immediate mode" design. The idea is that you "begin" and "end" mixing. When you begin mixing a number of frames you need to
call `ma_mixer_begin()`. This will initialize the accumulation buffer to zero (silence) in preparation for mixing. Next, you can start mixing audio data which
can be done in several ways, depending on the source of the audio data. In the example above we are using a `ma_decoder` as the input data source. This will
automatically convert the input data to an appropriate format for mixing.
Each call to ma_mixer_mix_*() accumulates from the beginning of the accumulation buffer.
Once all of your input data has been mixed you need to call `ma_mixer_end()`. This is where the data in the accumulation buffer has volume applied, is clipped
and has the effect applied, in that order. Finally, the data is output to the specified buffer in the format specified when the mixer was first initialized,
overwriting anything that was previously contained within the buffer, unless it's a submix in which case it will be mixed with the parent mixer. See section
below for more details.
The mixing API also supports submixing. This is where the final output of one mixer is mixed directly into the accumulation buffer of another mixer. A common
example is a game with a music submix and an effects submix, which are then combined to form the master mix. Example:
```c
ma_uint64 frameCountIn;
ma_uint64 frameCountOut = desiredOutputFrameCount; // <-- Must be set to the desired number of output frames. Upon returning, will contain the actual number of output frames.
ma_mixer_begin(&masterMixer, NULL, &frameCountOut, &frameCountIn);
{
ma_uint64 submixFrameCountIn;
ma_uint64 submixFrameCountOut; // <-- No pre-initialization required for a submix as it's derived from the parent mix's input frame count.
// Music submix.
ma_mixer_begin(&musicMixer, &masterMixer, &submixFrameCountIn, &submixFrameCountOut);
{
ma_mixer_mix_data_source(&musicMixer, &musicDecoder, 0, submixFrameCountIn, isMusicLooping);
}
ma_mixer_end(&musicMixer, &masterMixer, NULL, 0);
// Effects submix.
ma_mixer_begin(&effectsMixer, &masterMixer, &submixFrameCountIn, &submixFrameCountOut);
{
ma_mixer_mix_data_source(&effectsMixer, &decoder1, 0, frameCountIn, isLooping1);
ma_mixer_mix_data_source(&effectsMixer, &decoder2, 0, frameCountIn, isLooping2);
}
ma_mixer_end(&effectsMixer, &masterMixer, NULL, 0);
}
ma_mixer_end(&masterMixer, NULL, pFinalMix); // pFinalMix must be large enough to store frameCountOut frames in the mixer's format (specified at initialization time).
```
If you want to use submixing, you need to ensure the accumulation buffers of each mixer is large enough to accomodate each other. That is, the accumulation
buffer of the sub-mixer needs to be large enough to store the required number of input frames returned by the parent call to `ma_mixer_begin()`. If you are not
doing any resampling you can just make the accumulation buffers the same size and you will fine. If you want to submix, you can only call `ma_mixer_begin()`
between the begin and end pairs of the parent mixer, which can be a master mix or another submix.
Implementation Details and Performance Guidelines
-------------------------------------------------
There are two main factors which affect mixing performance: data conversion and data movement. This section will detail the implementation of the ma_mixer API
and hopefully give you a picture on how best to identify and avoid potential performance pitfalls.
TODO: Write me.
Below a summary of some things to keep in mind for high performance mixing:
* Choose a sample format at compile time and use it for everything. Optimized pipelines will be implemented for ma_format_s16 and ma_format_f32. The most
common format is ma_format_f32 which will work in almost all cases. If you're building a game, ma_format_s16 may also work. Professional audio work will
likely require ma_format_f32 for the added precision for authoring work. Do not use ma_format_s24 if you have high performance requirements as it is not
nicely aligned and thus requires an inefficient conversion to 32-bit.
* If you're building a game, try to use a consistent sample format, channel count and sample rate for all of your audio files, or at least all of your
audio files for a specific category (same format for all sfx, same format for all music, same format for all voices, etc.)
* Be mindful of when you perform resampling. Most desktop platforms output at a sample rate of 48000Hz or 44100Hz. If your input data is, for example,
22050Hz, consider doing your mixing at 22050Hz, and then doing a final resample to the playback device's output format. In this example, resampling all
of your data sources to 48000Hz before mixing may be unnecessarily inefficient because it'll need to perform mixing on a greater number of samples.
*/
MA_API size_t ma_get_accumulation_bytes_per_sample(ma_format format);
MA_API size_t ma_get_accumulation_bytes_per_frame(ma_format format, ma_uint32 channels);
typedef struct
{
ma_format format;
ma_uint32 channels;
ma_uint64 accumulationBufferSizeInFrames;
void* pPreAllocatedAccumulationBuffer;
ma_allocation_callbacks allocationCallbacks;
float volume;
} ma_mixer_config;
MA_API ma_mixer_config ma_mixer_config_init(ma_format format, ma_uint32 channels, ma_uint64 accumulationBufferSizeInFrames, void* pPreAllocatedAccumulationBuffer, const ma_allocation_callbacks* pAllocationCallbacks);
typedef struct
{
ma_format format; /* This will be the format output by ma_mixer_end(). */
ma_uint32 channels;
ma_uint64 accumulationBufferSizeInFrames;
void* pAccumulationBuffer; /* In the accumulation format. */
ma_allocation_callbacks allocationCallbacks;
ma_bool32 ownsAccumulationBuffer;
float volume;
ma_effect* pEffect; /* The effect to apply after mixing input sources. */
struct
{
ma_uint64 frameCountIn;
ma_uint64 frameCountOut;
ma_bool32 isInsideBeginEnd;
} mixingState;
} ma_mixer;
/*
Initialize a mixer.
A mixer is used to mix/layer/blend sounds together.
Parameters
----------
pConfig (in)
A pointer to the mixer's configuration. Cannot be NULL. See remarks.
pMixer (out)
A pointer to the mixer object being initialized.
Return Value
------------
MA_SUCCESS if successful; any other error code otherwise.
Thread Safety
-------------
Unsafe. You should not be trying to initialize a mixer from one thread, while at the same time trying to use it on another.
Callback Safety
---------------
This is safe to call in the data callback, but do if you do so, keep in mind that if you do not supply a pre-allocated accumulation buffer it will allocate
memory on the heap for you.
Remarks
-------
The mixer can be configured via the `pConfig` argument. The config object is initialized with `ma_mixer_config_init()`. Individual configuration settings can
then be set directly on the structure. Below are the members of the `ma_mixer_config` object.
format
The sample format to use for mixing. This is the format that will be output by `ma_mixer_end()`.
channels
The channel count to use for mixing. This is the number of channels that will be output by `ma_mixer_end()`.
accumulationBufferSizeInFrames
A mixer uses a fixed sized buffer for it's entire life time. This specifies the size in PCM frames of the accumulation buffer. When calling
`ma_mixer_begin()`, the requested output frame count will be clamped based on the value of this property. You should not use this propertry to
determine how many frames to mix at a time with `ma_mixer_mix_*()` - use the value returned by `ma_mixer_begin()`.
pPreAllocatedAccumulationBuffer
A pointer to a pre-allocated buffer to use for the accumulation buffer. This can be null in which case a buffer will be allocated for you using the
specified allocation callbacks, if any. You can calculate the size in bytes of the accumulation buffer like so:
```c
sizeInBytes = config.accumulationBufferSizeInFrames * ma_get_accumulation_bytes_per_frame(config.format, config.channels)
```
Note that you should _not_ use `ma_get_bytes_per_frame()` when calculating the size of the buffer because the accumulation buffer requires a higher bit
depth for accumulation in order to avoid wrapping.
allocationCallbacks
Memory allocation callbacks to use for allocating memory for the accumulation buffer. If all callbacks in this object are NULL, `MA_MALLOC()` and
`MA_FREE()` will be used.
volume
The default output volume in linear scale. Defaults to 1. This can be changed after initialization with `ma_mixer_set_volume()`.
*/
MA_API ma_result ma_mixer_init(ma_mixer_config* pConfig, ma_mixer* pMixer);
/*
Uninitializes a mixer.
Parameters:
-----------
pMixer (in)
A pointer to the mixer being unintialized.
Thread Safety
-------------
Unsafe. You should not be uninitializing a mixer while using it on another thread.
Callback Safety
---------------
If you did not specify a pre-allocated accumulation buffer, this will free it.
Remarks
-------
If you specified a pre-allocated buffer it will be left as-is. Otherwise it will be freed using the allocation callbacks specified in the config when the mixer
was initialized.
*/
MA_API void ma_mixer_uninit(ma_mixer* pMixer);
/*
Marks the beginning of a mix of a specified number of frames.
When you begin mixing, you must specify how many frames you want to mix. You specify the number of output frames you want, and upon returning you will receive
the number of output frames you'll actually get. When an effect is attached, there may be a chance that the number of input frames required to output the given
output frame count differs. The input frame count is also returned, and this is number of frames you must use with the `ma_mixer_mix_*()` APIs, provided that
number of input frames are available to you at mixing time.
Each call to `ma_mixer_begin()` must be matched with a call to `ma_mixer_end()`. In between these you mix audio data using the `ma_mixer_mix_*()` APIs. When
you call `ma_mixer_end()`, the number of frames that are output will be equal to the output frame count. When you call `ma_mixer_mix_*()`, you specify a frame
count based on the input frame count.
Parameters
----------
pMixer (in)
A pointer to the relevant mixer.
pParentMixer (in, optional)
A pointer to the parent mixer. Set this to non-NULL if you want the output of `pMixer` to be mixed with `pParentMixer`. Otherwise, if you want to output
directly to a buffer, set this to NULL. You would set this to NULL for a master mixer, and non-NULL for a submix. See remarks.
pFrameCountOut (in, out)
On input, specifies the desired number of output frames to mix in this iteration. The requested number of output frames may not be able to fit in the
internal accumulation buffer which means on output this variable will receive the actual number of output frames. On input, this will be ignored if
`pParentMixer` is non-NULL because the output frame count of a submix must be compatible with the parent mixer.
pFramesCountIn (out)
A pointer to the variable that will receive the number of input frames to mix with each call to `ma_mixer_mix_*()`. This will usually always equal the
output frame count, but will be different if an effect is applied and that effect performs resampling. See remarks.
Return Value
------------
MA_SUCCESS if successful; any other error code otherwise.
Thread Safety
-------------
This can be called from any thread so long as you perform your own synchronization against the `pMixer` and `pParentMixer` object.
Callback Safety
---------------
Safe.
Remarks
-------
When you call `ma_mixer_begin()`, you need to specify how many output frames you want. The number of input frames required to generate those output frames can
differ, however. This will only happen if you have an effect attached (see `ma_mixer_set_effect()`) and if one of the effects in the chain performs resampling.
The input frame count will be returned by the `pFrameCountIn` parameter, and this is how many frames should be used when mixing with `ma_mixer_mix_*()`. See
examples below.
The mixer API supports the concept of submixing which is where the output of one mixer is mixed with that of another. A common example from a game:
Master
SFX
Music
Voices
In the example above, "Master" is the master mix and "SFX", "Music" and "Voices" are submixes. When you call `ma_mixer_begin()` for the "Master" mix, you would
set `pParentMixer` to NULL. For the "SFX", "Music" and "Voices" you would set it to a pointer to the master mixer, and you must call `ma_mixer_begin()` and
`ma_mixer_end()` between the begin and end pairs of the parent mixer. If you want to perform submixing, you need to pass the same parent mixer (`pParentMixer`)
to `ma_mixer_end()`. See example 2 for an example on how to do submixing.
Example 1
---------
This example shows a basic mixer without any submixing.
```c
ma_uint64 frameCountIn;
ma_uint64 frameCountOut = desiredFrameCount; // <-- On input specifies what you want, on output receives what you actually got.
ma_mixer_begin(&mixer, NULL, &frameCountOut, &frameCountIn);
{
ma_mixer_mix_data_source(&mixer, &decoder1, frameCountIn, isLooping1);
ma_mixer_mix_data_source(&mixer, &decoder2, frameCountIn, isLooping2);
}
ma_mixer_end(&mixer, NULL, pFramesOut); // <-- pFramesOut must be large enough to receive frameCountOut frames in mixer.format/mixer.channels format.
```
Example 2
---------
This example shows how you can do submixing.
```c
ma_uint64 frameCountIn;
ma_uint64 frameCountOut = desiredFrameCount; // <-- On input specifies what you want, on output receives what you actually got.
ma_mixer_begin(&masterMixer, NULL, &frameCountOut, &frameCountIn);
{
ma_uint64 submixFrameCountIn;
// SFX submix.
ma_mixer_begin(&sfxMixer, &masterMixer, &submixFrameCountIn, NULL); // Output frame count not required for submixing.
{
ma_mixer_mix_data_source(&sfxMixer, &sfxDecoder1, 0, submixFrameCountIn, isSFXLooping1);
ma_mixer_mix_data_source(&sfxMixer, &sfxDecoder2, 0, submixFrameCountIn, isSFXLooping2);
}
ma_mixer_end(&sfxMixer, &masterMixer, NULL, 0);
// Voice submix.
ma_mixer_begin(&voiceMixer, &masterMixer, &submixFrameCountIn, NULL);
{
ma_mixer_mix_data_source(&voiceMixer, &voiceDecoder1, 0, submixFrameCountIn, isVoiceLooping1);
}
ma_mixer_end(&voiceMixer, &masterMixer, NULL, 0);
// Music submix.
ma_mixer_begin(&musicMixer, &masterMixer, &submixFrameCountIn, NULL);
{
ma_mixer_mix_data_source(&musicMixer, &musicDecoder1, 0, submixFrameCountIn, isMusicLooping1);
}
ma_mixer_end(&musicMixer, &masterMixer, NULL, 0);
}
ma_mixer_end(&masterMixer, NULL, pFramesOut, 0); // <-- pFramesOut must be large enough to receive frameCountOut frames in mixer.format/mixer.channels format.
```
See Also
--------
ma_mixer_end()
ma_mixer_set_effect()
ma_mixer_get_effect()
*/
MA_API ma_result ma_mixer_begin(ma_mixer* pMixer, ma_mixer* pParentMixer, ma_uint64* pFrameCountOut, ma_uint64* pFrameCountIn);
/*
Applies volume, performs clipping, applies the effect (if any) and outputs the final mix to the specified output buffer or mixed with another mixer.
Parameters
----------
pMixer (in)
A pointer to the mixer.
pParentMixer (in, optional)
A pointer to the parent mixer. If this is non-NULL, the output of `pMixer` will be mixed with `pParentMixer`. It is an error for `pParentMixer` and
`pFramesOut` to both be non-NULL. If this is non-NULL, it must have also been specified as the parent mixer in the prior call to `ma_mixer_begin()`.
pFramesOut (in, optional)
A pointer to the buffer that will receive the final mixed output. The output buffer must be in the format specified by the mixer's configuration that was
used to initialized it. The required size in frames is defined by the output frame count returned by `ma_mixer_begin()`. It is an error for `pFramesOut`
and `pParentMixer` to both be non-NULL.
outputOffsetInFrames (in)
The offset in frames to start writing the output data to the destination buffer.
Return Value
------------
MA_SUCCESS if successful; any other error code otherwise.
Remarks
-------
It is an error both both `pParentMixer` and `pFramesOut` to both be NULL or non-NULL. You must specify one or the other.
When outputting to a parent mixer (`pParentMixer` is non-NULL), the output is mixed with the parent mixer. Otherwise (`pFramesOut` is non-NULL), the output
will overwrite anything already in the output buffer.
When calculating the final output, the volume will be applied before clipping, which is done before applying the effect (if any).
See documentation for `ma_mixer_begin()` for an example on how to use `ma_mixer_end()`.
See Also
--------
ma_mixer_begin()
ma_mixer_set_volume()
ma_mixer_get_volume()
ma_mixer_set_effect()
ma_mixer_get_effect()
*/
MA_API ma_result ma_mixer_end(ma_mixer* pMixer, ma_mixer* pParentMixer, void* pFramesOut, ma_uint64 outputOffsetInFrames);
/*
Mixes audio data from a buffer containing raw PCM data.
Parameters
----------
pMixer (in)
A pointer to the mixer.
pFramesIn (in)
A pointer to the buffer containing the raw PCM data to mix with the mixer. The data contained within this buffer is assumed to be of the same format as the
mixer, which was specified when the mixer was initialized. Use `ma_mixer_mix_pcm_frames_ex()` to mix data of a different format.
frameCountIn (in)
The number of frames to mix. This cannot exceed the number of input frames returned by `ma_mixer_begin()`. If it does, an error will be returned. If it is
less, silence will be mixed to make up the excess.
formatIn (in)
The sample format of the input data.
channelsIn (in)
The channel count of the input data.
Return Value
------------
MA_SUCCESS if successful; any other error code otherwise.
Remarks
-------
Each call to this function will start mixing from the start of the internal accumulation buffer.
This will automatically convert the data to the mixer's native format. The sample format will be converted without dithering. Channels will be converted based
on the default channel map.
See Also
--------
ma_mixer_mix_pcm_frames()
ma_mixer_begin()
ma_mixer_end()
*/
MA_API ma_result ma_mixer_mix_pcm_frames(ma_mixer* pMixer, const void* pFramesIn, ma_uint64 offsetInFrames, ma_uint64 frameCountIn, float volume, ma_format formatIn, ma_uint32 channelsIn);
/*
Mixes audio data from a data source
Parameters
----------
pMixer (in)
A pointer to the mixer.
pDataSource (in)
A pointer to the data source to read input data from.
frameCountIn (in)
The number of frames to mix. This cannot exceed the number of input frames returned by `ma_mixer_begin()`. If it does, an error will be returned. If it is
less, silence will be mixed to make up the excess.
pFrameCountOut (out)
Receives the number of frames that were processed from the data source.
formatIn (in)
The sample format of the input data.
channelsIn (in)
The channel count of the input data.
Return Value
------------
MA_SUCCESS if successful; any other error code otherwise.
Remarks
-------
Each call to this function will start mixing from the start of the internal accumulation buffer.
This will automatically convert the data to the mixer's native format. The sample format will be converted without dithering. Channels will be converted based
on the default channel map.
See Also
--------
ma_mixer_begin()
ma_mixer_end()
*/
MA_API ma_result ma_mixer_mix_data_source(ma_mixer* pMixer, ma_data_source* pDataSource, ma_uint64 offsetInFrames, ma_uint64 frameCountIn, ma_uint64* pFrameCountOut, float volume, ma_effect* pEffect, ma_bool32 loop);
MA_API ma_result ma_mixer_mix_rb(ma_mixer* pMixer, ma_rb* pRB, ma_uint64 offsetInFrames, ma_uint64 frameCountIn, ma_uint64* pFrameCountOut, float volume, ma_effect* pEffect, ma_format formatIn, ma_uint32 channelsIn); /* Caller is the consumer. */
MA_API ma_result ma_mixer_mix_pcm_rb(ma_mixer* pMixer, ma_pcm_rb* pRB, ma_uint64 offsetInFrames, ma_uint64 frameCountIn, ma_uint64* pFrameCountOut, float volume, ma_effect* pEffect); /* Caller is the consumer. */
MA_API ma_result ma_mixer_set_volume(ma_mixer* pMixer, float volume);
MA_API ma_result ma_mixer_get_volume(ma_mixer* pMixer, float* pVolume);
MA_API ma_result ma_mixer_set_gain_db(ma_mixer* pMixer, float gainDB);
MA_API ma_result ma_mixer_get_gain_db(ma_mixer* pMixer, float* pGainDB);
MA_API ma_result ma_mixer_set_effect(ma_mixer* pMixer, ma_effect* pEffect);
MA_API ma_result ma_mixer_get_effect(ma_mixer* pMixer, ma_effect** ppEffect);
MA_API ma_result ma_mixer_get_output_data_format(ma_mixer* pMixer, ma_format* pFormat, ma_uint32* pChannels);
MA_API ma_result ma_mixer_get_input_data_format(ma_mixer* pMixer, ma_format* pFormat, ma_uint32* pChannels);
/* /*
...@@ -2318,6 +1728,26 @@ MA_API ma_result ma_sound_group_get_time_in_frames(const ma_sound_group* pGroup, ...@@ -2318,6 +1728,26 @@ MA_API ma_result ma_sound_group_get_time_in_frames(const ma_sound_group* pGroup,
#if defined(MA_IMPLEMENTATION) || defined(MINIAUDIO_IMPLEMENTATION) #if defined(MA_IMPLEMENTATION) || defined(MINIAUDIO_IMPLEMENTATION)
MA_API size_t ma_get_accumulation_bytes_per_sample(ma_format format)
{
size_t bytesPerSample[ma_format_count] = {
0, /* ma_format_unknown */
sizeof(ma_int16), /* ma_format_u8 */
sizeof(ma_int32), /* ma_format_s16 */
sizeof(ma_int64), /* ma_format_s24 */
sizeof(ma_int64), /* ma_format_s32 */
sizeof(float) /* ma_format_f32 */
};
return bytesPerSample[format];
}
MA_API size_t ma_get_accumulation_bytes_per_frame(ma_format format, ma_uint32 channels)
{
return ma_get_accumulation_bytes_per_sample(format) * channels;
}
/* 10ms @ 48K = 480. Must never exceed 65535. */ /* 10ms @ 48K = 480. Must never exceed 65535. */
#ifndef MA_DEFAULT_NODE_CACHE_CAP_IN_FRAMES_PER_BUS #ifndef MA_DEFAULT_NODE_CACHE_CAP_IN_FRAMES_PER_BUS
...@@ -2328,6 +1758,7 @@ MA_API ma_result ma_sound_group_get_time_in_frames(const ma_sound_group* pGroup, ...@@ -2328,6 +1758,7 @@ MA_API ma_result ma_sound_group_get_time_in_frames(const ma_sound_group* pGroup,
static ma_result ma_node_read_pcm_frames(ma_node* pNode, ma_uint32 outputBusIndex, float* pFramesOut, ma_uint32 frameCount, ma_uint32* pFramesRead, ma_uint64 globalTime); static ma_result ma_node_read_pcm_frames(ma_node* pNode, ma_uint32 outputBusIndex, float* pFramesOut, ma_uint32 frameCount, ma_uint32* pFramesRead, ma_uint64 globalTime);
static MA_INLINE ma_int16 ma_float_to_fixed_16(float x) static MA_INLINE ma_int16 ma_float_to_fixed_16(float x)
{ {
return (ma_int16)(x * (1 << 8)); return (ma_int16)(x * (1 << 8));
...@@ -4511,26 +3942,6 @@ MA_API ma_result ma_effect_get_input_data_format(ma_effect* pEffect, ma_format* ...@@ -4511,26 +3942,6 @@ MA_API ma_result ma_effect_get_input_data_format(ma_effect* pEffect, ma_format*
MA_API size_t ma_get_accumulation_bytes_per_sample(ma_format format)
{
size_t bytesPerSample[ma_format_count] = {
0, /* ma_format_unknown */
sizeof(ma_int16), /* ma_format_u8 */
sizeof(ma_int32), /* ma_format_s16 */
sizeof(ma_int64), /* ma_format_s24 */
sizeof(ma_int64), /* ma_format_s32 */
sizeof(float) /* ma_format_f32 */
};
return bytesPerSample[format];
}
MA_API size_t ma_get_accumulation_bytes_per_frame(ma_format format, ma_uint32 channels)
{
return ma_get_accumulation_bytes_per_sample(format) * channels;
}
#if 0 #if 0
...@@ -5349,734 +4760,6 @@ static void ma_mix_accumulation_buffers_ex(void* pDst, ma_format formatOut, ma_u ...@@ -5349,734 +4760,6 @@ static void ma_mix_accumulation_buffers_ex(void* pDst, ma_format formatOut, ma_u
MA_API ma_mixer_config ma_mixer_config_init(ma_format format, ma_uint32 channels, ma_uint64 accumulationBufferSizeInFrames, void* pPreAllocatedAccumulationBuffer, const ma_allocation_callbacks* pAllocationCallbacks)
{
ma_mixer_config config;
MA_ZERO_OBJECT(&config);
config.format = format;
config.channels = channels;
config.accumulationBufferSizeInFrames = accumulationBufferSizeInFrames;
config.pPreAllocatedAccumulationBuffer = pPreAllocatedAccumulationBuffer;
config.volume = 1;
ma_allocation_callbacks_init_copy(&config.allocationCallbacks, pAllocationCallbacks);
return config;
}
MA_API ma_result ma_mixer_init(ma_mixer_config* pConfig, ma_mixer* pMixer)
{
if (pMixer == NULL) {
return MA_INVALID_ARGS;
}
MA_ZERO_OBJECT(pMixer);
if (pConfig == NULL) {
return MA_INVALID_ARGS;
}
if (pConfig->accumulationBufferSizeInFrames == 0) {
return MA_INVALID_ARGS; /* Must have an accumulation buffer. */
}
pMixer->format = pConfig->format;
pMixer->channels = pConfig->channels;
pMixer->accumulationBufferSizeInFrames = pConfig->accumulationBufferSizeInFrames;
pMixer->pAccumulationBuffer = pConfig->pPreAllocatedAccumulationBuffer;
ma_allocation_callbacks_init_copy(&pMixer->allocationCallbacks, &pConfig->allocationCallbacks);
pMixer->volume = pConfig->volume;
if (pMixer->pAccumulationBuffer == NULL) {
ma_uint64 accumulationBufferSizeInBytes = pConfig->accumulationBufferSizeInFrames * ma_get_accumulation_bytes_per_frame(pMixer->format, pMixer->channels);
if (accumulationBufferSizeInBytes > MA_SIZE_MAX) {
return MA_OUT_OF_MEMORY;
}
pMixer->pAccumulationBuffer = ma__malloc_from_callbacks((size_t)accumulationBufferSizeInBytes, &pMixer->allocationCallbacks); /* Safe cast. */
if (pMixer->pAccumulationBuffer == NULL) {
return MA_OUT_OF_MEMORY;
}
pMixer->ownsAccumulationBuffer = MA_TRUE;
} else {
pMixer->ownsAccumulationBuffer = MA_FALSE;
}
return MA_SUCCESS;
}
MA_API void ma_mixer_uninit(ma_mixer* pMixer)
{
if (pMixer == NULL) {
return;
}
if (pMixer->ownsAccumulationBuffer) {
ma__free_from_callbacks(pMixer->pAccumulationBuffer, &pMixer->allocationCallbacks);
}
}
MA_API ma_result ma_mixer_begin(ma_mixer* pMixer, ma_mixer* pParentMixer, ma_uint64* pFrameCountOut, ma_uint64* pFrameCountIn)
{
ma_uint64 frameCountOut;
ma_uint64 frameCountIn;
if (pMixer == NULL) {
return MA_INVALID_ARGS;
}
if (pMixer->mixingState.isInsideBeginEnd == MA_TRUE) {
return MA_INVALID_OPERATION; /* Cannot call this while already inside a begin/end pair. */
}
/* If we're submixing we need to make the frame counts compatible with the parent mixer. */
if (pParentMixer != NULL) {
/* The output frame count must match the input frame count of the parent. If this cannot be accommodated we need to fail. */
frameCountOut = pParentMixer->mixingState.frameCountIn;
} else {
if (pFrameCountOut == NULL) {
return MA_INVALID_ARGS; /* The desired output frame count is required for a root level mixer. */
}
frameCountOut = *pFrameCountOut;
}
if (pMixer->pEffect != NULL) {
frameCountIn = ma_effect_get_required_input_frame_count(pMixer->pEffect, frameCountOut);
if (frameCountIn > pMixer->accumulationBufferSizeInFrames) {
/*
The required number of input frames for the requested number of output frames is too much to fit in the accumulation buffer. We need
to reduce the output frame count to accommodate.
*/
ma_uint64 newFrameCountOut;
newFrameCountOut = ma_effect_get_expected_output_frame_count(pMixer->pEffect, pMixer->accumulationBufferSizeInFrames);
MA_ASSERT(newFrameCountOut <= frameCountOut);
frameCountOut = newFrameCountOut;
frameCountIn = ma_effect_get_required_input_frame_count(pMixer->pEffect, frameCountOut);
}
} else {
frameCountIn = frameCountOut;
if (frameCountIn > pMixer->accumulationBufferSizeInFrames) {
frameCountIn = pMixer->accumulationBufferSizeInFrames;
frameCountOut = pMixer->accumulationBufferSizeInFrames;
}
}
/* If the output frame count cannot match the parent's input frame count we need to fail. */
if (pParentMixer != NULL && frameCountOut != pParentMixer->mixingState.frameCountIn) {
return MA_INVALID_OPERATION; /* Not compatible with the parent mixer. */
}
pMixer->mixingState.isInsideBeginEnd = MA_TRUE;
pMixer->mixingState.frameCountOut = frameCountOut;
pMixer->mixingState.frameCountIn = frameCountIn;
ma_zero_memory_64(pMixer->pAccumulationBuffer, frameCountIn * ma_get_accumulation_bytes_per_frame(pMixer->format, pMixer->channels));
if (pFrameCountOut != NULL) {
*pFrameCountOut = frameCountOut;
}
if (pFrameCountIn != NULL) {
*pFrameCountIn = frameCountIn;
}
return MA_SUCCESS;
}
MA_API ma_result ma_mixer_end(ma_mixer* pMixer, ma_mixer* pParentMixer, void* pFramesOut, ma_uint64 outputOffsetInFrames)
{
if (pMixer == NULL) {
return MA_INVALID_ARGS;
}
/* It's an error for both pParentMixer and pFramesOut to be NULL. */
if (pParentMixer == NULL && pFramesOut == NULL) {
return MA_INVALID_ARGS;
}
/* If both pParentMixer and pFramesOut are both non-NULL, it indicates an error on the callers side. Make sure they're aware of it. */
if (pParentMixer != NULL && pFramesOut != NULL) {
MA_ASSERT(MA_FALSE);
return MA_INVALID_ARGS;
}
if (pMixer->mixingState.isInsideBeginEnd == MA_FALSE) {
return MA_INVALID_OPERATION; /* No matching begin. */
}
/* Completely different paths if we're outputting to a parent mixer rather than directly to an output buffer. */
if (pParentMixer != NULL) {
ma_format localFormatOut;
ma_uint32 localChannelsOut;
ma_format parentFormatIn;
ma_uint32 parentChannelsIn;
void* pDst;
/*
We need to accumulate the output of pMixer straight into the accumulation buffer of pParentMixer. If the output format of pMixer is different
to the input format of pParentMixer it needs to be converted.
*/
ma_mixer_get_output_data_format(pMixer, &localFormatOut, &localChannelsOut);
ma_mixer_get_input_data_format(pParentMixer, &parentFormatIn, &parentChannelsIn);
/* A reminder that the output frame count of pMixer must match the input frame count of pParentMixer. */
MA_ASSERT(pMixer->mixingState.frameCountOut == pParentMixer->mixingState.frameCountIn);
pDst = ma_offset_ptr(pParentMixer->pAccumulationBuffer, outputOffsetInFrames * ma_get_accumulation_bytes_per_frame(parentFormatIn, parentChannelsIn));
if (pMixer->pEffect == NULL) {
/* No effect. Input needs to come straight from the accumulation buffer. */
ma_mix_accumulation_buffers_ex(pDst, parentFormatIn, parentChannelsIn, pMixer->pAccumulationBuffer, localFormatOut, localChannelsOut, pMixer->mixingState.frameCountOut, pMixer->volume);
} else {
/* With effect. Input needs to be pre-processed from the effect. */
ma_volume_and_clip_and_effect_pcm_frames(pDst, parentFormatIn, parentChannelsIn, pParentMixer->mixingState.frameCountIn, pMixer->pAccumulationBuffer, pMixer->format, pMixer->channels, pMixer->mixingState.frameCountIn, pMixer->volume, pMixer->pEffect, /*isAccumulation*/ MA_TRUE);
}
} else {
/* We're not submixing so we can overwite. */
void* pDst;
pDst = ma_offset_ptr(pFramesOut, outputOffsetInFrames * ma_get_bytes_per_frame(pMixer->format, pMixer->channels));
if (pMixer->pEffect == NULL) {
/* All we need to do is convert the accumulation buffer to the output format. */
ma_volume_and_clip_pcm_frames(pDst, pMixer->pAccumulationBuffer, pMixer->mixingState.frameCountOut, pMixer->format, pMixer->channels, pMixer->volume);
} else {
/* We need to run our accumulation through the effect. */
ma_volume_and_clip_and_effect_pcm_frames(pDst, pMixer->format, pMixer->channels, pMixer->mixingState.frameCountOut, pMixer->pAccumulationBuffer, pMixer->format, pMixer->channels, pMixer->mixingState.frameCountIn, pMixer->volume, pMixer->pEffect, /*isAccumulation*/ MA_FALSE);
}
}
pMixer->mixingState.isInsideBeginEnd = MA_FALSE;
pMixer->mixingState.frameCountOut = 0;
pMixer->mixingState.frameCountIn = 0;
return MA_SUCCESS;
}
MA_API ma_result ma_mixer_mix_pcm_frames(ma_mixer* pMixer, const void* pFramesIn, ma_uint64 offsetInFrames, ma_uint64 frameCountIn, float volume, ma_format formatIn, ma_uint32 channelsIn)
{
if (pMixer == NULL || pFramesIn == NULL) {
return MA_INVALID_ARGS;
}
if (frameCountIn > pMixer->mixingState.frameCountIn) {
return MA_INVALID_ARGS; /* Passing in too many input frames. */
}
ma_mix_pcm_frames(ma_offset_ptr(pMixer->pAccumulationBuffer, offsetInFrames * ma_get_accumulation_bytes_per_frame(pMixer->format, pMixer->channels)), pFramesIn, frameCountIn, formatIn, channelsIn, volume);
return MA_SUCCESS;
}
static ma_result ma_mixer_mix_data_source_mmap(ma_mixer* pMixer, ma_data_source* pDataSource, ma_uint64 offsetInFrames, ma_uint64 frameCountIn, ma_uint64* pFrameCountOut, float volume, ma_effect* pEffect, ma_format formatIn, ma_uint32 channelsIn, ma_bool32 loop)
{
ma_result result = MA_SUCCESS;
ma_uint64 totalFramesProcessed = 0;
void* pRunningAccumulationBuffer = NULL;
ma_bool32 preEffectConversionRequired = MA_FALSE;
ma_format effectFormatIn = ma_format_unknown;
ma_uint32 effectChannelsIn = 0;
ma_format effectFormatOut = ma_format_unknown;
ma_uint32 effectChannelsOut = 0;
MA_ASSERT(pMixer != NULL);
MA_ASSERT(pDataSource != NULL);
if (pFrameCountOut != NULL) {
*pFrameCountOut = 0;
}
if ((offsetInFrames + frameCountIn) > pMixer->mixingState.frameCountIn) {
return MA_INVALID_ARGS; /* Passing in too many input frames. */
}
/* Initially offset the accumulation buffer by the offset. */
pRunningAccumulationBuffer = ma_offset_ptr(pMixer->pAccumulationBuffer, offsetInFrames * ma_get_accumulation_bytes_per_frame(pMixer->format, pMixer->channels));
if (pEffect != NULL) {
/* We need to know the effect's input and output data format before we'll be able to apply it properly. */
result = ma_effect_get_input_data_format(pEffect, &effectFormatIn, &effectChannelsIn, NULL);
if (result != MA_SUCCESS) {
return result;
}
result = ma_effect_get_output_data_format(pEffect, &effectFormatOut, &effectChannelsOut, NULL);
if (result != MA_SUCCESS) {
return result;
}
preEffectConversionRequired = (formatIn != effectFormatIn || channelsIn != effectChannelsIn);
}
while (totalFramesProcessed < frameCountIn) {
void* pMappedBuffer;
ma_uint64 framesToProcess = frameCountIn - totalFramesProcessed;
if (pEffect == NULL) {
/* Fast path. Mix directly from the data source and don't bother applying an effect. */
result = ma_data_source_map(pDataSource, &pMappedBuffer, &framesToProcess);
if (result != MA_SUCCESS) {
break; /* Failed to map. Abort. */
}
if (framesToProcess == 0) {
break; /* Wasn't able to map any data. Abort. */
}
ma_mix_pcm_frames_ex(pRunningAccumulationBuffer, pMixer->format, pMixer->channels, pMappedBuffer, formatIn, channelsIn, framesToProcess, volume);
result = ma_data_source_unmap(pDataSource, framesToProcess);
} else {
/* Slow path. Need to apply an effect. This requires the use of an intermediary buffer. */
ma_uint8 effectInBuffer [MA_DATA_CONVERTER_STACK_BUFFER_SIZE];
ma_uint8 effectOutBuffer[MA_DATA_CONVERTER_STACK_BUFFER_SIZE];
ma_uint32 effectInBufferCap = sizeof(effectInBuffer) / ma_get_bytes_per_frame(effectFormatIn, effectChannelsIn);
ma_uint32 effectOutBufferCap = sizeof(effectOutBuffer) / ma_get_bytes_per_frame(effectFormatOut, effectChannelsOut);
ma_uint64 framesMapped;
if (framesToProcess > effectOutBufferCap) {
framesToProcess = effectOutBufferCap;
}
framesMapped = ma_effect_get_required_input_frame_count(pEffect, framesToProcess);
if (framesMapped > effectInBufferCap) {
framesMapped = effectInBufferCap;
}
/* We need to map our input data first. The input data will be either fed directly into the effect, or will be converted first. */
result = ma_data_source_map(pDataSource, &pMappedBuffer, &framesMapped);
if (result != MA_SUCCESS) {
break; /* Failed to map. Abort. */
}
/* We have the data from the data source so no we can apply the effect. */
if (preEffectConversionRequired == MA_FALSE) {
/* Fast path. No format required before applying the effect. */
ma_effect_process_pcm_frames(pEffect, pMappedBuffer, &framesMapped, effectOutBuffer, &framesToProcess);
} else {
/* Slow path. Need to convert the data before applying the effect. */
ma_convert_pcm_frames_format_and_channels(effectInBuffer, effectFormatIn, effectChannelsIn, pMappedBuffer, formatIn, channelsIn, framesMapped, ma_dither_mode_none);
ma_effect_process_pcm_frames(pEffect, effectInBuffer, &framesMapped, effectOutBuffer, &framesToProcess);
}
/* The effect has been applied so now we can mix it. */
ma_mix_pcm_frames_ex(pRunningAccumulationBuffer, pMixer->format, pMixer->channels, effectOutBuffer, effectFormatOut, effectChannelsOut, framesToProcess, volume);
/* We're finished with the input data. */
result = ma_data_source_unmap(pDataSource, framesMapped); /* Do this last because the result code is used below to determine whether or not we need to loop. */
}
totalFramesProcessed += framesToProcess;
pRunningAccumulationBuffer = ma_offset_ptr(pRunningAccumulationBuffer, framesToProcess * ma_get_accumulation_bytes_per_frame(pMixer->format, pMixer->channels));
if (result != MA_SUCCESS) {
if (result == MA_AT_END) {
if (loop) {
ma_data_source_seek_to_pcm_frame(pDataSource, 0);
result = MA_SUCCESS; /* Make sure we don't return MA_AT_END which will happen if we conicidentally hit the end of the data source at the same time as we finish outputting. */
} else {
break; /* We've reached the end and we're not looping. */
}
} else {
break; /* An error occurred. */
}
}
}
if (pFrameCountOut != NULL) {
*pFrameCountOut = totalFramesProcessed;
}
return result;
}
static ma_result ma_mixer_mix_data_source_read(ma_mixer* pMixer, ma_data_source* pDataSource, ma_uint64 offsetInFrames, ma_uint64 frameCountIn, ma_uint64* pFrameCountOut, float volume, ma_effect* pEffect, ma_format formatIn, ma_uint32 channelsIn, ma_bool32 loop)
{
ma_result result = MA_SUCCESS;
ma_uint8 preMixBuffer[MA_DATA_CONVERTER_STACK_BUFFER_SIZE];
ma_uint32 preMixBufferCap;
ma_uint64 totalFramesProcessed = 0;
void* pRunningAccumulationBuffer;
ma_format effectFormatIn = ma_format_unknown;
ma_uint32 effectChannelsIn = 0;
ma_format preMixFormat = ma_format_unknown;
ma_uint32 preMixChannels = 0;
ma_bool32 preEffectConversionRequired = MA_FALSE;
MA_ASSERT(pMixer != NULL);
MA_ASSERT(pDataSource != NULL);
if (pFrameCountOut != NULL) {
*pFrameCountOut = 0;
}
if ((offsetInFrames + frameCountIn) > pMixer->mixingState.frameCountIn) {
return MA_INVALID_ARGS; /* Passing in too many input frames. */
}
if (pEffect == NULL) {
preMixFormat = formatIn;
preMixChannels = channelsIn;
} else {
/* We need to know the effect's input and output data format before we'll be able to apply it properly. */
result = ma_effect_get_input_data_format(pEffect, &effectFormatIn, &effectChannelsIn, NULL);
if (result != MA_SUCCESS) {
return result;
}
result = ma_effect_get_output_data_format(pEffect, &preMixFormat, &preMixChannels, NULL);
if (result != MA_SUCCESS) {
return result;
}
preEffectConversionRequired = (formatIn != effectFormatIn || channelsIn != effectChannelsIn);
}
preMixBufferCap = sizeof(preMixBuffer) / ma_get_bytes_per_frame(preMixFormat, preMixChannels);
totalFramesProcessed = 0;
/* Initially offset the accumulation buffer by the offset. */
pRunningAccumulationBuffer = ma_offset_ptr(pMixer->pAccumulationBuffer, offsetInFrames * ma_get_accumulation_bytes_per_frame(pMixer->format, pMixer->channels));
while (totalFramesProcessed < frameCountIn) {
ma_uint64 framesRead;
ma_uint64 framesToRead = frameCountIn - totalFramesProcessed;
if (framesToRead > preMixBufferCap) {
framesToRead = preMixBufferCap;
}
if (pEffect == NULL) {
result = ma_data_source_read_pcm_frames(pDataSource, preMixBuffer, framesToRead, &framesRead, loop);
ma_mix_pcm_frames_ex(pRunningAccumulationBuffer, pMixer->format, pMixer->channels, preMixBuffer, formatIn, channelsIn, framesRead, volume);
} else {
ma_uint8 callbackBuffer[MA_DATA_CONVERTER_STACK_BUFFER_SIZE];
ma_uint8 effectInBuffer[MA_DATA_CONVERTER_STACK_BUFFER_SIZE];
ma_uint32 callbackBufferCap = sizeof(callbackBuffer) / ma_get_bytes_per_frame(formatIn, channelsIn);
ma_uint32 effectInBufferCap = sizeof(effectInBuffer) / ma_get_bytes_per_frame(effectFormatIn, effectChannelsIn);
ma_uint64 effectFrameCountOut;
ma_uint64 effectFrameCountIn;
ma_uint64 framesReadFromCallback;
ma_uint64 framesToReadFromCallback = ma_effect_get_required_input_frame_count(pEffect, framesToRead);
if (framesToReadFromCallback > callbackBufferCap) {
framesToReadFromCallback = callbackBufferCap;
}
if (framesToReadFromCallback > effectInBufferCap) {
framesToReadFromCallback = effectInBufferCap;
}
/*
We can now read some data from the callback. We should never read more input frame than will be consumed. If the format of the callback is the same as the effect's input
format we can save ourselves a copy and run on a slightly faster path.
*/
if (preEffectConversionRequired == MA_FALSE) {
/* Fast path. No need for conversion between the callback and the */
result = ma_data_source_read_pcm_frames(pDataSource, effectInBuffer, framesToReadFromCallback, &framesReadFromCallback, loop);
} else {
/* Slow path. Conversion between the callback and the effect required. */
result = ma_data_source_read_pcm_frames(pDataSource, callbackBuffer, framesToReadFromCallback, &framesReadFromCallback, loop);
ma_convert_pcm_frames_format_and_channels(effectInBuffer, effectFormatIn, effectChannelsIn, callbackBuffer, formatIn, channelsIn, framesReadFromCallback, ma_dither_mode_none);
}
/* We have our input data for the effect so now we just process as much as we can based on our input and output frame counts. */
effectFrameCountIn = framesReadFromCallback;
effectFrameCountOut = framesToRead;
ma_effect_process_pcm_frames(pEffect, effectInBuffer, &effectFrameCountIn, preMixBuffer, &effectFrameCountOut);
/* At this point the effect should be applied and we can mix it. */
framesRead = (ma_uint32)effectFrameCountOut; /* Safe cast. */
ma_mix_pcm_frames_ex(pRunningAccumulationBuffer, pMixer->format, pMixer->channels, preMixBuffer, preMixFormat, preMixChannels, framesRead, volume);
/* An emergency failure case. Abort if we didn't consume any input nor any output frames. */
if (framesRead == 0 && framesReadFromCallback == 0) {
break;
}
}
totalFramesProcessed += framesRead;
pRunningAccumulationBuffer = ma_offset_ptr(pRunningAccumulationBuffer, framesRead * ma_get_accumulation_bytes_per_frame(pMixer->format, pMixer->channels));
/* If the data source is busy we need to end mixing now. */
if (result == MA_BUSY || result == MA_AT_END) {
break;
}
}
if (pFrameCountOut != NULL) {
*pFrameCountOut = totalFramesProcessed;
}
return result;
}
MA_API ma_result ma_mixer_mix_data_source(ma_mixer* pMixer, ma_data_source* pDataSource, ma_uint64 offsetInFrames, ma_uint64 frameCountIn, ma_uint64* pFrameCountOut, float volume, ma_effect* pEffect, ma_bool32 loop)
{
ma_result result;
ma_format formatIn;
ma_uint32 channelsIn;
ma_bool32 supportsMMap = MA_FALSE;
ma_data_source_callbacks* pDataSourceCallbacks = (ma_data_source_callbacks*)pDataSource;
if (pMixer == NULL) {
return MA_INVALID_ARGS;
}
result = ma_data_source_get_data_format(pDataSource, &formatIn, &channelsIn, NULL);
if (result != MA_SUCCESS) {
return result;
}
/* Use memory mapping if it's available. */
if (pDataSourceCallbacks->onMap != NULL && pDataSourceCallbacks->onUnmap != NULL) {
supportsMMap = MA_TRUE;
}
if (supportsMMap) {
/* Fast path. This is memory mapping mode. */
return ma_mixer_mix_data_source_mmap(pMixer, pDataSourceCallbacks, offsetInFrames, frameCountIn, pFrameCountOut, volume, pEffect, formatIn, channelsIn, loop);
} else {
/* Slow path. This is reading mode. */
return ma_mixer_mix_data_source_read(pMixer, pDataSourceCallbacks, offsetInFrames, frameCountIn, pFrameCountOut, volume, pEffect, formatIn, channelsIn, loop);
}
}
typedef struct
{
ma_data_source_callbacks ds;
ma_rb* pRB;
ma_format format;
ma_uint32 channels;
void* pMappedBuffer;
} ma_rb_data_source;
static ma_result ma_rb_data_source__on_map(ma_data_source* pDataSource, void** ppFramesOut, ma_uint64* pFrameCount)
{
ma_rb_data_source* pRB = (ma_rb_data_source*)pDataSource;
ma_result result;
ma_uint32 bpf = ma_get_bytes_per_frame(pRB->format, pRB->channels);
size_t sizeInBytes;
sizeInBytes = (size_t)(*pFrameCount * bpf);
result = ma_rb_acquire_read(pRB->pRB, &sizeInBytes, ppFramesOut);
*pFrameCount = sizeInBytes / bpf;
pRB->pMappedBuffer = *ppFramesOut;
return result;
}
static ma_result ma_rb_data_source__on_unmap(ma_data_source* pDataSource, ma_uint64 frameCount)
{
ma_rb_data_source* pRB = (ma_rb_data_source*)pDataSource;
ma_result result;
ma_uint32 bpf = ma_get_bytes_per_frame(pRB->format, pRB->channels);
size_t sizeInBytes;
sizeInBytes = (size_t)(frameCount * bpf);
result = ma_rb_commit_read(pRB->pRB, sizeInBytes, pRB->pMappedBuffer);
pRB->pMappedBuffer = NULL;
return result; /* We never actually return MA_AT_END here because a ring buffer doesn't have any notion of an end. */
}
static ma_result ma_rb_data_source__on_get_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate)
{
ma_rb_data_source* pRB = (ma_rb_data_source*)pDataSource;
*pFormat = pRB->format;
*pChannels = pRB->channels;
*pSampleRate = 0; /* No sample rate. */
return MA_SUCCESS;
}
static ma_result ma_rb_data_source_init(ma_rb* pRB, ma_format format, ma_uint32 channels, ma_rb_data_source* pDataSource)
{
if (pDataSource == NULL) {
return MA_INVALID_ARGS;
}
MA_ZERO_OBJECT(pDataSource); /* For safety. */
if (pRB == NULL) {
return MA_INVALID_ARGS;
}
pDataSource->ds.onRead = NULL;
pDataSource->ds.onSeek = NULL; /* We can't really seek in a ring buffer - there's no notion of a beginning and an end in a ring buffer. */
pDataSource->ds.onMap = ma_rb_data_source__on_map;
pDataSource->ds.onUnmap = ma_rb_data_source__on_unmap;
pDataSource->ds.onGetDataFormat = ma_rb_data_source__on_get_format;
pDataSource->ds.onGetCursor = NULL;
pDataSource->ds.onGetLength = NULL;
pDataSource->pRB = pRB;
pDataSource->format = format;
pDataSource->channels = channels;
return MA_SUCCESS;
}
MA_API ma_result ma_mixer_mix_rb(ma_mixer* pMixer, ma_rb* pRB, ma_uint64 offsetInFrames, ma_uint64 frameCountIn, ma_uint64* pFrameCountOut, float volume, ma_effect* pEffect, ma_format formatIn, ma_uint32 channelsIn)
{
/* Ring buffer mixing can be implemented in terms of a memory mapped data source. */
ma_rb_data_source ds;
ma_rb_data_source_init(pRB, formatIn, channelsIn, &ds); /* Will never fail and does not require an uninit() implementation. */
return ma_mixer_mix_data_source(pMixer, &ds, offsetInFrames, frameCountIn, pFrameCountOut, volume, pEffect, MA_TRUE); /* Ring buffers always loop, but the loop parameter will never actually be used because ma_rb_data_source__on_unmap() will never return MA_AT_END. */
}
MA_API ma_result ma_mixer_mix_pcm_rb(ma_mixer* pMixer, ma_pcm_rb* pRB, ma_uint64 offsetInFrames, ma_uint64 frameCountIn, ma_uint64* pFrameCountOut, float volume, ma_effect* pEffect)
{
return ma_mixer_mix_rb(pMixer, &pRB->rb, offsetInFrames, frameCountIn, pFrameCountOut, volume, pEffect, pRB->format, pRB->channels);
}
MA_API ma_result ma_mixer_set_volume(ma_mixer* pMixer, float volume)
{
if (pMixer == NULL) {
return MA_INVALID_ARGS;
}
if (volume < 0.0f || volume > 1.0f) {
return MA_INVALID_ARGS;
}
pMixer->volume = volume;
return MA_SUCCESS;
}
MA_API ma_result ma_mixer_get_volume(ma_mixer* pMixer, float* pVolume)
{
if (pVolume == NULL) {
return MA_INVALID_ARGS;
}
if (pMixer == NULL) {
*pVolume = 0;
return MA_INVALID_ARGS;
}
*pVolume = pMixer->volume;
return MA_SUCCESS;
}
MA_API ma_result ma_mixer_set_gain_db(ma_mixer* pMixer, float gainDB)
{
if (gainDB > 0) {
return MA_INVALID_ARGS;
}
return ma_mixer_set_volume(pMixer, ma_gain_db_to_factor(gainDB));
}
MA_API ma_result ma_mixer_get_gain_db(ma_mixer* pMixer, float* pGainDB)
{
float factor;
ma_result result;
if (pGainDB == NULL) {
return MA_INVALID_ARGS;
}
result = ma_mixer_get_volume(pMixer, &factor);
if (result != MA_SUCCESS) {
*pGainDB = 0;
return result;
}
*pGainDB = ma_factor_to_gain_db(factor);
return MA_SUCCESS;
}
MA_API ma_result ma_mixer_set_effect(ma_mixer* pMixer, ma_effect* pEffect)
{
if (pMixer == NULL) {
return MA_INVALID_ARGS;
}
if (pMixer->pEffect == pEffect) {
return MA_SUCCESS; /* No-op. */
}
/* The effect cannot be changed if we're in the middle of a begin/end pair. */
if (pMixer->mixingState.isInsideBeginEnd) {
return MA_INVALID_OPERATION;
}
pMixer->pEffect = pEffect;
return MA_SUCCESS;
}
MA_API ma_result ma_mixer_get_effect(ma_mixer* pMixer, ma_effect** ppEffect)
{
if (ppEffect == NULL) {
return MA_INVALID_ARGS;
}
*ppEffect = NULL; /* Safety. */
if (pMixer == NULL) {
return MA_INVALID_ARGS;
}
*ppEffect = pMixer->pEffect;
return MA_SUCCESS;
}
MA_API ma_result ma_mixer_get_output_data_format(ma_mixer* pMixer, ma_format* pFormat, ma_uint32* pChannels)
{
if (pMixer == NULL) {
return MA_INVALID_ARGS;
}
/* If we have an effect, the output data format will be the effect's output data format. */
if (pMixer->pEffect != NULL) {
return ma_effect_get_output_data_format(pMixer->pEffect, pFormat, pChannels, NULL);
} else {
if (pFormat != NULL) {
*pFormat = pMixer->format;
}
if (pChannels != NULL) {
*pChannels = pMixer->channels;
}
return MA_SUCCESS;
}
}
MA_API ma_result ma_mixer_get_input_data_format(ma_mixer* pMixer, ma_format* pFormat, ma_uint32* pChannels)
{
if (pMixer == NULL) {
return MA_INVALID_ARGS;
}
if (pFormat != NULL) {
*pFormat = pMixer->format;
}
if (pChannels != NULL) {
*pChannels = pMixer->channels;
}
return MA_SUCCESS;
}
MA_API ma_result ma_slot_allocator_init(ma_slot_allocator* pAllocator) MA_API ma_result ma_slot_allocator_init(ma_slot_allocator* pAllocator)
{ {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment