From 5579d1f6cc878a64c809bcdc1f32b54c2f9df87d Mon Sep 17 00:00:00 2001 From: dan-hoang <56205882+dan-hoang@users.noreply.github.com> Date: Tue, 10 Mar 2026 17:25:23 -0700 Subject: [PATCH] Update audio_raw_stream.c --- examples/audio/audio_raw_stream.c | 237 +++++++++++----------------- examples/audio/audio_raw_stream.png | Bin 16736 -> 16468 bytes 2 files changed, 88 insertions(+), 149 deletions(-) diff --git a/examples/audio/audio_raw_stream.c b/examples/audio/audio_raw_stream.c index b327e92af..169806927 100644 --- a/examples/audio/audio_raw_stream.c +++ b/examples/audio/audio_raw_stream.c @@ -1,56 +1,25 @@ /******************************************************************************************* -* -* raylib [audio] example - raw stream -* -* Example complexity rating: [★★★☆] 3/4 -* -* Example originally created with raylib 1.6, last time updated with raylib 4.2 -* -* Example created by Ramon Santamaria (@raysan5) and reviewed by James Hofmann (@triplefox) -* -* 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-2025 Ramon Santamaria (@raysan5) and James Hofmann (@triplefox) -* -********************************************************************************************/ + * + * raylib [audio] example - raw stream + * + * Example complexity rating: [★★★☆] 3/4 + * + * Example originally created with raylib 1.6, last time updated with raylib 4.2 + * + * Example created by Ramon Santamaria (@raysan5) and reviewed by James Hofmann (@triplefox) + * + * 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-2025 Ramon Santamaria (@raysan5) and James Hofmann (@triplefox) + * + ********************************************************************************************/ #include "raylib.h" +#include -#include // Required for: malloc(), free() -#include // Required for: sinf() -#include // Required for: memcpy() - -#define MAX_SAMPLES 512 -#define MAX_SAMPLES_PER_UPDATE 4096 - -// Cycles per second (hz) -float frequency = 440.0f; - -// Audio frequency, for smoothing -float audioFrequency = 440.0f; - -// Previous value, used to test if sine needs to be rewritten, and to smoothly modulate frequency -float oldFrequency = 1.0f; - -// Index for audio rendering -float sineIdx = 0.0f; - -// Audio input processing callback -void AudioInputCallback(void *buffer, unsigned int frames) -{ - audioFrequency = frequency + (audioFrequency - frequency)*0.95f; - - float incr = audioFrequency/44100.0f; - short *d = (short *)buffer; - - for (unsigned int i = 0; i < frames; i++) - { - d[i] = (short)(32000.0f*sinf(2*PI*sineIdx)); - sineIdx += incr; - if (sineIdx > 1.0f) sineIdx -= 1.0f; - } -} +#define BUFFER_SIZE 4096 +#define SAMPLE_RATE 44100 //------------------------------------------------------------------------------------ // Program main entry point @@ -63,44 +32,25 @@ int main(void) const int screenHeight = 450; InitWindow(screenWidth, screenHeight, "raylib [audio] example - raw stream"); + SetTargetFPS(30); - InitAudioDevice(); // Initialize audio device + InitAudioDevice(); - SetAudioStreamBufferSizeDefault(MAX_SAMPLES_PER_UPDATE); + // Set the number of samples the stream will keep in memory at a time to BUFFER_SIZE + SetAudioStreamBufferSizeDefault(BUFFER_SIZE); + float buffer[BUFFER_SIZE] = {}; - // Init raw audio stream (sample rate: 44100, sample size: 16bit-short, channels: 1-mono) - AudioStream stream = LoadAudioStream(44100, 16, 1); + // Init raw audio stream (sample rate: 44100, sample size: 32bit-float, channels: 1-mono) + AudioStream stream = LoadAudioStream(SAMPLE_RATE, 32, 1); + float pan = 0.0f; + SetAudioStreamPan(stream, pan); + PlayAudioStream(stream); - SetAudioStreamCallback(stream, AudioInputCallback); + int sineFrequency = 440; + int newSineFrequency = 440; + int sineIndex = 0; + double sineStartTime = 0.0; - // Buffer for the single cycle waveform we are synthesizing - short *data = (short *)malloc(sizeof(short)*MAX_SAMPLES); - - // Frame buffer, describing the waveform when repeated over the course of a frame - short *writeBuf = (short *)malloc(sizeof(short)*MAX_SAMPLES_PER_UPDATE); - - PlayAudioStream(stream); // Start processing stream buffer (no data loaded currently) - - // Position read in to determine next frequency - Vector2 mousePosition = { -100.0f, -100.0f }; - - /* - // Cycles per second (hz) - float frequency = 440.0f; - - // Previous value, used to test if sine needs to be rewritten, and to smoothly modulate frequency - float oldFrequency = 1.0f; - - // Cursor to read and copy the samples of the sine wave buffer - int readCursor = 0; - */ - - // Computed size in samples of the sine wave - int waveLength = 1; - - Vector2 position = { 0, 0 }; - - SetTargetFPS(30); // Set our game to run at 30 frames-per-second //-------------------------------------------------------------------------------------- // Main game loop @@ -108,92 +58,84 @@ int main(void) { // Update //---------------------------------------------------------------------------------- - mousePosition = GetMousePosition(); - if (IsMouseButtonDown(MOUSE_BUTTON_LEFT)) + if (IsKeyDown(KEY_UP)) { - float fp = (float)(mousePosition.y); - frequency = 40.0f + (float)(fp); + newSineFrequency += 10; - float pan = (float)(mousePosition.x)/(float)screenWidth; - SetAudioStreamPan(stream, pan); + if (newSineFrequency > 12500) + newSineFrequency = 12500; } - // Rewrite the sine wave - // Compute two cycles to allow the buffer padding, simplifying any modulation, resampling, etc. - if (frequency != oldFrequency) + if (IsKeyDown(KEY_DOWN)) { - // Compute wavelength. Limit size in both directions - //int oldWavelength = waveLength; - waveLength = (int)(22050/frequency); - if (waveLength > MAX_SAMPLES/2) waveLength = MAX_SAMPLES/2; - if (waveLength < 1) waveLength = 1; - - // Write sine wave - for (int i = 0; i < waveLength*2; i++) - { - data[i] = (short)(sinf(((2*PI*(float)i/waveLength)))*32000); - } - // Make sure the rest of the line is flat - for (int j = waveLength*2; j < MAX_SAMPLES; j++) - { - data[j] = (short)0; - } + newSineFrequency -= 10; - // Scale read cursor's position to minimize transition artifacts - //readCursor = (int)(readCursor*((float)waveLength/(float)oldWavelength)); - oldFrequency = frequency; + if (newSineFrequency < 20) + newSineFrequency = 20; } - /* - // Refill audio stream if required - if (IsAudioStreamProcessed(stream)) + if (IsKeyDown(KEY_LEFT)) { - // Synthesize a buffer that is exactly the requested size - int writeCursor = 0; - - while (writeCursor < MAX_SAMPLES_PER_UPDATE) - { - // Start by trying to write the whole chunk at once - int writeLength = MAX_SAMPLES_PER_UPDATE-writeCursor; - - // Limit to the maximum readable size - int readLength = waveLength-readCursor; + pan -= 0.01f; + SetAudioStreamPan(stream, pan); - if (writeLength > readLength) writeLength = readLength; + if (pan < -1.0f) + pan = -1.0f; + } - // Write the slice - memcpy(writeBuf + writeCursor, data + readCursor, writeLength*sizeof(short)); + if (IsKeyDown(KEY_RIGHT)) + { + pan += 0.01f; + SetAudioStreamPan(stream, pan); - // Update cursors and loop audio - readCursor = (readCursor + writeLength)%waveLength; + if (pan > 1.0f) + pan = 1.0f; + } - writeCursor += writeLength; + if (IsAudioStreamProcessed(stream)) + { + for (int i = 0; i < BUFFER_SIZE; i++) + { + int wavelength = SAMPLE_RATE/sineFrequency; + buffer[i] = sin(2*PI*sineIndex/wavelength); + sineIndex++; + + if (sineIndex >= wavelength) + { + sineFrequency = newSineFrequency; + sineIndex = 0; + sineStartTime = GetTime(); + } } - // Copy finished frame to audio stream - UpdateAudioStream(stream, writeBuf, MAX_SAMPLES_PER_UPDATE); + UpdateAudioStream(stream, buffer, BUFFER_SIZE); } - */ + //---------------------------------------------------------------------------------- // Draw //---------------------------------------------------------------------------------- BeginDrawing(); - - ClearBackground(RAYWHITE); - - DrawText(TextFormat("sine frequency: %i",(int)frequency), GetScreenWidth() - 220, 10, 20, RED); - DrawText("click mouse button to change frequency or pan", 10, 10, 20, DARKGRAY); - - // Draw the current buffer state proportionate to the screen - for (int i = 0; i < screenWidth; i++) - { - position.x = (float)i; - position.y = 250 + 50*data[i*MAX_SAMPLES/screenWidth]/32000.0f; - - DrawPixelV(position, RED); - } + ClearBackground(RAYWHITE); + + DrawText(TextFormat("sine frequency: %i", sineFrequency), screenWidth - 220, 10, 20, RED); + DrawText(TextFormat("pan: %.2f", pan), screenWidth - 220, 30, 20, RED); + DrawText("Up/down to change frequency", 10, 10, 20, DARKGRAY); + DrawText("Left/right to pan", 10, 30, 20, DARKGRAY); + + int windowStart = (GetTime() - sineStartTime)*SAMPLE_RATE; + int windowSize = 0.1f*SAMPLE_RATE; + int wavelength = SAMPLE_RATE/sineFrequency; + + // Draw a sine wave with the same frequency as the one being sent to the audio stream + for (int i = 0; i < screenWidth; i++) { + int t0 = windowStart + i*windowSize/screenWidth; + int t1 = windowStart + (i + 1)*windowSize/screenWidth; + Vector2 startPos = { i, 250 + 50*sin(2*PI*t0/wavelength) }; + Vector2 endPos = { i + 1, 250 + 50*sin(2*PI*t1/wavelength) }; + DrawLineV(startPos, endPos, RED); + } EndDrawing(); //---------------------------------------------------------------------------------- @@ -201,9 +143,6 @@ int main(void) // De-Initialization //-------------------------------------------------------------------------------------- - free(data); // Unload sine wave data - free(writeBuf); // Unload write buffer - UnloadAudioStream(stream); // Close raw audio stream and delete buffers from RAM CloseAudioDevice(); // Close audio device (music streaming is automatically stopped) diff --git a/examples/audio/audio_raw_stream.png b/examples/audio/audio_raw_stream.png index 344f4a71077431c9f50c72e2815552acef26f7ed..b061abd2df6350a81782d55c1f960c08fee0e1de 100644 GIT binary patch literal 16468 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 literal 16736 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