chore: clean recommit
This commit is contained in:
parent
8024c6ac40
commit
60ad2e7fb1
122 changed files with 23950 additions and 323 deletions
263
Examples/Audio/AmpEnvelope.cs
Normal file
263
Examples/Audio/AmpEnvelope.cs
Normal file
|
|
@ -0,0 +1,263 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* 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)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using static Raylib_cs.Raylib;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
159
Examples/Audio/MixedProcessor.cs
Normal file
159
Examples/Audio/MixedProcessor.cs
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* 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;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using static Raylib_cs.Raylib;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
182
Examples/Audio/RawStream.cs
Normal file
182
Examples/Audio/RawStream.cs
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* 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)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using static Raylib_cs.Raylib;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
114
Examples/Audio/SoundMulti.cs
Normal file
114
Examples/Audio/SoundMulti.cs
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* 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)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using static Raylib_cs.Raylib;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
154
Examples/Audio/SoundPositioning.cs
Normal file
154
Examples/Audio/SoundPositioning.cs
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* 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)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using static Raylib_cs.Raylib;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
306
Examples/Audio/StreamCallback.cs
Normal file
306
Examples/Audio/StreamCallback.cs
Normal file
|
|
@ -0,0 +1,306 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* 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;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using static Raylib_cs.Raylib;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
222
Examples/Audio/StreamEffects.cs
Normal file
222
Examples/Audio/StreamEffects.cs
Normal file
|
|
@ -0,0 +1,222 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* 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;
|
||||
using static Raylib_cs.Raylib;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue