From cff632544cfe071cb0982de83cc6204218fcd309 Mon Sep 17 00:00:00 2001 From: dan-hoang <56205882+dan-hoang@users.noreply.github.com> Date: Thu, 12 Mar 2026 16:51:01 -0700 Subject: [PATCH] Add audio_stream_callback.c --- examples/audio/audio_stream_callback.c | 267 +++++++++++++++++++++++ examples/audio/audio_stream_callback.png | Bin 0 -> 15750 bytes 2 files changed, 267 insertions(+) create mode 100644 examples/audio/audio_stream_callback.c create mode 100644 examples/audio/audio_stream_callback.png diff --git a/examples/audio/audio_stream_callback.c b/examples/audio/audio_stream_callback.c new file mode 100644 index 000000000..147c4d8e9 --- /dev/null +++ b/examples/audio/audio_stream_callback.c @@ -0,0 +1,267 @@ +/******************************************************************************************* +* +* raylib [audio] example - stream callback +* +* Example complexity rating: [★★★☆] 3/4 +* +* Example originally created with raylib 6.0, last time updated with raylib 6.0 +* +* Example created by Dan Hoang (@dan-hoang) and reviewed by +* +* 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) 2015-2026 +* +********************************************************************************************/ + +#include "raylib.h" +#include +#include + +#define BUFFER_SIZE 4096 +#define SAMPLE_RATE 44100 + +// This example sends a wave to the audio device +// The user gets the choice of four waves: sine, square, triangle, and sawtooth +// A stream is set up to play to the audio device +// The stream is hooked to a callback that generates a wave +// The callback used is determined by user choice +typedef enum +{ + SINE, + SQUARE, + TRIANGLE, + SAWTOOTH +} WaveType; + +int waveFrequency = 440; +int newWaveFrequency = 440; +int waveIndex = 0; + +// Buffer for keeping the last second of uploaded audio, part of which will be drawn on the screen +float buffer[SAMPLE_RATE] = {}; + +void SineCallback(void *framesOut, unsigned int frameCount) +{ + int wavelength = SAMPLE_RATE/waveFrequency; + + // Synthesize the sine wave + for (int i = 0; i < frameCount; i++) + { + ((float *)framesOut)[i] = sin(2*PI*waveIndex/wavelength); + + waveIndex++; + + if (waveIndex >= wavelength) + { + waveFrequency = newWaveFrequency; + waveIndex = 0; + } + } + + // Save the synthesized samples for later drawing + for (int i = 0; i < SAMPLE_RATE - frameCount; i++) buffer[i] = buffer[i + frameCount]; + for (int i = 0; i < frameCount; i++) buffer[SAMPLE_RATE - frameCount + i] = ((float *)framesOut)[i]; +} + +void SquareCallback(void *framesOut, unsigned int frameCount) +{ + int wavelength = SAMPLE_RATE/waveFrequency; + + // Synthesize the square wave + for (int i = 0; i < frameCount; i++) + { + ((float *)framesOut)[i] = (waveIndex < wavelength/2)? 1 : -1; + waveIndex++; + + if (waveIndex >= wavelength) + { + waveFrequency = newWaveFrequency; + waveIndex = 0; + } + } + + // Save the synthesized samples for later drawing + for (int i = 0; i < SAMPLE_RATE - frameCount; i++) buffer[i] = buffer[i + frameCount]; + for (int i = 0; i < frameCount; i++) buffer[SAMPLE_RATE - frameCount + i] = ((float *)framesOut)[i]; +} + +void TriangleCallback(void *framesOut, unsigned int frameCount) +{ + int wavelength = SAMPLE_RATE/waveFrequency; + + // Synthesize the triangle wave + for (int i = 0; i < frameCount; i++) + { + ((float *)framesOut)[i] = (waveIndex < wavelength/2)? (-1 + 2.0f*waveIndex/(wavelength/2)) : (1 - 2.0f*(waveIndex - wavelength/2)/(wavelength/2)); + waveIndex++; + + if (waveIndex >= wavelength) + { + waveFrequency = newWaveFrequency; + waveIndex = 0; + } + } + + // Save the synthesized samples for later drawing + for (int i = 0; i < SAMPLE_RATE - frameCount; i++) buffer[i] = buffer[i + frameCount]; + for (int i = 0; i < frameCount; i++) buffer[SAMPLE_RATE - frameCount + i] = ((float *)framesOut)[i]; +} + +void SawtoothCallback(void *framesOut, unsigned int frameCount) +{ + int wavelength = SAMPLE_RATE/waveFrequency; + + // Synthesize the sawtooth wave + for (int i = 0; i < frameCount; i++) + { + ((float *)framesOut)[i] = -1 + 2.0f*waveIndex/wavelength; + waveIndex++; + + if (waveIndex >= wavelength) + { + waveFrequency = newWaveFrequency; + waveIndex = 0; + } + } + + // Save the synthesized samples for later drawing + for (int i = 0; i < SAMPLE_RATE - frameCount; i++) buffer[i] = buffer[i + frameCount]; + for (int i = 0; i < frameCount; i++) buffer[SAMPLE_RATE - frameCount + i] = ((float *)framesOut)[i]; +} + +AudioCallback waveCallbacks[] = { SineCallback, SquareCallback, TriangleCallback, SawtoothCallback }; +char *waveTypesAsString[] = { "sine", "square", "triangle", "sawtooth" }; + +//------------------------------------------------------------------------------------ +// Program main entry point +//------------------------------------------------------------------------------------ +int main(void) +{ + // Initialization + //-------------------------------------------------------------------------------------- + const int screenWidth = 800; + const int screenHeight = 450; + + InitWindow(screenWidth, screenHeight, "raylib [audio] example - stream callback"); + + InitAudioDevice(); + + // Set the number of samples the stream will keep in memory at a time to BUFFER_SIZE + SetAudioStreamBufferSizeDefault(BUFFER_SIZE); + + // Init raw audio stream (sample rate: 44100, sample size: 32bit-float, channels: 1-mono) + AudioStream stream = LoadAudioStream(SAMPLE_RATE, 32, 1); + PlayAudioStream(stream); + + // Configure it so that waveCallbacks[waveType] is called whenever stream is out of samples + WaveType waveType = SINE; + SetAudioStreamCallback(stream, waveCallbacks[waveType]); + + SetTargetFPS(30); + //-------------------------------------------------------------------------------------- + + // Main game loop + while (!WindowShouldClose()) // Detect window close button or ESC key + { + // Update + //---------------------------------------------------------------------------------- + + if (IsKeyDown(KEY_UP)) + { + newWaveFrequency += 10; + if (newWaveFrequency > 12500) newWaveFrequency = 12500; + } + + if (IsKeyDown(KEY_DOWN)) + { + newWaveFrequency -= 10; + if (newWaveFrequency < 20) newWaveFrequency = 20; + } + + if (IsKeyPressed(KEY_LEFT)) + { + switch (waveType) + { + case SINE: + { + waveType = SAWTOOTH; + } break; + case SQUARE: + { + waveType = SINE; + } break; + case TRIANGLE: + { + waveType = SQUARE; + } break; + case SAWTOOTH: + { + waveType = TRIANGLE; + } break; + } + + SetAudioStreamCallback(stream, waveCallbacks[waveType]); + } + + if (IsKeyPressed(KEY_RIGHT)) + { + switch (waveType) + { + case SINE: + { + waveType = SQUARE; + } break; + case SQUARE: + { + waveType = TRIANGLE; + } break; + case TRIANGLE: + { + waveType = SAWTOOTH; + } break; + case SAWTOOTH: + { + waveType = SINE; + } break; + } + + SetAudioStreamCallback(stream, waveCallbacks[waveType]); + } + + //---------------------------------------------------------------------------------- + + // Draw + //---------------------------------------------------------------------------------- + BeginDrawing(); + + ClearBackground(RAYWHITE); + DrawText(TextFormat("frequency: %i", newWaveFrequency), screenWidth - 220, 10, 20, RED); + DrawText(TextFormat("wave type: %s", waveTypesAsString[waveType]), screenWidth - 220, 30, 20, RED); + DrawText("Up/down to change frequency", 10, 10, 20, DARKGRAY); + DrawText("Left/right to change wave type", 10, 30, 20, DARKGRAY); + + // Draw the last 10 ms of uploaded audio + for (int i = 0; i < screenWidth; i++) + { + Vector2 startPos = { i, 250 - 50*buffer[SAMPLE_RATE - SAMPLE_RATE/100 + i*SAMPLE_RATE/100/screenWidth] }; + Vector2 endPos = { i + 1, 250 - 50*buffer[SAMPLE_RATE - SAMPLE_RATE/100 + (i + 1)*SAMPLE_RATE/100/screenWidth] }; + DrawLineV(startPos, endPos, RED); + } + + EndDrawing(); + //---------------------------------------------------------------------------------- + } + + // De-Initialization + //-------------------------------------------------------------------------------------- + TakeScreenshot("hello.png"); + UnloadAudioStream(stream); // Close raw audio stream and delete buffers from RAM + CloseAudioDevice(); // Close audio device (music streaming is automatically stopped) + + CloseWindow(); // Close window and OpenGL context + //-------------------------------------------------------------------------------------- + + return 0; +} diff --git a/examples/audio/audio_stream_callback.png b/examples/audio/audio_stream_callback.png new file mode 100644 index 0000000000000000000000000000000000000000..12305744e5f06d9f1f465524f75db1dedca46753 GIT binary patch literal 15750 zcmeHOYg7|w8cw)I1QHc7L;_@4gl;P$1m)6T3YZvd06~$aYNH?yh#Xe|DfeiAMJ_>f zRd9u1Xe1~WccVgtl?W;bXaW%wjaCHYQt<*oKq;Lh;IT;JkKMDLJChrtL`isv`$W3~jn9hVIx zrLMEXXmD^U4v`2rSL|zMz!0O*vmW5FDK&s@Fup7+wFW?B&T_EJj3$y62xtEzctQDG zhS(%W>`71sgLnyrewLiMO-mfE#bCHo{^J0tAtL zepR%;Z6KGi|9rir;RFXCXY+X4cNW&h#aMBmtUGR3t$X1!Y;Ool%IVncA zZ^&Vo#hdp#(gd>&h~hWVQmw$$us~LJIjbkP$Vj7n_+Vo*GsKu26o=O$dC=vD!UU^P+WVM%X1DbbdrFjUJS(FLX_;U;Aprf@63|KZSU ztPy!%(;d*t&0$fnJTR<#q~x=gZDgxrM^Uapc;At$57wTY=(?pZaw*_p6YF}yw>>!~ z*}Y^VfoIi1yw(Uvmrq50SP$+;{)Hm7Bn}X?`O(O-5Sx0(V|Vjx0>^P1CV*R(OO@ei zQWGA!sBpidfD{+prQ`Q{Q1s>cl4rJ&Lu*d<)pS1>8YdobbR!QOoyv9#BaL3mM-ohf zdAMc2<#uMFgg_`RHE&+ml~U8IUk}_gnU$9yVcFj!^gBRi<*^Rs&D9CJt$j|!ZPV9o ziVr(M-nqAdn!h_%elD-51G|f(1?aCVCzx+nhYHZ)k4ZZx zv)jIzx-Ib;%0kACq7`;{4xD%t+WkoAvezLt%sA9JBvL1spNUoTf_8}$2rGH)eka#PgE8%&HviD>=8@`C(;LAGR5?7-;&C;g)_R!2J14!V*XZTp!uKs<|E zJarFt1R{BamN|F{P2F(7N|BJQ)7U?d1dy+E(LeUD2BK2}R(g~D`wDV}dn`wQhbl5l zXsW?+u;BFVeJ2!u;2NkK5jf(%OiBU?2#<`7B=1l z7qECyokq5C^^S!(mm9L2{iKw$v3sOzOWnfK9KJGbpjsceHo?EyAP?<@k7;XhcKm$W z5H+IS)4z{@M$C7{yC=7pXw$~_upg{m7N@(j!XHyud*=sE_dR*W!p3mKG8030TlO)+ zu=ia4$RgZOsN({rSvIFnEAA!+B>B<=vQqSCj3syqil^p9U9DqCUUg4gWKU4=(XwEc>U=zXWPA7N03MJ zkhGt%)<%XBJ)h&wk`ZKCi*DJ246}k_a404+BFu0@l&EFAvGlgz4y3H&bvMy7Kxh%u z(8lu<+;@&rHb1%T(^YLzjyg56MQ2k_5sQ7ee_7a>w8}Kep@coJH|g_}zG$8&@wVpQ zDpHJ$$|6EO>%AOk|9Mi^BZkZ>_b)v~_UXp7-HXR3>XL^~-KYhsGTY8gOWt<~_VWgd zDAsk~xs#6vuP_d>Om{ccoUn z)MGjL6OQPm6qRFYM5Ts(PtzXrR3#m1S1D(*v8mh7Dja-G`~17==3NdpM_i}m!aG@< zo^!xQcYyPYrfLYwln~@vJWFW_W;Uo@5CkX+3YCL$L9X>s6rdQtrBdJ${SLAd17PN6f^Z&j2DIhSTw%3;Z(~R0$cf+QnYP-Ch3g|LZD`7`0$X~7) zH*(-?r9L9P1O(iK7#6orjBmEn7b-71KeU`?J>UEc+nyeYMAX{M-dvS8aW*0p&C3os zCucJsKdx3JR|`()hqmstX*a6uBbBOb@`47eqJ?{36$C{C*kE>eAY}b3$~Mv+l{Q}_ z*nc&w2evI%I_?Xqx};$xfO$iQJJdfM4X)qXa|?7 z>m+U{`r4>?loE@a@irrn{6bz|5RO&#bj=aSA z49Ea$1d3bSwA}KYvUG;PI{h*VO>O6-$5w#bDf7W)P3>|mQmYJHuw>$DMr8x-lbm%9 zGwR~^Plv%p5&_&76#hYRJ5}{Ug~xwmtLbNvnu@KB(2^8dl73|CH$(=-K3}M4fr^&T zq-cR=2C(LW%*u~`6a>u-(99tJt_|{bAa6&}vVe?EhzwIL#LvLnfy$)+L74;^S}<4! YBk1H8IU*