diff python/src/audio.rs @ 783:ec1e06402a97

Replace SDL2_mixer with the kira crate
author Link Mauve <linkmauve@linkmauve.fr>
date Fri, 21 Nov 2025 10:21:59 +0100
parents
children
line wrap: on
line diff
new file mode 100644
--- /dev/null
+++ b/python/src/audio.rs
@@ -0,0 +1,101 @@
+use kira::sound::static_sound::StaticSoundData;
+use kira::sound::streaming::{StreamingSoundData, StreamingSoundHandle};
+use kira::sound::FromFileError;
+use kira::{AudioManager, AudioManagerSettings, Tween};
+use pyo3::prelude::*;
+use std::collections::HashMap;
+use std::io::Cursor;
+use std::path::PathBuf;
+
+#[pyclass(module = "libtouhou")]
+pub struct Audio {
+    loader: Py<super::Loader>,
+    manager: AudioManager,
+    cache: HashMap<String, StaticSoundData>,
+    bgms: [Option<(String, String)>; 4],
+    current_music: Option<StreamingSoundHandle<FromFileError>>,
+}
+
+#[pymethods]
+impl Audio {
+    #[new]
+    fn new(loader: Py<super::Loader>, bgms: [Option<(String, String)>; 4]) -> Audio {
+        let manager =
+            AudioManager::<kira::DefaultBackend>::new(AudioManagerSettings::default()).unwrap();
+        let cache = HashMap::new();
+        Audio {
+            loader,
+            manager,
+            cache,
+            bgms,
+            current_music: None,
+        }
+    }
+
+    fn play_bgm(&mut self, py: Python, number: usize) {
+        let Some((_name, filename)) = &self.bgms[number] else {
+            eprintln!("Unspecified bgm number {number}");
+            return;
+        };
+
+        // Load the loop points corresponding to this bgm.
+        let mut filename = PathBuf::from(filename);
+        filename.set_extension("pos");
+        let loader = self.loader.borrow(py);
+        let loop_points = loader
+            .get_loop_points(filename.file_name().unwrap().to_str().unwrap())
+            .unwrap();
+
+        // Then try to open the music file.
+        filename.set_extension("*");
+        let path = loader
+            .game_dir
+            .clone()
+            .and_then(|dir| Some(dir.join(&filename)))
+            .unwrap_or(filename);
+        let mut music = None;
+        for path in glob::glob(path.to_str().unwrap())
+            .unwrap()
+            .map(Result::unwrap)
+            .map(PathBuf::from)
+        {
+            match StreamingSoundData::from_file(&path) {
+                Ok(sound) => {
+                    music = Some(sound);
+                    break;
+                }
+                Err(err) => {
+                    eprintln!("Error while opening {path:?} as a music file: {err:?}");
+                    continue;
+                }
+            }
+        }
+        let Some(music) = music else {
+            eprintln!("Unable to find bgm, let’s keep the previous one playing…");
+            return;
+        };
+
+        // Stop the previous playing one.
+        if let Some(current_music) = &mut self.current_music {
+            current_music.stop(Tween::default());
+        }
+
+        // And now we can start playing the new one!
+        let mut current_music = self.manager.play(music).unwrap();
+        // TODO: Fetch the sample rate from the file, instead of hardcoding it.
+        current_music.set_loop_region(
+            (loop_points.start as f64 / 44100.0)..(loop_points.end as f64 / 44100.0),
+        );
+        self.current_music = Some(current_music);
+    }
+
+    fn play(&mut self, py: Python, name: &str) {
+        let sound = self.cache.entry(name.to_string()).or_insert_with(|| {
+            let loader = self.loader.borrow(py);
+            let bytes = loader.get_file_internal(name).unwrap();
+            let cursor = Cursor::new(bytes);
+            StaticSoundData::from_cursor(cursor).unwrap()
+        });
+        self.manager.play(sound.clone()).unwrap();
+    }
+}