From 2a8f1e11caf31790e1fc0efb1edc541366266085 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Mon, 1 Jul 2024 15:08:20 -0400 Subject: [PATCH] audio: Add gain support to audio streams and logical audio devices. Fixes #10028. --- include/SDL3/SDL_audio.h | 105 ++++++++++++++++++++++++++++++ src/audio/SDL_audio.c | 60 ++++++++++++++--- src/audio/SDL_audiocvt.c | 78 +++++++++++++++++++--- src/audio/SDL_audioqueue.c | 10 +-- src/audio/SDL_audioqueue.h | 2 +- src/audio/SDL_sysaudio.h | 10 ++- src/dynapi/SDL_dynapi.sym | 4 ++ src/dynapi/SDL_dynapi_overrides.h | 4 ++ src/dynapi/SDL_dynapi_procs.h | 4 ++ test/testaudio.c | 35 +++++++++- 10 files changed, 286 insertions(+), 26 deletions(-) diff --git a/include/SDL3/SDL_audio.h b/include/SDL3/SDL_audio.h index c3bf5e94f7..8ee76d166a 100644 --- a/include/SDL3/SDL_audio.h +++ b/include/SDL3/SDL_audio.h @@ -718,6 +718,62 @@ extern SDL_DECLSPEC int SDLCALL SDL_ResumeAudioDevice(SDL_AudioDeviceID dev); */ extern SDL_DECLSPEC SDL_bool SDLCALL SDL_AudioDevicePaused(SDL_AudioDeviceID dev); +/** + * Get the gain of an audio device. + * + * The gain of a device is its volume; a larger gain means a louder output, + * with a gain of zero being silence. + * + * Audio devices default to a gain of 1.0f (no change in output). + * + * Physical devices may not have their gain changed, only logical devices, + * and this function will always return -1.0f when used on physical devices. + * + * \param devid the audio device to query. + * \returns the gain of the device, or -1.0f on error. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_SetAudioDeviceGain + */ +extern SDL_DECLSPEC float SDLCALL SDL_GetAudioDeviceGain(SDL_AudioDeviceID devid); + +/** + * Change the gain of an audio device. + * + * The gain of a device is its volume; a larger gain means a louder output, + * with a gain of zero being silence. + * + * Audio devices default to a gain of 1.0f (no change in output). + * + * Physical devices may not have their gain changed, only logical devices, + * and this function will always return -1 when used on physical devices. While + * it might seem attractive to adjust several logical devices at once in this + * way, it would allow an app or library to interfere with another portion of + * the program's otherwise-isolated devices. + * + * This is applied, along with any per-audiostream gain, during playback to + * the hardware, and can be continuously changed to create various effects. + * On recording devices, this will adjust the gain before passing the data + * into an audiostream; that recording audiostream can then adjust its gain + * further when outputting the data elsewhere, if it likes, but that second + * gain is not applied until the data leaves the audiostream again. + * + * \param devid the audio device on which to change gain. + * \param gain the gain. 1.0f is no change, 0.0f is silence. + * \returns 0 on success, or -1 on error. + * + * \threadsafety It is safe to call this function from any thread, as it holds + * a stream-specific mutex while running. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_GetAudioDeviceGain + */ +extern SDL_DECLSPEC int SDLCALL SDL_SetAudioDeviceGain(SDL_AudioDeviceID devid, float gain); + /** * Close a previously-opened audio device. * @@ -981,6 +1037,51 @@ extern SDL_DECLSPEC float SDLCALL SDL_GetAudioStreamFrequencyRatio(SDL_AudioStre */ extern SDL_DECLSPEC int SDLCALL SDL_SetAudioStreamFrequencyRatio(SDL_AudioStream *stream, float ratio); +/** + * Get the gain of an audio stream. + * + * The gain of a stream is its volume; a larger gain means a louder output, + * with a gain of zero being silence. + * + * Audio streams default to a gain of 1.0f (no change in output). + * + * \param stream the SDL_AudioStream to query. + * \returns the gain of the stream, or -1.0f on error. + * + * \threadsafety It is safe to call this function from any thread, as it holds + * a stream-specific mutex while running. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_SetAudioStreamGain + */ +extern SDL_DECLSPEC float SDLCALL SDL_GetAudioStreamGain(SDL_AudioStream *stream); + +/** + * Change the gain of an audio stream. + * + * The gain of a stream is its volume; a larger gain means a louder output, + * with a gain of zero being silence. + * + * Audio streams default to a gain of 1.0f (no change in output). + * + * This is applied during SDL_GetAudioStreamData, and can be continuously + * changed to create various effects. + * + * \param stream the stream on which the gain is being changed. + * \param gain the gain. 1.0f is no change, 0.0f is silence. + * \returns 0 on success, or -1 on error. + * + * \threadsafety It is safe to call this function from any thread, as it holds + * a stream-specific mutex while running. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_GetAudioStreamGain + */ +extern SDL_DECLSPEC int SDLCALL SDL_SetAudioStreamGain(SDL_AudioStream *stream, float gain); + + /** * Add data to the stream. * @@ -1465,6 +1566,10 @@ extern SDL_DECLSPEC SDL_AudioStream *SDLCALL SDL_OpenAudioDeviceStream(SDL_Audio * changed. However, this only covers frequency and channel count; data is * always provided here in SDL_AUDIO_F32 format. * + * The postmix callback runs _after_ logical device gain and audiostream gain + * have been applied, which is to say you can make the output data louder + * at this point than the gain settings would suggest. + * * \param userdata a pointer provided by the app through * SDL_SetAudioPostmixCallback, for its own use. * \param spec the current format of audio that is to be submitted to the diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c index 0c1edacdae..4a3c7281f6 100644 --- a/src/audio/SDL_audio.c +++ b/src/audio/SDL_audio.c @@ -1103,7 +1103,7 @@ SDL_bool SDL_PlaybackAudioThreadIterate(SDL_AudioDevice *device) // We should have updated this elsewhere if the format changed! SDL_assert(SDL_AudioSpecsEqual(&stream->dst_spec, &device->spec)); - const int br = SDL_AtomicGet(&logdev->paused) ? 0 : SDL_GetAudioStreamData(stream, device_buffer, buffer_size); + const int br = SDL_AtomicGet(&logdev->paused) ? 0 : SDL_GetAudioStreamDataAdjustGain(stream, device_buffer, buffer_size, logdev->gain); if (br < 0) { // Probably OOM. Kill the audio device; the whole thing is likely dying soon anyhow. failed = SDL_TRUE; SDL_memset(device_buffer, device->silence_value, buffer_size); // just supply silence to the device before we die. @@ -1143,7 +1143,7 @@ SDL_bool SDL_PlaybackAudioThreadIterate(SDL_AudioDevice *device) for iterating here because the binding linked list can only change while the device lock is held. (we _do_ lock the stream during binding/unbinding to make sure that two threads can't try to bind the same stream to different devices at the same time, though.) */ - const int br = SDL_GetAudioStreamData(stream, device->work_buffer, work_buffer_size); + const int br = SDL_GetAudioStreamDataAdjustGain(stream, device->work_buffer, work_buffer_size, logdev->gain); if (br < 0) { // Probably OOM. Kill the audio device; the whole thing is likely dying soon anyhow. failed = SDL_TRUE; break; @@ -1161,8 +1161,8 @@ SDL_bool SDL_PlaybackAudioThreadIterate(SDL_AudioDevice *device) if (((Uint8 *) final_mix_buffer) != device_buffer) { // !!! FIXME: we can't promise the device buf is aligned/padded for SIMD. - //ConvertAudio(needed_samples / device->spec.channels, final_mix_buffer, SDL_AUDIO_F32, device->spec.channels, NULL, device_buffer, device->spec.format, device->spec.channels, NULL, NULL); - ConvertAudio(needed_samples / device->spec.channels, final_mix_buffer, SDL_AUDIO_F32, device->spec.channels, NULL, device->work_buffer, device->spec.format, device->spec.channels, NULL, NULL); + //ConvertAudio(needed_samples / device->spec.channels, final_mix_buffer, SDL_AUDIO_F32, device->spec.channels, NULL, device_buffer, device->spec.format, device->spec.channels, NULL, NULL, 1.0f); + ConvertAudio(needed_samples / device->spec.channels, final_mix_buffer, SDL_AUDIO_F32, device->spec.channels, NULL, device->work_buffer, device->spec.format, device->spec.channels, NULL, NULL, 1.0f); SDL_memcpy(device_buffer, device->work_buffer, buffer_size); } } @@ -1250,7 +1250,7 @@ SDL_bool SDL_RecordingAudioThreadIterate(SDL_AudioDevice *device) void *output_buffer = device->work_buffer; // I don't know why someone would want a postmix on a recording device, but we offer it for API consistency. - if (logdev->postmix) { + if (logdev->postmix || (logdev->gain != 1.0f)) { // move to float format. SDL_AudioSpec outspec; SDL_copyp(&outspec, &device->spec); @@ -1258,13 +1258,15 @@ SDL_bool SDL_RecordingAudioThreadIterate(SDL_AudioDevice *device) output_buffer = device->postmix_buffer; const int frames = br / SDL_AUDIO_FRAMESIZE(device->spec); br = frames * SDL_AUDIO_FRAMESIZE(outspec); - ConvertAudio(frames, device->work_buffer, device->spec.format, outspec.channels, NULL, device->postmix_buffer, SDL_AUDIO_F32, outspec.channels, NULL, NULL); - logdev->postmix(logdev->postmix_userdata, &outspec, device->postmix_buffer, br); + ConvertAudio(frames, device->work_buffer, device->spec.format, outspec.channels, NULL, device->postmix_buffer, SDL_AUDIO_F32, outspec.channels, NULL, NULL, logdev->gain); + if (logdev->postmix) { + logdev->postmix(logdev->postmix_userdata, &outspec, device->postmix_buffer, br); + } } for (SDL_AudioStream *stream = logdev->bound_streams; stream; stream = stream->next_binding) { // We should have updated this elsewhere if the format changed! - SDL_assert(stream->src_spec.format == (logdev->postmix ? SDL_AUDIO_F32 : device->spec.format)); + SDL_assert(stream->src_spec.format == ((logdev->postmix || (logdev->gain != 1.0f)) ? SDL_AUDIO_F32 : device->spec.format)); SDL_assert(stream->src_spec.channels == device->spec.channels); SDL_assert(stream->src_spec.freq == device->spec.freq); @@ -1695,6 +1697,7 @@ SDL_AudioDeviceID SDL_OpenAudioDevice(SDL_AudioDeviceID devid, const SDL_AudioSp SDL_AtomicSet(&logdev->paused, 0); retval = logdev->instance_id = AssignAudioDeviceInstanceId(device->recording, /*islogical=*/SDL_TRUE); logdev->physical_device = device; + logdev->gain = 1.0f; logdev->opened_as_default = wants_default; logdev->next = device->logical_devices; if (device->logical_devices) { @@ -1752,6 +1755,44 @@ SDL_bool SDL_AudioDevicePaused(SDL_AudioDeviceID devid) return retval; } +float SDL_GetAudioDeviceGain(SDL_AudioDeviceID devid) +{ + SDL_AudioDevice *device = NULL; + SDL_LogicalAudioDevice *logdev = ObtainLogicalAudioDevice(devid, &device); + const float retval = logdev ? logdev->gain : -1.0f; + ReleaseAudioDevice(device); + return retval; +} + +int SDL_SetAudioDeviceGain(SDL_AudioDeviceID devid, float gain) +{ + if (gain < 0.0f) { + return SDL_InvalidParamError("gain"); + } + + SDL_AudioDevice *device = NULL; + SDL_LogicalAudioDevice *logdev = ObtainLogicalAudioDevice(devid, &device); + int retval = -1; + if (logdev) { + logdev->gain = gain; + if (device->recording) { + const SDL_bool need_float32 = (logdev->postmix || logdev->gain != 1.0f); + for (SDL_AudioStream *stream = logdev->bound_streams; stream; stream = stream->next_binding) { + // set the proper end of the stream to the device's format. + // SDL_SetAudioStreamFormat does a ton of validation just to memcpy an audiospec. + SDL_LockMutex(stream->lock); + stream->src_spec.format = need_float32 ? SDL_AUDIO_F32 : device->spec.format; + SDL_UnlockMutex(stream->lock); + } + } + + UpdateAudioStreamFormatsPhysical(device); + retval = 0; + } + ReleaseAudioDevice(device); + return retval; +} + int SDL_SetAudioPostmixCallback(SDL_AudioDeviceID devid, SDL_AudioPostmixCallback callback, void *userdata) { SDL_AudioDevice *device = NULL; @@ -1770,11 +1811,12 @@ int SDL_SetAudioPostmixCallback(SDL_AudioDeviceID devid, SDL_AudioPostmixCallbac logdev->postmix_userdata = userdata; if (device->recording) { + const SDL_bool need_float32 = (callback || logdev->gain != 1.0f); for (SDL_AudioStream *stream = logdev->bound_streams; stream; stream = stream->next_binding) { // set the proper end of the stream to the device's format. // SDL_SetAudioStreamFormat does a ton of validation just to memcpy an audiospec. SDL_LockMutex(stream->lock); - stream->src_spec.format = callback ? SDL_AUDIO_F32 : device->spec.format; + stream->src_spec.format = need_float32 ? SDL_AUDIO_F32 : device->spec.format; SDL_UnlockMutex(stream->lock); } } diff --git a/src/audio/SDL_audiocvt.c b/src/audio/SDL_audiocvt.c index e05c88ee65..29cd63dc6d 100644 --- a/src/audio/SDL_audiocvt.c +++ b/src/audio/SDL_audiocvt.c @@ -194,10 +194,14 @@ static void SwizzleAudio(const int num_frames, void *dst, const void *src, int c // // The scratch buffer must be able to store `num_frames * CalculateMaxSampleFrameSize(src_format, src_channels, dst_format, dst_channels)` bytes. // If the scratch buffer is NULL, this restriction applies to the output buffer instead. +// +// Since this is a convenient point that audio goes through even if it doesn't need format conversion, +// we also handle gain adjustment here, so we don't have to make another pass over the data later. +// Strictly speaking, this is also a "conversion". :) void ConvertAudio(int num_frames, const void *src, SDL_AudioFormat src_format, int src_channels, const Uint8 *src_map, void *dst, SDL_AudioFormat dst_format, int dst_channels, const Uint8 *dst_map, - void* scratch) + void* scratch, float gain) { SDL_assert(src != NULL); SDL_assert(dst != NULL); @@ -243,7 +247,7 @@ void ConvertAudio(int num_frames, } // see if we can skip float conversion entirely. - if (src_channels == dst_channels) { + if ((src_channels == dst_channels) && (gain == 1.0f)) { if (src_format == dst_format) { // nothing to do, we're already in the right format, just copy it over if necessary. if (dst_map) { @@ -280,6 +284,23 @@ void ConvertAudio(int num_frames, src = buf; } + // Gain adjustment + if (gain != 1.0f) { + float *buf = (float *)(dstconvert ? scratch : dst); + const int total_samples = num_frames * src_channels; + if (src == buf) { + for (int i = 0; i < total_samples; i++) { + buf[i] *= gain; + } + } else { + float *fsrc = (float *)src; + for (int i = 0; i < total_samples; i++) { + buf[i] = fsrc[i] * gain; + } + } + src = buf; + } + // Channel conversion if (channelconvert) { @@ -379,6 +400,7 @@ SDL_AudioStream *SDL_CreateAudioStream(const SDL_AudioSpec *src_spec, const SDL_ } retval->freq_ratio = 1.0f; + retval->gain = 1.0f; retval->queue = SDL_CreateAudioQueue(8192); if (!retval->queue) { @@ -593,6 +615,35 @@ int SDL_SetAudioStreamFrequencyRatio(SDL_AudioStream *stream, float freq_ratio) return 0; } +float SDL_GetAudioStreamGain(SDL_AudioStream *stream) +{ + if (!stream) { + SDL_InvalidParamError("stream"); + return -1.0f; + } + + SDL_LockMutex(stream->lock); + const float gain = stream->gain; + SDL_UnlockMutex(stream->lock); + + return gain; +} + +int SDL_SetAudioStreamGain(SDL_AudioStream *stream, float gain) +{ + if (!stream) { + return SDL_InvalidParamError("stream"); + } else if (gain < 0.0f) { + return SDL_InvalidParamError("gain"); + } + + SDL_LockMutex(stream->lock); + stream->gain = gain; + SDL_UnlockMutex(stream->lock); + + return 0; +} + static int CheckAudioStreamIsFullySetup(SDL_AudioStream *stream) { if (stream->src_spec.format == 0) { @@ -820,7 +871,7 @@ static Sint64 GetAudioStreamHead(SDL_AudioStream* stream, SDL_AudioSpec* out_spe // You must hold stream->lock and validate your parameters before calling this! // Enough input data MUST be available! -static int GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int output_frames) +static int GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int output_frames, float gain) { const SDL_AudioSpec* src_spec = &stream->input_spec; const SDL_AudioSpec* dst_spec = &stream->dst_spec; @@ -854,7 +905,7 @@ static int GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int ou } } - if (SDL_ReadFromAudioQueue(stream->queue, buf, dst_format, dst_channels, dst_map, 0, output_frames, 0, work_buffer) != buf) { + if (SDL_ReadFromAudioQueue(stream->queue, buf, dst_format, dst_channels, dst_map, 0, output_frames, 0, work_buffer, gain) != buf) { return SDL_SetError("Not enough data in queue"); } @@ -919,10 +970,15 @@ static int GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int ou return -1; } + // adjust gain either before resampling or after, depending on which point has less + // samples to process. + const float preresample_gain = (input_frames > output_frames) ? 1.0f : gain; + const float postresample_gain = (input_frames > output_frames) ? gain : 1.0f; + // (dst channel map is NULL because we'll do the final swizzle on ConvertAudio after resample.) const Uint8* input_buffer = SDL_ReadFromAudioQueue(stream->queue, NULL, resample_format, resample_channels, NULL, - padding_frames, input_frames, padding_frames, work_buffer); + padding_frames, input_frames, padding_frames, work_buffer, preresample_gain); if (!input_buffer) { return SDL_SetError("Not enough data in queue (resample)"); @@ -939,13 +995,13 @@ static int GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int ou resample_rate, &stream->resample_offset); // Convert to the final format, if necessary (src channel map is NULL because SDL_ReadFromAudioQueue already handled this). - ConvertAudio(output_frames, resample_buffer, resample_format, resample_channels, NULL, buf, dst_format, dst_channels, dst_map, work_buffer); + ConvertAudio(output_frames, resample_buffer, resample_format, resample_channels, NULL, buf, dst_format, dst_channels, dst_map, work_buffer, postresample_gain); return 0; } // get converted/resampled data from the stream -int SDL_GetAudioStreamData(SDL_AudioStream *stream, void *voidbuf, int len) +int SDL_GetAudioStreamDataAdjustGain(SDL_AudioStream *stream, void *voidbuf, int len, float extra_gain) { Uint8 *buf = (Uint8 *) voidbuf; @@ -970,6 +1026,7 @@ int SDL_GetAudioStreamData(SDL_AudioStream *stream, void *voidbuf, int len) return -1; } + const float gain = stream->gain * extra_gain; const int dst_frame_size = SDL_AUDIO_FRAMESIZE(stream->dst_spec); len -= len % dst_frame_size; // chop off any fractional sample frame. @@ -1029,7 +1086,7 @@ int SDL_GetAudioStreamData(SDL_AudioStream *stream, void *voidbuf, int len) output_frames = SDL_min(output_frames, chunk_size); output_frames = (int) SDL_min(output_frames, available_frames); - if (GetAudioStreamDataInternal(stream, &buf[total], output_frames) != 0) { + if (GetAudioStreamDataInternal(stream, &buf[total], output_frames, gain) != 0) { total = total ? total : -1; break; } @@ -1046,6 +1103,11 @@ int SDL_GetAudioStreamData(SDL_AudioStream *stream, void *voidbuf, int len) return total; } +int SDL_GetAudioStreamData(SDL_AudioStream *stream, void *voidbuf, int len) +{ + return SDL_GetAudioStreamDataAdjustGain(stream, voidbuf, len, 1.0f); +} + // number of converted/resampled bytes available for output int SDL_GetAudioStreamAvailable(SDL_AudioStream *stream) { diff --git a/src/audio/SDL_audioqueue.c b/src/audio/SDL_audioqueue.c index 41c8af3b45..e9110b5ac0 100644 --- a/src/audio/SDL_audioqueue.c +++ b/src/audio/SDL_audioqueue.c @@ -514,7 +514,7 @@ static const Uint8 *PeekIntoAudioQueueFuture(SDL_AudioQueue *queue, Uint8 *data, const Uint8 *SDL_ReadFromAudioQueue(SDL_AudioQueue *queue, Uint8 *dst, SDL_AudioFormat dst_format, int dst_channels, const Uint8 *dst_map, int past_frames, int present_frames, int future_frames, - Uint8 *scratch) + Uint8 *scratch, float gain) { SDL_AudioTrack *track = queue->head; @@ -552,7 +552,7 @@ const Uint8 *SDL_ReadFromAudioQueue(SDL_AudioQueue *queue, // Do we still need to copy/convert the data? if (dst) { ConvertAudio(past_frames + present_frames + future_frames, ptr, - src_format, src_channels, src_map, dst, dst_format, dst_channels, dst_map, scratch); + src_format, src_channels, src_map, dst, dst_format, dst_channels, dst_map, scratch, gain); ptr = dst; } @@ -570,19 +570,19 @@ const Uint8 *SDL_ReadFromAudioQueue(SDL_AudioQueue *queue, Uint8 *ptr = dst; if (src_past_bytes) { - ConvertAudio(past_frames, PeekIntoAudioQueuePast(queue, scratch, src_past_bytes), src_format, src_channels, src_map, dst, dst_format, dst_channels, dst_map, scratch); + ConvertAudio(past_frames, PeekIntoAudioQueuePast(queue, scratch, src_past_bytes), src_format, src_channels, src_map, dst, dst_format, dst_channels, dst_map, scratch, gain); dst += dst_past_bytes; scratch += dst_past_bytes; } if (src_present_bytes) { - ConvertAudio(present_frames, ReadFromAudioQueue(queue, scratch, src_present_bytes), src_format, src_channels, src_map, dst, dst_format, dst_channels, dst_map, scratch); + ConvertAudio(present_frames, ReadFromAudioQueue(queue, scratch, src_present_bytes), src_format, src_channels, src_map, dst, dst_format, dst_channels, dst_map, scratch, gain); dst += dst_present_bytes; scratch += dst_present_bytes; } if (src_future_bytes) { - ConvertAudio(future_frames, PeekIntoAudioQueueFuture(queue, scratch, src_future_bytes), src_format, src_channels, src_map, dst, dst_format, dst_channels, dst_map, scratch); + ConvertAudio(future_frames, PeekIntoAudioQueueFuture(queue, scratch, src_future_bytes), src_format, src_channels, src_map, dst, dst_format, dst_channels, dst_map, scratch, gain); dst += dst_future_bytes; scratch += dst_future_bytes; } diff --git a/src/audio/SDL_audioqueue.h b/src/audio/SDL_audioqueue.h index 26675ce295..46dce2d19b 100644 --- a/src/audio/SDL_audioqueue.h +++ b/src/audio/SDL_audioqueue.h @@ -69,7 +69,7 @@ size_t SDL_NextAudioQueueIter(SDL_AudioQueue *queue, void **inout_iter, SDL_Audi const Uint8 *SDL_ReadFromAudioQueue(SDL_AudioQueue *queue, Uint8 *dst, SDL_AudioFormat dst_format, int dst_channels, const Uint8 *dst_map, int past_frames, int present_frames, int future_frames, - Uint8 *scratch); + Uint8 *scratch, float gain); // Get the total number of bytes currently queued size_t SDL_GetAudioQueueQueued(SDL_AudioQueue *queue); diff --git a/src/audio/SDL_sysaudio.h b/src/audio/SDL_sysaudio.h index 9973be9bf1..b5c2335e2c 100644 --- a/src/audio/SDL_sysaudio.h +++ b/src/audio/SDL_sysaudio.h @@ -118,17 +118,19 @@ extern SDL_bool SDL_ChannelMapIsBogus(const Uint8 *map, int channels); extern void ConvertAudio(int num_frames, const void *src, SDL_AudioFormat src_format, int src_channels, const Uint8 *src_map, void *dst, SDL_AudioFormat dst_format, int dst_channels, const Uint8 *dst_map, - void* scratch); + void* scratch, float gain); // Compare two SDL_AudioSpecs, return SDL_TRUE if they match exactly. // Using SDL_memcmp directly isn't safe, since potential padding (and unused parts of the channel map) might not be initialized. extern SDL_bool SDL_AudioSpecsEqual(const SDL_AudioSpec *a, const SDL_AudioSpec *b); - // Special case to let something in SDL_audiocvt.c access something in SDL_audio.c. Don't use this. extern void OnAudioStreamCreated(SDL_AudioStream *stream); extern void OnAudioStreamDestroy(SDL_AudioStream *stream); +// This just lets audio playback apply logical device gain at the same time as audiostream gain, so it's one multiplication instead of thousands. +extern int SDL_GetAudioStreamDataAdjustGain(SDL_AudioStream *stream, void *voidbuf, int len, float extra_gain); + typedef struct SDL_AudioDriverImpl { void (*DetectDevices)(SDL_AudioDevice **default_playback, SDL_AudioDevice **default_recording); @@ -196,6 +198,7 @@ struct SDL_AudioStream SDL_AudioSpec src_spec; SDL_AudioSpec dst_spec; float freq_ratio; + float gain; struct SDL_AudioQueue* queue; @@ -230,6 +233,9 @@ struct SDL_LogicalAudioDevice // If whole logical device is paused (process no streams bound to this device). SDL_AtomicInt paused; + // Volume of the device output. + float gain; + // double-linked list of all audio streams currently bound to this opened device. SDL_AudioStream *bound_streams; diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym index 40d7177a8a..1f5f8ca542 100644 --- a/src/dynapi/SDL_dynapi.sym +++ b/src/dynapi/SDL_dynapi.sym @@ -167,6 +167,7 @@ SDL3_0.0.0 { SDL_GetAssertionHandler; SDL_GetAssertionReport; SDL_GetAudioDeviceFormat; + SDL_GetAudioDeviceGain; SDL_GetAudioDeviceName; SDL_GetAudioDriver; SDL_GetAudioPlaybackDevices; @@ -176,6 +177,7 @@ SDL3_0.0.0 { SDL_GetAudioStreamDevice; SDL_GetAudioStreamFormat; SDL_GetAudioStreamFrequencyRatio; + SDL_GetAudioStreamGain; SDL_GetAudioStreamProperties; SDL_GetAudioStreamQueued; SDL_GetBasePath; @@ -682,9 +684,11 @@ SDL3_0.0.0 { SDL_SendJoystickEffect; SDL_SendJoystickVirtualSensorData; SDL_SetAssertionHandler; + SDL_SetAudioDeviceGain; SDL_SetAudioPostmixCallback; SDL_SetAudioStreamFormat; SDL_SetAudioStreamFrequencyRatio; + SDL_SetAudioStreamGain; SDL_SetAudioStreamGetCallback; SDL_SetAudioStreamPutCallback; SDL_SetBooleanProperty; diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h index 76f69d781b..36fec9b648 100644 --- a/src/dynapi/SDL_dynapi_overrides.h +++ b/src/dynapi/SDL_dynapi_overrides.h @@ -191,6 +191,7 @@ #define SDL_GetAndroidSDKVersion SDL_GetAndroidSDKVersion_REAL #define SDL_GetAssertionHandler SDL_GetAssertionHandler_REAL #define SDL_GetAssertionReport SDL_GetAssertionReport_REAL +#define SDL_GetAudioDeviceGain SDL_GetAudioDeviceGain_REAL #define SDL_GetAudioDeviceFormat SDL_GetAudioDeviceFormat_REAL #define SDL_GetAudioDeviceName SDL_GetAudioDeviceName_REAL #define SDL_GetAudioDriver SDL_GetAudioDriver_REAL @@ -201,6 +202,7 @@ #define SDL_GetAudioStreamDevice SDL_GetAudioStreamDevice_REAL #define SDL_GetAudioStreamFormat SDL_GetAudioStreamFormat_REAL #define SDL_GetAudioStreamFrequencyRatio SDL_GetAudioStreamFrequencyRatio_REAL +#define SDL_GetAudioStreamGain SDL_GetAudioStreamGain_REAL #define SDL_GetAudioStreamProperties SDL_GetAudioStreamProperties_REAL #define SDL_GetAudioStreamQueued SDL_GetAudioStreamQueued_REAL #define SDL_GetBasePath SDL_GetBasePath_REAL @@ -707,9 +709,11 @@ #define SDL_SendJoystickEffect SDL_SendJoystickEffect_REAL #define SDL_SendJoystickVirtualSensorData SDL_SendJoystickVirtualSensorData_REAL #define SDL_SetAssertionHandler SDL_SetAssertionHandler_REAL +#define SDL_SetAudioDeviceGain SDL_SetAudioDeviceGain_REAL #define SDL_SetAudioPostmixCallback SDL_SetAudioPostmixCallback_REAL #define SDL_SetAudioStreamFormat SDL_SetAudioStreamFormat_REAL #define SDL_SetAudioStreamFrequencyRatio SDL_SetAudioStreamFrequencyRatio_REAL +#define SDL_SetAudioStreamGain SDL_SetAudioStreamGain_REAL #define SDL_SetAudioStreamGetCallback SDL_SetAudioStreamGetCallback_REAL #define SDL_SetAudioStreamPutCallback SDL_SetAudioStreamPutCallback_REAL #define SDL_SetBooleanProperty SDL_SetBooleanProperty_REAL diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h index 50613fd993..16c54b3658 100644 --- a/src/dynapi/SDL_dynapi_procs.h +++ b/src/dynapi/SDL_dynapi_procs.h @@ -212,6 +212,7 @@ SDL_DYNAPI_PROC(int,SDL_GetAndroidSDKVersion,(void),(),return) SDL_DYNAPI_PROC(SDL_AssertionHandler,SDL_GetAssertionHandler,(void **a),(a),return) SDL_DYNAPI_PROC(const SDL_AssertData*,SDL_GetAssertionReport,(void),(),return) SDL_DYNAPI_PROC(int,SDL_GetAudioDeviceFormat,(SDL_AudioDeviceID a, SDL_AudioSpec *b, int *c),(a,b,c),return) +SDL_DYNAPI_PROC(float,SDL_GetAudioDeviceGain,(SDL_AudioDeviceID a),(a),return) SDL_DYNAPI_PROC(const char*,SDL_GetAudioDeviceName,(SDL_AudioDeviceID a),(a),return) SDL_DYNAPI_PROC(const char*,SDL_GetAudioDriver,(int a),(a),return) SDL_DYNAPI_PROC(SDL_AudioDeviceID*,SDL_GetAudioPlaybackDevices,(int *a),(a),return) @@ -221,6 +222,7 @@ SDL_DYNAPI_PROC(int,SDL_GetAudioStreamData,(SDL_AudioStream *a, void *b, int c), SDL_DYNAPI_PROC(SDL_AudioDeviceID,SDL_GetAudioStreamDevice,(SDL_AudioStream *a),(a),return) SDL_DYNAPI_PROC(int,SDL_GetAudioStreamFormat,(SDL_AudioStream *a, SDL_AudioSpec *b, SDL_AudioSpec *c),(a,b,c),return) SDL_DYNAPI_PROC(float,SDL_GetAudioStreamFrequencyRatio,(SDL_AudioStream *a),(a),return) +SDL_DYNAPI_PROC(float,SDL_GetAudioStreamGain,(SDL_AudioStream *a),(a),return) SDL_DYNAPI_PROC(SDL_PropertiesID,SDL_GetAudioStreamProperties,(SDL_AudioStream *a),(a),return) SDL_DYNAPI_PROC(int,SDL_GetAudioStreamQueued,(SDL_AudioStream *a),(a),return) SDL_DYNAPI_PROC(char*,SDL_GetBasePath,(void),(),return) @@ -718,9 +720,11 @@ SDL_DYNAPI_PROC(int,SDL_SendGamepadEffect,(SDL_Gamepad *a, const void *b, int c) SDL_DYNAPI_PROC(int,SDL_SendJoystickEffect,(SDL_Joystick *a, const void *b, int c),(a,b,c),return) SDL_DYNAPI_PROC(int,SDL_SendJoystickVirtualSensorData,(SDL_Joystick *a, SDL_SensorType b, Uint64 c, const float *d, int e),(a,b,c,d,e),return) SDL_DYNAPI_PROC(void,SDL_SetAssertionHandler,(SDL_AssertionHandler a, void *b),(a,b),) +SDL_DYNAPI_PROC(int,SDL_SetAudioDeviceGain,(SDL_AudioDeviceID a, float b),(a,b),return) SDL_DYNAPI_PROC(int,SDL_SetAudioPostmixCallback,(SDL_AudioDeviceID a, SDL_AudioPostmixCallback b, void *c),(a,b,c),return) SDL_DYNAPI_PROC(int,SDL_SetAudioStreamFormat,(SDL_AudioStream *a, const SDL_AudioSpec *b, const SDL_AudioSpec *c),(a,b,c),return) SDL_DYNAPI_PROC(int,SDL_SetAudioStreamFrequencyRatio,(SDL_AudioStream *a, float b),(a,b),return) +SDL_DYNAPI_PROC(int,SDL_SetAudioStreamGain,(SDL_AudioStream *a, float b),(a,b),return) SDL_DYNAPI_PROC(int,SDL_SetAudioStreamGetCallback,(SDL_AudioStream *a, SDL_AudioStreamCallback b, void *c),(a,b,c),return) SDL_DYNAPI_PROC(int,SDL_SetAudioStreamPutCallback,(SDL_AudioStream *a, SDL_AudioStreamCallback b, void *c),(a,b,c),return) SDL_DYNAPI_PROC(int,SDL_SetBooleanProperty,(SDL_PropertiesID a, const char *b, SDL_bool c),(a,b,c),return) diff --git a/test/testaudio.c b/test/testaudio.c index 9f8d561e66..728f4b6705 100644 --- a/test/testaudio.c +++ b/test/testaudio.c @@ -94,6 +94,7 @@ struct Thing float z; Uint8 r, g, b, a; float progress; + float meter; float scale; Uint64 createticks; Texture *texture; @@ -103,6 +104,7 @@ struct Thing void (*ondrag)(Thing *thing, int button, float x, float y); void (*ondrop)(Thing *thing, int button, float x, float y); void (*ondraw)(Thing *thing, SDL_Renderer *renderer); + void (*onmousewheel)(Thing *thing, float y); Thing *prev; Thing *next; @@ -355,6 +357,16 @@ static void DrawOneThing(SDL_Renderer *renderer, Thing *thing) SDL_SetRenderDrawColor(renderer, 255, 255, 255, 128); SDL_RenderFillRect(renderer, &r); } + + if (thing->meter > 0.0f) { + SDL_FRect r; + r.w = 10.0f; + r.h = thing->rect.h * ((thing->meter > 1.0f) ? 1.0f : thing->meter); + r.x = thing->rect.x + thing->rect.w + 2.0f; + r.y = (thing->rect.y + thing->rect.h) - r.h; + SDL_SetRenderDrawColor(renderer, 255, 255, 255, 128); + SDL_RenderFillRect(renderer, &r); + } } static void DrawThings(SDL_Renderer *renderer) @@ -581,6 +593,13 @@ static void StreamThing_ondrop(Thing *thing, int button, float x, float y) } } +static void StreamThing_onmousewheel(Thing *thing, float y) +{ + thing->meter += y * 0.01f; + thing->meter = SDL_clamp(thing->meter, 0.0f, 1.0f); + SDL_SetAudioStreamGain(thing->data.stream.stream, thing->meter); +} + static void StreamThing_ondraw(Thing *thing, SDL_Renderer *renderer) { if (thing->line_connected_to) { /* are we playing? Update progress bar, and bounce the levels a little. */ @@ -618,7 +637,9 @@ static Thing *CreateStreamThing(const SDL_AudioSpec *spec, const Uint8 *buf, con thing->ondrag = StreamThing_ondrag; thing->ondrop = StreamThing_ondrop; thing->ondraw = StreamThing_ondraw; + thing->onmousewheel = StreamThing_onmousewheel; thing->can_be_dropped_onto = can_be_dropped_onto; + thing->meter = 1.0f; /* gain. */ } return thing; } @@ -898,6 +919,13 @@ static void LogicalDeviceThing_ondraw(Thing *thing, SDL_Renderer *renderer) } } +static void LogicalDeviceThing_onmousewheel(Thing *thing, float y) +{ + thing->meter += y * 0.01f; + thing->meter = SDL_clamp(thing->meter, 0.0f, 1.0f); + SDL_SetAudioDeviceGain(thing->data.logdev.devid, thing->meter); +} + static Thing *CreateLogicalDeviceThing(Thing *parent, const SDL_AudioDeviceID which, const float x, const float y) { static const ThingType can_be_dropped_onto[] = { THING_TRASHCAN, THING_NULL }; @@ -917,10 +945,12 @@ static Thing *CreateLogicalDeviceThing(Thing *parent, const SDL_AudioDeviceID wh SDL_SetTextureBlendMode(thing->data.logdev.visualizer, SDL_BLENDMODE_BLEND); } thing->line_connected_to = physthing; + thing->meter = 1.0f; thing->ontick = LogicalDeviceThing_ontick; thing->ondrag = DeviceThing_ondrag; thing->ondrop = LogicalDeviceThing_ondrop; thing->ondraw = LogicalDeviceThing_ondraw; + thing->onmousewheel = LogicalDeviceThing_onmousewheel; thing->can_be_dropped_onto = can_be_dropped_onto; SetLogicalDeviceTitlebar(thing); @@ -1181,7 +1211,10 @@ int SDL_AppEvent(void *appstate, const SDL_Event *event) break; case SDL_EVENT_MOUSE_WHEEL: - UpdateMouseOver(event->wheel.mouse_x, event->wheel.mouse_y); + thing = UpdateMouseOver(event->wheel.mouse_x, event->wheel.mouse_y); + if (thing && thing->onmousewheel) { + thing->onmousewheel(thing, event->wheel.y * ((event->wheel.direction == SDL_MOUSEWHEEL_FLIPPED) ? -1.0f : 1.0f)); + } break; case SDL_EVENT_KEY_DOWN: