nightshade-api 0.45.0

Procedural high level API for the nightshade game engine
Documentation
//! Sound loading and one call playback. The listener rides the active camera.

use nightshade::prelude::*;

/// Which mixer bus a sound plays on. Pass it to [`set_bus_volume`].
pub use nightshade::prelude::AudioBus;

/// Registers a sound under `name` from ogg, wav, mp3, or flac bytes.
pub fn load_sound(world: &mut World, name: &str, bytes: &[u8]) {
    match nightshade::ecs::audio::systems::load_sound_from_cursor(bytes.to_vec()) {
        Ok(data) => audio_engine_load_sound(&mut world.resources.audio, name, data),
        Err(error) => tracing::error!("Failed to load sound {name}: {error}"),
    }
}

/// Plays a loaded sound once, everywhere. Returns the source entity, which
/// you can [`despawn`](crate::prelude::despawn) to stop it early.
pub fn play_sound(world: &mut World, name: &str) -> Entity {
    let entity = spawn_entities(world, AUDIO_SOURCE, 1)[0];
    world
        .core
        .set_audio_source(entity, AudioSource::new(name).playing());
    entity
}

/// Plays a loaded sound on repeat until despawned. The call for music and
/// ambience.
pub fn play_sound_looping(world: &mut World, name: &str) -> Entity {
    let entity = spawn_entities(world, AUDIO_SOURCE, 1)[0];
    world
        .core
        .set_audio_source(entity, AudioSource::new(name).with_looping(true).playing());
    entity
}

/// Sets the volume of a playing sound, 0.0 silent to 1.0 full.
pub fn set_volume(world: &mut World, entity: Entity, volume: f32) {
    if let Some(source) = world.core.get_audio_source_mut(entity) {
        source.volume = volume;
    }
}

/// Plays a loaded sound once at a position in the world, spatialized
/// relative to the camera.
pub fn play_sound_at(world: &mut World, name: &str, position: Vec3) -> Entity {
    let entity = spawn_entities(
        world,
        AUDIO_SOURCE | LOCAL_TRANSFORM | LOCAL_TRANSFORM_DIRTY | GLOBAL_TRANSFORM,
        1,
    )[0];
    assign_local_transform(
        world,
        entity,
        LocalTransform {
            translation: position,
            ..Default::default()
        },
    );
    world
        .core
        .set_audio_source(entity, AudioSource::new(name).with_spatial(true).playing());
    entity
}

/// Stops a playing sound and frees it, without despawning the entity. Replaying
/// it (set the source playing again, or spawn a new one) restarts from the
/// beginning. To keep the playback position, use [`pause_sound`] instead.
pub fn stop_sound(world: &mut World, entity: Entity) {
    if let Some(source) = world.core.get_audio_source_mut(entity) {
        source.playing = false;
    }
}

/// Holds a playing sound at its current position. Unlike [`stop_sound`] the
/// sound is not freed, so [`resume_sound`] continues from where it paused.
pub fn pause_sound(world: &mut World, entity: Entity) {
    if let Some(source) = world.core.get_audio_source_mut(entity) {
        source.paused = true;
    }
}

/// Resumes a [`pause_sound`]d sound from where it stopped.
pub fn resume_sound(world: &mut World, entity: Entity) {
    if let Some(source) = world.core.get_audio_source_mut(entity) {
        source.paused = false;
    }
}

/// Fades a playing sound to a target volume over `seconds`, applied live to the
/// running sound rather than the next time it starts. The basis for crossfades.
pub fn fade_volume(world: &mut World, entity: Entity, volume: f32, seconds: f32) {
    if let Some(source) = world.core.get_audio_source_mut(entity) {
        source.volume = volume;
    }
    audio_engine_fade_volume(&mut world.resources.audio, entity, volume, seconds);
}

/// Crossfades two playing sounds over `seconds`: `fade_out` drops to silence
/// while `fade_in` rises to `volume`. Both must already be playing, typically
/// looping music on the [`AudioBus::Music`] bus.
pub fn crossfade(world: &mut World, fade_out: Entity, fade_in: Entity, volume: f32, seconds: f32) {
    fade_volume(world, fade_out, 0.0, seconds);
    fade_volume(world, fade_in, volume, seconds);
}

/// Sets the volume of a whole mixer bus in decibels, fading over `fade_seconds`.
/// 0.0 dB is unity, negative attenuates. Use [`AudioBus::Music`] for a music
/// fade, [`AudioBus::Master`] for an overall duck.
pub fn set_bus_volume(world: &mut World, bus: AudioBus, decibels: f32, fade_seconds: f32) {
    set_audio_bus_volume(world, bus, decibels, fade_seconds);
}

/// Ducks the music and ambient buses under voice, `0.0` for no ducking up to
/// `1.0` for full attenuation, easing over `fade_seconds`.
pub fn duck_voice(world: &mut World, amount: f32, fade_seconds: f32) {
    set_voice_ducking(world, amount, fade_seconds);
}

/// Sets a sound's playback rate, which scales speed and pitch together: 1.0 is
/// normal, 2.0 is double speed an octave up, 0.5 is half speed an octave down.
/// Takes effect the next time the sound starts.
pub fn set_pitch(world: &mut World, entity: Entity, rate: f32) {
    if let Some(source) = world.core.get_audio_source_mut(entity) {
        source.playback_rate = rate as f64;
    }
}

/// Sets the distance falloff of a spatial sound: full volume within `min` units
/// of the listener, fading to silence by `max`.
pub fn set_spatial_distance(world: &mut World, entity: Entity, min: f32, max: f32) {
    if let Some(source) = world.core.get_audio_source_mut(entity) {
        source.min_distance = min;
        source.max_distance = max;
    }
}