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: u16Pre-packed 16-bit ADSR lower word.
upper: u16Pre-packed 16-bit ADSR upper word.
Implementations§
Source§impl Adsr
impl Adsr
Sourcepub const fn default_tone() -> Self
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].
Sourcepub const fn percussive() -> Self
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.
Sourcepub const fn sample_one_shot() -> Self
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.
Sourcepub const fn sample() -> Self
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.
Sourcepub const fn passthrough() -> Self
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§
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 Twhere
T: 'static + ?Sized,
impl<T> Any for Twhere
T: 'static + ?Sized,
§impl<T> Borrow<T> for Twhere
T: ?Sized,
impl<T> Borrow<T> for Twhere
T: ?Sized,
§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
§unsafe fn clone_to_uninit(&self, dest: *mut u8)
unsafe fn clone_to_uninit(&self, dest: *mut u8)
clone_to_uninit)