diff --git a/examples/audio/audio_mixed_processor.c b/examples/audio/audio_mixed_processor.c new file mode 100644 index 000000000..3a008f3e2 --- /dev/null +++ b/examples/audio/audio_mixed_processor.c @@ -0,0 +1,123 @@ +/******************************************************************************************* +* +* raylib [audio] example - Mixed audio processing +* +* Example originally created with raylib 4.2, last time updated with raylib 4.2 +* +* Example contributed by hkc (@hatkidchan) and reviewed by Ramon Santamaria (@raysan5) +* +* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified, +* BSD-like license that allows static linking with closed source software +* +* Copyright (c) 2023 hkc (@hatkidchan) +* +********************************************************************************************/ +#include "raylib.h" +#include + +static float exponent = 1.0f; // Audio exponentiation value +static float averageVolume[400] = { 0.0f }; // Average volume history + +//------------------------------------------------------------------------------------ +// Audio processing function +//------------------------------------------------------------------------------------ +void ProcessAudio(void *buffer, unsigned int frames) +{ + float *samples = (float *)buffer; // Samples internally stored as s + float average = 0.0f; // Temporary average volume + + for (unsigned int frame = 0; frame < frames; frame++) + { + float *left = &samples[frame * 2 + 0], *right = &samples[frame * 2 + 1]; + + *left = powf(fabsf(*left), exponent) * ( (*left < 0.0f)? -1.0f : 1.0f ); + *right = powf(fabsf(*right), exponent) * ( (*right < 0.0f)? -1.0f : 1.0f ); + + average += fabsf(*left) / frames; // accumulating average volume + average += fabsf(*right) / frames; + } + + // Moving history to the left + for (int i = 0; i < 399; i++) averageVolume[i] = averageVolume[i + 1]; + + averageVolume[399] = average; // Adding last average value +} + +//------------------------------------------------------------------------------------ +// Program main entry point +//------------------------------------------------------------------------------------ +int main(void) +{ + // Initialization + //-------------------------------------------------------------------------------------- + const int screenWidth = 800; + const int screenHeight = 450; + + InitWindow(screenWidth, screenHeight, "raylib [audio] example - processing mixed output"); + + InitAudioDevice(); // Initialize audio device + + AttachAudioMixedProcessor(ProcessAudio); + + Music music = LoadMusicStream("resources/country.mp3"); + Sound sound = LoadSound("resources/coin.wav"); + + PlayMusicStream(music); + + SetTargetFPS(60); // Set our game to run at 60 frames-per-second + //-------------------------------------------------------------------------------------- + + // Main game loop + while (!WindowShouldClose()) // Detect window close button or ESC key + { + // Update + //---------------------------------------------------------------------------------- + UpdateMusicStream(music); // Update music buffer with new stream data + + // Modify processing variables + //---------------------------------------------------------------------------------- + if (IsKeyPressed(KEY_LEFT)) exponent -= 0.05f; + if (IsKeyPressed(KEY_RIGHT)) exponent += 0.05f; + + if (exponent <= 0.5f) exponent = 0.5f; + if (exponent >= 3.0f) exponent = 3.0f; + + if (IsKeyPressed(KEY_SPACE)) PlaySound(sound); + + // Draw + //---------------------------------------------------------------------------------- + BeginDrawing(); + + ClearBackground(RAYWHITE); + + DrawText("MUSIC SHOULD BE PLAYING!", 255, 150, 20, LIGHTGRAY); + + DrawText(TextFormat("EXPONENT = %.2f", exponent), 215, 180, 20, LIGHTGRAY); + + DrawRectangle(199, 199, 402, 34, LIGHTGRAY); + for (int i = 0; i < 400; i++) + { + DrawLine(201 + i, 232 - averageVolume[i] * 32, 201 + i, 232, MAROON); + } + DrawRectangleLines(199, 199, 402, 34, GRAY); + + DrawText("PRESS SPACE TO PLAY OTHER SOUND", 200, 250, 20, LIGHTGRAY); + DrawText("USE LEFT AND RIGHT ARROWS TO ALTER DISTORTION", 140, 280, 20, LIGHTGRAY); + + EndDrawing(); + //---------------------------------------------------------------------------------- + } + + // De-Initialization + //-------------------------------------------------------------------------------------- + UnloadMusicStream(music); // Unload music stream buffers from RAM + + DetachAudioMixedProcessor(ProcessAudio); // Disconnect audio processor + + CloseAudioDevice(); // Close audio device (music streaming is automatically stopped) + + CloseWindow(); // Close window and OpenGL context + //-------------------------------------------------------------------------------------- + + return 0; +} diff --git a/examples/audio/audio_mixed_processor.png b/examples/audio/audio_mixed_processor.png new file mode 100644 index 000000000..8575a836d Binary files /dev/null and b/examples/audio/audio_mixed_processor.png differ diff --git a/src/raudio.c b/src/raudio.c index 443fb924e..5f5332153 100644 --- a/src/raudio.c +++ b/src/raudio.c @@ -372,6 +372,7 @@ typedef struct AudioData { AudioBuffer *last; // Pointer to last AudioBuffer in the list int defaultSize; // Default audio buffer size for audio streams } Buffer; + rAudioProcessor *mixedProcessor; struct { unsigned int poolCounter; // AudioBuffer pointers pool counter AudioBuffer *pool[MAX_AUDIO_BUFFER_POOL_CHANNELS]; // Multichannel AudioBuffer pointers pool @@ -388,7 +389,8 @@ static AudioData AUDIO = { // Global AUDIO context // After some math, considering a sampleRate of 48000, a buffer refill rate of 1/60 seconds and a // standard double-buffering system, a 4096 samples buffer has been chosen, it should be enough // In case of music-stalls, just increase this number - .Buffer.defaultSize = 0 + .Buffer.defaultSize = 0, + .mixedProcessor = NULL }; //---------------------------------------------------------------------------------- @@ -2278,6 +2280,60 @@ void DetachAudioStreamProcessor(AudioStream stream, AudioCallback process) ma_mutex_unlock(&AUDIO.System.lock); } +// Add processor to audio pipeline. Order of processors is important +// Works the same way as {Attach,Detach}AudioStreamProcessor functions, except +// these two work on the already mixed output just before sending it to the +// sound hardware. +void AttachAudioMixedProcessor(AudioCallback process) +{ + ma_mutex_lock(&AUDIO.System.lock); + + rAudioProcessor *processor = (rAudioProcessor *)RL_CALLOC(1, sizeof(rAudioProcessor)); + processor->process = process; + + rAudioProcessor *last = AUDIO.mixedProcessor; + + while (last && last->next) + { + last = last->next; + } + if (last) + { + processor->prev = last; + last->next = processor; + } + else AUDIO.mixedProcessor = processor; + + ma_mutex_unlock(&AUDIO.System.lock); +} + +void DetachAudioMixedProcessor(AudioCallback process) +{ + ma_mutex_lock(&AUDIO.System.lock); + + rAudioProcessor *processor = AUDIO.mixedProcessor; + + while (processor) + { + rAudioProcessor *next = processor->next; + rAudioProcessor *prev = processor->prev; + + if (processor->process == process) + { + if (AUDIO.mixedProcessor == processor) AUDIO.mixedProcessor = next; + if (prev) prev->next = next; + if (next) next->prev = prev; + + RL_FREE(processor); + } + + processor = next; + } + + ma_mutex_unlock(&AUDIO.System.lock); +} + + //---------------------------------------------------------------------------------- // Module specific Functions Definition //---------------------------------------------------------------------------------- @@ -2519,6 +2575,13 @@ static void OnSendAudioDataToDevice(ma_device *pDevice, void *pFramesOut, const } } + rAudioProcessor *processor = AUDIO.mixedProcessor; + while (processor) + { + processor->process(pFramesOut, frameCount); + processor = processor->next; + } + ma_mutex_unlock(&AUDIO.System.lock); } diff --git a/src/raylib.h b/src/raylib.h index c1b85abdf..73c6d2242 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -1580,6 +1580,9 @@ RLAPI void SetAudioStreamCallback(AudioStream stream, AudioCallback callback); RLAPI void AttachAudioStreamProcessor(AudioStream stream, AudioCallback processor); // Attach audio stream processor to stream RLAPI void DetachAudioStreamProcessor(AudioStream stream, AudioCallback processor); // Detach audio stream processor from stream +RLAPI void AttachAudioMixedProcessor(AudioCallback processor); // Attach audio stream processor to the entire audio pipeline +RLAPI void DetachAudioMixedProcessor(AudioCallback processor); // Detach audio stream processor from the entire audio pipeline + #if defined(__cplusplus) } #endif