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+ }
0 commit comments