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 zcmeAS@N?(olHy`uVBq!ia0y~yU{+vYU_8XZ#K6EXgVAa}0|Ntdv6E*A2M5RPhyD*3 z7!(*hT^vIy;@;jiob=S9i0wh<(TH8%cOAZ2R5Q)AYV5Idm$qv3nd{(pySmxR`(&6W zPiR0dyNQR|XIr)Xy}UZ@2sh;$>*5W;eiZ+GOs&s}}@T7J4+@Y=PT*I(ap?$b8& z`DM>OU#(KFUv@}x^Q`yTDvjP8I}YwTVm4C+(#$AOWi*cnzolDj>%3sEv(*3Gsm9MX zY)yM_mz*v8m~#u$^Se1)_u421L>%A^dw6!M38W43z>+C%!@S4qUa#id@;%~sy2ZcW zhL+m{J{L>$+pPP@C0do8^F669B|mmA*WQnhpMSpk%>A2{+|SJ$-rZaE&8o_8!IB2) zHIr^<1wvXl2aH+oB-jSWzYiDg_%*jW=J^lV!#%gJS$;Y8{7hMz>+$0~*LT$2mbSES ze=K@!Z`IXj`90g-J-$@FSQ~ z_j1|G9{c<)Ynvyaq40oh^#QdgCP)~6h++b@1mm*Z(wxDy4Tr#v9SzcJy7L#j6b3a6 z8JX6rDY_sL&xug%Kc`gQW=-R^5MW)`o%Rnhx+d-nNl|01f_UVnY*+MC`l za~K$zrVA7_mv-hFc|ud37gOGbbz7phKCe0^>$X0v`mvFHQJr{|>2vKh?%S>&`(F8b z*Oz^_%DGJ+-|2gK_Fyp+3rCE{0p4q^W~W0qL7AJOF&mTxj@iw8`kG@-VSb6**?AG~ zkFD6N_&W3h$VQer?FVeHx4W=`RWmZNY~$Q<@Yb``FO^Kj=Gu?U*}Auiadi{cYdduU7lGL^%W$K0IQ| z+YndN?9&fPsypU2NUwSQcBbV1z5~C`X=a6`Ux_)tw*0n;X+6kJhX=t2c&8;pEaVX2 z5G!aFz2RorQWICv*O;|%R8P`xbB_xcxKAJ*#EuTKYCr?JWGD&PO-fl z0ty0p57@Nlgg8Qj`yDvxzROGa3rRf;jRB125ew&ZLDL$`AJy`7NIC=S)!*4ZS?kB~ zetCNh0R@K#PE7ar*YCfO=TDqbr)JpNX%z_5giBVy&9^1Ns5zvq_!H@!ZuSgm-Mj9}egNx8_m z`J6W14hO2H{gM5x_D9A)qwx6L&CmYp3lyDUbCugy_2a{yxsuzZ_uJ-&X$kL`(9lr2 z@a(QvA=mA@?f?JWBY*aJ=svC5ua)QD{5UhC-7i!AUEUU--GBGgeky2u-EM97GuM}y z;|{2l`pIaK{d2zk`u+dzev63v`{~bv{vYel^IywMe|_>QE1UbHhk<{h-v2mUIW2Db zz0Yw^57t!8*Eyg5uVl;7+v>0N9&dmAv%>oU9~09x*|MKZ*)eqy`G4QfzIW(Q{TF|+ zm}l}^yY9U@c7FctTAS_0_s->?UGRIsiG$&<&zn#ECH3cYIDhvRe)ihi2jh+QhR;7Q zzpv^?!Gk|v!uPh?)F!C~1UVeg+PU_6M9BN)pFjP1?f&o+|K5Xpm#*7?EBxW+PluJ_ z`M3M6G{0&*um0s*?Yf%G-Dmf!+3asQd9$yb_ZF|6%>JzVd9Oa1{+MpX-)-||>%G5U z8`?#u9o)Ys>i_X=N3YMHcwcVwdMp0!Ki@u2V2oco-=cj&t45J%W4^q+eE7`Om2s{Q zj`z#||MLF7rRDAVKmGN;Wp+MxzxU~3`1G}+bw6v$=YBM*j;)H%jXHYx(Q7V#_AgQE z=l?I0dVFgCnTMb4`;ObsxujoQ5s_$+wx{KM)?UW=UOB#qd)wAuyPSRBE+*mq!-Ty3 zHJ=KMKF|NIw&DHH!@R$=wwhI16m6L5J+bV-^U0|j`MI~P&iMZH_2K#U^>zRM?frX1 zn8}@mWsP#xzFM6PnhCag8=tH{Z`Vte={$ z)sz0|HrMIr{O!DPxwn_RT|ECr)$f$^zaBk2XLo1i=Clv>rO*4H-v9qJpXu7o+l7aJ zFx&r~nU=D$thsdi_m-o7e{MM^HC^n-pT^LtS}}KUvEs&*x53To z9;~=2yZ^<5LbBg1Q zfWU^b1wUIL<%oiTkN~RD91+$RYMj8HENf&l>=Yp-5=RdU z%bJOO<*-6gmWk;apUHe!jp4?`bj{M)}wHZ+)C`Uo2a+TiSP;1(Y5et6!OU*q&(JJb93@Au1o2Gy_6w3Mq)?>zPA`SZ$@ zrKh(?mw)p(Ub_D2{PXL!c^v!p>TKoTnzW^_cb&!FkpPBaF=eT`8slPub{p#wVMOvW)w?_n(WKaq=e9M)Ugg z^I?CjId&Y}byzQ284`FCv>vc&PyL;D_St{8qaP=}%{{y0S=FCEC+_i`3zz7RxjtY1 z=Iqeld%`zg&E8*oZSuW2-_NG+yY=Tpt+C~&tAFlPDc_cT-#cgi^BqpN3-^6Gb7JW~ zqxQ|!XU`m$V9MJt2V#dqgTsafY0xO!HKxz|-M4-B+?!v0{`v29_2=cnrT@F_E4dSu zXj7LaYP|W?+`~WL?dAG;KKc2b)ne6J=fY-19zFB(&D`y4zdP*xsq?je&W*K`PWx;- zSD!8ZnDY$Bj)SuTjoP5eaf9Ol-fL_+&c!T$_@th``Tj%Wz_p| zj%`(EImVQ?;h5b$AxMLg#f-b=_m3Yp{!HYDrD2v|OnDoAncah>_%cq8h}}18TtHcc zg+t&4H%CORbUsY=ZWfj`u{m~1(9H9Nm1WJoUUOJ{$1*Wp(=(HYW$|B3OkwwrNy9Ra z9wTG6`0Rd|QMHVWtMec8LgOZ3dPBq3?z8SNqxLm4T;2W=9-`h32W}mGrVKU8A>QG@ ztJ@W@Huw!yg@m_BpP@}|hXwiy314&fz%uw7p&!N9!|l98E9&e2zxn>{+qVs`f4%*5 zp1-};Hs;lt{x_l0=d*rh?el(fVrjL1&iy-8SD*c@7QJ@JU1r<6Hx=y9uWz0AzoOSY z?E1XJwXbtuZ}}{Ldd>Os*DSy6I)6gvep*?~cdLDye?PNVE>6jNoqszndh2bjy|VU; zKh>Q57Uca)=_y_UgfzzBj4p!~dwX|FK@qO{@U!Rrx&t&a8 zeda{jnThu&+{!xb^}JpS?s

x=!%k|uH z%kLYr4_BpFFWbH-_1W*RyXV&4HTA!PGDHo|v!S9A|HUe%hHl?~H!k(rGtY(t+T2%u za7t>wVbi`AYzmEt1M+zrxa68X{M>p1sd!yf?Bp%DP=8d;8bp{VOl@*?&r@ z3;P^<%{2Yoftsglm7j-~A3L#j^IUoRdAV=qulyO=>)!X==HIs;x8uHFTV?*~hs-nW zp63_NRqp%r=fp>uXRqfN8huuOBKg^V@iOT(#vxZ#AW1Jz_W|4LE8qI-yR+(lOZN9K zn)%+y{?nVCTf@AM&$)l;8sBN3zSC>c_g#A(`i@g(`uz76?+xrf{rGd6Yp6^ZK=0CG6epi|wo>(tF`MK4W-=|M4rUH~iaXp0?8R^3v;ncb>1_zx3<1>XfB#^WVPK zdwaj)#o32DlaBA+&bN2g-ie=$-k$h&^3vY#d(5}5cs{eXdT-yHS8->LrpN5uzTftC z-1^e#UuQg>e?7KpYQO#av$@MYg|%->%fEX1#EP9;Yu|r3!h21&?4}gdJt5$B%Sv8T zSaljAUeFv`+rAdsHSrJ^*l_8jDKw;4adSjmEx+*#l5ZPJ7#X$a#yCR~z=B;34Wk-I z17|eljOGq-MLt?|j24fBtnS$IwCN=S1LFqJT7c1-Yp7J~Kk{EFGxjJ>IV{7#z`)?? L>gTe~DWM4fc&%^l literal 0 HcmV?d00001 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