Skip to main content

siglus_scene_vm/audio/
kira_hub.rs

1use std::fmt;
2
3#[cfg(any(target_os = "horizon", target_os = "vita"))]
4use super::switch_backend::SwitchBackend as PlatformBackend;
5use anyhow::{Context, Result, anyhow};
6use kira::Volume;
7#[cfg(not(any(target_os = "horizon", target_os = "vita")))]
8use kira::manager::backend::DefaultBackend as PlatformBackend;
9use kira::manager::{AudioManager, AudioManagerSettings};
10use kira::sound::static_sound::{StaticSoundData, StaticSoundHandle};
11#[cfg(not(target_arch = "wasm32"))]
12use kira::sound::streaming::{StreamingSoundData, StreamingSoundHandle};
13use kira::track::{TrackBuilder, TrackHandle};
14use kira::tween::Tween;
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum TrackKind {
18    Bgm,
19    Se,
20    Pcm,
21    Koe,
22    Mov,
23}
24
25/// Shared Kira backend.
26///
27/// Kira 0.9 uses `AudioManager::play` plus `StaticSoundData::output_destination(&track)`.
28/// We keep one sub-track per Siglus category (BGM/SE/PCM).
29pub struct AudioHub {
30    manager: Option<AudioManager<PlatformBackend>>,
31    bgm: Option<TrackHandle>,
32    se: Option<TrackHandle>,
33    pcm: Option<TrackHandle>,
34    koe: Option<TrackHandle>,
35    mov: Option<TrackHandle>,
36    bgm_base: u8,
37    se_base: u8,
38    pcm_base: u8,
39    koe_base: u8,
40    mov_base: u8,
41    bgm_master: u8,
42    se_master: u8,
43    pcm_master: u8,
44    koe_master: u8,
45    mov_master: u8,
46}
47
48impl fmt::Debug for AudioHub {
49    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50        f.debug_struct("AudioHub")
51            .field("enabled", &self.manager.is_some())
52            .finish()
53    }
54}
55
56impl Default for AudioHub {
57    fn default() -> Self {
58        Self::new()
59    }
60}
61
62impl AudioHub {
63    pub fn new() -> Self {
64        // Replays on the virtual clock run silent: waits on a playing sound
65        // would otherwise end by the device's real playback time.
66        let manager = if cfg!(feature = "virtual-clock") {
67            Err(anyhow!("virtual clock: audio off"))
68        } else {
69            AudioManager::<PlatformBackend>::new(AudioManagerSettings::default())
70                .map_err(|e| anyhow!("{e}"))
71        };
72        match manager {
73            Ok(mut manager) => {
74                let bgm = manager.add_sub_track(TrackBuilder::default()).ok();
75                let se = manager.add_sub_track(TrackBuilder::default()).ok();
76                let pcm = manager.add_sub_track(TrackBuilder::default()).ok();
77                let koe = manager.add_sub_track(TrackBuilder::default()).ok();
78                let mov = manager.add_sub_track(TrackBuilder::default()).ok();
79                Self {
80                    manager: Some(manager),
81                    bgm,
82                    se,
83                    pcm,
84                    koe,
85                    mov,
86                    bgm_base: 255,
87                    se_base: 255,
88                    pcm_base: 255,
89                    koe_base: 255,
90                    mov_base: 255,
91                    bgm_master: 255,
92                    se_master: 255,
93                    pcm_master: 255,
94                    koe_master: 255,
95                    mov_master: 255,
96                }
97            }
98            Err(e) => {
99                eprintln!("kira init failed, audio disabled: {:#}", e);
100                Self {
101                    manager: None,
102                    bgm: None,
103                    se: None,
104                    pcm: None,
105                    koe: None,
106                    mov: None,
107                    bgm_base: 255,
108                    se_base: 255,
109                    pcm_base: 255,
110                    koe_base: 255,
111                    mov_base: 255,
112                    bgm_master: 255,
113                    se_master: 255,
114                    pcm_master: 255,
115                    koe_master: 255,
116                    mov_master: 255,
117                }
118            }
119        }
120    }
121
122    pub fn is_enabled(&self) -> bool {
123        self.manager.is_some()
124    }
125
126    fn track_ref(&self, kind: TrackKind) -> Option<&TrackHandle> {
127        match kind {
128            TrackKind::Bgm => self.bgm.as_ref(),
129            TrackKind::Se => self.se.as_ref(),
130            TrackKind::Pcm => self.pcm.as_ref(),
131            TrackKind::Koe => self.koe.as_ref(),
132            TrackKind::Mov => self.mov.as_ref(),
133        }
134    }
135
136    fn track_mut(&mut self, kind: TrackKind) -> Option<&mut TrackHandle> {
137        match kind {
138            TrackKind::Bgm => self.bgm.as_mut(),
139            TrackKind::Se => self.se.as_mut(),
140            TrackKind::Pcm => self.pcm.as_mut(),
141            TrackKind::Koe => self.koe.as_mut(),
142            TrackKind::Mov => self.mov.as_mut(),
143        }
144    }
145
146    fn track_base_mut(&mut self, kind: TrackKind) -> &mut u8 {
147        match kind {
148            TrackKind::Bgm => &mut self.bgm_base,
149            TrackKind::Se => &mut self.se_base,
150            TrackKind::Pcm => &mut self.pcm_base,
151            TrackKind::Koe => &mut self.koe_base,
152            TrackKind::Mov => &mut self.mov_base,
153        }
154    }
155
156    fn track_master_mut(&mut self, kind: TrackKind) -> &mut u8 {
157        match kind {
158            TrackKind::Bgm => &mut self.bgm_master,
159            TrackKind::Se => &mut self.se_master,
160            TrackKind::Pcm => &mut self.pcm_master,
161            TrackKind::Koe => &mut self.koe_master,
162            TrackKind::Mov => &mut self.mov_master,
163        }
164    }
165
166    fn track_base(&self, kind: TrackKind) -> u8 {
167        match kind {
168            TrackKind::Bgm => self.bgm_base,
169            TrackKind::Se => self.se_base,
170            TrackKind::Pcm => self.pcm_base,
171            TrackKind::Koe => self.koe_base,
172            TrackKind::Mov => self.mov_base,
173        }
174    }
175
176    fn track_master(&self, kind: TrackKind) -> u8 {
177        match kind {
178            TrackKind::Bgm => self.bgm_master,
179            TrackKind::Se => self.se_master,
180            TrackKind::Pcm => self.pcm_master,
181            TrackKind::Koe => self.koe_master,
182            TrackKind::Mov => self.mov_master,
183        }
184    }
185
186    fn apply_track_volume(&mut self, kind: TrackKind) {
187        let base = self.track_base(kind) as f64 / 255.0;
188        let master = self.track_master(kind) as f64 / 255.0;
189        let amp = base * master;
190        let Some(track) = self.track_mut(kind) else {
191            return;
192        };
193        track.set_volume(Volume::Amplitude(amp), Tween::default());
194    }
195
196    fn apply_track_volume_fade(&mut self, kind: TrackKind, fade_ms: i64) {
197        let base = self.track_base(kind) as f64 / 255.0;
198        let master = self.track_master(kind) as f64 / 255.0;
199        let amp = base * master;
200        let Some(track) = self.track_mut(kind) else {
201            return;
202        };
203        let tween = if fade_ms > 0 {
204            Tween {
205                duration: std::time::Duration::from_millis(fade_ms as u64),
206                ..Tween::default()
207            }
208        } else {
209            Tween::default()
210        };
211        track.set_volume(Volume::Amplitude(amp), tween);
212    }
213
214    pub fn play_static(
215        &mut self,
216        kind: TrackKind,
217        data: StaticSoundData,
218    ) -> Result<StaticSoundHandle> {
219        // Decide output destination before taking a mutable borrow of the manager.
220        let data = if let Some(track) = self.track_ref(kind) {
221            data.output_destination(track)
222        } else {
223            data
224        };
225
226        let manager = self
227            .manager
228            .as_mut()
229            .ok_or_else(|| anyhow!("audio disabled"))?;
230
231        manager.play(data).context("kira: play static sound")
232    }
233
234    #[cfg(not(target_arch = "wasm32"))]
235    pub fn play_streaming<E: Send + 'static>(
236        &mut self,
237        kind: TrackKind,
238        data: StreamingSoundData<E>,
239    ) -> Result<StreamingSoundHandle<E>> {
240        let data = if let Some(track) = self.track_ref(kind) {
241            data.output_destination(track)
242        } else {
243            data
244        };
245        let manager = self
246            .manager
247            .as_mut()
248            .ok_or_else(|| anyhow!("audio disabled"))?;
249        manager
250            .play(data)
251            .map_err(|_| anyhow!("kira: play streaming sound failed"))
252    }
253
254    pub fn set_track_volume_raw(&mut self, kind: TrackKind, volume_raw: u8) {
255        *self.track_base_mut(kind) = volume_raw;
256        self.apply_track_volume(kind);
257    }
258
259    pub fn set_track_master_volume_raw(&mut self, kind: TrackKind, volume_raw: u8) {
260        *self.track_master_mut(kind) = volume_raw;
261        self.apply_track_volume(kind);
262    }
263
264    pub fn set_track_volume_raw_fade(&mut self, kind: TrackKind, volume_raw: u8, fade_ms: i64) {
265        *self.track_base_mut(kind) = volume_raw;
266        self.apply_track_volume_fade(kind, fade_ms);
267    }
268}