chore(deps): migrate kira 0.9 → 0.12

- Import paths simplified: manager/tween modules re-exported from kira root
  (AudioManager, AudioManagerSettings, DefaultBackend, Tween all via kira::*)
- Volume::Amplitude removed; replaced with Value<Decibels> using a new
  amplitude_to_decibels() helper (20*log10 conversion, clamps to SILENCE)
- output_destination field removed from StaticSoundSettings; sounds routed
  to sub-tracks by calling TrackHandle::play() directly instead of
  AudioManager::play()
- set_volume() now accepts f32 (Decibels) not f64
- start_ambient_loop signature updated to take &mut Option<TrackHandle>

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
funman300
2026-04-28 13:54:01 -07:00
parent 21d0c289b5
commit 71c0c273a1
3 changed files with 239 additions and 84 deletions
+54 -39
View File
@@ -23,13 +23,10 @@
use std::io::Cursor;
use bevy::prelude::*;
use kira::manager::backend::DefaultBackend;
use kira::manager::{AudioManager, AudioManagerSettings};
use kira::sound::static_sound::{StaticSoundData, StaticSoundHandle};
use kira::sound::Region;
use kira::track::{TrackBuilder, TrackHandle};
use kira::tween::Tween;
use kira::Volume;
use kira::{AudioManager, AudioManagerSettings, Decibels, DefaultBackend, Tween, Value};
use crate::events::{
CardFaceRevealedEvent, CardFlippedEvent, DrawRequestEvent, GameWonEvent, MoveRejectedEvent,
@@ -46,6 +43,16 @@ const RECYCLE_VOLUME: f64 = 0.5;
/// Volume amplitude for the ambient music loop placeholder.
const AMBIENT_VOLUME: f64 = 0.05;
/// Converts a linear amplitude (0.01.0+) to the `Decibels` type used by
/// kira 0.12. Clamps to `Decibels::SILENCE` for non-positive amplitudes.
fn amplitude_to_decibels(amplitude: f32) -> Decibels {
if amplitude <= 0.0 {
Decibels::SILENCE
} else {
Decibels(20.0 * amplitude.log10())
}
}
/// Returns `true` when a `DrawRequestEvent` will recycle the waste pile back
/// to stock rather than drawing a new card.
///
@@ -56,7 +63,7 @@ fn is_recycle(stock_len: usize) -> bool {
}
/// Pre-decoded sound effects. Cheap to clone (frames are an `Arc<[Frame]>`),
/// so we hand a fresh handle to `manager.play()` on every event.
/// so we hand a fresh handle to `track.play()` on every event.
#[derive(Resource, Clone)]
pub struct SoundLibrary {
pub deal: StaticSoundData,
@@ -104,7 +111,7 @@ impl Plugin for AudioPlugin {
warn!("failed to decode embedded SFX assets; SFX disabled");
}
let (sfx_track, music_track) = match manager.as_mut() {
let (sfx_track, mut music_track) = match manager.as_mut() {
Some(mgr) => {
let sfx = mgr.add_sub_track(TrackBuilder::default()).ok();
let music = mgr.add_sub_track(TrackBuilder::default()).ok();
@@ -116,7 +123,7 @@ impl Plugin for AudioPlugin {
// Start the ambient loop placeholder (card_flip.wav looped at very low
// volume through music_track).
let ambient_handle =
start_ambient_loop(manager.as_mut(), library.as_ref(), &music_track);
start_ambient_loop(manager.as_mut(), library.as_ref(), &mut music_track);
app.insert_non_send_resource(AudioState {
manager,
@@ -190,20 +197,22 @@ fn decode(bytes: &'static [u8]) -> Option<StaticSoundData> {
fn start_ambient_loop(
manager: Option<&mut AudioManager<DefaultBackend>>,
library: Option<&SoundLibrary>,
music_track: &Option<TrackHandle>,
music_track: &mut Option<TrackHandle>,
) -> Option<StaticSoundHandle> {
let manager = manager?;
let lib = library?;
let mut data = lib.flip.clone();
// Loop the entire file from start to end.
data.settings.loop_region = Some(Region::default());
data.settings.volume = Volume::Amplitude(AMBIENT_VOLUME).into();
if let Some(track) = music_track {
data.settings.output_destination = track.id().into();
}
data.settings.volume = Value::Fixed(amplitude_to_decibels(AMBIENT_VOLUME as f32));
match manager.play(data) {
let result = if let Some(track) = music_track.as_mut() {
track.play(data)
} else {
manager.play(data)
};
match result {
Ok(handle) => Some(handle),
Err(e) => {
warn!("failed to start ambient loop: {e}");
@@ -213,16 +222,17 @@ fn start_ambient_loop(
}
fn play(audio: &mut AudioState, sound: &StaticSoundData) {
let Some(manager) = audio.manager.as_mut() else {
return;
};
let data = sound.clone();
// Route SFX through the dedicated sfx_track so its volume is independent
// of the music_track volume.
let mut data = sound.clone();
if let Some(track) = &audio.sfx_track {
data.settings.output_destination = track.id().into();
}
if let Err(e) = manager.play(data) {
let result = if let Some(track) = audio.sfx_track.as_mut() {
track.play(data)
} else if let Some(manager) = audio.manager.as_mut() {
manager.play(data)
} else {
return;
};
if let Err(e) = result {
warn!("failed to play SFX: {e}");
}
}
@@ -234,15 +244,17 @@ impl AudioState {
/// explicit volume override so callers can play sounds at a fraction of their
/// normal level. Silently does nothing when audio is unavailable.
pub fn play_sfx_at_volume(&mut self, sound: &StaticSoundData, volume: f64) {
let Some(manager) = self.manager.as_mut() else {
let mut data = sound.clone();
data.settings.volume = Value::Fixed(amplitude_to_decibels(volume as f32));
let result = if let Some(track) = self.sfx_track.as_mut() {
track.play(data)
} else if let Some(manager) = self.manager.as_mut() {
manager.play(data)
} else {
return;
};
let mut data = sound.clone();
data.settings.volume = Volume::Amplitude(volume).into();
if let Some(track) = &self.sfx_track {
data.settings.output_destination = track.id().into();
}
if let Err(e) = manager.play(data) {
if let Err(e) = result {
warn!("failed to play SFX at volume {volume}: {e}");
}
}
@@ -250,13 +262,13 @@ impl AudioState {
fn set_sfx_volume(audio: &mut AudioState, volume: f32) {
if let Some(track) = audio.sfx_track.as_mut() {
track.set_volume(volume.clamp(0.0, 1.0) as f64, Tween::default());
track.set_volume(amplitude_to_decibels(volume.clamp(0.0, 1.0)), Tween::default());
}
}
fn set_music_volume(audio: &mut AudioState, volume: f32) {
if let Some(track) = audio.music_track.as_mut() {
track.set_volume(volume.clamp(0.0, 1.0) as f64, Tween::default());
track.set_volume(amplitude_to_decibels(volume.clamp(0.0, 1.0)), Tween::default());
}
}
@@ -345,14 +357,17 @@ fn play_on_draw(
if is_recycle(stock_len) {
let mut data = lib.flip.clone();
data.settings.volume = Volume::Amplitude(RECYCLE_VOLUME).into();
if let Some(track) = &audio.sfx_track {
data.settings.output_destination = track.id().into();
}
if let Some(manager) = audio.manager.as_mut() {
if let Err(e) = manager.play(data) {
warn!("failed to play recycle SFX: {e}");
}
data.settings.volume =
Value::Fixed(amplitude_to_decibels(RECYCLE_VOLUME as f32));
let result = if let Some(track) = audio.sfx_track.as_mut() {
track.play(data)
} else if let Some(manager) = audio.manager.as_mut() {
manager.play(data)
} else {
continue;
};
if let Err(e) = result {
warn!("failed to play recycle SFX: {e}");
}
} else {
play(&mut audio, &lib.flip);