← ziglang.inraylib-zigGitHub

amp envelope

★☆☆☆by Arbinda Rizki Muhammad (@arbipink)

There is no browser build of this example yet.

Is it the same as C?

//! raylib [audio] example - amp envelope
//!
//! Zig-API translation of examples/audio/audio_amp_envelope.c (raylib 6.2).
//! Example contributed by Arbinda Rizki Muhammad (@arbipink) and reviewed by Ramon Santamaria (@raysan5)
//! Example licensed under an unmodified zlib/libpng license.
//! Copyright (c) 2026 Arbinda Rizki Muhammad (@arbipink)

const rl = @import("raylib");
const gui = @import("raygui");

const buffer_size = 4096;
const sample_rate = 44100;

/// Calculate the time delta for ONE sample (1/44100)
const sample_time: f32 = 1.0 / @as(f32, sample_rate);

/// Grouping all ADSR parameters and state into a struct
const Envelope = struct {
    attack_time: f32,
    decay_time: f32,
    sustain_level: f32,
    release_time: f32,
    current_value: f32 = 0.0,
    state: State = .idle,

    /// Wave state
    const State = enum { idle, attack, decay, sustain, release };

    /// Advance the envelope by one sample.
    fn update(env: *Envelope) void {
        switch (env.state) {
            .attack => {
                env.current_value += (1.0 / env.attack_time) * sample_time;
                if (env.current_value >= 1.0) {
                    env.current_value = 1.0;
                    env.state = .decay;
                }
            },
            .decay => {
                env.current_value -= ((1.0 - env.sustain_level) / env.decay_time) * sample_time;
                if (env.current_value <= env.sustain_level) {
                    env.current_value = env.sustain_level;
                    env.state = .sustain;
                }
            },
            .sustain => env.current_value = env.sustain_level,
            .release => {
                env.current_value -= (env.sustain_level / env.release_time) * sample_time;
                if (env.current_value <= 0.001) { // Use a small threshold to avoid infinite tail
                    env.current_value = 0.0;
                    env.state = .idle;
                }
            },
            .idle => {},
        }
    }

    /// Draw the envelope shape inside `bounds`.
    fn drawGraph(env: Envelope, bounds: rl.Rectangle) void {
        rl.drawRectangleRec(bounds, rl.fade(.light_gray, 0.3));
        rl.drawRectangleLinesEx(bounds, 1, .gray);

        // Fixed visual width for sustain stage since it's an amplitude not a time value
        const sustain_width: f32 = 1.0;

        // Total time to visualize (sum of A, D, R + a padding for Sustain)
        const total_time = env.attack_time + env.decay_time + sustain_width + env.release_time;

        const scale_x = bounds.width / total_time;
        const scale_y = bounds.height;

        const start: rl.Vector2 = .{ .x = bounds.x, .y = bounds.y + bounds.height };
        const peak: rl.Vector2 = .{ .x = start.x + (env.attack_time * scale_x), .y = bounds.y };
        const sustain: rl.Vector2 = .{ .x = peak.x + (env.decay_time * scale_x), .y = bounds.y + (1.0 - env.sustain_level) * scale_y };
        const rel: rl.Vector2 = .{ .x = sustain.x + (sustain_width * scale_x), .y = sustain.y };
        const end: rl.Vector2 = .{ .x = rel.x + (env.release_time * scale_x), .y = bounds.y + bounds.height };

        rl.drawLineV(start, peak, .sky_blue);
        rl.drawLineV(peak, sustain, .blue);
        rl.drawLineV(sustain, rel, .dark_blue);
        rl.drawLineV(rel, end, .orange);

        rl.drawText("ADSR Visualizer", @intFromFloat(bounds.x), @intFromFloat(bounds.y - 20), 10, .dark_gray);
    }
};

/// Sine wave sample at `audio_time`, scaled by the envelope; advances the phase.
fn sineSample(envelope_value: f32, audio_time: *f32) f32 {
    const frequency = 440;
    const sample = envelope_value * @sin(2.0 * rl.pi * @as(f32, frequency) * audio_time.*);
    audio_time.* += 1.0 / @as(f32, sample_rate);
    return sample;
}

pub fn main() !void {
    // Initialization
    const screen_width = 800;
    const screen_height = 450;

    rl.initWindow(screen_width, screen_height, "raylib [audio] example - amp envelope");
    defer rl.closeWindow();

    rl.initAudioDevice();
    defer rl.closeAudioDevice();

    // Set the number of samples the stream will keep in memory at a time to buffer_size
    rl.setAudioStreamBufferSizeDefault(buffer_size);
    var buffer: [buffer_size]f32 = @splat(0);

    // Init raw audio stream (sample rate: 44100, sample size: 32bit-float, channels: 1-mono)
    const stream = try rl.loadAudioStream(sample_rate, 32, 1);
    defer rl.unloadAudioStream(stream);

    // Init Phase
    var audio_time: f32 = 0.0;

    var env: Envelope = .{
        .attack_time = 1.0,
        .decay_time = 1.0,
        .sustain_level = 0.5,
        .release_time = 1.0,
    };

    rl.setTargetFPS(60);

    // Main game loop
    while (!rl.windowShouldClose()) {
        // Update
        if (rl.isKeyPressed(.space)) env.state = .attack;

        if (rl.isKeyReleased(.space) and env.state != .idle) env.state = .release;

        if (rl.isAudioStreamProcessed(stream)) {
            if (env.state != .idle or env.current_value > 0.0) {
                for (&buffer) |*sample| {
                    env.update();
                    sample.* = sineSample(env.current_value, &audio_time);
                }
            } else {
                // Clear buffer if silent to avoid looping noise
                @memset(&buffer, 0);
                audio_time = 0.0;
            }

            rl.updateAudioStream(stream, &buffer, buffer_size);
        }

        if (!rl.isAudioStreamPlaying(stream)) rl.playAudioStream(stream);

        // Draw
        rl.beginDrawing();
        defer rl.endDrawing();

        rl.clearBackground(.ray_white);

        _ = gui.sliderBar(.{ .x = 100, .y = 60, .width = 400, .height = 30 }, "Attack (s)", rl.textFormat("{d:.2}s", .{env.attack_time}), &env.attack_time, 0.1, 3.0);
        _ = gui.sliderBar(.{ .x = 100, .y = 100, .width = 400, .height = 30 }, "Decay (s)", rl.textFormat("{d:.2}s", .{env.decay_time}), &env.decay_time, 0.1, 3.0);
        _ = gui.sliderBar(.{ .x = 100, .y = 140, .width = 400, .height = 30 }, "Sustain", rl.textFormat("{d:.2}", .{env.sustain_level}), &env.sustain_level, 0.0, 1.0);
        _ = gui.sliderBar(.{ .x = 100, .y = 180, .width = 400, .height = 30 }, "Release (s)", rl.textFormat("{d:.2}s", .{env.release_time}), &env.release_time, 0.1, 3.0);

        env.drawGraph(.{ .x = 100, .y = 250, .width = 400, .height = 100 });

        rl.drawCircleV(.{ .x = 520, .y = 350 - (env.current_value * 100) }, 5, .maroon);
        rl.drawText(rl.textFormat("Current Gain: {d:.2}", .{env.current_value}), 535, @intFromFloat(345 - (env.current_value * 100)), 10, .maroon);

        rl.drawText("Press SPACE to PLAY the sound!", 200, 400, 20, .light_gray);
    }
}
/*******************************************************************************************
*
*   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);
}

From raylib 6.2. The original on GitHub.