Commit 0d898911 authored by David Reid's avatar David Reid

Add support for per-device master volume control.

New Device APIs:
  * ma_device_set_master_volume()
  * ma_device_get_master_volume()
  * ma_device_set_master_gain_db()
  * ma_device_get_master_gain_db()

New Helper APIs
  * ma_copy_and_apply_volume_factor_u8()
  * ma_copy_and_apply_volume_factor_s16()
  * ma_copy_and_apply_volume_factor_s24()
  * ma_copy_and_apply_volume_factor_s32()
  * ma_copy_and_apply_volume_factor_f32()
  * ma_apply_volume_factor_u8()
  * ma_apply_volume_factor_s16()
  * ma_apply_volume_factor_s24()
  * ma_apply_volume_factor_s32()
  * ma_apply_volume_factor_f32()
  * ma_copy_and_apply_volume_factor_pcm_frames_u8()
  * ma_copy_and_apply_volume_factor_pcm_frames_s16()
  * ma_copy_and_apply_volume_factor_pcm_frames_s24()
  * ma_copy_and_apply_volume_factor_pcm_frames_s32()
  * ma_copy_and_apply_volume_factor_pcm_frames_f32()
  * ma_copy_and_apply_volume_factor_pcm_frames()
  * ma_apply_volume_factor_pcm_frames_u8()
  * ma_apply_volume_factor_pcm_frames_s16()
  * ma_apply_volume_factor_pcm_frames_s24()
  * ma_apply_volume_factor_pcm_frames_s32()
  * ma_apply_volume_factor_pcm_frames_f32()
  * ma_apply_volume_factor_pcm_frames()
  * ma_factor_to_gain_db()
  * ma_gain_db_to_factor()
parent b733bf25
...@@ -2385,6 +2385,7 @@ MA_ALIGNED_STRUCT(MA_SIMD_ALIGNMENT) ma_device ...@@ -2385,6 +2385,7 @@ MA_ALIGNED_STRUCT(MA_SIMD_ALIGNMENT) ma_device
ma_bool32 isOwnerOfContext : 1; /* When set to true, uninitializing the device will also uninitialize the context. Set to true when NULL is passed into ma_device_init(). */ ma_bool32 isOwnerOfContext : 1; /* When set to true, uninitializing the device will also uninitialize the context. Set to true when NULL is passed into ma_device_init(). */
ma_bool32 noPreZeroedOutputBuffer : 1; ma_bool32 noPreZeroedOutputBuffer : 1;
ma_bool32 noClip : 1; ma_bool32 noClip : 1;
float masterVolumeFactor;
struct struct
{ {
char name[256]; /* Maybe temporary. Likely to be replaced with a query API. */ char name[256]; /* Maybe temporary. Likely to be replaced with a query API. */
...@@ -2912,6 +2913,59 @@ Thread Safety: SAFE ...@@ -2912,6 +2913,59 @@ Thread Safety: SAFE
*/ */
ma_bool32 ma_device_is_started(ma_device* pDevice); ma_bool32 ma_device_is_started(ma_device* pDevice);
/*
Sets the master volume factor for the device.
The volume factor must be between 0 (silence) and 1 (full volume). Use ma_device_set_master_gain_db() to
use decibel notation, where 0 is full volume.
This applies the volume factor across all channels.
Return Value
------------
MA_SUCCESS if the volume was set successfully.
MA_INVALID_ARGS if pDevice is NULL.
MA_INVALID_ARGS if the volume factor is not within the range of [0, 1].
*/
ma_result ma_device_set_master_volume(ma_device* pDevice, float volume);
/*
Retrieves the master volume factor for the device.
Return Value
------------
MA_SUCCESS if successful.
MA_INVALID_ARGS if pDevice is NULL.
MA_INVALID_ARGS if pVolume is NULL.
*/
ma_result ma_device_get_master_volume(ma_device* pDevice, float* pVolume);
/*
Sets the master volume for the device as gain in decibels.
A gain of 0 is full volume, whereas a gain of < 0 will decrease the volume.
This applies the gain across all channels.
Return Value
------------
MA_SUCCESS if the volume was set successfully.
MA_INVALID_ARGS if pDevice is NULL.
MA_INVALID_ARGS if the gain is > 0.
*/
ma_result ma_device_set_master_gain_db(ma_device* pDevice, float gainDB);
/*
Retrieves the master gain in decibels.
Return Value
------------
MA_SUCCESS if successful.
MA_INVALID_ARGS if pDevice is NULL.
MA_INVALID_ARGS if pGainDB is NULL.
*/
ma_result ma_device_get_master_gain_db(ma_device* pDevice, float* pGainDB);
/* /*
Helper function for initializing a ma_context_config object. Helper function for initializing a ma_context_config object.
...@@ -3037,6 +3091,48 @@ Clips f32 samples. ...@@ -3037,6 +3091,48 @@ Clips f32 samples.
void ma_clip_samples_f32(float* p, ma_uint32 sampleCount); void ma_clip_samples_f32(float* p, ma_uint32 sampleCount);
MA_INLINE void ma_clip_pcm_frames_f32(float* p, ma_uint32 frameCount, ma_uint32 channels) { ma_clip_samples_f32(p, frameCount*channels); } MA_INLINE void ma_clip_pcm_frames_f32(float* p, ma_uint32 frameCount, ma_uint32 channels) { ma_clip_samples_f32(p, frameCount*channels); }
/*
Helper for applying a volume factor to samples.
Note that the source and destination buffers can be the same, in which case it'll perform the operation in-place.
*/
void ma_copy_and_apply_volume_factor_u8(ma_uint8* pSamplesOut, const ma_uint8* pSamplesIn, ma_uint32 sampleCount, float factor);
void ma_copy_and_apply_volume_factor_s16(ma_int16* pSamplesOut, const ma_int16* pSamplesIn, ma_uint32 sampleCount, float factor);
void ma_copy_and_apply_volume_factor_s24(void* pSamplesOut, const void* pSamplesIn, ma_uint32 sampleCount, float factor);
void ma_copy_and_apply_volume_factor_s32(ma_int32* pSamplesOut, const ma_int32* pSamplesIn, ma_uint32 sampleCount, float factor);
void ma_copy_and_apply_volume_factor_f32(float* pSamplesOut, const float* pSamplesIn, ma_uint32 sampleCount, float factor);
void ma_apply_volume_factor_u8(ma_uint8* pSamples, ma_uint32 sampleCount, float factor);
void ma_apply_volume_factor_s16(ma_int16* pSamples, ma_uint32 sampleCount, float factor);
void ma_apply_volume_factor_s24(void* pSamples, ma_uint32 sampleCount, float factor);
void ma_apply_volume_factor_s32(ma_int32* pSamples, ma_uint32 sampleCount, float factor);
void ma_apply_volume_factor_f32(float* pSamples, ma_uint32 sampleCount, float factor);
void ma_copy_and_apply_volume_factor_pcm_frames_u8(ma_uint8* pPCMFramesOut, const ma_uint8* pPCMFramesIn, ma_uint32 frameCount, ma_uint32 channels, float factor);
void ma_copy_and_apply_volume_factor_pcm_frames_s16(ma_int16* pPCMFramesOut, const ma_int16* pPCMFramesIn, ma_uint32 frameCount, ma_uint32 channels, float factor);
void ma_copy_and_apply_volume_factor_pcm_frames_s24(void* pPCMFramesOut, const void* pPCMFramesIn, ma_uint32 frameCount, ma_uint32 channels, float factor);
void ma_copy_and_apply_volume_factor_pcm_frames_s32(ma_int32* pPCMFramesOut, const ma_int32* pPCMFramesIn, ma_uint32 frameCount, ma_uint32 channels, float factor);
void ma_copy_and_apply_volume_factor_pcm_frames_f32(float* pPCMFramesOut, const float* pPCMFramesIn, ma_uint32 frameCount, ma_uint32 channels, float factor);
void ma_copy_and_apply_volume_factor_pcm_frames(void* pFramesOut, const void* pFramesIn, ma_uint32 frameCount, ma_format format, ma_uint32 channels, float factor);
void ma_apply_volume_factor_pcm_frames_u8(ma_uint8* pFrames, ma_uint32 frameCount, ma_uint32 channels, float factor);
void ma_apply_volume_factor_pcm_frames_s16(ma_int16* pFrames, ma_uint32 frameCount, ma_uint32 channels, float factor);
void ma_apply_volume_factor_pcm_frames_s24(void* pFrames, ma_uint32 frameCount, ma_uint32 channels, float factor);
void ma_apply_volume_factor_pcm_frames_s32(ma_int32* pFrames, ma_uint32 frameCount, ma_uint32 channels, float factor);
void ma_apply_volume_factor_pcm_frames_f32(float* pFrames, ma_uint32 frameCount, ma_uint32 channels, float factor);
void ma_apply_volume_factor_pcm_frames(void* pFrames, ma_uint32 frameCount, ma_format format, ma_uint32 channels, float factor);
/*
Helper for converting a linear factor to gain in decibels.
*/
float ma_factor_to_gain_db(float factor);
/*
Helper for converting gain in decibels to a linear factor.
*/
float ma_gain_db_to_factor(float gain);
#endif /* MA_NO_DEVICE_IO */ #endif /* MA_NO_DEVICE_IO */
...@@ -5322,6 +5418,188 @@ void ma_clip_samples_f32(float* p, ma_uint32 sampleCount) ...@@ -5322,6 +5418,188 @@ void ma_clip_samples_f32(float* p, ma_uint32 sampleCount)
} }
void ma_copy_and_apply_volume_factor_u8(ma_uint8* pSamplesOut, const ma_uint8* pSamplesIn, ma_uint32 sampleCount, float factor)
{
ma_uint32 iSample;
if (pSamplesOut == NULL || pSamplesIn == NULL) {
return;
}
for (iSample = 0; iSample < sampleCount; iSample += 1) {
pSamplesOut[iSample] = (ma_uint8)(pSamplesIn[iSample] * factor);
}
}
void ma_copy_and_apply_volume_factor_s16(ma_int16* pSamplesOut, const ma_int16* pSamplesIn, ma_uint32 sampleCount, float factor)
{
ma_uint32 iSample;
if (pSamplesOut == NULL || pSamplesIn == NULL) {
return;
}
for (iSample = 0; iSample < sampleCount; iSample += 1) {
pSamplesOut[iSample] = (ma_int16)(pSamplesIn[iSample] * factor);
}
}
void ma_copy_and_apply_volume_factor_s24(void* pSamplesOut, const void* pSamplesIn, ma_uint32 sampleCount, float factor)
{
ma_uint32 iSample;
ma_uint8* pSamplesOut8;
ma_uint8* pSamplesIn8;
if (pSamplesOut == NULL || pSamplesIn == NULL) {
return;
}
pSamplesOut8 = (ma_uint8*)pSamplesOut;
pSamplesIn8 = (ma_uint8*)pSamplesIn;
for (iSample = 0; iSample < sampleCount; iSample += 1) {
ma_int32 sampleS32;
sampleS32 = (ma_int32)(((ma_uint32)(pSamplesIn8[iSample*3+0]) << 8) | ((ma_uint32)(pSamplesIn8[iSample*3+1]) << 16) | ((ma_uint32)(pSamplesIn8[iSample*3+2])) << 24);
sampleS32 = (ma_int32)(sampleS32 * factor);
pSamplesOut8[iSample*3+0] = (ma_uint8)(((ma_uint32)sampleS32 & 0x0000FF00) >> 8);
pSamplesOut8[iSample*3+1] = (ma_uint8)(((ma_uint32)sampleS32 & 0x00FF0000) >> 16);
pSamplesOut8[iSample*3+2] = (ma_uint8)(((ma_uint32)sampleS32 & 0xFF000000) >> 24);
}
}
void ma_copy_and_apply_volume_factor_s32(ma_int32* pSamplesOut, const ma_int32* pSamplesIn, ma_uint32 sampleCount, float factor)
{
ma_uint32 iSample;
if (pSamplesOut == NULL || pSamplesIn == NULL) {
return;
}
for (iSample = 0; iSample < sampleCount; iSample += 1) {
pSamplesOut[iSample] = (ma_int32)(pSamplesIn[iSample] * factor);
}
}
void ma_copy_and_apply_volume_factor_f32(float* pSamplesOut, const float* pSamplesIn, ma_uint32 sampleCount, float factor)
{
ma_uint32 iSample;
if (pSamplesOut == NULL || pSamplesIn == NULL) {
return;
}
for (iSample = 0; iSample < sampleCount; iSample += 1) {
pSamplesOut[iSample] = pSamplesIn[iSample] * factor;
}
}
void ma_apply_volume_factor_u8(ma_uint8* pSamples, ma_uint32 sampleCount, float factor)
{
ma_copy_and_apply_volume_factor_u8(pSamples, pSamples, sampleCount, factor);
}
void ma_apply_volume_factor_s16(ma_int16* pSamples, ma_uint32 sampleCount, float factor)
{
ma_copy_and_apply_volume_factor_s16(pSamples, pSamples, sampleCount, factor);
}
void ma_apply_volume_factor_s24(void* pSamples, ma_uint32 sampleCount, float factor)
{
ma_copy_and_apply_volume_factor_s24(pSamples, pSamples, sampleCount, factor);
}
void ma_apply_volume_factor_s32(ma_int32* pSamples, ma_uint32 sampleCount, float factor)
{
ma_copy_and_apply_volume_factor_s32(pSamples, pSamples, sampleCount, factor);
}
void ma_apply_volume_factor_f32(float* pSamples, ma_uint32 sampleCount, float factor)
{
ma_copy_and_apply_volume_factor_f32(pSamples, pSamples, sampleCount, factor);
}
void ma_copy_and_apply_volume_factor_pcm_frames_u8(ma_uint8* pPCMFramesOut, const ma_uint8* pPCMFramesIn, ma_uint32 frameCount, ma_uint32 channels, float factor)
{
ma_copy_and_apply_volume_factor_u8(pPCMFramesOut, pPCMFramesIn, frameCount*channels, factor);
}
void ma_copy_and_apply_volume_factor_pcm_frames_s16(ma_int16* pPCMFramesOut, const ma_int16* pPCMFramesIn, ma_uint32 frameCount, ma_uint32 channels, float factor)
{
ma_copy_and_apply_volume_factor_s16(pPCMFramesOut, pPCMFramesIn, frameCount*channels, factor);
}
void ma_copy_and_apply_volume_factor_pcm_frames_s24(void* pPCMFramesOut, const void* pPCMFramesIn, ma_uint32 frameCount, ma_uint32 channels, float factor)
{
ma_copy_and_apply_volume_factor_s24(pPCMFramesOut, pPCMFramesIn, frameCount*channels, factor);
}
void ma_copy_and_apply_volume_factor_pcm_frames_s32(ma_int32* pPCMFramesOut, const ma_int32* pPCMFramesIn, ma_uint32 frameCount, ma_uint32 channels, float factor)
{
ma_copy_and_apply_volume_factor_s32(pPCMFramesOut, pPCMFramesIn, frameCount*channels, factor);
}
void ma_copy_and_apply_volume_factor_pcm_frames_f32(float* pPCMFramesOut, const float* pPCMFramesIn, ma_uint32 frameCount, ma_uint32 channels, float factor)
{
ma_copy_and_apply_volume_factor_f32(pPCMFramesOut, pPCMFramesIn, frameCount*channels, factor);
}
void ma_copy_and_apply_volume_factor_pcm_frames(void* pPCMFramesOut, const void* pPCMFramesIn, ma_uint32 frameCount, ma_format format, ma_uint32 channels, float factor)
{
switch (format)
{
case ma_format_u8: ma_copy_and_apply_volume_factor_pcm_frames_u8 ((ma_uint8*)pPCMFramesOut, (const ma_uint8*)pPCMFramesIn, frameCount, channels, factor); return;
case ma_format_s16: ma_copy_and_apply_volume_factor_pcm_frames_s16((ma_int16*)pPCMFramesOut, (const ma_int16*)pPCMFramesIn, frameCount, channels, factor); return;
case ma_format_s24: ma_copy_and_apply_volume_factor_pcm_frames_s24( pPCMFramesOut, pPCMFramesIn, frameCount, channels, factor); return;
case ma_format_s32: ma_copy_and_apply_volume_factor_pcm_frames_s32((ma_int32*)pPCMFramesOut, (const ma_int32*)pPCMFramesIn, frameCount, channels, factor); return;
case ma_format_f32: ma_copy_and_apply_volume_factor_pcm_frames_f32( (float*)pPCMFramesOut, (const float*)pPCMFramesIn, frameCount, channels, factor); return;
default: return; /* Do nothing. */
}
}
void ma_apply_volume_factor_pcm_frames_u8(ma_uint8* pPCMFrames, ma_uint32 frameCount, ma_uint32 channels, float factor)
{
ma_copy_and_apply_volume_factor_pcm_frames_u8(pPCMFrames, pPCMFrames, frameCount, channels, factor);
}
void ma_apply_volume_factor_pcm_frames_s16(ma_int16* pPCMFrames, ma_uint32 frameCount, ma_uint32 channels, float factor)
{
ma_copy_and_apply_volume_factor_pcm_frames_s16(pPCMFrames, pPCMFrames, frameCount, channels, factor);
}
void ma_apply_volume_factor_pcm_frames_s24(void* pPCMFrames, ma_uint32 frameCount, ma_uint32 channels, float factor)
{
ma_copy_and_apply_volume_factor_pcm_frames_s24(pPCMFrames, pPCMFrames, frameCount, channels, factor);
}
void ma_apply_volume_factor_pcm_frames_s32(ma_int32* pPCMFrames, ma_uint32 frameCount, ma_uint32 channels, float factor)
{
ma_copy_and_apply_volume_factor_pcm_frames_s32(pPCMFrames, pPCMFrames, frameCount, channels, factor);
}
void ma_apply_volume_factor_pcm_frames_f32(float* pPCMFrames, ma_uint32 frameCount, ma_uint32 channels, float factor)
{
ma_copy_and_apply_volume_factor_pcm_frames_f32(pPCMFrames, pPCMFrames, frameCount, channels, factor);
}
void ma_apply_volume_factor_pcm_frames(void* pPCMFrames, ma_uint32 frameCount, ma_format format, ma_uint32 channels, float factor)
{
ma_copy_and_apply_volume_factor_pcm_frames(pPCMFrames, pPCMFrames, frameCount, format, channels, factor);
}
float ma_factor_to_gain_db(float factor)
{
return (float)(20*log10(factor));
}
float ma_gain_db_to_factor(float gain)
{
return (float)pow(10, gain/20.0);
}
static MA_INLINE void ma_device__on_data(ma_device* pDevice, void* pFramesOut, const void* pFramesIn, ma_uint32 frameCount) static MA_INLINE void ma_device__on_data(ma_device* pDevice, void* pFramesOut, const void* pFramesIn, ma_uint32 frameCount)
{ {
ma_device_callback_proc onData; ma_device_callback_proc onData;
...@@ -5332,10 +5610,39 @@ static MA_INLINE void ma_device__on_data(ma_device* pDevice, void* pFramesOut, c ...@@ -5332,10 +5610,39 @@ static MA_INLINE void ma_device__on_data(ma_device* pDevice, void* pFramesOut, c
ma_zero_pcm_frames(pFramesOut, frameCount, pDevice->playback.format, pDevice->playback.channels); ma_zero_pcm_frames(pFramesOut, frameCount, pDevice->playback.format, pDevice->playback.channels);
} }
onData(pDevice, pFramesOut, pFramesIn, frameCount); /* Volume control of input makes things a bit awkward because the input buffer is read-only. We'll need to use a temp buffer and loop in this case. */
if (pFramesIn != NULL && pDevice->masterVolumeFactor < 1) {
ma_uint8 tempFramesIn[8192];
ma_uint32 bpfCapture = ma_get_bytes_per_frame(pDevice->capture.format, pDevice->capture.channels);
ma_uint32 bpfPlayback = ma_get_bytes_per_frame(pDevice->playback.format, pDevice->playback.channels);
ma_uint32 totalFramesProcessed = 0;
while (totalFramesProcessed < frameCount) {
ma_uint32 framesToProcessThisIteration = frameCount - totalFramesProcessed;
if (framesToProcessThisIteration > sizeof(tempFramesIn)/bpfCapture) {
framesToProcessThisIteration = sizeof(tempFramesIn)/bpfCapture;
}
ma_copy_and_apply_volume_factor_pcm_frames(tempFramesIn, ma_offset_ptr(pFramesIn, totalFramesProcessed*bpfCapture), framesToProcessThisIteration, pDevice->capture.format, pDevice->capture.channels, pDevice->masterVolumeFactor);
onData(pDevice, ma_offset_ptr(pFramesOut, totalFramesProcessed*bpfPlayback), tempFramesIn, framesToProcessThisIteration);
if (!pDevice->noClip && pFramesOut != NULL && pDevice->playback.format == ma_format_f32) { totalFramesProcessed += framesToProcessThisIteration;
ma_clip_pcm_frames_f32((float*)pFramesOut, frameCount, pDevice->playback.channels); }
} else {
onData(pDevice, pFramesOut, pFramesIn, frameCount);
}
/* Volume control and clipping for playback devices. */
if (pFramesOut != NULL) {
if (pDevice->masterVolumeFactor < 1) {
if (pFramesIn == NULL) { /* <-- In full-duplex situations, the volume will have been applied to the input samples before the data callback. Applying it again post-callback will incorrectly compound it. */
ma_apply_volume_factor_pcm_frames(pFramesOut, frameCount, pDevice->playback.format, pDevice->playback.channels, pDevice->masterVolumeFactor);
}
}
if (!pDevice->noClip && pDevice->playback.format == ma_format_f32) {
ma_clip_pcm_frames_f32((float*)pFramesOut, frameCount, pDevice->playback.channels);
}
} }
} }
} }
...@@ -25725,6 +26032,7 @@ ma_result ma_device_init(ma_context* pContext, const ma_device_config* pConfig, ...@@ -25725,6 +26032,7 @@ ma_result ma_device_init(ma_context* pContext, const ma_device_config* pConfig,
pDevice->noPreZeroedOutputBuffer = config.noPreZeroedOutputBuffer; pDevice->noPreZeroedOutputBuffer = config.noPreZeroedOutputBuffer;
pDevice->noClip = config.noClip; pDevice->noClip = config.noClip;
pDevice->masterVolumeFactor = 1;
/* /*
When passing in 0 for the format/channels/rate/chmap it means the device will be using whatever is chosen by the backend. If everything is set When passing in 0 for the format/channels/rate/chmap it means the device will be using whatever is chosen by the backend. If everything is set
...@@ -26118,6 +26426,60 @@ ma_bool32 ma_device_is_started(ma_device* pDevice) ...@@ -26118,6 +26426,60 @@ ma_bool32 ma_device_is_started(ma_device* pDevice)
return ma_device__get_state(pDevice) == MA_STATE_STARTED; return ma_device__get_state(pDevice) == MA_STATE_STARTED;
} }
ma_result ma_device_set_master_volume(ma_device* pDevice, float volume)
{
if (pDevice == NULL) {
return MA_INVALID_ARGS;
}
if (volume < 0.0f || volume > 1.0f) {
return MA_INVALID_ARGS;
}
pDevice->masterVolumeFactor = volume;
return MA_SUCCESS;
}
ma_result ma_device_get_master_volume(ma_device* pDevice, float* pVolume)
{
if (pDevice == NULL || pVolume == NULL) {
return MA_INVALID_ARGS;
}
*pVolume = pDevice->masterVolumeFactor;
return MA_SUCCESS;
}
ma_result ma_device_set_master_gain_db(ma_device* pDevice, float gainDB)
{
if (gainDB > 0) {
return MA_INVALID_ARGS;
}
return ma_device_set_master_volume(pDevice, ma_gain_db_to_factor(gainDB));
}
ma_result ma_device_get_master_gain_db(ma_device* pDevice, float* pGainDB)
{
float factor;
ma_result result;
if (pGainDB == NULL) {
return MA_INVALID_ARGS;
}
result = ma_device_get_master_volume(pDevice, &factor);
if (result != MA_SUCCESS) {
return result;
}
*pGainDB = ma_factor_to_gain_db(factor);
return MA_SUCCESS;
}
ma_context_config ma_context_config_init() ma_context_config ma_context_config_init()
{ {
...@@ -34910,6 +35272,9 @@ v0.9.8 - 2019-xx-xx ...@@ -34910,6 +35272,9 @@ v0.9.8 - 2019-xx-xx
- Add support for clipping samples after the data callback has returned. This only applies when the playback sample format is - Add support for clipping samples after the data callback has returned. This only applies when the playback sample format is
configured as ma_format_f32. If you are doing clipping yourself, you can disable this overhead by setting noClip to true in configured as ma_format_f32. If you are doing clipping yourself, you can disable this overhead by setting noClip to true in
the device config. the device config.
- Add support for master volume control for devices.
- Use ma_device_set_master_volume() to set the volume to a factor between 0 and 1, where 0 is silence and 1 is full volume.
- Use ma_device_set_master_gain_db() to set the volume in decibels where 0 is full volume and < 0 reduces the volume.
- Fix warnings emitted by GCC when `__inline__` is undefined or defined as nothing. - Fix warnings emitted by GCC when `__inline__` is undefined or defined as nothing.
v0.9.7 - 2019-08-28 v0.9.7 - 2019-08-28
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