mirror of
https://github.com/raysan5/raylib.git
synced 2026-04-06 07:38:25 +00:00
239 lines
8.1 KiB
C
239 lines
8.1 KiB
C
/*******************************************************************************************
|
|
*
|
|
* 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> // Required for: sinf()
|
|
|
|
#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(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) && (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(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);
|
|
}
|