- the quest giver's dialog and the quest log are the reference's own `QuestFrame.xml` and `QuestLogFrame.xml`, run off the player's chain at the reference's own toc seats; our 1366-line and 1999-line copies are deleted, and the verbs the stock files call are built — the four `GameTooltip` quest-item and reward-spell setters, the reward spell itself (parsed on all three packets and dropped by the model until now), and two verbs; the detail pane could never scroll because `UpdateScrollChildRect` measured a frame late where the reference's measure is synchronous, so it measures through the installed font engine now; and ten things the reference does that ours did not, each found by a test that had enshrined ours — a shift-click never selects, an empty log hides the whole detail pane and says "No Active Quests", the abandon confirm lives in the popup registry (1944) - the quest bindings' law, read off the binary and folded back: no 1.12 packet carries a quest background material — `GetQuestBackgroundMaterial` (`0x502230`) forks on the dialog's SOURCE object, an item answering its template's PageMaterial, a GameObject its template data, a creature nil (FrameXML's "Parchment"), the id naming a `PageTextMaterial.dbc` row whose six words are nowhere in the binary; its twin `ItemTextGetMaterial` has the same body, so the reader window already held every piece and the giver's feed runs the same resolve; `QuestChooseRewardError` is game-error row `0x98`, and the row plays `igQuestFailed` first, which the verb now queues; the reward-spell getters' third return is `Spell.dbc` Attributes bit 5 (1161 of 22357 rows set — craft-cast spells set, `Pattern:` teaching spells clear), read off the catalog now (1946) - the "20 ms frames" of a raid, chased to their number: the probe's worst frames now carry their churn (entities, assets, first-play sound decodes, live particles, the GPU reading) and in a Blackrock raid with sound on every one of those read flat; then the same window on the PACED clock — under vsync with pipelined rendering the raw delta is mis-split into long/short pairs that sum to exact refresh multiples, so an alone Stormwind leg read 115 of 300 frames over budget with cpu and gpu both at their means, while the paced clock read 0 skipped frames in every leg; what remains real in the raid is `cpu_p99` at 22–23 ms with sound on; and every submesh the streamer spawns now carries why it is an entity, counted per reason — 5.8k resident ADT-doodad submeshes at the Stormwind auction house, 5.3k of them hidden, three prop families the retained pass declines by rule, each riding every per-`Mesh3d` sweep for ~0.4 ms of the alone frame (1947) - a raid's dozen voices cost the mixer ~1.5 ms of every 60 Hz frame: upstream kira spatializes every frame — normalizes, the attenuation curve and a dB→amplitude `powf` per frame per spatial track, two more `powf`s for the volume tween, ~1.7 M `powf`s a second at 48 kHz × 12 tracks for gains that change imperceptibly across a chunk — so kira 0.12.1 is vendored with one patch: the spatial gains and the volume × fade amplitude are evaluated at the chunk's two ends and interpolated across its ~10.7 ms, the granularity FMOD 3, the reference's own mixer, updates them at; nothing a listener can name changes (1945); and rig poses are evaluated in parallel over rigs — each reads its shared clips and writes only its own pose, and a raid's ~400 live rigs were 0.4 ms of one thread walked serially on the main thread's critical path (1947) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
257 lines
7.9 KiB
Rust
257 lines
7.9 KiB
Rust
/*!
|
|
# Kira
|
|
|
|
Kira is a backend-agnostic library to create expressive audio for games. It provides
|
|
tweens for smoothly adjusting properties of sounds, a flexible [mixer](track)
|
|
for applying [effects](effect) to audio, a [clock] system for precisely timing audio events,
|
|
and [spatial audio](crate::track#spatial-tracks) support.
|
|
|
|
To get started, create an [`AudioManager`] and use it to [play](AudioManager::play) a
|
|
[`StaticSoundData`](crate::sound::static_sound::StaticSoundData) or
|
|
[`StreamingSoundData`](crate::sound::streaming::StreamingSoundData).
|
|
|
|
## Examples
|
|
|
|
Playing a sound multiple times simultaneously:
|
|
|
|
```rust, no_run
|
|
# extern crate kira;
|
|
#
|
|
use kira::{
|
|
AudioManager, AudioManagerSettings, DefaultBackend,
|
|
sound::static_sound::{StaticSoundData, StaticSoundSettings},
|
|
};
|
|
|
|
// Create an audio manager. This plays sounds and manages resources.
|
|
let mut manager = AudioManager::<DefaultBackend>::new(AudioManagerSettings::default())?;
|
|
let sound_data = StaticSoundData::from_file("sound.ogg")?;
|
|
manager.play(sound_data.clone())?;
|
|
// After a couple seconds...
|
|
manager.play(sound_data.clone())?;
|
|
// Cloning the sound data will not use any extra memory.
|
|
# Result::<(), Box<dyn std::error::Error>>::Ok(())
|
|
```
|
|
|
|
Gradually speeding up a sound over time:
|
|
|
|
```rust, no_run
|
|
# extern crate kira;
|
|
#
|
|
use std::time::Duration;
|
|
|
|
use kira::{
|
|
AudioManager, AudioManagerSettings, DefaultBackend,
|
|
sound::static_sound::{StaticSoundData, StaticSoundSettings},
|
|
Tween,
|
|
};
|
|
|
|
let mut manager = AudioManager::<DefaultBackend>::new(AudioManagerSettings::default())?;
|
|
let sound_data = StaticSoundData::from_file("sound.ogg")?;
|
|
let mut sound = manager.play(sound_data)?;
|
|
// Start smoothly adjusting the playback rate parameter.
|
|
sound.set_playback_rate(
|
|
2.0,
|
|
Tween {
|
|
duration: Duration::from_secs(3),
|
|
..Default::default()
|
|
},
|
|
);
|
|
# Result::<(), Box<dyn std::error::Error>>::Ok(())
|
|
```
|
|
|
|
Playing a sound with a low-pass filter applied (this makes the
|
|
audio sound muffled):
|
|
|
|
```rust, no_run
|
|
# extern crate kira;
|
|
#
|
|
use kira::{
|
|
AudioManager, AudioManagerSettings, DefaultBackend,
|
|
sound::static_sound::{StaticSoundData, StaticSoundSettings},
|
|
track::TrackBuilder,
|
|
effect::filter::FilterBuilder,
|
|
};
|
|
|
|
let mut manager = AudioManager::<DefaultBackend>::new(AudioManagerSettings::default())?;
|
|
// Create a mixer sub-track with a filter.
|
|
let mut track = manager.add_sub_track({
|
|
let mut builder = TrackBuilder::new();
|
|
builder.add_effect(FilterBuilder::new().cutoff(1000.0));
|
|
builder
|
|
})?;
|
|
// Play the sound on the track.
|
|
let sound_data = StaticSoundData::from_file("sound.ogg")?;
|
|
track.play(sound_data)?;
|
|
# Result::<(), Box<dyn std::error::Error>>::Ok(())
|
|
```
|
|
|
|
Playing sounds in time with a musical beat:
|
|
|
|
```rust, no_run
|
|
# extern crate kira;
|
|
#
|
|
use kira::{
|
|
AudioManager, AudioManagerSettings, DefaultBackend,
|
|
sound::static_sound::{StaticSoundData, StaticSoundSettings},
|
|
clock::ClockSpeed,
|
|
};
|
|
|
|
const TEMPO: f64 = 120.0;
|
|
|
|
let mut manager = AudioManager::<DefaultBackend>::new(AudioManagerSettings::default())?;
|
|
// Create a clock that ticks 120 times per second. In this case,
|
|
// each tick is one musical beat. We can use a tick to represent any
|
|
// arbitrary amount of time.
|
|
let mut clock = manager.add_clock(ClockSpeed::TicksPerMinute(TEMPO))?;
|
|
// Play a sound 2 ticks (beats) from now.
|
|
let sound_data_1 = StaticSoundData::from_file("sound1.ogg")?
|
|
.start_time(clock.time() + 2);
|
|
manager.play(sound_data_1)?;
|
|
// Play a different sound 4 ticks (beats) from now.
|
|
let sound_data_2 = StaticSoundData::from_file("sound2.ogg")?
|
|
.start_time(clock.time() + 4);
|
|
manager.play(sound_data_2)?;
|
|
// Start the clock.
|
|
clock.start();
|
|
# Result::<(), Box<dyn std::error::Error>>::Ok(())
|
|
```
|
|
|
|
## Features
|
|
|
|
The Kira crate has the following feature flags:
|
|
|
|
- `cpal` (enabled by default) - enables the cpal backend and makes it the default for
|
|
audio managers. This allows Kira to talk to the operating system to output audio. Most
|
|
users should leave this enabled.
|
|
- `symphonia` (enabled by default) - allows loading and streaming audio from common
|
|
audio formats, like MP3 and WAV.
|
|
- Audio formats: `aiff`, `caf`, `isomp4`, `mkv`, `ogg`, `wav` - these correspond to the
|
|
Symphonia features of the same name
|
|
- Audio codecs: `aac`, `adpcm`, `alac`, `he-aac`, `he-aac-v2`, `flac`, `mp1`, `mp2`, `mp3`,
|
|
`mpa`, `opus', `pcm`, `vorbis`, `wavpack` - these correspond to the Symphonia features
|
|
of the same name
|
|
- `mp3`, `ogg`, `vorbis`, `flac`, `wav`, and `pcm` are enabled by default.
|
|
- [See Symphonia's documentation for more information about these flags.](https://github.com/pdeljanov/Symphonia?tab=readme-ov-file#formats-demuxers)
|
|
- `serde` - adds `Serialize` and `Deserialize` implementations for the following types:
|
|
- [`Capacities`]
|
|
- [`ClockSpeed`](crate::clock::ClockSpeed)
|
|
- [`DistortionKind`](crate::effect::distortion::DistortionKind)
|
|
- [`Easing`]
|
|
- [`EndPosition`](crate::sound::EndPosition)
|
|
- [`EqFilterKind`](crate::effect::eq_filter::EqFilterKind)
|
|
- [`FilterMode`](crate::effect::filter::FilterMode)
|
|
- [`Frame`]
|
|
- [`Mapping`]
|
|
- [`PlaybackPosition`](crate::sound::PlaybackPosition)
|
|
- [`PlaybackRate`]
|
|
- [`PlaybackState`](crate::sound::PlaybackState)
|
|
- [`Region`](crate::sound::Region)
|
|
- [`Decibels`]
|
|
- [`Waveform`](crate::modulator::lfo::Waveform)
|
|
- `assert_no_alloc` - uses the [`assert_no_alloc`](https://crates.io/crates/assert_no_alloc) crate
|
|
to cause panics if memory is allocated or deallocated on the audio thread. This is mainly useful
|
|
for people developing Kira itself.
|
|
|
|
## Performance using the `dev` profile
|
|
|
|
By default, Rust programs run with the `dev` profile are not optimized. This can
|
|
lead to poor performance of audio playback and long loading times for audio
|
|
files. You can alleviate this by building Kira and its audio-related
|
|
dependencies with a higher optimization level. Add the following to your
|
|
Cargo.toml:
|
|
|
|
```toml
|
|
[profile.dev.package.kira]
|
|
opt-level = 3
|
|
|
|
[profile.dev.package.cpal]
|
|
opt-level = 3
|
|
|
|
[profile.dev.package.symphonia]
|
|
opt-level = 3
|
|
|
|
[profile.dev.package.symphonia-bundle-mp3]
|
|
opt-level = 3
|
|
|
|
[profile.dev.package.symphonia-format-ogg]
|
|
opt-level = 3
|
|
|
|
[profile.dev.package.symphonia-codec-vorbis]
|
|
opt-level = 3
|
|
|
|
[profile.dev.package.symphonia-bundle-flac]
|
|
opt-level = 3
|
|
|
|
[profile.dev.package.symphonia-format-wav]
|
|
opt-level = 3
|
|
|
|
[profile.dev.package.symphonia-codec-pcm]
|
|
opt-level = 3
|
|
```
|
|
|
|
You can also build all of your projects with a higher optimization level by
|
|
using this snippet instead:
|
|
|
|
```toml
|
|
[profile.dev.package."*"]
|
|
opt-level = 3
|
|
```
|
|
|
|
Building dependencies with a higher optimization level does increase compile
|
|
times, but only when compiling your project from scratch. If you only make
|
|
changes to your crate, you're not recompiling the dependencies, so you don't
|
|
suffer from the longer compilation step in that case. Building dependencies
|
|
optimized and the main crate unoptimized can be a good balance of performance
|
|
and compile times for games.
|
|
*/
|
|
|
|
#![cfg_attr(docsrs, feature(doc_cfg))]
|
|
#![allow(clippy::collapsible_if)]
|
|
#![allow(clippy::collapsible_else_if)]
|
|
#![allow(clippy::doc_lazy_continuation)]
|
|
#![allow(clippy::large_enum_variant)]
|
|
#![allow(clippy::enum_variant_names)]
|
|
#![warn(clippy::todo)]
|
|
#![warn(missing_docs)]
|
|
#![allow(clippy::tabs_in_doc_comments)]
|
|
|
|
pub mod backend;
|
|
pub mod clock;
|
|
pub mod command;
|
|
mod decibels;
|
|
pub mod effect;
|
|
mod error;
|
|
mod frame;
|
|
pub mod info;
|
|
pub mod listener;
|
|
mod manager;
|
|
mod mix;
|
|
pub mod modulator;
|
|
mod panning;
|
|
mod parameter;
|
|
mod playback_rate;
|
|
mod playback_state_manager;
|
|
mod semitones;
|
|
pub mod sound;
|
|
mod start_time;
|
|
#[cfg(test)]
|
|
mod test_helpers;
|
|
pub mod track;
|
|
mod tween;
|
|
mod value;
|
|
|
|
pub use backend::DefaultBackend;
|
|
pub use decibels::*;
|
|
pub use error::*;
|
|
pub use frame::*;
|
|
pub use manager::*;
|
|
pub use mix::*;
|
|
pub use panning::*;
|
|
pub use parameter::*;
|
|
pub use pastey;
|
|
pub use playback_rate::*;
|
|
pub use semitones::*;
|
|
pub use start_time::*;
|
|
pub use tween::*;
|
|
pub use value::*;
|