Watch
2
0
Fork
You've already forked raylib-cs
0

WASM examples (+ backports of new official examples) (#344)

* chg: New build system that uses the officially distributed binaries, bumped version to 8.0.0, simplified git workflow, removed deprecated OpenGL 1.1 functionality.

* chg: Modernize CI workflow, enable SourceLink

- Bump workflow actions to latest majors (Node 24); drop deprecated softprops/action-gh-release@v1
- Trigger push builds on main instead of master
- Create local nuget feed dir before pack (fixes NU1301)
- Enable Microsoft.SourceLink.GitHub for debugging symbols (ref PR #340)

* fix: centralized version data in Directory.build.props, and fixed various interop details that had incorrect function signatures

* chore: updated readme

* fix: version the native extract marker and chain download via DependsOnTargets

The .extracted marker now includes the raylib package name, so bumping
TargetRaylibTag re-extracts the new archive instead of silently keeping
(and packing/copying) the previous version's files.

_PrepareNativeLibrary and _StageWasmNative now depend directly on
_DownloadAndExtractInternal instead of CallTarget-ing it; dependency
targets run in the same project instance, so the resolved properties
(RaylibPackageName etc.) propagate naturally.

* fix: let the binding build for browser-wasm on both net8.0 and net10.0

The net8-era wasm workload (Microsoft.NET.Runtime.WebAssembly.Sdk 8.0.x,
auto-imported for RID browser-wasm) treats every browser-wasm project as
a wasm app: it forces OutputType=Exe after project evaluation (CS5001
for a classlib) and hooks its app-bundle build after Build, which errors
because a library has no assemblies to bundle. Opt Raylib-cs out via
DisableAutoWasmBuildApp (props time, before the workload defaults its
trigger) and pin OutputType back to Library in Directory.Build.targets
(evaluated after the workload props, so the assignment wins). net10's
wasm SDK needs neither workaround.

* chore: readme updated

* chg: simplifying build logic - a simple line in the documentation should save us the code here

* fix: Wrong signature of FrameBufferComplete

* chore: readme update

* feat: samples default to local project reference, and can optionally use the nuget package

* feat: backporting existing raylib-cs examples and new official raylib examples to WASM, adopting raylib's original code style

* chore: readme, gitignore, and targets backport.

* fix: Examples.csproj runs the download task when building locally

* feat: backporting existing raylib-cs examples and new official raylib examples to WASM, adopting raylib's original code style

* chore: readme, gitignore, and targets backport.

* chore: clean up linter warnings

* feat: html harness focuses the example and allows quick navigation with J/K instead.

* chore: readme mentions the property to use nuget vs. the local project reference

* feat: replaced the J/K navigation with good old HTML buttons

* chore: run dotnet format scoped default (was previously scoped to just 'style')
This commit is contained in:
tiger tiger tiger 2026-07-30 19:34:34 +02:00 committed by GitHub
commit 8c22e68c2a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
236 changed files with 40405 additions and 10896 deletions

View file

@ -0,0 +1,273 @@
/*******************************************************************************************
*
* raylib [audio] example - amp envelope
*
* Example complexity rating: [] 1/4
*
* Example originally created with raylib 6.0, last time updated with raylib 6.0
*
* Example contributed by Arbinda Rizki Muhammad (@arbipink) 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 Arbinda Rizki Muhammad (@arbipink)
*
********************************************************************************************/
namespace Examples.Audio;
public unsafe partial class AmpEnvelope : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
private const int BUFFER_SIZE = 4096;
private const int SAMPLE_RATE = 44100;
// Wave state
private enum ADSRState
{
Idle,
Attack,
Decay,
Sustain,
Release
}
// Grouping all ADSR parameters and state into a struct
private struct Envelope
{
public float AttackTime;
public float DecayTime;
public float SustainLevel;
public float ReleaseTime;
public float CurrentValue;
public ADSRState State;
}
public string Name => "Audio / Amp Envelope";
public string Title => "raylib [audio] example - amp envelope";
private float[] buffer;
private AudioStream stream;
private float audioTime;
private Envelope env;
public void Init()
{
InitAudioDevice();
// Set the number of samples the stream will keep in memory at a time to BUFFER_SIZE
SetAudioStreamBufferSizeDefault(BUFFER_SIZE);
buffer = new float[BUFFER_SIZE];
// Init raw audio stream (sample rate: 44100, sample size: 32bit-float, channels: 1-mono)
stream = LoadAudioStream(SAMPLE_RATE, 32, 1);
// Init Phase
audioTime = 0.0f;
// Initialize the struct
env = new Envelope
{
AttackTime = 1.0f,
DecayTime = 1.0f,
SustainLevel = 0.5f,
ReleaseTime = 1.0f,
CurrentValue = 0.0f,
State = ADSRState.Idle
};
}
public void Update()
{
// Update
//----------------------------------------------------------------------------------
if (IsKeyPressed(KeyboardKey.Space))
{
env.State = ADSRState.Attack;
}
if (IsKeyReleased(KeyboardKey.Space) && (env.State != ADSRState.Idle))
{
env.State = ADSRState.Release;
}
if (IsAudioStreamProcessed(stream))
{
if ((env.State != ADSRState.Idle) || (env.CurrentValue > 0.0f))
{
for (int i = 0; i < BUFFER_SIZE; i++)
{
UpdateEnvelope(ref env);
FillAudioBuffer(i, buffer, env.CurrentValue, ref audioTime);
}
}
else
{
// Clear buffer if silent to avoid looping noise
for (int i = 0; i < BUFFER_SIZE; i++)
{
buffer[i] = 0;
}
audioTime = 0.0f;
}
fixed (float* bufferPtr = buffer)
{
UpdateAudioStream(stream, bufferPtr, BUFFER_SIZE);
}
}
if (!IsAudioStreamPlaying(stream))
{
PlayAudioStream(stream);
}
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
// NOTE: raygui is not bound in raylib-cs, so the sliders below are left as reference.
// The envelope keeps its default parameters (Attack/Decay/Release = 1.0s, Sustain = 0.5).
//GuiSliderBar(new Rectangle( 100, 60, 400, 30 ), "Attack (s)", TextFormat("%2.2fs", env.AttackTime), ref env.AttackTime, 0.1f, 3.0f);
//GuiSliderBar(new Rectangle( 100, 100, 400, 30 ), "Decay (s)", TextFormat("%2.2fs", env.DecayTime), ref env.DecayTime, 0.1f, 3.0f);
//GuiSliderBar(new Rectangle( 100, 140, 400, 30 ), "Sustain", TextFormat("%2.2f", env.SustainLevel), ref env.SustainLevel, 0.0f, 1.0f);
//GuiSliderBar(new Rectangle( 100, 180, 400, 30 ), "Release (s)", TextFormat("%2.2fs", env.ReleaseTime), ref env.ReleaseTime, 0.1f, 3.0f);
DrawADSRGraph(ref env, new Rectangle(100, 250, 400, 100));
DrawCircleV(new Vector2(520, 350 - (env.CurrentValue * 100)), 5, Color.Maroon);
DrawText($"Current Gain: {env.CurrentValue:F2}", 535, (int)(345 - (env.CurrentValue * 100)), 10, Color.Maroon);
DrawText("Press SPACE to PLAY the sound!", 200, 400, 20, Color.LightGray);
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
UnloadAudioStream(stream);
CloseAudioDevice();
}
//------------------------------------------------------------------------------------
// Module Functions Definition
//------------------------------------------------------------------------------------
private static void FillAudioBuffer(int i, float[] buffer, float envelopeValue, ref float audioTime)
{
int frequency = 440;
buffer[i] = envelopeValue * MathF.Sin(2.0f * MathF.PI * frequency * audioTime);
audioTime += (1.0f / SAMPLE_RATE);
}
private static void UpdateEnvelope(ref Envelope env)
{
// Calculate the time delta for ONE sample (1/44100)
float sampleTime = 1.0f / SAMPLE_RATE;
switch (env.State)
{
case ADSRState.Attack:
{
env.CurrentValue += (1.0f / env.AttackTime) * sampleTime;
if (env.CurrentValue >= 1.0f)
{
env.CurrentValue = 1.0f;
env.State = ADSRState.Decay;
}
}
break;
case ADSRState.Decay:
{
env.CurrentValue -= ((1.0f - env.SustainLevel) / env.DecayTime) * sampleTime;
if (env.CurrentValue <= env.SustainLevel)
{
env.CurrentValue = env.SustainLevel;
env.State = ADSRState.Sustain;
}
}
break;
case ADSRState.Sustain:
{
env.CurrentValue = env.SustainLevel;
}
break;
case ADSRState.Release:
{
env.CurrentValue -= (env.SustainLevel / env.ReleaseTime) * sampleTime;
if (env.CurrentValue <= 0.001f) // Use a small threshold to avoid infinite tail
{
env.CurrentValue = 0.0f;
env.State = ADSRState.Idle;
}
}
break;
default:
break;
}
}
private static void DrawADSRGraph(ref Envelope env, Rectangle bounds)
{
DrawRectangleRec(bounds, Fade(Color.LightGray, 0.3f));
DrawRectangleLinesEx(bounds, 1, Color.Gray);
// Fixed visual width for sustain stage since it's an amplitude not a time value
float sustainWidth = 1.0f;
// Total time to visualize (sum of A, D, R + a padding for Sustain)
float totalTime = env.AttackTime + env.DecayTime + sustainWidth + env.ReleaseTime;
float scaleX = bounds.Width / totalTime;
float scaleY = bounds.Height;
Vector2 start = new Vector2(bounds.X, bounds.Y + bounds.Height);
Vector2 peak = new Vector2(start.X + (env.AttackTime * scaleX), bounds.Y);
Vector2 sustain = new Vector2(peak.X + (env.DecayTime * scaleX), bounds.Y + (1.0f - env.SustainLevel) * scaleY);
Vector2 rel = new Vector2(sustain.X + (sustainWidth * scaleX), sustain.Y);
Vector2 end = new Vector2(rel.X + (env.ReleaseTime * scaleX), bounds.Y + bounds.Height);
DrawLineV(start, peak, Color.SkyBlue);
DrawLineV(peak, sustain, Color.Blue);
DrawLineV(sustain, rel, Color.DarkBlue);
DrawLineV(rel, end, Color.Orange);
DrawText("ADSR Visualizer", (int)bounds.X, (int)bounds.Y - 20, 10, Color.DarkGray);
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
InitWindow(screenWidth, screenHeight, "raylib [audio] example - amp envelope");
SetTargetFPS(60);
//--------------------------------------------------------------------------------------
var game = new AmpEnvelope();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -0,0 +1,177 @@
/*******************************************************************************************
*
* raylib [audio] example - mixed processor
*
* Example complexity rating: [] 4/4
*
* 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-2025 hkc (@hatkidchan)
*
********************************************************************************************/
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Examples.Audio;
[ExcludeFromBrowser("AttachAudioMixedProcessor callback is unreliable on the wasm audio backend")]
public unsafe partial class MixedProcessor : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
public string Name => "Audio / Mixed Processor";
public string Title => "raylib [audio] example - mixed processor";
private static float exponent = 1.0f; // Audio exponentiation value
private static readonly float[] averageVolume = new float[400]; // Average volume history
private Music music;
private Sound sound;
//------------------------------------------------------------------------------------
// Audio processing function
//------------------------------------------------------------------------------------
[UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })]
private static void ProcessAudio(void* buffer, uint frames)
{
float* samples = (float*)buffer; // Samples internally stored as <float>s
float average = 0.0f; // Temporary average volume
for (uint frame = 0; frame < frames; frame++)
{
float* left = &samples[frame * 2 + 0];
float* right = &samples[frame * 2 + 1];
*left = MathF.Pow(MathF.Abs(*left), exponent) * ((*left < 0.0f) ? -1.0f : 1.0f);
*right = MathF.Pow(MathF.Abs(*right), exponent) * ((*right < 0.0f) ? -1.0f : 1.0f);
average += MathF.Abs(*left) / frames; // accumulating average volume
average += MathF.Abs(*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
}
public void Init()
{
InitAudioDevice(); // Initialize audio device
exponent = 1.0f;
Array.Clear(averageVolume, 0, averageVolume.Length);
AttachAudioMixedProcessor(&ProcessAudio);
music = LoadMusicStream("resources/audio/country.mp3");
sound = LoadSound("resources/audio/coin.wav");
PlayMusicStream(music);
}
public void Update()
{
// Update
//----------------------------------------------------------------------------------
UpdateMusicStream(music); // Update music buffer with new stream data
// Modify processing variables
//----------------------------------------------------------------------------------
if (IsKeyPressed(KeyboardKey.Left))
{
exponent -= 0.05f;
}
if (IsKeyPressed(KeyboardKey.Right))
{
exponent += 0.05f;
}
if (exponent <= 0.5f)
{
exponent = 0.5f;
}
if (exponent >= 3.0f)
{
exponent = 3.0f;
}
if (IsKeyPressed(KeyboardKey.Space))
{
PlaySound(sound);
}
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
DrawText("MUSIC SHOULD BE PLAYING!", 255, 150, 20, Color.LightGray);
DrawText($"EXPONENT = {exponent:F2}", 215, 180, 20, Color.LightGray);
DrawRectangle(199, 199, 402, 34, Color.LightGray);
for (int i = 0; i < 400; i++)
{
DrawLine(201 + i, 232 - (int)(averageVolume[i] * 32), 201 + i, 232, Color.Maroon);
}
DrawRectangleLines(199, 199, 402, 34, Color.Gray);
DrawText("PRESS SPACE TO PLAY OTHER SOUND", 200, 250, 20, Color.LightGray);
DrawText("USE LEFT AND RIGHT ARROWS TO ALTER DISTORTION", 140, 280, 20, Color.LightGray);
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
UnloadMusicStream(music); // Unload music stream buffers from RAM
DetachAudioMixedProcessor(&ProcessAudio); // Disconnect audio processor
CloseAudioDevice(); // Close audio device (music streaming is automatically stopped)
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
InitWindow(screenWidth, screenHeight, "raylib [audio] example - mixed processor");
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
//--------------------------------------------------------------------------------------
var game = new MixedProcessor();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -1,26 +1,34 @@
/*******************************************************************************************
*
* raylib [audio] example - Module playing (streaming)
* raylib [audio] example - module playing
*
* NOTE: This example requires OpenAL Soft library installed
* Example complexity rating: [] 1/4
*
* This example has been created using raylib 1.5 (www.raylib.com)
* raylib is licensed under an unmodified zlib/libpng license (View raylib.h for details)
* Example originally created with raylib 1.5, last time updated with raylib 3.5
*
* Copyright (c) 2016 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) 2016-2025 Ramon Santamaria (@raysan5)
*
********************************************************************************************/
using System.Numerics;
using static Raylib_cs.Raylib;
namespace Examples.Audio;
public class ModulePlaying
public partial class ModulePlaying : IExample
{
const int MaxCircles = 64;
private const int screenWidth = 800;
private const int screenHeight = 450;
struct CircleWave
private const int MaxCircles = 64;
public string Name => "Audio / Module Playing";
public string Title => "raylib [audio] example - module playing";
public ConfigFlags ConfigFlags => ConfigFlags.Msaa4xHint;
private struct CircleWave
{
public Vector2 Position;
public float Radius;
@ -29,20 +37,18 @@ public class ModulePlaying
public Color Color;
}
public static int Main()
private Color[] colors;
private CircleWave[] circles;
private Music music;
private float pitch;
private float timePlayed;
private bool pause;
public void Init()
{
// Initialization
//--------------------------------------------------------------------------------------
const int screenWidth = 800;
const int screenHeight = 450;
InitAudioDevice(); // Initialize audio device
SetConfigFlags(ConfigFlags.Msaa4xHint); // NOTE: Try to enable MSAA 4X
InitWindow(screenWidth, screenHeight, "raylib [audio] example - module playing (streaming)");
InitAudioDevice();
Color[] colors = new Color[14] {
colors = new Color[14] {
Color.Orange,
Color.Red,
Color.Gold,
@ -59,133 +65,163 @@ public class ModulePlaying
Color.Beige
};
// Creates ome circles for visual effect
CircleWave[] circles = new CircleWave[MaxCircles];
// Creates some circles for visual effect
circles = new CircleWave[MaxCircles];
for (int i = MaxCircles - 1; i >= 0; i--)
for (var i = MaxCircles - 1; i >= 0; i--)
{
circles[i].Alpha = 0.0f;
circles[i].Radius = GetRandomValue(10, 40);
circles[i].Position.X = GetRandomValue((int)circles[i].Radius, screenWidth - (int)circles[i].Radius);
circles[i].Position.Y = GetRandomValue((int)circles[i].Radius, screenHeight - (int)circles[i].Radius);
circles[i].Speed = (float)GetRandomValue(1, 100) / 20000.0f;
circles[i].Speed = (float)GetRandomValue(1, 100) / 2000.0f;
circles[i].Color = colors[GetRandomValue(0, 13)];
}
Music music = LoadMusicStream("resources/audio/mini1111.xm");
music = LoadMusicStream("resources/audio/mini1111.xm");
music.Looping = false;
float pitch = 1.0f;
pitch = 1.0f;
PlayMusicStream(music);
float timePlayed = 0.0f;
bool pause = false;
timePlayed = 0.0f;
pause = false;
}
SetTargetFPS(60);
public void Update()
{
// Update
//----------------------------------------------------------------------------------
UpdateMusicStream(music); // Update music buffer with new stream data
// Restart music playing (stop and play)
if (IsKeyPressed(KeyboardKey.Space))
{
StopMusicStream(music);
PlayMusicStream(music);
}
// Pause/Resume music playing
if (IsKeyPressed(KeyboardKey.P))
{
pause = !pause;
if (pause)
{
PauseMusicStream(music);
}
else
{
ResumeMusicStream(music);
}
}
if (IsKeyDown(KeyboardKey.Down))
{
pitch -= 0.01f;
}
else if (IsKeyDown(KeyboardKey.Up))
{
pitch += 0.01f;
}
SetMusicPitch(music, pitch);
// Get timePlayed scaled to bar dimensions
timePlayed = GetMusicTimePlayed(music) / GetMusicTimeLength(music) * (screenWidth - 40);
// Color circles animation
for (var i = MaxCircles - 1; (i >= 0) && !pause; i--)
{
circles[i].Alpha += circles[i].Speed;
circles[i].Radius += circles[i].Speed * 10.0f;
if (circles[i].Alpha > 1.0f)
{
circles[i].Speed *= -1;
}
if (circles[i].Alpha <= 0.0f)
{
circles[i].Alpha = 0.0f;
circles[i].Radius = GetRandomValue(10, 40);
circles[i].Position.X = GetRandomValue(
(int)circles[i].Radius,
screenWidth - (int)circles[i].Radius
);
circles[i].Position.Y = GetRandomValue(
(int)circles[i].Radius,
screenHeight - (int)circles[i].Radius
);
circles[i].Color = colors[GetRandomValue(0, 13)];
circles[i].Speed = (float)GetRandomValue(1, 100) / 2000.0f;
}
}
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
for (var i = MaxCircles - 1; i >= 0; i--)
{
DrawCircleV(
circles[i].Position,
circles[i].Radius,
Fade(circles[i].Color, circles[i].Alpha)
);
}
// Draw time bar
DrawRectangle(20, screenHeight - 20 - 12, screenWidth - 40, 12, Color.LightGray);
DrawRectangle(20, screenHeight - 20 - 12, (int)timePlayed, 12, Color.Maroon);
DrawRectangleLines(20, screenHeight - 20 - 12, screenWidth - 40, 12, Color.Gray);
// Draw help instructions
DrawRectangle(20, 20, 425, 145, Color.White);
DrawRectangleLines(20, 20, 425, 145, Color.Gray);
DrawText("PRESS SPACE TO RESTART MUSIC", 40, 40, 20, Color.Black);
DrawText("PRESS P TO PAUSE/RESUME", 40, 70, 20, Color.Black);
DrawText("PRESS UP/DOWN TO CHANGE SPEED", 40, 100, 20, Color.Black);
DrawText($"SPEED: {pitch:F6}", 40, 130, 20, Color.Maroon);
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
UnloadMusicStream(music); // Unload music stream buffers from RAM
CloseAudioDevice(); // Close audio device (music streaming is automatically stopped)
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
SetConfigFlags(ConfigFlags.Msaa4xHint); // NOTE: Try to enable MSAA 4X
InitWindow(screenWidth, screenHeight, "raylib [audio] example - module playing");
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
//--------------------------------------------------------------------------------------
var game = new ModulePlaying();
game.Init();
// Main game loop
while (!WindowShouldClose())
while (!WindowShouldClose()) // Detect window close button or ESC key
{
// Update
//----------------------------------------------------------------------------------
UpdateMusicStream(music); // Update music buffer with new stream data
// Restart music playing (stop and play)
if (IsKeyPressed(KeyboardKey.Space))
{
StopMusicStream(music);
PlayMusicStream(music);
}
// Pause/Resume music playing
if (IsKeyPressed(KeyboardKey.P))
{
pause = !pause;
if (pause)
{
PauseMusicStream(music);
}
else
{
ResumeMusicStream(music);
}
}
if (IsKeyDown(KeyboardKey.Down))
{
pitch -= 0.01f;
}
else if (IsKeyDown(KeyboardKey.Up))
{
pitch += 0.01f;
}
SetMusicPitch(music, pitch);
// Get timePlayed scaled to bar dimensions
timePlayed = GetMusicTimePlayed(music) / GetMusicTimeLength(music) * (screenWidth - 40);
// Color circles animation
for (int i = MaxCircles - 1; (i >= 0) && !pause; i--)
{
circles[i].Alpha += circles[i].Speed;
circles[i].Radius += circles[i].Speed * 10.0f;
if (circles[i].Alpha > 1.0f)
{
circles[i].Speed *= -1;
}
if (circles[i].Alpha <= 0.0f)
{
circles[i].Alpha = 0.0f;
circles[i].Radius = GetRandomValue(10, 40);
circles[i].Position.X = GetRandomValue(
(int)circles[i].Radius,
screenWidth - (int)circles[i].Radius
);
circles[i].Position.Y = GetRandomValue(
(int)circles[i].Radius,
screenHeight - (int)circles[i].Radius
);
circles[i].Color = colors[GetRandomValue(0, 13)];
circles[i].Speed = (float)GetRandomValue(1, 100) / 2000.0f;
}
}
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
for (int i = MaxCircles - 1; i >= 0; i--)
{
DrawCircleV(
circles[i].Position,
circles[i].Radius,
ColorAlpha(circles[i].Color, circles[i].Alpha)
);
}
// Draw time bar
DrawRectangle(20, screenHeight - 20 - 12, screenWidth - 40, 12, Color.LightGray);
DrawRectangle(20, screenHeight - 20 - 12, (int)timePlayed, 12, Color.Maroon);
DrawRectangleLines(20, screenHeight - 20 - 12, screenWidth - 40, 12, Color.Gray);
EndDrawing();
//----------------------------------------------------------------------------------
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
UnloadMusicStream(music);
CloseAudioDevice();
CloseWindow();
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;

View file

@ -1,104 +1,190 @@
/*******************************************************************************************
*
* raylib [audio] example - IntPtr playing (streaming)
* raylib [audio] example - music stream
*
* NOTE: This example requires OpenAL Soft library installed
* Example complexity rating: [] 1/4
*
* This example has been created using raylib 1.3 (www.raylib.com)
* raylib is licensed under an unmodified zlib/libpng license (View raylib.h for details)
* Example originally created with raylib 1.3, last time updated with raylib 4.2
*
* Copyright (c) 2015 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) 2015-2025 Ramon Santamaria (@raysan5)
*
********************************************************************************************/
using static Raylib_cs.Raylib;
namespace Examples.Audio;
public class MusicStreamDemo
public partial class MusicStreamDemo : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
public string Name => "Audio / Music Stream Demo";
public string Title => "raylib [audio] example - music stream";
public int TargetFps => 30;
private Music music;
private float timePlayed;
private bool pause;
private float pan;
private float volume;
public void Init()
{
InitAudioDevice(); // Initialize audio device
music = LoadMusicStream("resources/audio/country.mp3");
PlayMusicStream(music);
timePlayed = 0.0f; // Time played normalized [0.0f..1.0f]
pause = false; // Music playing paused
pan = 0.0f; // Default audio pan center [-1.0f..1.0f]
SetMusicPan(music, pan);
volume = 0.8f; // Default audio volume [0.0f..1.0f]
SetMusicVolume(music, volume);
}
public void Update()
{
// Update
//----------------------------------------------------------------------------------
UpdateMusicStream(music); // Update music buffer with new stream data
// Restart music playing (stop and play)
if (IsKeyPressed(KeyboardKey.Space))
{
StopMusicStream(music);
PlayMusicStream(music);
}
// Pause/Resume music playing
if (IsKeyPressed(KeyboardKey.P))
{
pause = !pause;
if (pause)
{
PauseMusicStream(music);
}
else
{
ResumeMusicStream(music);
}
}
// Set audio pan
if (IsKeyDown(KeyboardKey.Left))
{
pan -= 0.05f;
if (pan < -1.0f)
{
pan = -1.0f;
}
SetMusicPan(music, pan);
}
else if (IsKeyDown(KeyboardKey.Right))
{
pan += 0.05f;
if (pan > 1.0f)
{
pan = 1.0f;
}
SetMusicPan(music, pan);
}
// Set audio volume
if (IsKeyDown(KeyboardKey.Down))
{
volume -= 0.05f;
if (volume < 0.0f)
{
volume = 0.0f;
}
SetMusicVolume(music, volume);
}
else if (IsKeyDown(KeyboardKey.Up))
{
volume += 0.05f;
if (volume > 1.0f)
{
volume = 1.0f;
}
SetMusicVolume(music, volume);
}
// Get normalized time played for current music stream
timePlayed = GetMusicTimePlayed(music) / GetMusicTimeLength(music);
if (timePlayed > 1.0f)
{
timePlayed = 1.0f; // Make sure time played is no longer than music
}
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
DrawText("MUSIC SHOULD BE PLAYING!", 255, 150, 20, Color.LightGray);
DrawText("LEFT-RIGHT for PAN CONTROL", 320, 74, 10, Color.DarkBlue);
DrawRectangle(300, 100, 200, 12, Color.LightGray);
DrawRectangleLines(300, 100, 200, 12, Color.Gray);
DrawRectangle((int)(300 + (pan + 1.0f) / 2.0f * 200 - 5), 92, 10, 28, Color.DarkGray);
DrawRectangle(200, 200, 400, 12, Color.LightGray);
DrawRectangle(200, 200, (int)(timePlayed * 400.0f), 12, Color.Maroon);
DrawRectangleLines(200, 200, 400, 12, Color.Gray);
DrawText("PRESS SPACE TO RESTART MUSIC", 215, 250, 20, Color.LightGray);
DrawText("PRESS P TO PAUSE/RESUME MUSIC", 208, 280, 20, Color.LightGray);
DrawText("UP-DOWN for VOLUME CONTROL", 320, 334, 10, Color.DarkGreen);
DrawRectangle(300, 360, 200, 12, Color.LightGray);
DrawRectangleLines(300, 360, 200, 12, Color.Gray);
DrawRectangle((int)(300 + volume * 200 - 5), 352, 10, 28, Color.DarkGray);
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
UnloadMusicStream(music); // Unload music stream buffers from RAM
CloseAudioDevice(); // Close audio device (music streaming is automatically stopped)
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
const int screenWidth = 800;
const int screenHeight = 450;
InitWindow(screenWidth, screenHeight, "raylib [audio] example - music stream");
InitWindow(screenWidth, screenHeight, "raylib [audio] example - music playing (streaming)");
InitAudioDevice();
Music music = LoadMusicStream("resources/audio/country.mp3");
PlayMusicStream(music);
float timePlayed = 0.0f;
bool pause = false;
SetTargetFPS(60);
SetTargetFPS(30); // Set our game to run at 30 frames-per-second
//--------------------------------------------------------------------------------------
var game = new MusicStreamDemo();
game.Init();
// Main game loop
while (!WindowShouldClose())
while (!WindowShouldClose()) // Detect window close button or ESC key
{
// Update
//----------------------------------------------------------------------------------
UpdateMusicStream(music); // Update music buffer with new stream data
// Restart music playing (stop and play)
if (IsKeyPressed(KeyboardKey.Space))
{
StopMusicStream(music);
PlayMusicStream(music);
}
// Pause/Resume music playing
if (IsKeyPressed(KeyboardKey.P))
{
pause = !pause;
if (pause)
{
PauseMusicStream(music);
}
else
{
ResumeMusicStream(music);
}
}
// Get timePlayed scaled to bar dimensions (400 pixels)
timePlayed = GetMusicTimePlayed(music) / GetMusicTimeLength(music) * 400;
if (timePlayed > 400)
{
StopMusicStream(music);
}
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
DrawText("MUSIC SHOULD BE PLAYING!", 255, 150, 20, Color.LightGray);
DrawRectangle(200, 200, 400, 12, Color.LightGray);
DrawRectangle(200, 200, (int)timePlayed, 12, Color.Maroon);
DrawRectangleLines(200, 200, 400, 12, Color.Gray);
DrawText("PRESS SPACE TO RESTART MUSIC", 215, 250, 20, Color.LightGray);
DrawText("PRESS P TO PAUSE/RESUME MUSIC", 208, 280, 20, Color.LightGray);
EndDrawing();
//----------------------------------------------------------------------------------
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
UnloadMusicStream(music);
CloseAudioDevice();
CloseWindow();
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;

192
Examples/Audio/RawStream.cs Normal file
View file

@ -0,0 +1,192 @@
/*******************************************************************************************
*
* raylib [audio] example - raw stream
*
* Example complexity rating: [] 3/4
*
* Example originally created with raylib 1.6, last time updated with raylib 6.0
*
* 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-2026 Ramon Santamaria (@raysan5) and James Hofmann (@triplefox)
*
********************************************************************************************/
namespace Examples.Audio;
public unsafe partial class RawStream : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
private const int BUFFER_SIZE = 4096;
private const int SAMPLE_RATE = 44100;
public string Name => "Audio / Raw Stream";
public string Title => "raylib [audio] example - raw stream";
public int TargetFps => 30;
private float[] buffer;
private AudioStream stream;
private float pan;
private int sineFrequency;
private int newSineFrequency;
private int sineIndex;
private double sineStartTime;
public void Init()
{
InitAudioDevice();
// Set the number of samples the stream will keep in memory at a time to BUFFER_SIZE
SetAudioStreamBufferSizeDefault(BUFFER_SIZE);
buffer = new float[BUFFER_SIZE];
// Init raw audio stream (sample rate: 44100, sample size: 32bit-float, channels: 1-mono)
stream = LoadAudioStream(SAMPLE_RATE, 32, 1);
pan = 0.0f;
SetAudioStreamPan(stream, pan);
PlayAudioStream(stream);
sineFrequency = 440;
newSineFrequency = 440;
sineIndex = 0;
sineStartTime = 0.0;
}
public void Update()
{
// Update
//----------------------------------------------------------------------------------
if (IsKeyDown(KeyboardKey.Up))
{
newSineFrequency += 10;
if (newSineFrequency > 12500)
{
newSineFrequency = 12500;
}
}
if (IsKeyDown(KeyboardKey.Down))
{
newSineFrequency -= 10;
if (newSineFrequency < 20)
{
newSineFrequency = 20;
}
}
if (IsKeyDown(KeyboardKey.Left))
{
pan -= 0.01f;
if (pan < -1.0f)
{
pan = -1.0f;
}
SetAudioStreamPan(stream, pan);
}
if (IsKeyDown(KeyboardKey.Right))
{
pan += 0.01f;
if (pan > 1.0f)
{
pan = 1.0f;
}
SetAudioStreamPan(stream, pan);
}
if (IsAudioStreamProcessed(stream))
{
for (int i = 0; i < BUFFER_SIZE; i++)
{
int wl = SAMPLE_RATE / sineFrequency;
buffer[i] = MathF.Sin(2 * MathF.PI * sineIndex / wl);
sineIndex++;
if (sineIndex >= wl)
{
sineFrequency = newSineFrequency;
sineIndex = 0;
sineStartTime = GetTime();
}
}
fixed (float* bufferPtr = buffer)
{
UpdateAudioStream(stream, bufferPtr, BUFFER_SIZE);
}
}
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
DrawText($"sine frequency: {sineFrequency}", screenWidth - 220, 10, 20, Color.Red);
DrawText($"pan: {pan:F2}", screenWidth - 220, 30, 20, Color.Red);
DrawText("Up/down to change frequency", 10, 10, 20, Color.DarkGray);
DrawText("Left/right to pan", 10, 30, 20, Color.DarkGray);
int windowStart = (int)((GetTime() - sineStartTime) * SAMPLE_RATE);
int windowSize = (int)(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 = new Vector2(i, 250 + 50 * MathF.Sin(2 * MathF.PI * t0 / wavelength));
Vector2 endPos = new Vector2(i + 1, 250 + 50 * MathF.Sin(2 * MathF.PI * t1 / wavelength));
DrawLineV(startPos, endPos, Color.Red);
}
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
UnloadAudioStream(stream); // Close raw audio stream and delete buffers from RAM
CloseAudioDevice(); // Close audio device (music streaming is automatically stopped)
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
InitWindow(screenWidth, screenHeight, "raylib [audio] example - raw stream");
SetTargetFPS(30);
//--------------------------------------------------------------------------------------
var game = new RawStream();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -1,74 +1,98 @@
/*******************************************************************************************
*
* raylib [audio] example - Sound loading and playing
* raylib [audio] example - sound loading
*
* NOTE: This example requires OpenAL Soft library installed
* Example complexity rating: [] 1/4
*
* This example has been created using raylib 1.0 (www.raylib.com)
* raylib is licensed under an unmodified zlib/libpng license (View raylib.h for details)
* Example originally created with raylib 1.1, last time updated with raylib 3.5
*
* Copyright (c) 2014 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) 2014-2025 Ramon Santamaria (@raysan5)
*
********************************************************************************************/
using static Raylib_cs.Raylib;
namespace Examples.Audio;
public class SoundLoading
public partial class SoundLoading : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
public string Name => "Audio / Sound Loading";
public string Title => "raylib [audio] example - sound loading";
private Sound fxWav;
private Sound fxOgg;
public void Init()
{
InitAudioDevice(); // Initialize audio device
fxWav = LoadSound("resources/audio/sound.wav"); // Load WAV audio file
fxOgg = LoadSound("resources/audio/target.ogg"); // Load OGG audio file
}
public void Update()
{
// Update
//----------------------------------------------------------------------------------
if (IsKeyPressed(KeyboardKey.Space))
{
PlaySound(fxWav); // Play WAV sound
}
if (IsKeyPressed(KeyboardKey.Enter))
{
PlaySound(fxOgg); // Play OGG sound
}
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
DrawText("Press SPACE to PLAY the WAV sound!", 200, 180, 20, Color.LightGray);
DrawText("Press ENTER to PLAY the OGG sound!", 200, 220, 20, Color.LightGray);
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
UnloadSound(fxWav); // Unload sound data
UnloadSound(fxOgg); // Unload sound data
CloseAudioDevice(); // Close audio device
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
const int screenWidth = 800;
const int screenHeight = 450;
InitWindow(screenWidth, screenHeight, "raylib [audio] example - sound loading");
InitWindow(screenWidth, screenHeight, "raylib [audio] example - sound loading and playing");
InitAudioDevice();
Sound fxWav = LoadSound("resources/audio/sound.wav");
Sound fxOgg = LoadSound("resources/audio/target.ogg");
SetTargetFPS(60);
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
//--------------------------------------------------------------------------------------
var game = new SoundLoading();
game.Init();
// Main game loop
while (!WindowShouldClose())
while (!WindowShouldClose()) // Detect window close button or ESC key
{
// Update
//----------------------------------------------------------------------------------
if (IsKeyPressed(KeyboardKey.Space))
{
PlaySound(fxWav);
}
if (IsKeyPressed(KeyboardKey.Enter))
{
PlaySound(fxOgg);
}
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
DrawText("Press SPACE to PLAY the WAV sound!", 200, 180, 20, Color.LightGray);
DrawText("Press ENTER to PLAY the OGG sound!", 200, 220, 20, Color.LightGray);
EndDrawing();
//----------------------------------------------------------------------------------
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
UnloadSound(fxWav);
UnloadSound(fxOgg);
CloseAudioDevice();
CloseWindow();
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;

View file

@ -0,0 +1,122 @@
/*******************************************************************************************
*
* raylib [audio] example - sound multi
*
* Example complexity rating: [] 2/4
*
* Example originally created with raylib 5.0, last time updated with raylib 5.0
*
* Example contributed by Jeffery Myers (@JeffM2501) 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-2025 Jeffery Myers (@JeffM2501)
*
********************************************************************************************/
namespace Examples.Audio;
public partial class SoundMulti : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
private const int MAX_SOUNDS = 10;
public string Name => "Audio / Sound Multi";
public string Title => "raylib [audio] example - sound multi";
private Sound[] soundArray = new Sound[MAX_SOUNDS];
private int currentSound;
public void Init()
{
InitAudioDevice(); // Initialize audio device
// Load audio file into the first slot as the 'source' sound,
// this sound owns the sample data
soundArray[0] = LoadSound("resources/audio/sound.wav");
// Load an alias of the sound into slots 1-9. These do not own the sound data, but can be played
for (int i = 1; i < MAX_SOUNDS; i++)
{
soundArray[i] = LoadSoundAlias(soundArray[0]);
}
currentSound = 0; // Set the sound list to the start
}
public void Update()
{
// Update
//----------------------------------------------------------------------------------
if (IsKeyPressed(KeyboardKey.Space))
{
PlaySound(soundArray[currentSound]); // Play the next open sound slot
currentSound++; // Increment the sound slot
// If the sound slot is out of bounds, go back to 0
if (currentSound >= MAX_SOUNDS)
{
currentSound = 0;
}
// NOTE: Another approach would be to look at the list for the first sound
// that is not playing and use that slot
}
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
DrawText("Press SPACE to PLAY a WAV sound!", 200, 180, 20, Color.LightGray);
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
for (int i = 1; i < MAX_SOUNDS; i++)
{
UnloadSoundAlias(soundArray[i]); // Unload sound aliases
}
UnloadSound(soundArray[0]); // Unload source sound data
CloseAudioDevice(); // Close audio device
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
InitWindow(screenWidth, screenHeight, "raylib [audio] example - sound multi");
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
//--------------------------------------------------------------------------------------
var game = new SoundMulti();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -0,0 +1,156 @@
/*******************************************************************************************
*
* raylib [audio] example - sound positioning
*
* Example complexity rating: [] 2/4
*
* Example originally created with raylib 5.5, last time updated with raylib 5.5
*
* Example contributed by Le Juez Victor (@Bigfoot71) 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) 2025 Le Juez Victor (@Bigfoot71)
*
********************************************************************************************/
namespace Examples.Audio;
public partial class SoundPositioning : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
public string Name => "Audio / Sound Positioning";
public string Title => "raylib [audio] example - sound positioning";
public bool CursorDisabled => true;
private Sound sound;
private Camera3D camera;
public void Init()
{
InitAudioDevice();
sound = LoadSound("resources/audio/coin.wav");
camera = new Camera3D
{
Position = new Vector3(0, 5, 5),
Target = new Vector3(0, 0, 0),
Up = new Vector3(0, 1, 0),
FovY = 60,
Projection = CameraProjection.Perspective
};
}
public void Update()
{
// Update
//----------------------------------------------------------------------------------
UpdateCamera(ref camera, CameraMode.Free);
float th = (float)GetTime();
Vector3 spherePos = new Vector3(
5.0f * MathF.Cos(th),
0.0f,
5.0f * MathF.Sin(th)
);
SetSoundPosition(camera, sound, spherePos, 1.0f);
if (!IsSoundPlaying(sound))
{
PlaySound(sound);
}
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
BeginMode3D(camera);
DrawGrid(10, 2);
DrawSphere(spherePos, 0.5f, Color.Red);
EndMode3D();
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
UnloadSound(sound);
CloseAudioDevice(); // Close audio device
}
//------------------------------------------------------------------------------------
// Module Functions Definition
//------------------------------------------------------------------------------------
// Set sound 3d position
private static void SetSoundPosition(Camera3D listener, Sound sound, Vector3 position, float maxDist)
{
// Calculate direction vector and distance between listener and sound source
Vector3 direction = Vector3.Subtract(position, listener.Position);
float distance = direction.Length();
// Apply logarithmic distance attenuation and clamp between 0-1
float attenuation = 1.0f / (1.0f + (distance / maxDist));
attenuation = Math.Clamp(attenuation, 0.0f, 1.0f);
// Calculate normalized vectors for spatial positioning
Vector3 normalizedDirection = Vector3.Normalize(direction);
Vector3 forward = Vector3.Normalize(Vector3.Subtract(listener.Target, listener.Position));
Vector3 right = Vector3.Normalize(Vector3.Cross(listener.Up, forward));
// Reduce volume for sounds behind the listener
float dotProduct = Vector3.Dot(forward, normalizedDirection);
if (dotProduct < 0.0f)
{
attenuation *= (1.0f + dotProduct * 0.5f);
}
// Set stereo panning based on sound position relative to listener
float pan = 0.5f + 0.5f * Vector3.Dot(normalizedDirection, right);
// Apply final sound properties
SetSoundVolume(sound, attenuation);
SetSoundPan(sound, pan);
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
InitWindow(screenWidth, screenHeight, "raylib [audio] example - sound positioning");
DisableCursor();
SetTargetFPS(60);
//--------------------------------------------------------------------------------------
var game = new SoundPositioning();
game.Init();
// Main game loop
while (!WindowShouldClose())
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -0,0 +1,369 @@
/*******************************************************************************************
*
* 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 Ramon Santamaria (@raysan5)
*
* NOTE: Example sends a wave to the audio device,
* user gets the choice of four waves: sine, square, triangle, and sawtooth
* A stream is set up to play to the audio device; stream is hooked to a callback that
* generates a wave, that is determined by user choice
*
* 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 Dan Hoang (@dan-hoang)
*
********************************************************************************************/
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Examples.Audio;
[ExcludeFromBrowser("SetAudioStreamCallback is unreliable on the wasm audio backend")]
public unsafe partial class StreamCallback : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
private const int BUFFER_SIZE = 4096;
private const int SAMPLE_RATE = 44100;
// Wave type
private enum WaveType
{
Sine,
Square,
Triangle,
Sawtooth
}
public string Name => "Audio / Stream Callback";
public string Title => "raylib [audio] example - stream callback";
public int TargetFps => 30;
private static int waveFrequency = 440;
private static int newWaveFrequency = 440;
private static int waveIndex = 0;
// Buffer to keep the last second of uploaded audio,
// part of which will be drawn on the screen
private static readonly float[] buffer = new float[SAMPLE_RATE];
private static readonly string[] waveTypesAsString = { "sine", "square", "triangle", "sawtooth" };
private AudioStream stream;
private WaveType waveType;
public void Init()
{
InitAudioDevice();
waveFrequency = 440;
newWaveFrequency = 440;
waveIndex = 0;
Array.Clear(buffer, 0, buffer.Length);
// 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)
stream = LoadAudioStream(SAMPLE_RATE, 32, 1);
PlayAudioStream(stream);
// Configure it so that the callback for waveType is called whenever stream is out of samples
waveType = WaveType.Sine;
SetWaveCallback();
}
// Attach the callback matching the current waveType to the stream
private void SetWaveCallback()
{
switch (waveType)
{
case WaveType.Sine:
SetAudioStreamCallback(stream, &SineCallback);
break;
case WaveType.Square:
SetAudioStreamCallback(stream, &SquareCallback);
break;
case WaveType.Triangle:
SetAudioStreamCallback(stream, &TriangleCallback);
break;
case WaveType.Sawtooth:
SetAudioStreamCallback(stream, &SawtoothCallback);
break;
}
}
public void Update()
{
// Update
//----------------------------------------------------------------------------------
if (IsKeyDown(KeyboardKey.Up))
{
newWaveFrequency += 10;
if (newWaveFrequency > 12500)
{
newWaveFrequency = 12500;
}
}
if (IsKeyDown(KeyboardKey.Down))
{
newWaveFrequency -= 10;
if (newWaveFrequency < 20)
{
newWaveFrequency = 20;
}
}
if (IsKeyPressed(KeyboardKey.Left))
{
if (waveType == WaveType.Sine)
{
waveType = WaveType.Sawtooth;
}
else if (waveType == WaveType.Square)
{
waveType = WaveType.Sine;
}
else if (waveType == WaveType.Triangle)
{
waveType = WaveType.Square;
}
else
{
waveType = WaveType.Triangle;
}
SetWaveCallback();
}
if (IsKeyPressed(KeyboardKey.Right))
{
if (waveType == WaveType.Sine)
{
waveType = WaveType.Square;
}
else if (waveType == WaveType.Square)
{
waveType = WaveType.Triangle;
}
else if (waveType == WaveType.Triangle)
{
waveType = WaveType.Sawtooth;
}
else
{
waveType = WaveType.Sine;
}
SetWaveCallback();
}
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
DrawText($"frequency: {newWaveFrequency}", screenWidth - 220, 10, 20, Color.Red);
DrawText($"wave type: {waveTypesAsString[(int)waveType]}", screenWidth - 220, 30, 20, Color.Red);
DrawText("Up/down to change frequency", 10, 10, 20, Color.DarkGray);
DrawText("Left/right to change wave type", 10, 30, 20, Color.DarkGray);
// Draw the last 10 ms of uploaded audio
for (int i = 0; i < screenWidth; i++)
{
Vector2 startPos = new Vector2(i, 250 - 50 * buffer[WaveSampleIndex(i)]);
Vector2 endPos = new Vector2(i + 1, 250 - 50 * buffer[WaveSampleIndex(i + 1)]);
DrawLineV(startPos, endPos, Color.Red);
}
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
UnloadAudioStream(stream); // Close raw audio stream and delete buffers from RAM
CloseAudioDevice(); // Close audio device (music streaming is automatically stopped)
}
// Maps a screen column to a sample index in the last 10 ms of the buffer. The final column
// maps one sample past the end of the buffer; upstream C reads out of bounds there, so we
// clamp to the last valid sample (visually identical, but safe under C# bounds checking).
private static int WaveSampleIndex(int column)
{
int index = SAMPLE_RATE - SAMPLE_RATE / 100 + column * SAMPLE_RATE / 100 / screenWidth;
return index < SAMPLE_RATE ? index : SAMPLE_RATE - 1;
}
//------------------------------------------------------------------------------------
// Module Functions Definition
//------------------------------------------------------------------------------------
[UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })]
private static void SineCallback(void* framesOut, uint frameCount)
{
int fc = (int)frameCount;
int wavelength = SAMPLE_RATE / waveFrequency;
float* frames = (float*)framesOut;
// Synthesize the sine wave
for (int i = 0; i < fc; i++)
{
frames[i] = MathF.Sin(2 * MathF.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 - fc; i++)
{
buffer[i] = buffer[i + fc];
}
for (int i = 0; i < fc; i++)
{
buffer[SAMPLE_RATE - fc + i] = frames[i];
}
}
[UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })]
private static void SquareCallback(void* framesOut, uint frameCount)
{
int fc = (int)frameCount;
int wavelength = SAMPLE_RATE / waveFrequency;
float* frames = (float*)framesOut;
// Synthesize the square wave
for (int i = 0; i < fc; i++)
{
frames[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 - fc; i++)
{
buffer[i] = buffer[i + fc];
}
for (int i = 0; i < fc; i++)
{
buffer[SAMPLE_RATE - fc + i] = frames[i];
}
}
[UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })]
private static void TriangleCallback(void* framesOut, uint frameCount)
{
int fc = (int)frameCount;
int wavelength = SAMPLE_RATE / waveFrequency;
float* frames = (float*)framesOut;
// Synthesize the triangle wave
for (int i = 0; i < fc; i++)
{
frames[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 - fc; i++)
{
buffer[i] = buffer[i + fc];
}
for (int i = 0; i < fc; i++)
{
buffer[SAMPLE_RATE - fc + i] = frames[i];
}
}
[UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })]
private static void SawtoothCallback(void* framesOut, uint frameCount)
{
int fc = (int)frameCount;
int wavelength = SAMPLE_RATE / waveFrequency;
float* frames = (float*)framesOut;
// Synthesize the sawtooth wave
for (int i = 0; i < fc; i++)
{
frames[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 - fc; i++)
{
buffer[i] = buffer[i + fc];
}
for (int i = 0; i < fc; i++)
{
buffer[SAMPLE_RATE - fc + i] = frames[i];
}
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
InitWindow(screenWidth, screenHeight, "raylib [audio] example - stream callback");
SetTargetFPS(30);
//--------------------------------------------------------------------------------------
var game = new StreamCallback();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -0,0 +1,248 @@
/*******************************************************************************************
*
* raylib [audio] example - stream effects
*
* Example complexity rating: [] 4/4
*
* Example originally created with raylib 4.2, last time updated with raylib 5.0
*
* 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) 2022-2025 Ramon Santamaria (@raysan5)
*
********************************************************************************************/
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Examples.Audio;
[ExcludeFromBrowser("AttachAudioStreamProcessor is unreliable on the wasm audio backend")]
public unsafe partial class StreamEffects : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
public string Name => "Audio / Stream Effects";
public string Title => "raylib [audio] example - stream effects";
//----------------------------------------------------------------------------------
// Global Variables Definition
//----------------------------------------------------------------------------------
private static float[] delayBuffer = null;
private static uint delayBufferSize = 0;
private static uint delayReadIndex = 2;
private static uint delayWriteIndex = 0;
// Low-pass filter state (was a function-local static in the C example)
private static readonly float[] low = { 0.0f, 0.0f };
private Music music;
private float timePlayed;
private bool pause;
private bool enableEffectLPF;
private bool enableEffectDelay;
public void Init()
{
InitAudioDevice(); // Initialize audio device
music = LoadMusicStream("resources/audio/country.mp3");
// Allocate buffer for the delay effect
delayBufferSize = 48000 * 2; // 1 second delay (device sampleRate*channels)
delayBuffer = new float[delayBufferSize];
delayReadIndex = 2;
delayWriteIndex = 0;
low[0] = 0.0f;
low[1] = 0.0f;
PlayMusicStream(music);
timePlayed = 0.0f; // Time played normalized [0.0f..1.0f]
pause = false; // Music playing paused
enableEffectLPF = false; // Enable effect low-pass-filter
enableEffectDelay = false; // Enable effect delay (1 second)
}
public void Update()
{
// Update
//----------------------------------------------------------------------------------
UpdateMusicStream(music); // Update music buffer with new stream data
// Restart music playing (stop and play)
if (IsKeyPressed(KeyboardKey.Space))
{
StopMusicStream(music);
PlayMusicStream(music);
}
// Pause/Resume music playing
if (IsKeyPressed(KeyboardKey.P))
{
pause = !pause;
if (pause)
{
PauseMusicStream(music);
}
else
{
ResumeMusicStream(music);
}
}
// Add/Remove effect: lowpass filter
if (IsKeyPressed(KeyboardKey.F))
{
enableEffectLPF = !enableEffectLPF;
if (enableEffectLPF)
{
AttachAudioStreamProcessor(music.Stream, &AudioProcessEffectLPF);
}
else
{
DetachAudioStreamProcessor(music.Stream, &AudioProcessEffectLPF);
}
}
// Add/Remove effect: delay
if (IsKeyPressed(KeyboardKey.D))
{
enableEffectDelay = !enableEffectDelay;
if (enableEffectDelay)
{
AttachAudioStreamProcessor(music.Stream, &AudioProcessEffectDelay);
}
else
{
DetachAudioStreamProcessor(music.Stream, &AudioProcessEffectDelay);
}
}
// Get normalized time played for current music stream
timePlayed = GetMusicTimePlayed(music) / GetMusicTimeLength(music);
if (timePlayed > 1.0f)
{
timePlayed = 1.0f; // Make sure time played is no longer than music
}
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
DrawText("MUSIC SHOULD BE PLAYING!", 245, 150, 20, Color.LightGray);
DrawRectangle(200, 180, 400, 12, Color.LightGray);
DrawRectangle(200, 180, (int)(timePlayed * 400.0f), 12, Color.Maroon);
DrawRectangleLines(200, 180, 400, 12, Color.Gray);
DrawText("PRESS SPACE TO RESTART MUSIC", 215, 230, 20, Color.LightGray);
DrawText("PRESS P TO PAUSE/RESUME MUSIC", 208, 260, 20, Color.LightGray);
DrawText($"PRESS F TO TOGGLE LPF EFFECT: {(enableEffectLPF ? "ON" : "OFF")}", 200, 320, 20, Color.Gray);
DrawText($"PRESS D TO TOGGLE DELAY EFFECT: {(enableEffectDelay ? "ON" : "OFF")}", 180, 350, 20, Color.Gray);
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
UnloadMusicStream(music); // Unload music stream buffers from RAM
CloseAudioDevice(); // Close audio device (music streaming is automatically stopped)
delayBuffer = null; // Free delay buffer
}
//------------------------------------------------------------------------------------
// Module Functions Definition
//------------------------------------------------------------------------------------
// Audio effect: lowpass filter
[UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })]
private static void AudioProcessEffectLPF(void* buffer, uint frames)
{
const float cutoff = 70.0f / 44100.0f; // 70 Hz lowpass filter
const float k = cutoff / (cutoff + 0.1591549431f); // RC filter formula
// Converts the buffer data before using it
float* bufferData = (float*)buffer;
for (uint i = 0; i < frames * 2; i += 2)
{
float l = bufferData[i];
float r = bufferData[i + 1];
low[0] += k * (l - low[0]);
low[1] += k * (r - low[1]);
bufferData[i] = low[0];
bufferData[i + 1] = low[1];
}
}
// Audio effect: delay
[UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })]
private static void AudioProcessEffectDelay(void* buffer, uint frames)
{
float* bufferData = (float*)buffer;
fixed (float* delay = delayBuffer)
{
for (uint i = 0; i < frames * 2; i += 2)
{
float leftDelay = delay[delayReadIndex++]; // ERROR: Reading buffer -> WHY??? Maybe thread related???
float rightDelay = delay[delayReadIndex++];
if (delayReadIndex == delayBufferSize)
{
delayReadIndex = 0;
}
bufferData[i] = 0.5f * bufferData[i] + 0.5f * leftDelay;
bufferData[i + 1] = 0.5f * bufferData[i + 1] + 0.5f * rightDelay;
delay[delayWriteIndex++] = bufferData[i];
delay[delayWriteIndex++] = bufferData[i + 1];
if (delayWriteIndex == delayBufferSize)
{
delayWriteIndex = 0;
}
}
}
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
InitWindow(screenWidth, screenHeight, "raylib [audio] example - stream effects");
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
//--------------------------------------------------------------------------------------
var game = new StreamEffects();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}