Example/audio adsr (#5713)

* add  audio_amp_envelope example

* Change 1.0f to env->sustainLevel in case the user
release the spacebar before attack state done
This commit is contained in:
Arbinda Rizki Muhammad
2026-04-04 16:04:24 +07:00
committed by GitHub
parent f36533cbd8
commit bb7b8d70e9
3 changed files with 6286 additions and 0 deletions

View File

@@ -0,0 +1,235 @@
/*******************************************************************************************
*
* 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)
*
********************************************************************************************/
#include "raylib.h"
#define RAYGUI_IMPLEMENTATION
#include "raygui.h"
#include <math.h>
#define BUFFER_SIZE 4096
#define SAMPLE_RATE 44100
// Wave state
typedef enum {
IDLE,
ATTACK,
DECAY,
SUSTAIN,
RELEASE
} ADSRState;
// Grouping all ADSR parameters and state into a struct
typedef struct {
float attackTime;
float decayTime;
float sustainLevel;
float releaseTime;
float currentValue;
ADSRState state;
} Envelope;
//------------------------------------------------------------------------------------
// Module Functions Declaration
//------------------------------------------------------------------------------------
static void FillAudioBuffer(int i, float* buffer, float envelopeValue, float* audioTime);
static void UpdateEnvelope(Envelope* env);
static void DrawADSRGraph(const Envelope *env, Rectangle bounds);
//------------------------------------------------------------------------------------
// Program main entry point
//------------------------------------------------------------------------------------
int main(void)
{
// Initialization
//--------------------------------------------------------------------------------------
const int screenWidth = 800;
const int screenHeight = 450;
InitWindow(screenWidth, screenHeight, "raylib [audio] example - amp envelope");
InitAudioDevice();
// Set the number of samples the stream will keep in memory at a time to BUFFER_SIZE
SetAudioStreamBufferSizeDefault(BUFFER_SIZE);
float buffer[BUFFER_SIZE] = { 0 };
// Init raw audio stream (sample rate: 44100, sample size: 32bit-float, channels: 1-mono)
AudioStream stream = LoadAudioStream(SAMPLE_RATE, 32, 1);
// Init Phase
float audioTime = 0.0f;
// Initialize the struct
Envelope env = {
.attackTime = 1.0f,
.decayTime = 1.0f,
.sustainLevel = 0.5f,
.releaseTime = 1.0f,
.currentValue = 0.0f,
.state = IDLE
};
SetTargetFPS(60);
//--------------------------------------------------------------------------------------
// Main game loop
while (!WindowShouldClose())
{
// Update
//----------------------------------------------------------------------------------
if (IsKeyPressed(KEY_SPACE)) {
env.state = ATTACK;
}
if (IsKeyReleased(KEY_SPACE)) {
if (env.state != IDLE) env.state = RELEASE;
}
if (IsAudioStreamProcessed(stream)) {
if (env.state != IDLE || env.currentValue > 0.0f) {
for (int i = 0; i < BUFFER_SIZE; i++)
{
UpdateEnvelope(&env);
FillAudioBuffer(i, buffer, env.currentValue, &audioTime);
}
} else {
// Clear buffer if silent to avoid looping noise
for (int i = 0; i < BUFFER_SIZE; i++) buffer[i] = 0;
audioTime = 0.0f;
}
UpdateAudioStream(stream, buffer, BUFFER_SIZE);
}
if (!IsAudioStreamPlaying(stream)) PlayAudioStream(stream);
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(RAYWHITE);
GuiSliderBar((Rectangle){ 100, 60, 400, 30 }, "Attack (s)", TextFormat("%2.2fs", env.attackTime), &env.attackTime, 0.1f, 3.0f);
GuiSliderBar((Rectangle){ 100, 100, 400, 30 }, "Decay (s)", TextFormat("%2.2fs", env.decayTime), &env.decayTime, 0.1f, 3.0f);
GuiSliderBar((Rectangle){ 100, 140, 400, 30 }, "Sustain", TextFormat("%2.2f", env.sustainLevel), &env.sustainLevel, 0.0f, 1.0f);
GuiSliderBar((Rectangle){ 100, 180, 400, 30 }, "Release (s)", TextFormat("%2.2fs", env.releaseTime), &env.releaseTime, 0.1f, 3.0f);
DrawADSRGraph(&env, (Rectangle){ 100, 250, 400, 100 });
DrawCircleV((Vector2){ 520, 350 - (env.currentValue * 100) }, 5, MAROON);
DrawText(TextFormat("Current Gain: %2.2f", env.currentValue), 535, 345 - (env.currentValue * 100), 10, MAROON);
DrawText("Press SPACE to PLAY the sound!", 200, 400, 20, LIGHTGRAY);
EndDrawing();
//----------------------------------------------------------------------------------
}
// De-Initialization
//--------------------------------------------------------------------------------------
UnloadAudioStream(stream);
CloseAudioDevice();
CloseWindow();
//--------------------------------------------------------------------------------------
return 0;
}
//------------------------------------------------------------------------------------
// Module Functions Definition
//------------------------------------------------------------------------------------
static void FillAudioBuffer(int i, float* buffer, float envelopeValue, float* audioTime)
{
int frequency = 440;
buffer[i] = envelopeValue * sinf(2.0f * PI * frequency * (*audioTime));
*audioTime += 1.0f / SAMPLE_RATE;
}
static void UpdateEnvelope(Envelope *env)
{
// Calculate the time delta for ONE sample (1/44100)
float sampleTime = 1.0f / SAMPLE_RATE;
switch(env->state)
{
case ATTACK:
env->currentValue += (1.0f / env->attackTime) * sampleTime;
if (env->currentValue >= 1.0f)
{
env->currentValue = 1.0f;
env->state = DECAY;
}
break;
case DECAY:
env->currentValue -= ((1.0f - env->sustainLevel) / env->decayTime) * sampleTime;
if (env->currentValue <= env->sustainLevel)
{
env->currentValue = env->sustainLevel;
env->state = SUSTAIN;
}
break;
case SUSTAIN:
env->currentValue = env->sustainLevel;
break;
case 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 = IDLE;
}
break;
default: break;
}
}
static void DrawADSRGraph(const Envelope *env, Rectangle bounds)
{
DrawRectangleRec(bounds, Fade(LIGHTGRAY, 0.3f));
DrawRectangleLinesEx(bounds, 1, 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 = { bounds.x, bounds.y + bounds.height };
Vector2 peak = { start.x + (env->attackTime * scaleX), bounds.y };
Vector2 sustain = { peak.x + (env->decayTime * scaleX), bounds.y + (1.0f - env->sustainLevel) * scaleY };
Vector2 rel = { sustain.x + (sustainWidth * scaleX), sustain.y };
Vector2 end = { rel.x + (env->releaseTime * scaleX), bounds.y + bounds.height };
DrawLineV(start, peak, SKYBLUE);
DrawLineV(peak, sustain, BLUE);
DrawLineV(sustain, rel, DARKBLUE);
DrawLineV(rel, end, ORANGE);
DrawText("ADSR Visualizer", bounds.x, bounds.y - 20, 10, DARKGRAY);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

6051
examples/audio/raygui.h Normal file

File diff suppressed because it is too large Load Diff