launcher/pages/audio/
tasks.rs1use crate::pages::audio::types::MusicPageState;
2#[cfg(target_os = "windows")]
3use crate::pages::audio::types::TargetBitDepth;
4use midas_audio::codecs::adpcm::{AdpcmState, SampleType};
5use midas_audio::types::Channels;
6#[cfg(target_os = "windows")]
7use rodio::Source;
8use std::fs::File;
9#[cfg(target_os = "windows")]
10use std::io::Write;
11use std::io::{BufReader, Read, Seek};
12#[cfg(target_os = "windows")]
13use std::num::NonZero;
14use std::path::Path;
15#[cfg(target_os = "windows")]
16use std::time::Duration;
17
18#[cfg(target_os = "windows")]
21pub fn import_music_file(
22 input_path: &Path,
23 music_dir: &Path,
24 state: &MusicPageState,
25 _storage: &supreme_fs::GameStorage,
26) -> Result<supreme_music::MusicConfig, String> {
27 let file_name = input_path
28 .file_stem()
29 .and_then(|s| s.to_str())
30 .ok_or_else(|| format!("Invalid filename: {}", input_path.display()))?;
31 let output_filename = format!("{file_name}.adp");
32 let output_path = music_dir.join(&output_filename);
33
34 if !music_dir.exists() {
36 return Err(format!(
37 "Music directory does not exist: {}. Game path may be incorrect.",
38 music_dir.display()
39 ));
40 }
41
42 let inp = File::open(input_path)
44 .map_err(|e| format!("Failed to open input file '{}': {e}", input_path.display()))?;
45 let source = rodio::Decoder::new(BufReader::new(inp)).map_err(|e| {
46 format!(
47 "Failed to decode audio file '{}': {e}. Ensure it's a valid WAV/MP3/Vorbis/FLAC file.",
48 input_path.display()
49 )
50 })?;
51
52 let input_channels = source.channels();
53 let input_rate = source.sample_rate();
54
55 use rodio::Source as _;
57 let input_samples: Vec<i16> = source.map(|s: f32| (s * i16::MAX as f32) as i16).collect();
58
59 let target_rate = state.target_sample_rate.to_u32(Some(input_rate.into()));
61 let target_channels = state.target_channels;
62 let target_channels_count = target_channels.count();
63
64 let processed_samples = midas_audio::dsp::resample_and_mix(
66 &input_samples,
67 input_channels.get() as usize,
68 input_rate.into(),
69 target_channels_count,
70 target_rate,
71 );
72
73 let frame_len = 1024;
75 let encoding_type = match state.target_bit_depth {
76 TargetBitDepth::Adpcm => match target_channels {
77 Channels::Stereo => midas_audio::codecs::adpcm::SampleType::AdpcmStereo,
78 Channels::Mono => midas_audio::codecs::adpcm::SampleType::AdpcmMono,
79 },
80 TargetBitDepth::EightBit => match target_channels {
81 Channels::Stereo => midas_audio::codecs::adpcm::SampleType::PcmStereo8,
82 Channels::Mono => midas_audio::codecs::adpcm::SampleType::PcmMono8,
83 },
84 TargetBitDepth::SixteenBit => match target_channels {
85 Channels::Stereo => midas_audio::codecs::adpcm::SampleType::PcmStereo16,
86 Channels::Mono => midas_audio::codecs::adpcm::SampleType::PcmMono16,
87 },
88 };
89
90 let mut adpcm_state =
91 AdpcmState::with_type(target_channels, frame_len, encoding_type, target_rate);
92
93 let mut out_file = File::create(&output_path).map_err(|e| {
94 format!(
95 "Failed to create output file '{}': {e}. Check directory permissions and disk space.",
96 output_path.display()
97 )
98 })?;
99 let mut out_buf = vec![0u8; 1024 * 64]; let mut offset = 0;
102 while offset < processed_samples.len() {
103 let (used, written) = adpcm_state.encode(&processed_samples[offset..], &mut out_buf);
104 out_file.write_all(&out_buf[..written]).map_err(|e| {
105 format!(
106 "Failed to write to output file '{}': {e}. Disk may be full.",
107 output_path.display()
108 )
109 })?;
110 offset += used / 2; if used == 0 && written == 0 {
112 break;
113 }
114 }
115
116 let relative_path = format!("data/music/{output_filename}");
117
118 Ok(supreme_music::MusicConfig {
119 filename: relative_path,
120 halfpipe: true,
121 tracks: true,
122 author: "Unknown".to_string(),
123 track: file_name.replace('_', " "),
124 volume: 0.8,
125 })
126}
127
128#[cfg(not(target_os = "windows"))]
131pub fn import_music_file(
132 _input_path: &Path,
133 _music_dir: &Path,
134 _state: &MusicPageState,
135 _storage: &supreme_fs::GameStorage,
136) -> Result<supreme_music::MusicConfig, String> {
137 Err("Music import is only available on Windows builds.".to_string())
138}
139
140pub struct AdpcmSource {
141 reader: BufReader<File>,
142 state: AdpcmState,
143 buffer: Vec<i16>, buffer_pos: usize,
145 sample_rate: u32,
146 #[cfg_attr(not(target_os = "windows"), allow(dead_code))]
148 channels: u16,
149}
150
151impl AdpcmSource {
152 pub fn new(file: File, sample_rate: u32) -> std::io::Result<Self> {
153 let mut reader = BufReader::new(file);
154
155 let mut header = [0u8; 12]; if reader.read_exact(&mut header).is_err() {
158 return Err(std::io::Error::new(
159 std::io::ErrorKind::InvalidData,
160 "Failed to read ADP header",
161 ));
162 }
163
164 if header[0] != 0x17 || header[1] != 0x42 {
165 return Err(std::io::Error::new(
166 std::io::ErrorKind::InvalidData,
167 "Not a MIDAS ADPCM stream",
168 ));
169 }
170
171 let type_byte = header[5];
172 let encoding_type = SampleType::from(type_byte);
173 let channels = match encoding_type {
174 SampleType::PcmMono8 | SampleType::PcmMono16 | SampleType::AdpcmMono => Channels::Mono,
175 SampleType::PcmStereo8 | SampleType::PcmStereo16 | SampleType::AdpcmStereo => {
176 Channels::Stereo
177 }
178 _ => {
179 return Err(std::io::Error::new(
180 std::io::ErrorKind::InvalidData,
181 format!("Unsupported sample type: {type_byte}"),
182 ));
183 }
184 };
185
186 let h_size = 6 + channels.count() * 3;
187 let read_sum = header[4];
188 let mut calc_sum: u8 = 0;
189 for (i, b) in header.iter().take(h_size).enumerate() {
190 if i != 4 {
191 calc_sum = calc_sum.wrapping_add(*b);
192 }
193 }
194
195 if calc_sum != read_sum {
196 return Err(std::io::Error::new(
197 std::io::ErrorKind::InvalidData,
198 format!(
199 "ADP header checksum mismatch: Expected {calc_sum} (original MIDAS), got {read_sum}"
200 ),
201 ));
202 }
203
204 reader.seek(std::io::SeekFrom::Start(0))?;
206
207 let mut state = AdpcmState::new(channels, 0); state.sample_rate = sample_rate;
209
210 Ok(Self {
211 reader,
212 state,
213 buffer: Vec::new(),
214 buffer_pos: 0,
215 sample_rate,
216 channels: channels.count() as u16,
217 })
218 }
219}
220
221impl Iterator for AdpcmSource {
222 type Item = f32;
223
224 fn next(&mut self) -> Option<Self::Item> {
225 if self.buffer_pos < self.buffer.len() {
226 let sample = self.buffer[self.buffer_pos];
227 self.buffer_pos += 1;
228 return Some(f32::from(sample) / 32768.0);
229 }
230
231 let mut in_buf = vec![0u8; 1200]; let _pos_before = self.reader.stream_position().ok()?;
236 let read = self.reader.read(&mut in_buf).ok()?;
237 if read == 0 {
238 return None; }
240
241 let mut out_samples = vec![0i16; 4096]; let (used, written) = match self.state.decode(&in_buf[..read], &mut out_samples) {
244 Ok(res) => res,
245 Err(e) => {
246 eprintln!("ADPCM Decode Error: {e}");
247 return None;
248 }
249 };
250
251 if used == 0 && written == 0 {
252 return None;
253 }
254
255 let read_u64 = read as u64;
257 let used_u64 = used as u64;
258 if used_u64 < read_u64 {
259 let back = (read_u64 - used_u64) as i64;
260 self.reader.seek(std::io::SeekFrom::Current(-back)).ok()?;
261 }
262
263 let samples_count = written / 2;
264 self.buffer = out_samples[..samples_count].to_vec();
265 self.buffer_pos = 0;
266
267 if self.buffer.is_empty() {
268 return None;
269 }
270
271 let sample = self.buffer[self.buffer_pos];
272 self.buffer_pos += 1;
273
274 self.sample_rate = self.state.sample_rate;
276
277 Some(f32::from(sample) / 32768.0)
278 }
279}
280
281#[cfg(target_os = "windows")]
282impl Source for AdpcmSource {
283 fn current_span_len(&self) -> Option<usize> {
284 None
285 }
286
287 fn channels(&self) -> NonZero<u16> {
288 NonZero::new(self.channels).unwrap()
289 }
290
291 fn sample_rate(&self) -> NonZero<u32> {
292 NonZero::new(self.sample_rate).unwrap()
293 }
294
295 fn total_duration(&self) -> Option<Duration> {
296 None
297 }
298}