Skip to main content
← PSoXide SDK guides and examples · SDK f2fec1c301f1 · All Cargo features enabled; check feature gates before use.

Adsr

Struct Adsr 

Source
pub struct Adsr {
    pub lower: u16,
    pub upper: u16,
}
Expand description

ADSR envelope descriptor. Builds the pair of 16-bit envelope registers the SPU needs.

Hardware bit-fields (PSX-SPX § “Voice 0..23 ADSR Register”):

ADSR Lower (u16):

  bits 0..=3  : sustain level (0..15, sets target after decay)
  bits 4..=7  : decay shift (0..15)
  bits 8..=14 : attack shift (0..127)
  bit  15     : attack mode (0 = linear, 1 = exponential)

ADSR Upper (u16):

  bits 0..=4  : release shift (0..31)
  bit  5      : release mode (0 = linear, 1 = exponential)
  bits 6..=12 : sustain shift (0..127)
  bit  13     : reserved
  bit  14     : sustain direction (0 = increase, 1 = decrease)
  bit  15     : sustain mode (0 = linear, 1 = exponential)

You rarely want to set all of these by hand; use Adsr::default_tone for a generic instrument-like preset or Adsr::percussive for a snappy one-shot.

Fields§

§lower: u16

Pre-packed 16-bit ADSR lower word.

§upper: u16

Pre-packed 16-bit ADSR upper word.

Implementations§

Source§

impl Adsr

Source

pub const fn default_tone() -> Self

Generic sustained-tone envelope: instant attack to full, hold at max sustain until Voice::release, then an exponential release (~100ms). The right default for melodies and held notes.

The original version packed “attack shift 0x7F” into ADSR1’s 5-bit attack-shift field (bits 14-10); the overflow encoded attack shift 31 + step 3 – the slowest envelope the hardware can express, so the voice never rose above the noise floor. Layout per PSX-SPX: ADSR1 = [15 atk mode][14-10 atk shift] [9-8 atk step][7-4 decay shift][3-0 sustain level]; ADSR2 = [15 sus mode][14 sus dir][12-8 sus shift][7-6 sus step] [5 rel mode][4-0 rel shift].

Source

pub const fn percussive() -> Self

Percussive one-shot – blips, UI clicks, hit SFX. Instant attack to full, then a snappy exponential fade in the sustain phase (~150ms), so the voice silences itself without a key-off.

Source

pub const fn sample_one_shot() -> Self

Preserve a one-shot sample’s own attack and decay until ADPCM END. Unlike percussive, this does not fade during a sample’s lead-in. Release is fast and exponential; configure the repeat address to the silence block (as Voice::configure_sample does) for END handling.

Source

pub const fn sample() -> Self

Full-level envelope for sampled playback that a caller will end itself (looped holds, streams). NOT for fire-and-forget one-shots: on real hardware the ADPCM END+mute terminator does not silence the voice, it drops it into RELEASE at this envelope’s release rate – which is the slowest the hardware encodes – while the voice loops from the repeat address. A one-shot keyed under this envelope therefore repeats at full volume indefinitely, and key-off does not audibly help. Measured by hardware-tests SB1 on console (2026-08-02): envelope 7FFF at 4.7 s with ENDX long set, 3.7 s after key-off. The emulator zeroes the envelope at the terminator instead, so only silicon shows it. Use Adsr::percussive for one-shots; SB1 measured it silent within two frames on the same console.

Source

pub const fn passthrough() -> Self

All-zero ADSR.

This does NOT hold the voice at key-on volume, despite being the obvious “no envelope” choice: zero means sustain level 0, so on real hardware the envelope decays to silence shortly after key-on. Verified on console, where a held tone faded out after ~3 seconds. Use Adsr::sample for a level that holds.

Trait Implementations§

Source§

impl Clone for Adsr

Source§

fn clone(&self) -> Adsr

Returns a duplicate of the value. Read more
1.0.0§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Adsr

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Copy for Adsr

Auto Trait Implementations§

§

impl Freeze for Adsr

§

impl RefUnwindSafe for Adsr

§

impl Send for Adsr

§

impl Sync for Adsr

§

impl Unpin for Adsr

§

impl UnsafeUnpin for Adsr

§

impl UnwindSafe for Adsr

Blanket Implementations§

§

impl<T> Any for T
where T: 'static + ?Sized,

§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
§

impl<T> Borrow<T> for T
where T: ?Sized,

§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
§

impl<T> BorrowMut<T> for T
where T: ?Sized,

§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<T> CloneToUninit for T
where T: Clone,

§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
§

impl<T> From<T> for T

§

fn from(t: T) -> T

Returns the argument unchanged.

§

impl<T, U> Into<U> for T
where U: From<T>,

§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of [From]<T> for U chooses to do.

§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.