From 1267eafe1f4b56593ea12db6a09b5a1071d3dcd3 Mon Sep 17 00:00:00 2001 From: ptarabbia Date: Thu, 16 Dec 2021 09:18:59 -0500 Subject: [PATCH] Make a separate demo for audio effect processors. Return information about the system audio in InitAudioDevice() --- examples/audio/audio_effect_processor.c | 179 ++++++++++++++++++++++ examples/audio/audio_effect_processor.png | Bin 0 -> 4820 bytes examples/audio/audio_music_stream.c | 34 ---- src/raudio.c | 21 ++- src/raudio.h | 10 +- src/raylib.h | 9 +- 6 files changed, 212 insertions(+), 41 deletions(-) create mode 100644 examples/audio/audio_effect_processor.c create mode 100644 examples/audio/audio_effect_processor.png diff --git a/examples/audio/audio_effect_processor.c b/examples/audio/audio_effect_processor.c new file mode 100644 index 000000000..6bf2c2db8 --- /dev/null +++ b/examples/audio/audio_effect_processor.c @@ -0,0 +1,179 @@ +/******************************************************************************************* +* +* raylib [audio] example - Audio effects (streaming) +* +* This example has been created using raylib 1.3 (www.raylib.com) +* raylib is licensed under an unmodified zlib/libpng license (View raylib.h for details) +* +* Copyright (c) 2015 Ramon Santamaria (@raysan5) +* +********************************************************************************************/ + +#include "raylib.h" +#include + +#define AUDIO_THREAD_MUSIC_UPDATE true + +// a simple lowpass filter applied to the music stream +static void processFilterEffect(float* buffer, unsigned int nframes) +{ + static float low[2] = { 0.0f, 0.0f }; + static const float cutoff = 70.0f / 44100.0f; // 70 Hz lowpass filter + const float k = cutoff / (cutoff + 0.1591549431f); // RC filter formula + + for (unsigned int i = 0; i < nframes*2; i+=2) + { + float l = buffer[i], r = buffer[i + 1]; + low[0] += k * (l - low[0]); + low[1] += k * (r - low[1]); + buffer[i] = low[0]; + buffer[i + 1] = low[1]; + } +} + + +float* delayBuffer = NULL; +unsigned int delayBufferSize = 0; +unsigned int delayReadIndex = 2; +unsigned int delayWriteIndex = 0; +static void processDelayEffect(float* buffer, unsigned int nframes) +{ + for (unsigned int i = 0; i < nframes*2; i+=2) + { + float leftDelay = delayBuffer[delayReadIndex++]; + float rightDelay = delayBuffer[delayReadIndex++]; + if (delayReadIndex == delayBufferSize) delayReadIndex = 0; + + buffer[i] = 0.5f * buffer[i] + 0.5f * leftDelay; + buffer[i+1] = 0.5f * buffer[i+1] + 0.5f * rightDelay; + + delayBuffer[delayWriteIndex++] = buffer[i]; + delayBuffer[delayWriteIndex++] = buffer[i+1]; + if (delayWriteIndex == delayBufferSize) delayWriteIndex = 0; + } +} + +int main(void) +{ + // Initialization + //-------------------------------------------------------------------------------------- + const int screenWidth = 800; + const int screenHeight = 450; + + InitWindow(screenWidth, screenHeight, "raylib [audio] example - audio effects"); + + AudioDeviceInfo audioDevice = InitAudioDevice(); // Initialize audio device + if (!audioDevice.channels) return -1; + + // allocate buffer for the delay effect + delayBufferSize = audioDevice.channels * audioDevice.sampleRate; // 1s delay + delayBuffer = (float*)RL_CALLOC(delayBufferSize, sizeof(float)); + + Music music = LoadMusicStream("resources/country.mp3"); + music.background = AUDIO_THREAD_MUSIC_UPDATE; + + PlayMusicStream(music); + + float timePlayed = 0.0f; + bool pause = false; + bool hasFilter = false; + bool hasDelay = false; + + 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 + //---------------------------------------------------------------------------------- + if (!AUDIO_THREAD_MUSIC_UPDATE) + { + UpdateMusicStream(music); // Update music buffer with new stream data + } + + // Restart music playing (stop and play) + if (IsKeyPressed(KEY_SPACE)) + { + StopMusicStream(music); + PlayMusicStream(music); + } + + // Pause/Resume music playing + if (IsKeyPressed(KEY_P)) + { + pause = !pause; + + if (pause) PauseMusicStream(music); + else ResumeMusicStream(music); + } + + // Add/Remove effects + if (IsKeyPressed(KEY_F)) + { + hasFilter = !hasFilter; + if (hasFilter) + { + AddAudioStreamProcessor(music.stream, &processFilterEffect); + } + else + { + RemoveAudioStreamProcessor(music.stream, &processFilterEffect); + } + } + + if (IsKeyPressed(KEY_D)) + { + hasDelay = !hasDelay; + if (hasDelay) + { + AddAudioStreamProcessor(music.stream, &processDelayEffect); + } + else + { + RemoveAudioStreamProcessor(music.stream, &processDelayEffect); + } + } + + // Get timePlayed scaled to bar dimensions (400 pixels) + timePlayed = GetMusicTimePlayed(music)/GetMusicTimeLength(music)*400; + + if (timePlayed > 400) StopMusicStream(music); + //---------------------------------------------------------------------------------- + + // Draw + //---------------------------------------------------------------------------------- + BeginDrawing(); + + ClearBackground(RAYWHITE); + + DrawText("MUSIC SHOULD BE PLAYING!", 255, 150, 20, LIGHTGRAY); + + DrawRectangle(200, 200, 400, 12, LIGHTGRAY); + DrawRectangle(200, 200, (int)timePlayed, 12, MAROON); + DrawRectangleLines(200, 200, 400, 12, GRAY); + DrawRectangleLines(200, 200, 400, 12, GRAY); + + DrawText("PRESS SPACE TO RESTART MUSIC", 215, 250, 20, LIGHTGRAY); + DrawText("PRESS P TO PAUSE/RESUME MUSIC", 208, 280, 20, LIGHTGRAY); + DrawText("PRESS F TO ADD/REMOVE FILTER EFFECT", 180, 310, 20, LIGHTGRAY); + DrawText("PRESS D TO ADD/REMOVE DELAY EFFECT", 180, 340, 20, LIGHTGRAY); + + EndDrawing(); + //---------------------------------------------------------------------------------- + } + + // De-Initialization + //-------------------------------------------------------------------------------------- + + UnloadMusicStream(music); // Unload music stream buffers from RAM + + CloseAudioDevice(); // Close audio device (music streaming is automatically stopped) + + RL_FREE(delayBuffer); // Free delay buffer + + CloseWindow(); // Close window and OpenGL context + //-------------------------------------------------------------------------------------- + + return 0; +} \ No newline at end of file diff --git a/examples/audio/audio_effect_processor.png b/examples/audio/audio_effect_processor.png new file mode 100644 index 0000000000000000000000000000000000000000..785055419bfe28f88358669d0e014b4d24820cc7 GIT binary patch literal 4820 zcmeHLeOQv`8Yf04wXK;>%c;|a%`NOhO%pL#D=qtS$5OHUz@@TeMu9?3fvB~vos^=o z4@+5Fi;)N-+{&?Q^dhh4H z@8`LH_wRY$=YBqUcl*Ydd|vedfj}>%ZrYFz0=*Cp0?oPY1qLMN=OnU#S5#Rtx@`y`@E4+fqAqB*JxF{LHM0nG=#p70^ZXD70udmAptsMrgF#;YXb`9XJRcOk6*C9)k#`j6 zkRYFGX9HcE9Cm&Ftlp`g^ki;F<803pTCx|&_YDCEbo+leT1B7xr_!&204b$-0U3yx z1gDI~cS z#3V!Np?Uu3TWU8<=yqJF|Jp69rX?bd4Tv4qYn5x7*X>sgVac2)1EH;~{W}0(erQd( zHSt@1h%Avnl) zz_7H!iHxl5Tx(~Nzc~G1GzRiFh7)Qzl8r}e%8#re!eOFq4Rh-P61WW#xE!LYbYF5&Kk}m| z`_q%>(=81stSsy@VE1cGPoKbtfsZHsvbBAR9X2h=d!YTEK%##eWluv?{ge*7_4eMp!P zg%q6~Ac6tbUBX-EiIIdE6Amgd4_=SNNzU!69PgJ7W>{Yr-8WH&9V!+1#%FBlS*D%L zT~y~4rHmBt1d(9eq;%8}fF3m*eFJ>_sW;bYN-dcL<4Rj7bdlB8y({J6%F8zw(mU-Y zKYwr|{&^R8{)#P_1Jb@Z8s;cjrra!Ja9Nwi&mfR(w}sbRJ~P^Rlf#M|R!>pY0wFHQ zzUXEYV4s*5FGM{U<%@-xj`Bj+hGp!LsmB2gHKKr`K60rxMJn>F)Dh3;exs1J7kNi1 z*SJ52yN_ISyw2-sBJc+&2wrx6uC>1@T8|9oq`{a>AFd|oYB5Z3GY;?$QK?~GT#Y?v zQsaxZXndmfZ3AZLnE$!?*z{Vuh-4ED7NJJNh$`WoMQF=f4$V+UUn;x#%1@?KX*RhR z*C_77CLrd~J39b?)QkL`jucbh{+T?r_VmD8Y^ycVdD8Kfc||S0+>Dif(*Acypg!1> zon%|O%X5I$rncB3%DXvjZA#=73( zkR1ctNl7oCTp~!=IS~-!Y0jp0IbpoPN9i(^0c}FdW5mlN7J5?yK1#nNHE=#XU=FydiGR>kr`zM#C9ktRvQ7G{P!Ra^{tC)0NcYP9bHT^gdvm2hK3vBHB(uZ@3G|9ukU=|YbKUacvj3vWZAhfY#SpAfV~hS7 z5MS)rM6azIG{zxRO9ha?9zS%*HFJQJj|Y;(8ATA3WO%)1$HBW|6)&eEo}pz8OniC- zKv9PxIiV*!Z7_Eb|>U~9GYUe^lM$dK&KXPKOX0S)uj@RghK@B&)#|ZU zA1E$NhNooe^urYXo)o2t=#q>u+u!Rq8VbfF9M<{n5l3+xwYQrs%zCsqn^o%azR+ei zPIO_>G-@&bWZilfd~0uHr73Ii1=k94gy3F;z)}3>b0ok92UMsXX8<5fpb|s*pAC#8 z&icYaRIO>7$e5b^b(ql8->Abb45c-!!(30rSbE>ZSaM@U;whL{6g|UpMsJ_CE~0ma z8K&>pL%v-DB!9qE-d9mY>%wMeu1g~eXQZ`jQMT1Kb6h1U)NpO&3*Cr8B5vukP`*nA z#!W{R;!tVwnXz8pYEk7V@ii@LDm#P3HG2oi@I`%nYkOUly$!i?@iOw`1e=4$n zRoku~NoMpc_davjJX+-6zyWg{bzbbwU9@p@m6a&gZLsB(cleh)#Bu zgxWsrQZVbk=K$e$(b9K7m#1 zU3gSnv+l;>*))lwtv&_DhbWzLSx+!xrU6P~|0Dts{j0%&)%rBelXW$sKDN8X8 zuF=n?EI-{Th@(zwc3&JGazCrz;>ybkh7(GJdd09;RDAi&^i`^_+9Z|T1x~*_I8s0L zbJB~j_|kniXFqv`r82j4Ou^}Gfa0Nodpj%Een44cdEv~52Y9~~+SNUD8~PER(brW&Xkh?0k=R|<;Fw}YRD5J6e+D7Y2X zAu?xLuORh~2L5D+QTYs5dbTE`J(``3`dmXaC!BMvsL3fUEu?6>$S*#pD-nmvlh1@R zaxU+&r4xB(`w+6nv{maLL)CUSgc0j3v2|;03?j#TS23tcQQ1nXOl$yMK1MeNo;eP-MVc9SM literal 0 HcmV?d00001 diff --git a/examples/audio/audio_music_stream.c b/examples/audio/audio_music_stream.c index 3ea6fcb74..551116179 100644 --- a/examples/audio/audio_music_stream.c +++ b/examples/audio/audio_music_stream.c @@ -14,23 +14,6 @@ #define AUDIO_THREAD_MUSIC_UPDATE true -// a simple lowpass filter applied to the music stream -static void audioEffectDemo(float* buffer, unsigned int nframes) -{ - static float low[2] = { 0.0f, 0.0f }; - static const float cutoff = 70.0f / 44100.0f; // 70 Hz lowpass filter - const float k = cutoff / (cutoff + 0.1591549431f); // RC filter formula - - for (unsigned int i = 0; i < nframes*2; i+=2) - { - float l = buffer[i], r = buffer[i + 1]; - low[0] += k * (l - low[0]); - low[1] += k * (r - low[1]); - buffer[i] = low[0]; - buffer[i + 1] = low[1]; - } -} - int main(void) { // Initialization @@ -49,7 +32,6 @@ int main(void) float timePlayed = 0.0f; bool pause = false; - bool hasfx = false; SetTargetFPS(60); // Set our game to run at 60 frames-per-second //-------------------------------------------------------------------------------------- @@ -80,20 +62,6 @@ int main(void) else ResumeMusicStream(music); } - // Add/Remove effect - if (IsKeyPressed(KEY_F)) - { - hasfx = !hasfx; - if (hasfx) - { - AddAudioStreamProcessor(music.stream, &audioEffectDemo); - } - else - { - RemoveAudioStreamProcessor(music.stream, &audioEffectDemo); - } - } - // Get timePlayed scaled to bar dimensions (400 pixels) timePlayed = GetMusicTimePlayed(music)/GetMusicTimeLength(music)*400; @@ -111,11 +79,9 @@ int main(void) DrawRectangle(200, 200, 400, 12, LIGHTGRAY); DrawRectangle(200, 200, (int)timePlayed, 12, MAROON); DrawRectangleLines(200, 200, 400, 12, GRAY); - DrawRectangleLines(200, 200, 400, 12, GRAY); DrawText("PRESS SPACE TO RESTART MUSIC", 215, 250, 20, LIGHTGRAY); DrawText("PRESS P TO PAUSE/RESUME MUSIC", 208, 280, 20, LIGHTGRAY); - DrawText("PRESS F TO ADD/REMOVE EFFECT", 208, 310, 20, LIGHTGRAY); EndDrawing(); //---------------------------------------------------------------------------------- diff --git a/src/raudio.c b/src/raudio.c index f40b6bdfa..b8b9b891b 100644 --- a/src/raudio.c +++ b/src/raudio.c @@ -418,8 +418,13 @@ void UntrackAudioBuffer(AudioBuffer *buffer); // Module Functions Definition - Audio Device initialization and Closing //---------------------------------------------------------------------------------- // Initialize audio device -void InitAudioDevice(void) +AudioDeviceInfo InitAudioDevice(void) { + AudioDeviceInfo deviceInfo; + deviceInfo.sampleRate = 0; + deviceInfo.channels = 0; + deviceInfo.bufferSize = 0; + // Init audio context ma_context_config ctxConfig = ma_context_config_init(); ctxConfig.logCallback = OnLog; @@ -428,7 +433,7 @@ void InitAudioDevice(void) if (result != MA_SUCCESS) { TRACELOG(LOG_WARNING, "AUDIO: Failed to initialize context"); - return; + return deviceInfo; } // Init audio device @@ -449,7 +454,7 @@ void InitAudioDevice(void) { TRACELOG(LOG_WARNING, "AUDIO: Failed to initialize playback device"); ma_context_uninit(&AUDIO.System.context); - return; + return deviceInfo; } // Keep the device running the whole time. May want to consider doing something a bit smarter and only have the device running @@ -460,7 +465,7 @@ void InitAudioDevice(void) TRACELOG(LOG_WARNING, "AUDIO: Failed to start playback device"); ma_device_uninit(&AUDIO.System.device); ma_context_uninit(&AUDIO.System.context); - return; + return deviceInfo; } // Mixing happens on a seperate thread which means we need to synchronize. I'm using a mutex here to make things simple, but may @@ -470,7 +475,7 @@ void InitAudioDevice(void) TRACELOG(LOG_WARNING, "AUDIO: Failed to create mutex for mixing"); ma_device_uninit(&AUDIO.System.device); ma_context_uninit(&AUDIO.System.context); - return; + return deviceInfo; } // Init dummy audio buffers pool for multichannel sound playing @@ -489,6 +494,11 @@ void InitAudioDevice(void) TRACELOG(LOG_INFO, " > Periods size: %d", AUDIO.System.device.playback.internalPeriodSizeInFrames*AUDIO.System.device.playback.internalPeriods); AUDIO.System.isReady = true; + + deviceInfo.sampleRate = AUDIO.System.device.playback.internalSampleRate; + deviceInfo.channels = AUDIO.System.device.playback.channels; + deviceInfo.bufferSize = AUDIO.System.device.playback.internalPeriodSizeInFrames; + return deviceInfo; } // Close the audio device for all contexts @@ -2195,6 +2205,7 @@ static ma_uint32 ReadAudioBufferFramesInInternalFormat(AudioBuffer *audioBuffer, if (audioBuffer->audioCallback) { audioBuffer->audioCallback(framesOut, frameCount, audioBuffer->audioCallbackData); + audioBuffer->framesProcessed += frameCount; return frameCount; } diff --git a/src/raudio.h b/src/raudio.h index bc74895dc..c7270814f 100644 --- a/src/raudio.h +++ b/src/raudio.h @@ -114,6 +114,13 @@ typedef struct Music { void *ctxData; // Audio context data, depends on type } Music; +typedef struct AudioDeviceInfo +{ + unsigned int sampleRate; + unsigned int channels; + unsigned int bufferSize; +} AudioDeviceInfo; + //---------------------------------------------------------------------------------- // Global Variables Definition //---------------------------------------------------------------------------------- @@ -128,7 +135,7 @@ extern "C" { // Prevents name mangling of functions #endif // Audio device management functions -void InitAudioDevice(void); // Initialize audio device and context +AudioDeviceInfo InitAudioDevice(void); // Initialize audio device and context void CloseAudioDevice(void); // Close the audio device and context bool IsAudioDeviceReady(void); // Check if audio device has been initialized successfully void SetMasterVolume(float volume); // Set master volume (listener) @@ -193,6 +200,7 @@ void SetAudioStreamBufferSizeDefault(int size); // Default size void SetAudioStreamCallback(AudioStream stream, void callback(void*, unsigned int, void*), void* callbackData); // Audio thread callback to request new data void AddAudioStreamProcessor(AudioStream stream, void (*process)(float*, unsigned int)); void RemoveAudioStreamProcessor(AudioStream stream, void (*process)(float*, unsigned int)); + #ifdef __cplusplus } #endif diff --git a/src/raylib.h b/src/raylib.h index 0a9d81660..8d1984eba 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -453,6 +453,13 @@ typedef struct Music { void *ctxData; // Audio context data, depends on type } Music; +typedef struct AudioDeviceInfo +{ + unsigned int sampleRate; + unsigned int channels; + unsigned int bufferSize; +} AudioDeviceInfo; + // VrDeviceInfo, Head-Mounted-Display device parameters typedef struct VrDeviceInfo { int hResolution; // Horizontal resolution in pixels @@ -1469,7 +1476,7 @@ RLAPI RayCollision GetRayCollisionQuad(Ray ray, Vector3 p1, Vector3 p2, Vector3 //------------------------------------------------------------------------------------ // Audio device management functions -RLAPI void InitAudioDevice(void); // Initialize audio device and context +RLAPI AudioDeviceInfo InitAudioDevice(void); // Initialize audio device and context RLAPI void CloseAudioDevice(void); // Close the audio device and context RLAPI bool IsAudioDeviceReady(void); // Check if audio device has been initialized successfully RLAPI void SetMasterVolume(float volume); // Set master volume (listener)