Files
benilla-octo/third_party/kira/src/modulator.rs
T
SamandClaude Fable 5.1 b1294ce9a1 the quest windows go stock, and the 20 ms frames were mis-split pairs
- 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>
2026-09-04 20:55:12 +07:00

206 lines
6.9 KiB
Rust

/*!
Global values that parameters (like volume and playback rate) can be linked to.
Any type that implements [`ModulatorBuilder`] can be added to an audio manager by
using [`AudioManager::add_modulator`](crate::AudioManager::add_modulator).
If needed, you can create custom modulators by implementing the [`ModulatorBuilder`]
and [`Modulator`] traits.
# Why modulators?
Many properties of things in Kira, like the volumes of sounds, can be smoothly
transitioned from one value to another without the use of modulators. Modulators
become handy when:
- You want to control multiple properties of objects in lockstep
- You need to change a property in a way that's more complicated than a simple
transition
# Tweener example
Let's say we have a music track with two layers that play simultaneously:
drums and piano. When the player character enters water, we want the music
to sound "underwater", so we'll fade out the drums and make the piano sound
more muffled using a low-pass filter.
For this situation, a [`tweener`] is appropriate. Tweeners hold a value
that doesn't change until we tell it to, and the value can be smoothly
transitioned.
The tweener is an input value that will generate multiple output values:
the drums volume and piano filter frequency. When the tweener is set to
`0.0`, that represents that the player is not underwater, and when it's
`1.0`, the player is submerged. (These are arbitrary values.)
| Tweener value | Drums volume | Piano filter frequency |
|---------------|--------------|------------------------|
| 0.0 | 1.0 | 20,000 Hz |
| 1.0 | 0.0 | 500 Hz |
First, let's create the tweener:
```no_run
use kira::{
AudioManager, AudioManagerSettings, DefaultBackend,
modulator::tweener::TweenerBuilder,
};
let mut manager = AudioManager::<DefaultBackend>::new(AudioManagerSettings::default())?;
let mut tweener = manager.add_modulator(TweenerBuilder { initial_value: 0.0 })?;
# Result::<(), Box<dyn std::error::Error>>::Ok(())
```
Next, we'll create a mixer track with a low-pass filter effect. The piano will play
on this track so we can make it sound more or less muffled.
```no_run
# use kira::{
# AudioManager, AudioManagerSettings, DefaultBackend,
# modulator::tweener::TweenerBuilder,
# };
use kira::{
effect::filter::FilterBuilder,
Mapping, Value, Easing,
track::TrackBuilder,
};
# let mut manager = AudioManager::<DefaultBackend>::new(AudioManagerSettings::default())?;
# let mut tweener = manager.add_modulator(TweenerBuilder { initial_value: 0.0 })?;
let filter_builder = FilterBuilder::new()
.cutoff(Value::from_modulator(&tweener, Mapping {
input_range: (0.0, 1.0),
output_range: (20_000.0, 500.0),
easing: Easing::Linear,
}));
let mut piano_track = manager.add_sub_track(TrackBuilder::new().with_effect(filter_builder))?;
# Result::<(), Box<dyn std::error::Error>>::Ok(())
```
We use a `Mapping` to map the input values of the tweener to the output values
of the filter cutoff frequency.
Finally, we'll play the sounds:
```no_run
# use kira::{
# AudioManager, AudioManagerSettings, DefaultBackend,
# modulator::tweener::TweenerBuilder,
# effect::filter::FilterBuilder,
# track::TrackBuilder,
# Mapping, Value, Easing,
# Decibels,
# };
use kira::sound::static_sound::StaticSoundData;
# let mut manager = AudioManager::<DefaultBackend>::new(AudioManagerSettings::default())?;
# let mut tweener = manager.add_modulator(TweenerBuilder { initial_value: 0.0 })?;
# let filter_builder = FilterBuilder::new()
# .cutoff(Value::from_modulator(&tweener, Mapping {
# input_range: (0.0, 1.0),
# output_range: (20_000.0, 500.0),
# easing: Easing::Linear,
# }));
# let mut piano_track = manager.add_sub_track(TrackBuilder::new().with_effect(filter_builder))?;
piano_track.play(StaticSoundData::from_file("piano.ogg")?)?;
manager.play(
StaticSoundData::from_file("drums.ogg")?
.volume(Value::from_modulator(&tweener, Mapping {
input_range: (0.0, 1.0),
output_range: (Decibels::IDENTITY, Decibels::SILENCE),
easing: Easing::Linear,
}))
)?;
# Result::<(), Box<dyn std::error::Error>>::Ok(())
```
Notice how we also use a `Mapping` to map the input range of the tweener to the
output values of the sound volume.
Once the player goes underwater, we can smoothly transition the tweener's value from
`0.0` to `1.0`, which will automatically change the drum volume and piano filter frequency.
```no_run
# use kira::{
# AudioManager, AudioManagerSettings, DefaultBackend,
# modulator::tweener::TweenerBuilder,
# track::TrackBuilder,
# effect::filter::FilterBuilder,
# sound::static_sound::StaticSoundData,
# Mapping, Value, Easing,
# Decibels,
# };
use kira::Tween;
use std::time::Duration;
# let mut manager = AudioManager::<DefaultBackend>::new(AudioManagerSettings::default())?;
# let mut tweener = manager.add_modulator(TweenerBuilder { initial_value: 0.0 })?;
# let filter_builder = FilterBuilder::new()
# .cutoff(Value::from_modulator(&tweener, Mapping {
# input_range: (0.0, 1.0),
# output_range: (20_000.0, 500.0),
# easing: Easing::Linear,
# }));
# let mut piano_track = manager.add_sub_track(TrackBuilder::new().with_effect(filter_builder))?;
# piano_track.play(StaticSoundData::from_file("piano.ogg")?)?;
# manager.play(
# StaticSoundData::from_file("drums.ogg")?
# .volume(Value::from_modulator(&tweener, Mapping {
# input_range: (0.0, 1.0),
# output_range: (Decibels::IDENTITY, Decibels::SILENCE),
# easing: Easing::Linear,
# }))
# )?;
tweener.set(1.0, Tween {
duration: Duration::from_secs(3),
..Default::default()
});
# Result::<(), Box<dyn std::error::Error>>::Ok(())
```
*/
pub mod lfo;
pub mod tweener;
use atomic_arena::Key;
use crate::info::Info;
/// Configures a modulator.
pub trait ModulatorBuilder {
/// Allows the user to control the modulator from gameplay code.
type Handle;
/// Creates the modulator and a handle to the modulator.
#[must_use]
fn build(self, id: ModulatorId) -> (Box<dyn Modulator>, Self::Handle);
}
/// Produces a stream of values that a parameter can be linked to.
pub trait Modulator: Send {
/// Called whenever a new batch of audio samples is requested by the backend.
///
/// This is a good place to put code that needs to run fairly frequently,
/// but not for every single audio sample.
fn on_start_processing(&mut self) {}
/// Updates the modulator.
///
/// `dt` is the time that's elapsed since the previous round of
/// processing (in seconds).
fn update(&mut self, dt: f64, info: &Info);
/// Returns the current output of the modulator.
#[must_use]
fn value(&self) -> f64;
/// Whether the modulator can be removed from the audio context.
#[must_use]
fn finished(&self) -> bool;
}
/// A unique identifier for a modulator.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ModulatorId(pub(crate) Key);