comparison 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
comparison
equal deleted inserted replaced
782:a30ce01b9154 783:ec1e06402a97
1 use kira::sound::static_sound::StaticSoundData;
2 use kira::sound::streaming::{StreamingSoundData, StreamingSoundHandle};
3 use kira::sound::FromFileError;
4 use kira::{AudioManager, AudioManagerSettings, Tween};
5 use pyo3::prelude::*;
6 use std::collections::HashMap;
7 use std::io::Cursor;
8 use std::path::PathBuf;
9
10 #[pyclass(module = "libtouhou")]
11 pub struct Audio {
12 loader: Py<super::Loader>,
13 manager: AudioManager,
14 cache: HashMap<String, StaticSoundData>,
15 bgms: [Option<(String, String)>; 4],
16 current_music: Option<StreamingSoundHandle<FromFileError>>,
17 }
18
19 #[pymethods]
20 impl Audio {
21 #[new]
22 fn new(loader: Py<super::Loader>, bgms: [Option<(String, String)>; 4]) -> Audio {
23 let manager =
24 AudioManager::<kira::DefaultBackend>::new(AudioManagerSettings::default()).unwrap();
25 let cache = HashMap::new();
26 Audio {
27 loader,
28 manager,
29 cache,
30 bgms,
31 current_music: None,
32 }
33 }
34
35 fn play_bgm(&mut self, py: Python, number: usize) {
36 let Some((_name, filename)) = &self.bgms[number] else {
37 eprintln!("Unspecified bgm number {number}");
38 return;
39 };
40
41 // Load the loop points corresponding to this bgm.
42 let mut filename = PathBuf::from(filename);
43 filename.set_extension("pos");
44 let loader = self.loader.borrow(py);
45 let loop_points = loader
46 .get_loop_points(filename.file_name().unwrap().to_str().unwrap())
47 .unwrap();
48
49 // Then try to open the music file.
50 filename.set_extension("*");
51 let path = loader
52 .game_dir
53 .clone()
54 .and_then(|dir| Some(dir.join(&filename)))
55 .unwrap_or(filename);
56 let mut music = None;
57 for path in glob::glob(path.to_str().unwrap())
58 .unwrap()
59 .map(Result::unwrap)
60 .map(PathBuf::from)
61 {
62 match StreamingSoundData::from_file(&path) {
63 Ok(sound) => {
64 music = Some(sound);
65 break;
66 }
67 Err(err) => {
68 eprintln!("Error while opening {path:?} as a music file: {err:?}");
69 continue;
70 }
71 }
72 }
73 let Some(music) = music else {
74 eprintln!("Unable to find bgm, let’s keep the previous one playing…");
75 return;
76 };
77
78 // Stop the previous playing one.
79 if let Some(current_music) = &mut self.current_music {
80 current_music.stop(Tween::default());
81 }
82
83 // And now we can start playing the new one!
84 let mut current_music = self.manager.play(music).unwrap();
85 // TODO: Fetch the sample rate from the file, instead of hardcoding it.
86 current_music.set_loop_region(
87 (loop_points.start as f64 / 44100.0)..(loop_points.end as f64 / 44100.0),
88 );
89 self.current_music = Some(current_music);
90 }
91
92 fn play(&mut self, py: Python, name: &str) {
93 let sound = self.cache.entry(name.to_string()).or_insert_with(|| {
94 let loader = self.loader.borrow(py);
95 let bytes = loader.get_file_internal(name).unwrap();
96 let cursor = Cursor::new(bytes);
97 StaticSoundData::from_cursor(cursor).unwrap()
98 });
99 self.manager.play(sound.clone()).unwrap();
100 }
101 }