Skip to content

Commit bb7b8d7

Browse files
authored
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
1 parent f36533c commit bb7b8d7

3 files changed

Lines changed: 6286 additions & 0 deletions

File tree

Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
/*******************************************************************************************
2+
*
3+
* raylib [audio] example - amp envelope
4+
*
5+
* Example complexity rating: [★☆☆☆] 1/4
6+
*
7+
* Example originally created with raylib 6.0, last time updated with raylib 6.0
8+
*
9+
* Example contributed by Arbinda Rizki Muhammad (@arbipink) and reviewed by Ramon Santamaria (@raysan5)
10+
*
11+
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
12+
* BSD-like license that allows static linking with closed source software
13+
*
14+
* Copyright (c) 2026 Arbinda Rizki Muhammad (@arbipink)
15+
*
16+
********************************************************************************************/
17+
18+
#include "raylib.h"
19+
20+
#define RAYGUI_IMPLEMENTATION
21+
#include "raygui.h"
22+
23+
#include <math.h>
24+
25+
#define BUFFER_SIZE 4096
26+
#define SAMPLE_RATE 44100
27+
28+
// Wave state
29+
typedef enum {
30+
IDLE,
31+
ATTACK,
32+
DECAY,
33+
SUSTAIN,
34+
RELEASE
35+
} ADSRState;
36+
37+
// Grouping all ADSR parameters and state into a struct
38+
typedef struct {
39+
float attackTime;
40+
float decayTime;
41+
float sustainLevel;
42+
float releaseTime;
43+
float currentValue;
44+
ADSRState state;
45+
} Envelope;
46+
47+
//------------------------------------------------------------------------------------
48+
// Module Functions Declaration
49+
//------------------------------------------------------------------------------------
50+
static void FillAudioBuffer(int i, float* buffer, float envelopeValue, float* audioTime);
51+
static void UpdateEnvelope(Envelope* env);
52+
static void DrawADSRGraph(const Envelope *env, Rectangle bounds);
53+
54+
//------------------------------------------------------------------------------------
55+
// Program main entry point
56+
//------------------------------------------------------------------------------------
57+
int main(void)
58+
{
59+
// Initialization
60+
//--------------------------------------------------------------------------------------
61+
const int screenWidth = 800;
62+
const int screenHeight = 450;
63+
64+
InitWindow(screenWidth, screenHeight, "raylib [audio] example - amp envelope");
65+
66+
InitAudioDevice();
67+
68+
// Set the number of samples the stream will keep in memory at a time to BUFFER_SIZE
69+
SetAudioStreamBufferSizeDefault(BUFFER_SIZE);
70+
float buffer[BUFFER_SIZE] = { 0 };
71+
72+
// Init raw audio stream (sample rate: 44100, sample size: 32bit-float, channels: 1-mono)
73+
AudioStream stream = LoadAudioStream(SAMPLE_RATE, 32, 1);
74+
75+
// Init Phase
76+
float audioTime = 0.0f;
77+
78+
// Initialize the struct
79+
Envelope env = {
80+
.attackTime = 1.0f,
81+
.decayTime = 1.0f,
82+
.sustainLevel = 0.5f,
83+
.releaseTime = 1.0f,
84+
.currentValue = 0.0f,
85+
.state = IDLE
86+
};
87+
88+
SetTargetFPS(60);
89+
//--------------------------------------------------------------------------------------
90+
91+
// Main game loop
92+
while (!WindowShouldClose())
93+
{
94+
// Update
95+
//----------------------------------------------------------------------------------
96+
97+
if (IsKeyPressed(KEY_SPACE)) {
98+
env.state = ATTACK;
99+
}
100+
101+
if (IsKeyReleased(KEY_SPACE)) {
102+
if (env.state != IDLE) env.state = RELEASE;
103+
}
104+
105+
if (IsAudioStreamProcessed(stream)) {
106+
107+
if (env.state != IDLE || env.currentValue > 0.0f) {
108+
for (int i = 0; i < BUFFER_SIZE; i++)
109+
{
110+
UpdateEnvelope(&env);
111+
FillAudioBuffer(i, buffer, env.currentValue, &audioTime);
112+
}
113+
} else {
114+
// Clear buffer if silent to avoid looping noise
115+
for (int i = 0; i < BUFFER_SIZE; i++) buffer[i] = 0;
116+
audioTime = 0.0f;
117+
}
118+
119+
UpdateAudioStream(stream, buffer, BUFFER_SIZE);
120+
}
121+
122+
if (!IsAudioStreamPlaying(stream)) PlayAudioStream(stream);
123+
//----------------------------------------------------------------------------------
124+
125+
// Draw
126+
//----------------------------------------------------------------------------------
127+
BeginDrawing();
128+
129+
ClearBackground(RAYWHITE);
130+
131+
GuiSliderBar((Rectangle){ 100, 60, 400, 30 }, "Attack (s)", TextFormat("%2.2fs", env.attackTime), &env.attackTime, 0.1f, 3.0f);
132+
GuiSliderBar((Rectangle){ 100, 100, 400, 30 }, "Decay (s)", TextFormat("%2.2fs", env.decayTime), &env.decayTime, 0.1f, 3.0f);
133+
GuiSliderBar((Rectangle){ 100, 140, 400, 30 }, "Sustain", TextFormat("%2.2f", env.sustainLevel), &env.sustainLevel, 0.0f, 1.0f);
134+
GuiSliderBar((Rectangle){ 100, 180, 400, 30 }, "Release (s)", TextFormat("%2.2fs", env.releaseTime), &env.releaseTime, 0.1f, 3.0f);
135+
136+
DrawADSRGraph(&env, (Rectangle){ 100, 250, 400, 100 });
137+
138+
DrawCircleV((Vector2){ 520, 350 - (env.currentValue * 100) }, 5, MAROON);
139+
DrawText(TextFormat("Current Gain: %2.2f", env.currentValue), 535, 345 - (env.currentValue * 100), 10, MAROON);
140+
141+
DrawText("Press SPACE to PLAY the sound!", 200, 400, 20, LIGHTGRAY);
142+
EndDrawing();
143+
//----------------------------------------------------------------------------------
144+
}
145+
// De-Initialization
146+
//--------------------------------------------------------------------------------------
147+
UnloadAudioStream(stream);
148+
CloseAudioDevice();
149+
150+
CloseWindow();
151+
//--------------------------------------------------------------------------------------
152+
153+
return 0;
154+
}
155+
156+
//------------------------------------------------------------------------------------
157+
// Module Functions Definition
158+
//------------------------------------------------------------------------------------
159+
static void FillAudioBuffer(int i, float* buffer, float envelopeValue, float* audioTime)
160+
{
161+
162+
int frequency = 440;
163+
buffer[i] = envelopeValue * sinf(2.0f * PI * frequency * (*audioTime));
164+
*audioTime += 1.0f / SAMPLE_RATE;
165+
}
166+
167+
static void UpdateEnvelope(Envelope *env)
168+
{
169+
// Calculate the time delta for ONE sample (1/44100)
170+
float sampleTime = 1.0f / SAMPLE_RATE;
171+
172+
switch(env->state)
173+
{
174+
case ATTACK:
175+
env->currentValue += (1.0f / env->attackTime) * sampleTime;
176+
if (env->currentValue >= 1.0f)
177+
{
178+
env->currentValue = 1.0f;
179+
env->state = DECAY;
180+
}
181+
break;
182+
183+
case DECAY:
184+
env->currentValue -= ((1.0f - env->sustainLevel) / env->decayTime) * sampleTime;
185+
if (env->currentValue <= env->sustainLevel)
186+
{
187+
env->currentValue = env->sustainLevel;
188+
env->state = SUSTAIN;
189+
}
190+
break;
191+
192+
case SUSTAIN:
193+
env->currentValue = env->sustainLevel;
194+
break;
195+
196+
case RELEASE:
197+
env->currentValue -= (env->sustainLevel / env->releaseTime) * sampleTime;
198+
if (env->currentValue <= 0.001f) // Use a small threshold to avoid infinite tail
199+
{
200+
env->currentValue = 0.0f;
201+
env->state = IDLE;
202+
}
203+
break;
204+
205+
default: break;
206+
}
207+
}
208+
209+
static void DrawADSRGraph(const Envelope *env, Rectangle bounds)
210+
{
211+
DrawRectangleRec(bounds, Fade(LIGHTGRAY, 0.3f));
212+
DrawRectangleLinesEx(bounds, 1, GRAY);
213+
214+
// Fixed visual width for sustain stage since it's an amplitude not a time value
215+
float sustainWidth = 1.0f;
216+
217+
// Total time to visualize (sum of A, D, R + a padding for Sustain)
218+
float totalTime = env->attackTime + env->decayTime + sustainWidth + env->releaseTime;
219+
220+
float scaleX = bounds.width / totalTime;
221+
float scaleY = bounds.height;
222+
223+
Vector2 start = { bounds.x, bounds.y + bounds.height };
224+
Vector2 peak = { start.x + (env->attackTime * scaleX), bounds.y };
225+
Vector2 sustain = { peak.x + (env->decayTime * scaleX), bounds.y + (1.0f - env->sustainLevel) * scaleY };
226+
Vector2 rel = { sustain.x + (sustainWidth * scaleX), sustain.y };
227+
Vector2 end = { rel.x + (env->releaseTime * scaleX), bounds.y + bounds.height };
228+
229+
DrawLineV(start, peak, SKYBLUE);
230+
DrawLineV(peak, sustain, BLUE);
231+
DrawLineV(sustain, rel, DARKBLUE);
232+
DrawLineV(rel, end, ORANGE);
233+
234+
DrawText("ADSR Visualizer", bounds.x, bounds.y - 20, 10, DARKGRAY);
235+
}
10.8 KB
Loading

0 commit comments

Comments
 (0)