Env

An ADSR in [0, 1] over `gate`, one voice per channel of it.

Env rs
out
Type name
audio:Env
Plane
audio
Tags
transform
Language
rust
Tier
native
Bundle
audio
Source
node-bundles/audio/Env.rs
Availability
available

Slots

SlotDirectionType
outoutputAUDIO

Parameters

env

NameTypeDefaultRangeDoc
gateboolfalseHIGH above zero; an audio reference is one voice per channel
attackfloat0.010 … 10seconds to full
decayfloat0.10 … 10seconds from full to `sustain`
sustainfloat10 … 1the level held while the gate stays HIGH
releasefloat0.10 … 10seconds from full to silence once the gate drops

Source

The current source of this node, as it stands in the goofi repository at node-bundles/audio/Env.rs.

node-bundles/audio/Env.rs
use goofi_audio_sdk::goofi_core::SlotType;
use goofi_audio_sdk::{high, AudioNode, Block, Edge, Manifest, OutputDecl, ParamDecl, ParamSpec, Tag, BLOCK, MAX_CHANNELS};

goofi_audio_sdk::params! {
    GATE = ParamDecl {
        group: "env",
        name: "gate",
        spec: ParamSpec::Bool { default: false },
        expression: None,
        doc: Some("HIGH above zero; an audio reference is one voice per channel"),
    },
    ATTACK = ParamDecl {
        group: "env",
        name: "attack",
        spec: ParamSpec::Float { default: 0.01, min: 0.0, max: 10.0 },
        expression: None,
        doc: Some("seconds to full"),
    },
    DECAY = ParamDecl {
        group: "env",
        name: "decay",
        spec: ParamSpec::Float { default: 0.1, min: 0.0, max: 10.0 },
        expression: None,
        doc: Some("seconds from full to `sustain`"),
    },
    SUSTAIN = ParamDecl {
        group: "env",
        name: "sustain",
        spec: ParamSpec::Float { default: 1.0, min: 0.0, max: 1.0 },
        expression: None,
        doc: Some("the level held while the gate stays HIGH"),
    },
    RELEASE = ParamDecl {
        group: "env",
        name: "release",
        spec: ParamSpec::Float { default: 0.1, min: 0.0, max: 10.0 },
        expression: None,
        doc: Some("seconds from full to silence once the gate drops"),
    },
}

static OUTS: &[OutputDecl] = &[OutputDecl { name: "out", kind: SlotType::Audio }];

static MANIFEST: Manifest = Manifest {
    tags: &[Tag::Transform],
    doc: "An ADSR in [0, 1] over `gate`, one voice per channel of it.",
    inputs: &[],
    outputs: OUTS,
    params: PARAMS,
};

#[derive(Clone, Copy, Default, PartialEq)]
enum Stage {
    #[default]
    Idle,
    Attack,
    Decay,
    Sustain,
    Release,
}

#[derive(Default)]
struct Env {
    stage: [Stage; MAX_CHANNELS as usize],
    level: [f32; MAX_CHANNELS as usize],
    edge: [Edge; MAX_CHANNELS as usize],
    step: f32,
}

impl AudioNode for Env {
    fn prepare(&mut self, rate: f64) {
        self.step = 1.0 / rate as f32;
    }

    fn process(&mut self, b: &mut Block<'_>) {
        let out = &mut b.outs[0];
        for c in 0..out.channels() as usize {
            let gate = b.params[P::GATE].chan(c);
            let attack = b.params[P::ATTACK].chan(c);
            let decay = b.params[P::DECAY].chan(c);
            let sustain = b.params[P::SUSTAIN].chan(c);
            let release = b.params[P::RELEASE].chan(c);
            let (stage, level, edge) = (&mut self.stage[c], &mut self.level[c], &mut self.edge[c]);
            let samples = out.chan_mut(c);
            for i in 0..BLOCK {
                if edge.rising(gate[i]) {
                    *stage = Stage::Attack;
                } else if !high(gate[i]) && !matches!(*stage, Stage::Idle | Stage::Release) {
                    *stage = Stage::Release;
                }
                let per = |seconds: f32| self.step / seconds.max(1e-4);
                match *stage {
                    Stage::Attack => {
                        *level += per(attack[i]);
                        if *level >= 1.0 {
                            *level = 1.0;
                            *stage = Stage::Decay;
                        }
                    }
                    Stage::Decay => {
                        *level -= per(decay[i]) * (1.0 - sustain[i]);
                        if *level <= sustain[i] {
                            *level = sustain[i];
                            *stage = Stage::Sustain;
                        }
                    }
                    Stage::Sustain => *level = sustain[i],
                    Stage::Release => {
                        *level -= per(release[i]);
                        if *level <= 0.0 {
                            *level = 0.0;
                            *stage = Stage::Idle;
                        }
                    }
                    Stage::Idle => *level = 0.0,
                }
                samples[i] = *level;
            }
        }
    }
}

goofi_audio_sdk::export!(Env, MANIFEST);

← All nodes

This reference describes goofi 3.1.0(537cd394), generated from a running instance on 2026-09-06.