From d26a56d4e1d8168912e3670a2ff3122b125fd94c Mon Sep 17 00:00:00 2001 From: hkc Date: Mon, 20 Feb 2023 13:13:24 +0300 Subject: [PATCH] Added mixed audio processor (#2929) * Use RL_QUADS/RL_TRIANGLES for single-pixel drawing Addresses problem mentioned in https://github.com/raysan5/raylib/issues/2744#issuecomment-1273568263 (in short: when drawing pixels using DrawPixel{,V} in camera mode, upscaled pixel becomes a line instead of bigger pixel) * [rtextures] Fixed scaling down in ImageTextEx Closes #2755 * Added global audio processor * Renamed struct member to follow naming conventions * Added example for AttachAudioMixedProcessor --- examples/audio/audio_mixed_processor.c | 123 +++++++++++++++++++++++ examples/audio/audio_mixed_processor.png | Bin 0 -> 8708 bytes src/raudio.c | 65 +++++++++++- src/raylib.h | 3 + 4 files changed, 190 insertions(+), 1 deletion(-) create mode 100644 examples/audio/audio_mixed_processor.c create mode 100644 examples/audio/audio_mixed_processor.png 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 0000000000000000000000000000000000000000..8575a836df6c39d1a35c82f10c0837262eb7f368 GIT binary patch literal 8708 zcmeHNYgp3Rz7}#!ZA_=Om8of2*=V=XF~bb$rj^}h@oq9O%WRfym2wdOQS&B0!kD$wooCGpePsRECtSU&X@D?Jm);yPdw|-TEF#QzxBSq z_gxEqId&w#%5tTpiHV8Tf&F`bG%;D=Y+?d=X$}QX?m>L;;A26`?gK&Q=H~pNqk|?U z&L#);?g~nel=8p(b$I(lv%zz1$i~2agmQZZwAd#*7am~mlYOWRafF|7%_s0x$bqke zkL4}=e*b#K&h_*>tG106u~4ViB*PS z2EG1OU$m_tJkT&nxyzuL1UA}8%SXr}<3+5~%{K%QFV&S}%w5En+CH>xyQdRf$|=Qj z>a%3hdXy}wvY(5JgC8dlYKgphmYnR3PZcRRXjkmxmv*%!vV^tSf#&rsjel$_UaPkm z=hs2Jk)getQg-fnK;5%amvq}xR6~lD!A~bR3N7lO;{HSu)pzwVB*Q9Xh)Mch??DD( z(BztOVYQ^vqHYq|5x0Ft^Bs(GOp&spz_)tL(iWYNIC&~71{FcIq>c&23XUj1>9a~z zTQe$nuJl14dGH&~zUo(g{11AE!|(*_-g!Jq7P|Cr>avV*@swk0E}sh=8@o(rnyJSqo?<(NoKBnb*KR+o zT9}Q@CQMqs=nrlxV2s&6Q}KzKrzbxhp6wj-Xj74)^HR_`j&R&6yO`eP;Tii+WI>{= zLL{jp2hfr#3Ha_%IQ$|;_s`00*npnO*VSph7ip)%8ZbQ)5-E_#su#*R^`Wv=36F(` zrc?h~ZnMF8(5#ZN{v=eN<1Z(nvraHF!9RA7!JXCDg9O|}J@~YA))+#dL|5l%s-XPd zH-=rg0D(w8*q>D!@*}Y@M-nNnv3vEy!^07b#woKBxFJ3o3gt?qzu0}F54L={P0eOn z_T!`a-p%DW+ypw~l)oWZ8$j~^+SCj@(R-a|V;MuE(V%!*rBVrtwQJC(XkMwM+>#{H zL{V*10O4$L3b8jmPe@5%lV^|_1fHaaJCZkE3NmvSY~@}O~4ccUj`j%eNnei)7Pj7c2 z-QumFWjub>i8l1&q>jtOk(0_wRY^cBEt8+>7n$oLa~XK6^Q!^1miKNvzE1DK zt5<`tD>avLPO$o_a&^@|YEmDI;QZnpPYC^S@uT;p>Po@RH{AEt2}+uD!iQ#Q3xa(4 z0M;9sdF@bv9;)@_05}q9u^m01p=Ny>9}@RAoJ~mM|HTVCJ11iBIK%4BX@lvvF<*B6 zn(f!m`;xQA26!-y;7lK;`Kh4nO}!|6AGJhS97BuHtmO244|GJyQLwg$Ze{lz{zjJ?Az>y4$!gm z*NKNGmH>>dBY#2{X(dE@wf8yC^@zbcP(SQU^Kj!PbvOjOP!b-+G3=1Nr8&~O?^57; z{X$r_HZ`n?;4(uB9^!D9TMtuW46hgtxbp8jbli2Liaw)?!oLpg;xF2sE^$E!l~`w! z-#`+d^`#|?ax`UWIxk*%%KDT>hi|f`?N*&ALs|F+A{gj9T1U-#t;5l?7dpynMY=6E z7rV{&t)ii)zBHHFQS9kEQ6X+i>u-UCKV&w34&wRU^wX)DotWZanj2=~3VUR#`(DnG zb55hlb%&a!n=$h9*^-y;9p1YJIcLdLf42 z66HY+#j~Bj!p;wB$3v;teKDujANvu(aBCp&kU_F13b|GiFeJC8woogo>Aj&twer?A zQPz|r&%8OF<=9VB_pHowO-%ZFU;59l-sk67^;*Fl=rz(=|D<;hDg5#~ z{7QSaq@*K%+8W#ZGdrFNiOO5G7%3nVxo;8%;Nj7-q3C3Kru@8z0zbLFMy6`DQoE71 zUDT$&X{~M=(}?O4s)_w|TePIa-QP zSm=cj5xv$qI`2}m@e*Xj#-=9qf$C>{9dX{e#OA+;Hb?Gwow?gk%~k--b#au<7S-7& zJ3vw#&%#$5N^34?7UriJi=ra{=;H73M#b<4&HWuajPJM@0!dg{a?}{g1vGyXIvU?G z2ZtjqV7eMZ72?hMQ!5r< ziK*zKgrzC>cU&e8vDex7$Cpx}QNe=b#`<-$l^12|M zZIKJySRSU?AY0~%KtxEz-3t{^>VP?a;8a0DVjQRPN=%GKD*snH(escfLCGa$392t( zZoaO3okhw`6sJaBA9EwKo_MpBs_tI?cb?Cah=L;W47ac&?YZ>beNHBkEYcQTm=y3; zYX2oj;KyJV#tcLQg=u;4=Qrt&5c*9zEArl}_s|#K*f@5`^p08SZrvQ_qEFr}Hy~EG zHOWko&cAJ}P@2T77ooLaS&LbrD+Pih8ghSK-%>}n?T&RK`K%P86MP;zX znrbY2Rj9qrpLVJ53`?R>Pa#@BOE2QK^TeoVYj6#@1P*ocz z3QFG*e(gF$W?>VS3oW|AVoHAS_^SbqHM@XKG26H&#Z zr*SntvrZsxOSb{O8`c1m+3C*>^OvQ492*e7Q{R`=_vU^cv-FH;KP?h(T?L!D|Gf}4 zF38`Zr1efV^AaSD=*wi5+jAC|CK(gLa?hZ#W_(p{lY>u&EmKP;V0$5uXa!()2FVU{Gy7E;2`A>`}(vO@J*C ziSRWB@{9w*)AGEH`yBz;vU!`6k>YA7G-RON-bkL!fGx}K7Z^ifAdpIw(8@@jyMR15 znE}R7G#rjY8T-$S0*UKw6S){dA)*kB&-`?w2YLk<@G?_{fr} z6kaI&plv!9s~edF#Hx{~rE<1)4rXWg=k9`$V0(7FDxMZN(piylG;yF0980D%Y`HH3 z9LS?1=?ld?Qn@ZYFCc_h-inbVN$M0%T~%y0IMg(K0BHo@Qz$7CrIwZf&K zJD%3n-I>~5IB=^szU!bEyEN%cyH^G(PI+c$WyKMcl)72N<%CEklDv9dlSt*Nlz{3u zc&$K?7?!O(pph2lE1LS0Qt7@O;)xNM}}x1wWq zJf{5Pj7SsG&m#1D9R-~IFIUuaoDH^Jp<9-IJD5W6k31t19l$Z%7rmXbuyY?VbLSrS zGVZBUa*gGxt7AGJ8m;wXUM^L9Pv+t05zj|E^3_86v)B24@|dg;MFiI?j>X6vCb)=0 z)7m=;wPj9zXQ~r<0r_Iz8RZl|a+3SpTR!C=a?cgNW?iAlwVh)QB1!3Y9tzig z*(v-UJ+`m%4C1WSLoay~AkAB40rqA*R%2IC@Q-3Jv3#U~6cX4`I?%&i+;u3wt16Dh zl?0DkINZg{w#&Bq$)~hnYiu{Uu!|DoexEpi62VeNFQY<#!Y(fo`_yTA;3r~NOdPKy zU9F4SDGK|cHNLYEN2tejUqdua6G#8BYuZ{} ztXg`}w@6I8d~G5e+ix(zR{_H|*FN*ax}S>eMz;vZBswa+<85Wl4B0#O8SG63Z@Ph< zL9dwLX2euTQBuif=|K8N=1{|_jwZ@tY6-Pa;UVo*zUrY)HhWW^iiHMVLUGaeY?O<#!PxF56L0NqBvchPa8-W*_S`8fa`j{((pb(bH3%7 zm6uUYgO;Da7L3Uu7>*|U!LhUFm6svY&p`#?q`gz?d7FI+{De^!zDEy$2aPz~{J{C- z%p+%>9dklHUpnT?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