From 283843f0e7e7506790313e4f228be5fdfd2fb022 Mon Sep 17 00:00:00 2001 From: Jairo Correa Date: Sat, 27 Jun 2026 16:35:59 -0300 Subject: [PATCH 1/2] ADDED: Audio recording --- examples/audio/audio_stream_recording.c | 148 ++++++++++++++++ examples/audio/audio_stream_recording.png | Bin 0 -> 15234 bytes src/raudio.c | 199 ++++++++++++++++++++++ src/raylib.h | 18 +- 4 files changed, 364 insertions(+), 1 deletion(-) create mode 100644 examples/audio/audio_stream_recording.c create mode 100644 examples/audio/audio_stream_recording.png diff --git a/examples/audio/audio_stream_recording.c b/examples/audio/audio_stream_recording.c new file mode 100644 index 000000000..636b053d7 --- /dev/null +++ b/examples/audio/audio_stream_recording.c @@ -0,0 +1,148 @@ +/******************************************************************************************* +* +* raylib [audio] example - stream recording +* +* Example complexity rating: [★★★☆] 3/4 +* +* Example originally created with raylib 6.0, last time updated with raylib 6.0 +* +* Example created by Jairo Correa (@jn-jairo) 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) 2026 Jairo Correa (@jn-jairo) +* +********************************************************************************************/ + +#include "raylib.h" + +#include // Required for: NULL +#include // Required for: memcpy() + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +static unsigned int maxFrameCount = 0; +static Wave wave = { 0 }; + +//------------------------------------------------------------------------------------ +// Module Functions Declaration +//------------------------------------------------------------------------------------ +static void RecordingCallback(const void *framesIn, unsigned int frameCount); + +//------------------------------------------------------------------------------------ +// Program main entry point +//------------------------------------------------------------------------------------ +int main(void) +{ + // Initialization + //-------------------------------------------------------------------------------------- + const int screenWidth = 800; + const int screenHeight = 450; + + InitWindow(screenWidth, screenHeight, "raylib [audio] example - stream recording"); + + InitAudioDevice(); + InitAudioRecordingDevice(); + // InitAudioRecordingDeviceEx(24000, 16, 1); + + // Configure it so that RecordingCallback is called whenever new samples are read from the microphone + SetAudioRecordingCallback(RecordingCallback); + + // Configure the wave to match the recording + wave.sampleRate = GetAudioRecordingSampleRate(); + wave.sampleSize = GetAudioRecordingSampleSize(); + wave.channels = GetAudioRecordingChannels(); + + // Allocate buffer for the audio recording + maxFrameCount = wave.sampleRate*5; // 5 seconds + wave.data = RL_CALLOC(maxFrameCount*wave.channels, wave.sampleSize/8); + + Sound sound = { 0 }; + + float timeRecorded = 0.0f; + + 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 + //---------------------------------------------------------------------------------- + + // Space key pressed or the recording buffer is full + if (IsKeyPressed(KEY_SPACE) || (IsAudioRecording() && (wave.frameCount == maxFrameCount))) + { + if (IsAudioRecording()) + { + StopAudioRecording(); + + // Reload and play the sound + UnloadSound(sound); + sound = LoadSoundFromWave(wave); + PlaySound(sound); + } + else + { + // Stop the sound and reset the recording frameCount + StopSound(sound); + wave.frameCount = 0; + + StartAudioRecording(); + } + } + + // Get timeRecorded scaled to bar dimensions + timeRecorded = ((float)wave.frameCount/(float)maxFrameCount)*(screenWidth - 40); + //---------------------------------------------------------------------------------- + + // Draw + //---------------------------------------------------------------------------------- + BeginDrawing(); + + ClearBackground(RAYWHITE); + + // Draw time bar + DrawRectangle(20, screenHeight - 20 - 12, screenWidth - 40, 12, LIGHTGRAY); + DrawRectangle(20, screenHeight - 20 - 12, (int)timeRecorded, 12, MAROON); + DrawRectangleLines(20, screenHeight - 20 - 12, screenWidth - 40, 12, GRAY); + + // Draw help instructions + DrawText("Press SPACE to START/STOP recording the sound!", 130, 180, 20, LIGHTGRAY); + + EndDrawing(); + //---------------------------------------------------------------------------------- + } + + // De-Initialization + //-------------------------------------------------------------------------------------- + CloseAudioRecordingDevice(); // Close audio recording device (recording is automatically stopped) + CloseAudioDevice(); // Close audio device (music streaming is automatically stopped) + + UnloadSound(sound); + UnloadWave(wave); + + CloseWindow(); // Close window and OpenGL context + //-------------------------------------------------------------------------------------- + + return 0; +} + +//------------------------------------------------------------------------------------ +// Module Functions Definition +//------------------------------------------------------------------------------------ +static void RecordingCallback(const void *framesIn, unsigned int frameCount) +{ + // Check how many frames left in the buffer + unsigned int framesLeft = maxFrameCount - wave.frameCount; + + // Do not overflow the buffer + if (framesLeft == 0) return; + unsigned int framesToCopy = (frameCount < framesLeft)? frameCount : framesLeft; + + // Append the new data + memcpy((unsigned char *)wave.data + wave.frameCount*wave.channels*wave.sampleSize/8, framesIn, framesToCopy*wave.channels*wave.sampleSize/8); + wave.frameCount += framesToCopy; +} diff --git a/examples/audio/audio_stream_recording.png b/examples/audio/audio_stream_recording.png new file mode 100644 index 0000000000000000000000000000000000000000..2fffd76e78809a4c6dc557d05cbdb1c1c91c982d GIT binary patch literal 15234 zcmeAS@N?(olHy`uVBq!ia0y~yU{+vYU_8XZ#=yWJp1k%11B11vr;B4qMO^ZqUteF> zw*?wVF)dcaL6mSXF1r{Y$J=;OKmselSk-zUgFVG$ZExFw42~Wwgh5Z4gPZU{?2*GaR-HNW5+6QQL;o4Z9$2*oe~&ASYbL;l7@>10Ntx2pM&POg9YOWlXef+^0<3v6nifXW!f0ARZtIWch|wG|nj=PY1bQEP zv|Jc17e>p4(Q;w5TmW~a(TLH~aVV6I3W^0kKc%t>$npN0D9W^0cEUvwdpu*+36i|8 zFA6ZUGOS$8ziZbnWu*rvc&9&H*B|iY@E`y8B~I%L+JDMF+ka5*{)>02rk@ubf9}Eg z{Nk;~EdmFo`muS}I_B&&33z(l!!v*JL+hwb4NeIqY$+zPuu>E)31x6x&}DH;cW@Jy z_TmtbS}C>iwaH1@gUaSM?6dbRoaXD%s~_{qQ+DOz@I8U2_qIHmI^lnNwMBmz|CH7P z9y2xNebvEZ|4Z5ryx=*Ju}Tu_Ab;aUfd$bVJ?9#F)Xch^8g57m23pF{Z`F3X3~=RarV@UpnxcWvypjKK17l zH}jp!U!Sz8@BEeV)V?U)uj8uwI}R1S|9n@@oC@g7cY5fSvxj%9q)yb+)eD3EJ^W<9 z_O5?UrQfRG8L{ybqx1rI%PcP6Be2Sz&C14YcJ^Uya0mu}!3bMupflWsM8d{}Ee(2e z-+fmoebP0ds-%4KD!Fi@1tlso?tD;L(tk^N=dQy~;>5H1?@W%enIN6NyZhiDxTa{xH+tYR+fd@SQQDTWFO-amUl=RJE7Gk<0 zmFX(4xaj+tg8|-l8$Mi;RW!fJfAtM-w|vy`>lsxE5?7wy&-iMx#i=3c=_`-b$K75p zo;dTed%A<0_YZv2C!okrlAa)-P?oE`GCS+#UbgZLs`5pD{nyw9xvwgi*`0l!dFeyT zJJ+sV&Qfc+G_${v@A*Qx^GBxDZ}{Vo`_)-@{>9ZX%S%+|U8y)E%D+YLc&Bgp7cccG z)7%%I-LX&-9E+Oe*dy7^xgk&Sz>lx5um4~C5OWH~S(C-h{ha&d*BKl@3By+67n>l1hogfA-l1C!~R56W)!e}V4fGW+=a$z(SK<%l~ zP#6sbSlfCuEsUmx(X@cvF&NDeqd8(UM~vnO^s%SWa$&Sw7%dk@%Z1T$0X$raMvRt@ zqow0$={QEkD}Zxi~Wkxikf5 zBPh|ZPw~OCXKtn3lKboa{;D@o!Z{o2=G-8sXt0YV+hi`zZGfPq64qE|hY_}KVK$n@ z2DKYj$04|F@!&-e%U+QmwWCe|EruH&p}@fKdB-9-Kh^~T3=9kmp00i_>zopr0I6L> A!T Backend: miniaudio | %s", ma_get_backend_name(AUDIO.RecordingSystem.context.backend)); + TRACELOG(LOG_INFO, " > Format: %s -> %s", ma_get_format_name(AUDIO.RecordingSystem.device.capture.format), ma_get_format_name(AUDIO.RecordingSystem.device.capture.internalFormat)); + TRACELOG(LOG_INFO, " > Channels: %d -> %d", AUDIO.RecordingSystem.device.capture.channels, AUDIO.RecordingSystem.device.capture.internalChannels); + TRACELOG(LOG_INFO, " > Sample rate: %d -> %d", AUDIO.RecordingSystem.device.sampleRate, AUDIO.RecordingSystem.device.capture.internalSampleRate); + TRACELOG(LOG_INFO, " > Periods size: %d", AUDIO.RecordingSystem.device.capture.internalPeriodSizeInFrames*AUDIO.RecordingSystem.device.capture.internalPeriods); + + AUDIO.RecordingSystem.isReady = true; +} + +// Initialize audio recording device +void InitAudioRecordingDevice(void) +{ + unsigned int sampleSize = ((AUDIO_DEVICE_FORMAT == ma_format_u8)? 8 : ((AUDIO_DEVICE_FORMAT == ma_format_s16)? 16 : 32)); + InitAudioRecordingDeviceEx(AUDIO_DEVICE_SAMPLE_RATE, sampleSize, AUDIO_DEVICE_CHANNELS); +} + +// Close the audio recording device for all contexts +void CloseAudioRecordingDevice(void) +{ + if (AUDIO.RecordingSystem.isReady) + { + ma_mutex_uninit(&AUDIO.RecordingSystem.lock); + ma_device_uninit(&AUDIO.RecordingSystem.device); + ma_context_uninit(&AUDIO.RecordingSystem.context); + + AUDIO.RecordingSystem.isReady = false; + + TRACELOG(LOG_INFO, "AUDIO: Recording device closed successfully"); + } + else TRACELOG(LOG_WARNING, "AUDIO: Recording device could not be closed, not currently initialized"); +} + +// Check if recording device has been initialized successfully +bool IsAudioRecordingDeviceReady(void) +{ + return AUDIO.RecordingSystem.isReady; +} + +// Get recording sample rate +unsigned int GetAudioRecordingSampleRate(void) +{ + return AUDIO.RecordingSystem.device.sampleRate; +} + +// Get recording sample size in bits +unsigned int GetAudioRecordingSampleSize(void) +{ + return 8 * ma_get_bytes_per_sample(AUDIO.RecordingSystem.device.capture.format); +} + +// Get recording channels +unsigned int GetAudioRecordingChannels(void) +{ + return AUDIO.RecordingSystem.device.capture.channels; +} + +// Set master volume (recording) +void SetMasterRecordingVolume(float volume) +{ + ma_device_set_master_volume(&AUDIO.RecordingSystem.device, volume); +} + +// Get master volume (recording) +float GetMasterRecordingVolume(void) +{ + float volume = 0.0f; + ma_device_get_master_volume(&AUDIO.RecordingSystem.device, &volume); + return volume; +} + +// Audio recording callback to receive new data +void SetAudioRecordingCallback(AudioRecordingCallback callback) +{ + ma_mutex_lock(&AUDIO.RecordingSystem.lock); + AUDIO.RecordingSystem.callback = callback; + ma_mutex_unlock(&AUDIO.RecordingSystem.lock); +} + +// Start audio recording +void StartAudioRecording(void) +{ + if (!AUDIO.RecordingSystem.isReady) + { + TRACELOG(LOG_WARNING, "AUDIO: Recording device could not be started, not currently initialized"); + return; + } + + ma_result result = ma_device_start(&AUDIO.RecordingSystem.device); + if (result != MA_SUCCESS) + { + TRACELOG(LOG_WARNING, "AUDIO: Failed to start recording device"); + return; + } +} + +// Stop audio recording +void StopAudioRecording(void) +{ + if (!AUDIO.RecordingSystem.isReady) + { + TRACELOG(LOG_WARNING, "AUDIO: Recording device could not be stopped, not currently initialized"); + return; + } + + ma_result result = ma_device_stop(&AUDIO.RecordingSystem.device); + if (result != MA_SUCCESS) + { + TRACELOG(LOG_WARNING, "AUDIO: Failed to stop recording device"); + return; + } +} + +// Check if device is recording +bool IsAudioRecording(void) +{ + if (!AUDIO.RecordingSystem.isReady) + { + return false; + } + + return ma_device_is_started(&AUDIO.RecordingSystem.device); +} + //---------------------------------------------------------------------------------- // Module Internal Functions Definition //---------------------------------------------------------------------------------- @@ -2653,6 +2836,22 @@ static void OnSendAudioDataToDevice(ma_device *pDevice, void *pFramesOut, const ma_mutex_unlock(&AUDIO.System.lock); } +// Sending audio data to device callback function +// This function will be called when miniaudio sends more data +static void OnReceiveAudioDataFromDevice(ma_device *pDevice, void *pFramesOut, const void *pFramesInput, ma_uint32 frameCount) +{ + AudioRecordingCallback callback = NULL; + + ma_mutex_lock(&AUDIO.RecordingSystem.lock); + callback = AUDIO.RecordingSystem.callback; + ma_mutex_unlock(&AUDIO.RecordingSystem.lock); + + if (callback) + { + callback(pFramesInput, frameCount); + } +} + // Main mixing function, pretty simple in this project, only an accumulation // NOTE: framesOut is both an input and an output, it is initially filled with zeros outside of this function static void MixAudioFrames(float *framesOut, const float *framesIn, ma_uint32 frameCount, AudioBuffer *buffer) diff --git a/src/raylib.h b/src/raylib.h index e49d6b2f7..8efcf4375 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -1663,9 +1663,10 @@ RLAPI RayCollision GetRayCollisionTriangle(Ray ray, Vector3 p1, Vector3 p2, Vect RLAPI RayCollision GetRayCollisionQuad(Ray ray, Vector3 p1, Vector3 p2, Vector3 p3, Vector3 p4); // Get collision info between ray and quad //------------------------------------------------------------------------------------ -// Audio Loading and Playing Functions (Module: audio) +// Audio Loading, Playing and Recording Functions (Module: audio) //------------------------------------------------------------------------------------ typedef void (*AudioCallback)(void *bufferData, unsigned int frames); +typedef void (*AudioRecordingCallback)(const void *bufferData, unsigned int frames); // Audio device management functions RLAPI void InitAudioDevice(void); // Initialize audio device and context @@ -1674,6 +1675,21 @@ RLAPI bool IsAudioDeviceReady(void); // Check i RLAPI void SetMasterVolume(float volume); // Set master volume (listener) RLAPI float GetMasterVolume(void); // Get master volume (listener) +// Audio recording device management functions +RLAPI void InitAudioRecordingDevice(void); // Initialize audio recording device +RLAPI void InitAudioRecordingDeviceEx(unsigned int sampleRate, unsigned int sampleSize, unsigned int channels); // Initialize audio recording device with custom sampleRate, sampleSize and channels +RLAPI void CloseAudioRecordingDevice(void); // Close the audio recording device for all contexts +RLAPI bool IsAudioRecordingDeviceReady(void); // Check if recording device has been initialized successfully +RLAPI unsigned int GetAudioRecordingSampleRate(void); // Get recording sample rate +RLAPI unsigned int GetAudioRecordingSampleSize(void); // Get recording sample size in bits +RLAPI unsigned int GetAudioRecordingChannels(void); // Get recording channels +RLAPI void SetMasterRecordingVolume(float volume); // Set master volume (recording) +RLAPI float GetMasterRecordingVolume(void); // Get master volume (recording) +RLAPI void SetAudioRecordingCallback(AudioRecordingCallback callback); // Audio recording callback to receive new data +RLAPI void StartAudioRecording(void); // Start audio recording +RLAPI void StopAudioRecording(void); // Stop audio recording +RLAPI bool IsAudioRecording(void); // Check if device is recording + // Wave/Sound loading/unloading functions RLAPI Wave LoadWave(const char *fileName); // Load wave data from file RLAPI Wave LoadWaveFromMemory(const char *fileType, const unsigned char *fileData, int dataSize); // Load wave from memory buffer, fileType refers to extension: i.e. '.wav' From 7ee1fbcd9e2d6ea148dce113df5e6b9a28042ca2 Mon Sep 17 00:00:00 2001 From: Jairo Correa Date: Sat, 27 Jun 2026 22:37:34 -0300 Subject: [PATCH 2/2] [raudio] Check if recording device is ready --- src/raudio.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/raudio.c b/src/raudio.c index c98894029..67ede77e9 100644 --- a/src/raudio.c +++ b/src/raudio.c @@ -2470,18 +2470,24 @@ bool IsAudioRecordingDeviceReady(void) // Get recording sample rate unsigned int GetAudioRecordingSampleRate(void) { + if (!AUDIO.RecordingSystem.isReady) return 0; + return AUDIO.RecordingSystem.device.sampleRate; } // Get recording sample size in bits unsigned int GetAudioRecordingSampleSize(void) { + if (!AUDIO.RecordingSystem.isReady) return 0; + return 8 * ma_get_bytes_per_sample(AUDIO.RecordingSystem.device.capture.format); } // Get recording channels unsigned int GetAudioRecordingChannels(void) { + if (!AUDIO.RecordingSystem.isReady) return 0; + return AUDIO.RecordingSystem.device.capture.channels; } @@ -2544,10 +2550,7 @@ void StopAudioRecording(void) // Check if device is recording bool IsAudioRecording(void) { - if (!AUDIO.RecordingSystem.isReady) - { - return false; - } + if (!AUDIO.RecordingSystem.isReady) return false; return ma_device_is_started(&AUDIO.RecordingSystem.device); }