mirror of
				https://github.com/doukutsu-rs/doukutsu-rs
				synced 2025-10-29 20:44:48 +00:00 
			
		
		
		
	add organya interpolation modes
This commit is contained in:
		
							parent
							
								
									a52d095e45
								
							
						
					
					
						commit
						a203af7e7b
					
				
							
								
								
									
										1378
									
								
								src/sound/fir.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1378
									
								
								src/sound/fir.rs
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							|  | @ -1,6 +1,8 @@ | |||
| use std::io; | ||||
| use std::io::{BufRead, BufReader, Lines}; | ||||
| use std::str::FromStr; | ||||
| use std::sync::mpsc; | ||||
| use std::sync::mpsc::{Receiver, Sender, TryRecvError}; | ||||
| use std::sync::mpsc::{Receiver, Sender}; | ||||
| use std::time::Duration; | ||||
| 
 | ||||
| use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; | ||||
|  | @ -23,9 +25,8 @@ use crate::sound::organya::Song; | |||
| use crate::sound::pixtone::{PixToneParameters, PixTonePlayback}; | ||||
| use crate::sound::wave_bank::SoundBank; | ||||
| use crate::str; | ||||
| use std::io::{BufReader, BufRead, Lines}; | ||||
| use std::str::FromStr; | ||||
| 
 | ||||
| mod fir; | ||||
| #[cfg(feature = "ogg-playback")] | ||||
| mod ogg_playback; | ||||
| mod org_playback; | ||||
|  | @ -50,6 +51,15 @@ enum SongFormat { | |||
|     OggMultiPart, | ||||
| } | ||||
| 
 | ||||
| #[derive(Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] | ||||
| pub enum InterpolationMode { | ||||
|     Nearest, | ||||
|     Linear, | ||||
|     Cosine, | ||||
|     Cubic, | ||||
|     Polyphase, | ||||
| } | ||||
| 
 | ||||
| impl SoundManager { | ||||
|     pub fn new(ctx: &mut Context) -> GameResult<SoundManager> { | ||||
|         let (tx, rx): (Sender<PlaybackMessage>, Receiver<PlaybackMessage>) = mpsc::channel(); | ||||
|  | @ -263,7 +273,9 @@ impl SoundManager { | |||
| 
 | ||||
|                     let _ = splits.next(); | ||||
|                     if let Some(str) = splits.next() { | ||||
|                         return str.trim().parse::<T>().map_err(|_| GameError::ParseError("failed to parse the value as specified type.".to_string())) | ||||
|                         return str.trim().parse::<T>().map_err(|_| { | ||||
|                             GameError::ParseError("failed to parse the value as specified type.".to_string()) | ||||
|                         }); | ||||
|                     } else { | ||||
|                         break; | ||||
|                     } | ||||
|  | @ -272,8 +284,8 @@ impl SoundManager { | |||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             return Err(GameError::ParseError("unexpected end.".to_string())) | ||||
|         }; | ||||
|             return Err(GameError::ParseError("unexpected end.".to_string())); | ||||
|         } | ||||
| 
 | ||||
|         for channel in params.channels.iter_mut() { | ||||
|             channel.enabled = next_string::<u8, R>(&mut reader)? != 0; | ||||
|  |  | |||
|  | @ -1,9 +1,30 @@ | |||
| use std::mem::MaybeUninit; | ||||
| 
 | ||||
| use crate::sound::fir::FIR; | ||||
| use crate::sound::fir::FIR_STEP; | ||||
| use crate::sound::organya::{Song as Organya, Version}; | ||||
| use crate::sound::stuff::*; | ||||
| use crate::sound::wav::*; | ||||
| use crate::sound::wave_bank::SoundBank; | ||||
| use crate::sound::InterpolationMode; | ||||
| 
 | ||||
| #[derive(Clone)] | ||||
| pub struct FIRData { | ||||
|     cache: Vec<f32>, | ||||
|     pos: usize, | ||||
| } | ||||
| 
 | ||||
| impl FIRData { | ||||
|     pub fn new() -> Self { | ||||
|         FIRData { cache: Vec::new(), pos: 0 } | ||||
|     } | ||||
| 
 | ||||
|     pub fn ensure_initialized(&mut self) { | ||||
|         if self.cache.is_empty() { | ||||
|             self.cache.resize(FIR.len() * 4, 0.0); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| pub(crate) struct OrgPlaybackEngine { | ||||
|     song: Organya, | ||||
|  | @ -24,6 +45,7 @@ pub(crate) struct OrgPlaybackEngine { | |||
|     frames_this_tick: usize, | ||||
|     frames_per_tick: usize, | ||||
|     pub loops: usize, | ||||
|     pub interpolation: InterpolationMode, | ||||
| } | ||||
| 
 | ||||
| pub struct SavedOrganyaPlaybackState { | ||||
|  | @ -33,10 +55,7 @@ pub struct SavedOrganyaPlaybackState { | |||
| 
 | ||||
| impl Clone for SavedOrganyaPlaybackState { | ||||
|     fn clone(&self) -> SavedOrganyaPlaybackState { | ||||
|         SavedOrganyaPlaybackState { | ||||
|             song: self.song.clone(), | ||||
|             play_pos: self.play_pos, | ||||
|         } | ||||
|         SavedOrganyaPlaybackState { song: self.song.clone(), play_pos: self.play_pos } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | @ -58,14 +77,11 @@ impl OrgPlaybackEngine { | |||
|             keys: [255; 8], | ||||
|             track_buffers: unsafe { std::mem::transmute(buffers) }, | ||||
|             play_pos: 0, | ||||
|             output_format: WavFormat { | ||||
|                 channels: 2, | ||||
|                 sample_rate: 44100, | ||||
|                 bit_depth: 16, | ||||
|             }, | ||||
|             output_format: WavFormat { channels: 2, sample_rate: 44100, bit_depth: 16 }, | ||||
|             frames_this_tick: 0, | ||||
|             frames_per_tick, | ||||
|             loops: 1, | ||||
|             interpolation: InterpolationMode::Linear, | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | @ -81,10 +97,7 @@ impl OrgPlaybackEngine { | |||
|     } | ||||
| 
 | ||||
|     pub fn get_state(&self) -> SavedOrganyaPlaybackState { | ||||
|         SavedOrganyaPlaybackState { | ||||
|             song: self.song.clone(), | ||||
|             play_pos: self.play_pos, | ||||
|         } | ||||
|         SavedOrganyaPlaybackState { song: self.song.clone(), play_pos: self.play_pos } | ||||
|     } | ||||
| 
 | ||||
|     pub fn set_state(&mut self, state: SavedOrganyaPlaybackState, samples: &SoundBank) { | ||||
|  | @ -97,11 +110,7 @@ impl OrgPlaybackEngine { | |||
|             let sound_index = song.tracks[i].inst.inst as usize; | ||||
|             let sound = samples.get_wave(sound_index).iter().map(|&x| x ^ 128).collect(); | ||||
| 
 | ||||
|             let format = WavFormat { | ||||
|                 channels: 1, | ||||
|                 sample_rate: 22050, | ||||
|                 bit_depth: 8, | ||||
|             }; | ||||
|             let format = WavFormat { channels: 1, sample_rate: 22050, bit_depth: 8 }; | ||||
| 
 | ||||
|             let rbuf = RenderBuffer::new_organya(WavSample { format, data: sound }); | ||||
| 
 | ||||
|  | @ -112,11 +121,7 @@ impl OrgPlaybackEngine { | |||
|             } | ||||
|         } | ||||
| 
 | ||||
|         for (idx, (track, buf)) in song.tracks[8..] | ||||
|             .iter() | ||||
|             .zip(self.track_buffers[128..].iter_mut()) | ||||
|             .enumerate() | ||||
|         { | ||||
|         for (idx, (track, buf)) in song.tracks[8..].iter().zip(self.track_buffers[128..].iter_mut()).enumerate() { | ||||
|             if self.song.version == Version::Extended { | ||||
|                 *buf = RenderBuffer::new(samples.samples[track.inst.inst as usize].clone()); | ||||
|             } else { | ||||
|  | @ -292,7 +297,7 @@ impl OrgPlaybackEngine { | |||
|                 self.update_play_state() | ||||
|             } | ||||
| 
 | ||||
|             mix(std::slice::from_mut(frame), self.output_format, &mut self.track_buffers); | ||||
|             mix(std::slice::from_mut(frame), self.interpolation, self.output_format, &mut self.track_buffers); | ||||
| 
 | ||||
|             self.frames_this_tick += 1; | ||||
| 
 | ||||
|  | @ -318,7 +323,7 @@ impl OrgPlaybackEngine { | |||
| } | ||||
| 
 | ||||
| // TODO: Create a MixingBuffer or something...
 | ||||
| pub fn mix(dst: &mut [u16], dst_fmt: WavFormat, srcs: &mut [RenderBuffer]) { | ||||
| fn mix(dst: &mut [u16], interpolation: InterpolationMode, dst_fmt: WavFormat, srcs: &mut [RenderBuffer]) { | ||||
|     let freq = dst_fmt.sample_rate as f64; | ||||
| 
 | ||||
|     for buf in srcs { | ||||
|  | @ -343,55 +348,178 @@ pub fn mix(dst: &mut [u16], dst_fmt: WavFormat, srcs: &mut [RenderBuffer]) { | |||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             #[allow(unused_variables)] | ||||
|             for frame in dst.iter_mut() { | ||||
|                 let pos = buf.position as usize + buf.base_pos; | ||||
|                 // -1..1
 | ||||
|                 let s1 = (buf.sample.data[pos] as f32 - 128.0) / 128.0; | ||||
|                 let s2 = (buf.sample.data[clamp(pos + 1, buf.base_pos + buf.len - 1)] as f32 - 128.0) / 128.0; | ||||
|                 let s3 = (buf.sample.data[clamp(pos + 2, buf.base_pos + buf.len - 1)] as f32 - 128.0) / 128.0; | ||||
|                 let s4 = (buf.sample.data[pos.saturating_sub(1)] as f32 - 128.0) / 128.0; | ||||
|             // s1: sample 1
 | ||||
|             // s2: sample 2
 | ||||
|             // sp: previous sample (before s1)
 | ||||
|             // sn: next sample (after s2)
 | ||||
|             // mu: position to interpolate for
 | ||||
|             fn cubic_interp(s1: f32, s2: f32, sp: f32, sn: f32, mu: f32) -> f32 { | ||||
|                 let mu2 = mu * mu; | ||||
|                 let a0 = sn - s2 - sp + s1; | ||||
|                 let a1 = sp - s1 - a0; | ||||
|                 let a2 = s2 - sp; | ||||
|                 let a3 = s1; | ||||
| 
 | ||||
|                 use std::f32::consts::PI; | ||||
|                 a0 * mu * mu2 + a1 * mu2 + a2 * mu + a3 | ||||
|             } | ||||
| 
 | ||||
|                 let r1 = buf.position.fract() as f32; | ||||
|                 let r2 = (1.0 - f32::cos(r1 * PI)) / 2.0; | ||||
|             if interpolation == InterpolationMode::Polyphase { | ||||
|                 buf.fir.ensure_initialized(); | ||||
|                 let fir_step = (FIR_STEP * advance as f32).floor(); | ||||
|                 let fir_step = if fir_step == 0.0 { FIR_STEP } else { fir_step }; | ||||
|                 let fir_gain = fir_step / FIR_STEP; | ||||
| 
 | ||||
|                 //let s = s1; // No interp
 | ||||
|                 //let s = s1 + (s2 - s1) * r1; // Linear interp
 | ||||
|                 //let s = s1 * (1.0 - r2) + s2 * r2; // Cosine interp
 | ||||
|                 let s = cubic_interp(s1, s2, s4, s3, r1); // Cubic interp
 | ||||
|                                                           // Ideally we want sinc/lanczos interpolation, since that's what DirectSound appears to use.
 | ||||
|                 let mut count = 0isize; | ||||
| 
 | ||||
|                 // -128..128
 | ||||
|                 let sl = s * pan_l * vol * 128.0; | ||||
|                 let sr = s * pan_r * vol * 128.0; | ||||
|                 // optimized for debug mode
 | ||||
|                 // bound / arithmetic checks give a HUGE performance hit in this code
 | ||||
|                 let fl = FIR.len() as f32; | ||||
| 
 | ||||
|                 buf.position += advance; | ||||
|                 // raw pointer access is much faster than get_unchecked
 | ||||
|                 let fir_ptr = FIR.as_ptr(); | ||||
|                 let cache_ptr = buf.fir.cache.as_mut_ptr(); | ||||
| 
 | ||||
|                 if buf.position as usize >= buf.len { | ||||
|                     if buf.looping && buf.nloops != 1 { | ||||
|                         buf.position %= buf.len as f64; | ||||
|                         if buf.nloops != -1 { | ||||
|                             buf.nloops -= 1; | ||||
|                 for (n, _) in dst.iter().enumerate() { | ||||
|                     count += 1; | ||||
|                     let pos = buf.position as usize + buf.base_pos; | ||||
|                     let i = (buf.fir.pos + n) % buf.fir.cache.len(); | ||||
| 
 | ||||
|                     let s1 = (buf.sample.data[pos] as f32 - 128.0) / 128.0; | ||||
|                     let s2 = (buf.sample.data[clamp(pos + 1, buf.base_pos + buf.len - 1)] as f32 - 128.0) / 128.0; | ||||
|                     let r1 = buf.position.fract() as f32; | ||||
| 
 | ||||
|                     buf.fir.cache[i] = s1 + (s2 - s1) * r1; | ||||
| 
 | ||||
|                     buf.position += advance; | ||||
| 
 | ||||
|                     if buf.position as usize >= buf.len { | ||||
|                         if buf.looping && buf.nloops != 1 { | ||||
|                             buf.position %= buf.len as f64; | ||||
|                             if buf.nloops != -1 { | ||||
|                                 buf.nloops -= 1; | ||||
|                             } | ||||
|                         } else { | ||||
|                             buf.position = 0.0; | ||||
|                             buf.playing = false; | ||||
| 
 | ||||
|                             let silent_frames = dst.len() - n; | ||||
|                             for m in 0..silent_frames { | ||||
|                                 let i = (buf.fir.pos + n + m) % buf.fir.cache.len(); | ||||
|                                 buf.fir.cache[i] = 0.0; | ||||
|                             } | ||||
|                             count += silent_frames as isize; | ||||
| 
 | ||||
|                             break; | ||||
|                         } | ||||
|                     } else { | ||||
|                         buf.position = 0.0; | ||||
|                         buf.playing = false; | ||||
|                         break; | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|                 let [mut l, mut r] = frame.to_le_bytes(); | ||||
|                 // -128..127
 | ||||
|                 let xl = (l ^ 128) as i8; | ||||
|                 let xr = (r ^ 128) as i8; | ||||
|                 let cl = buf.fir.cache.len() as isize; | ||||
| 
 | ||||
|                 // 0..255
 | ||||
|                 l = xl.saturating_add(sl as i8) as u8 ^ 128; | ||||
|                 r = xr.saturating_add(sr as i8) as u8 ^ 128; | ||||
|                 for n in 0..count { | ||||
|                     let mut insamp_idx = (buf.fir.pos as isize).wrapping_add(n).wrapping_rem(cl); | ||||
|                     let mut acc = 0.0; | ||||
|                     let mut step = 0.0; | ||||
| 
 | ||||
|                 *frame = u16::from_le_bytes([l, r]); | ||||
|                     while step < fl { | ||||
|                         unsafe { | ||||
|                             acc += (*fir_ptr.add(step as usize)) * (*cache_ptr.add((insamp_idx) as usize)); | ||||
|                             insamp_idx = if insamp_idx == 0 { cl.wrapping_sub(1) } else { insamp_idx.wrapping_sub(1) }; | ||||
|                             step += fir_step; | ||||
|                         } | ||||
|                     } | ||||
| 
 | ||||
|                     acc *= fir_gain; | ||||
| 
 | ||||
|                     let sl = acc * pan_l * vol * 128.0; | ||||
|                     let sr = acc * pan_r * vol * 128.0; | ||||
| 
 | ||||
|                     let frame = unsafe { dst.get_unchecked_mut(n as usize) }; | ||||
|                     let [mut l, mut r] = frame.to_be_bytes(); | ||||
|                     // -128..127
 | ||||
|                     let xl = (l ^ 128) as i8; | ||||
|                     let xr = (r ^ 128) as i8; | ||||
| 
 | ||||
|                     // 0..255
 | ||||
|                     l = xl.saturating_add(sl as i8) as u8 ^ 128; | ||||
|                     r = xr.saturating_add(sr as i8) as u8 ^ 128; | ||||
| 
 | ||||
|                     *frame = u16::from_be_bytes([l, r]); | ||||
|                 } | ||||
| 
 | ||||
|                 buf.fir.pos += count as usize; | ||||
|             } else { | ||||
|                 for frame in dst.iter_mut() { | ||||
|                     let pos = buf.position as usize + buf.base_pos; | ||||
| 
 | ||||
|                     // -1..1
 | ||||
|                     let s = match interpolation { | ||||
|                         InterpolationMode::Nearest => (buf.sample.data[pos] as f32 - 128.0) / 128.0, | ||||
|                         InterpolationMode::Linear => { | ||||
|                             let s1 = (buf.sample.data[pos] as f32 - 128.0) / 128.0; | ||||
|                             let s2 = | ||||
|                                 (buf.sample.data[clamp(pos + 1, buf.base_pos + buf.len - 1)] as f32 - 128.0) / 128.0; | ||||
|                             let r1 = buf.position.fract() as f32; | ||||
| 
 | ||||
|                             s1 + (s2 - s1) * r1 | ||||
|                         } | ||||
|                         InterpolationMode::Cosine => { | ||||
|                             use std::f32::consts::PI; | ||||
| 
 | ||||
|                             let s1 = (buf.sample.data[pos] as f32 - 128.0) / 128.0; | ||||
|                             let s2 = | ||||
|                                 (buf.sample.data[clamp(pos + 1, buf.base_pos + buf.len - 1)] as f32 - 128.0) / 128.0; | ||||
| 
 | ||||
|                             let r1 = buf.position.fract() as f32; | ||||
|                             let r2 = (1.0 - f32::cos(r1 * PI)) / 2.0; | ||||
| 
 | ||||
|                             s1 * (1.0 - r2) + s2 * r2 | ||||
|                         } | ||||
|                         InterpolationMode::Cubic => { | ||||
|                             let s1 = (buf.sample.data[pos] as f32 - 128.0) / 128.0; | ||||
|                             let s2 = | ||||
|                                 (buf.sample.data[clamp(pos + 1, buf.base_pos + buf.len - 1)] as f32 - 128.0) / 128.0; | ||||
|                             let s3 = | ||||
|                                 (buf.sample.data[clamp(pos + 2, buf.base_pos + buf.len - 1)] as f32 - 128.0) / 128.0; | ||||
|                             let s4 = (buf.sample.data[pos.saturating_sub(1)] as f32 - 128.0) / 128.0; | ||||
| 
 | ||||
|                             let r1 = buf.position.fract() as f32; | ||||
| 
 | ||||
|                             cubic_interp(s1, s2, s4, s3, r1) | ||||
|                         } | ||||
|                         InterpolationMode::Polyphase => unreachable!(), | ||||
|                     }; | ||||
| 
 | ||||
|                     // -128..128
 | ||||
|                     let sl = s * pan_l * vol * 128.0; | ||||
|                     let sr = s * pan_r * vol * 128.0; | ||||
| 
 | ||||
|                     buf.position += advance; | ||||
| 
 | ||||
|                     if buf.position as usize >= buf.len { | ||||
|                         if buf.looping && buf.nloops != 1 { | ||||
|                             buf.position %= buf.len as f64; | ||||
|                             if buf.nloops != -1 { | ||||
|                                 buf.nloops -= 1; | ||||
|                             } | ||||
|                         } else { | ||||
|                             buf.position = 0.0; | ||||
|                             buf.playing = false; | ||||
|                             break; | ||||
|                         } | ||||
|                     } | ||||
| 
 | ||||
|                     let [mut l, mut r] = frame.to_be_bytes(); | ||||
|                     // -128..127
 | ||||
|                     let xl = (l ^ 128) as i8; | ||||
|                     let xr = (r ^ 128) as i8; | ||||
| 
 | ||||
|                     // 0..255
 | ||||
|                     l = xl.saturating_add(sl as i8) as u8 ^ 128; | ||||
|                     r = xr.saturating_add(sr as i8) as u8 ^ 128; | ||||
| 
 | ||||
|                     *frame = u16::from_be_bytes([l, r]); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | @ -414,6 +542,7 @@ pub struct RenderBuffer { | |||
|     pub len: usize, | ||||
|     // -1 = infinite
 | ||||
|     pub nloops: i32, | ||||
|     pub fir: FIRData, | ||||
| } | ||||
| 
 | ||||
| impl RenderBuffer { | ||||
|  | @ -429,6 +558,7 @@ impl RenderBuffer { | |||
|             looping: false, | ||||
|             base_pos: 0, | ||||
|             nloops: -1, | ||||
|             fir: FIRData::new(), | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | @ -439,18 +569,12 @@ impl RenderBuffer { | |||
|             volume: 0, | ||||
|             pan: 0, | ||||
|             len: 0, | ||||
|             sample: WavSample { | ||||
|                 format: WavFormat { | ||||
|                     channels: 2, | ||||
|                     sample_rate: 22050, | ||||
|                     bit_depth: 16, | ||||
|                 }, | ||||
|                 data: vec![], | ||||
|             }, | ||||
|             sample: WavSample { format: WavFormat { channels: 2, sample_rate: 22050, bit_depth: 16 }, data: vec![] }, | ||||
|             playing: false, | ||||
|             looping: false, | ||||
|             base_pos: 0, | ||||
|             nloops: -1, | ||||
|             fir: FIRData::new(), | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										105
									
								
								src/sound/wav.rs
									
									
									
									
									
								
							
							
						
						
									
										105
									
								
								src/sound/wav.rs
									
									
									
									
									
								
							|  | @ -1,16 +1,22 @@ | |||
| use std::fmt; | ||||
| use std::io; | ||||
| use std::io::ErrorKind; | ||||
| 
 | ||||
| use byteorder::{ReadBytesExt, LE}; | ||||
| 
 | ||||
| #[derive(Copy, Clone, Eq, PartialEq, Debug)] | ||||
| pub struct RiffChunk { | ||||
|     id: [u8; 4], | ||||
|     length: u32 | ||||
|     length: u32, | ||||
| } | ||||
| 
 | ||||
| use std::fmt; | ||||
| 
 | ||||
| impl fmt::Display for RiffChunk { | ||||
|     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||
|         use std::ascii::escape_default as esc; | ||||
|         
 | ||||
|         write!(f, "chunk \"{}{}{}{}\", length: {}", | ||||
| 
 | ||||
|         write!( | ||||
|             f, | ||||
|             "chunk \"{}{}{}{}\", length: {}", | ||||
|             esc(self.id[0]), | ||||
|             esc(self.id[1]), | ||||
|             esc(self.id[2]), | ||||
|  | @ -24,28 +30,26 @@ impl fmt::Display for RiffChunk { | |||
| pub struct WavFormat { | ||||
|     pub channels: u16, | ||||
|     pub sample_rate: u32, | ||||
|     pub bit_depth: u16 | ||||
|     pub bit_depth: u16, | ||||
| } | ||||
| 
 | ||||
| impl fmt::Display for WavFormat { | ||||
|     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||
|         write!(f, "{} channels, {} Hz, {}-bit", | ||||
|             self.channels, | ||||
|             self.sample_rate, | ||||
|             self.bit_depth | ||||
|         ) | ||||
|         write!(f, "{} channels, {} Hz, {}-bit", self.channels, self.sample_rate, self.bit_depth) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[derive(Clone)] | ||||
| pub struct WavSample { | ||||
|     pub format: WavFormat, | ||||
|     pub data: Vec<u8> | ||||
|     pub data: Vec<u8>, | ||||
| } | ||||
| 
 | ||||
| impl fmt::Display for WavSample { | ||||
|     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||
|         write!(f, "{}, {} samples", | ||||
|         write!( | ||||
|             f, | ||||
|             "{}, {} samples", | ||||
|             self.format, | ||||
|             // num_bytes / bytes_per_sample
 | ||||
|             self.data.len() / ((self.format.bit_depth / 8) * self.format.channels) as usize | ||||
|  | @ -53,16 +57,13 @@ impl fmt::Display for WavSample { | |||
|     } | ||||
| } | ||||
| 
 | ||||
| use byteorder::{LE, ReadBytesExt}; | ||||
| use std::io; | ||||
| 
 | ||||
| impl RiffChunk { | ||||
|     pub fn read_from<R: io::Read>(mut f: R) -> io::Result<RiffChunk> { | ||||
|         let mut id = [0; 4]; | ||||
|         
 | ||||
| 
 | ||||
|         f.read_exact(&mut id)?; | ||||
|         let length = f.read_u32::<LE>()?; | ||||
|         
 | ||||
| 
 | ||||
|         Ok(RiffChunk { id, length }) | ||||
|     } | ||||
| } | ||||
|  | @ -70,51 +71,51 @@ impl RiffChunk { | |||
| impl WavSample { | ||||
|     pub fn read_from<R: io::Read>(mut f: R) -> io::Result<WavSample> { | ||||
|         let riff = RiffChunk::read_from(&mut f)?; | ||||
|         
 | ||||
| 
 | ||||
|         match &riff.id { | ||||
|             b"RIFF" => {}, | ||||
|             b"RIFX" => panic!("Cannot handle RIFX data!"), | ||||
|             _       => panic!("Expected RIFF signature, found {}", riff) | ||||
|             b"RIFF" => {} | ||||
|             b"RIFX" => return Err(io::Error::new(ErrorKind::InvalidData, "Cannot handle RIFX data!".to_owned())), | ||||
|             _ => { | ||||
|                 return Err(io::Error::new(ErrorKind::InvalidData, format!("Expected RIFF signature, found {}", riff))) | ||||
|             } | ||||
|         } | ||||
|         
 | ||||
| 
 | ||||
|         let mut rfmt = [0; 4]; | ||||
|         
 | ||||
| 
 | ||||
|         f.read_exact(&mut rfmt)?; | ||||
|         
 | ||||
|         assert_eq!(rfmt, *b"WAVE"); | ||||
|         
 | ||||
| 
 | ||||
|         if rfmt != *b"WAVE" { | ||||
|             return Err(io::Error::new(ErrorKind::InvalidData, "Expected 'WAVE' RIFF chunk.".to_owned())); | ||||
|         } | ||||
| 
 | ||||
|         let fmt = RiffChunk::read_from(&mut f)?; | ||||
|         
 | ||||
|         assert_eq!(fmt.id, *b"fmt "); | ||||
|         //assert_eq!(fmt.length, 16);
 | ||||
|         
 | ||||
| 
 | ||||
|         if fmt.id != *b"fmt " { | ||||
|             return Err(io::Error::new(ErrorKind::InvalidData, "Expected 'fmt ' RIFF chunk.".to_owned())); | ||||
|         } | ||||
| 
 | ||||
|         let afmt = f.read_u16::<LE>()?; | ||||
|         
 | ||||
|         debug_assert!(afmt == 1); | ||||
|         
 | ||||
| 
 | ||||
|         if afmt != 1 { | ||||
|             return Err(io::Error::new(ErrorKind::InvalidData, "Only PCM audio data is supported.".to_owned())); | ||||
|         } | ||||
| 
 | ||||
|         let channels = f.read_u16::<LE>()?; | ||||
|         let samples  = f.read_u32::<LE>()?; | ||||
|         let samples = f.read_u32::<LE>()?; | ||||
|         let _brate = f.read_u32::<LE>()?; | ||||
|         let _balgn = f.read_u16::<LE>()?; | ||||
|         let bits     = f.read_u16::<LE>()?; | ||||
|         
 | ||||
|         let bits = f.read_u16::<LE>()?; | ||||
| 
 | ||||
|         let data = RiffChunk::read_from(&mut f)?; | ||||
|         
 | ||||
|         assert_eq!(data.id, *b"data"); | ||||
|         
 | ||||
| 
 | ||||
|         if data.id != *b"data" { | ||||
|             return Err(io::Error::new(ErrorKind::InvalidData, "Expected 'data' RIFF chunk.".to_owned())); | ||||
|         } | ||||
| 
 | ||||
|         let mut buf = vec![0; data.length as usize]; | ||||
|         
 | ||||
| 
 | ||||
|         f.read_exact(&mut buf)?; | ||||
|         
 | ||||
|         Ok( | ||||
|             WavSample { | ||||
|                 format: WavFormat { | ||||
|                     channels, | ||||
|                     sample_rate: samples, | ||||
|                     bit_depth: bits | ||||
|                 }, | ||||
|                 data: buf | ||||
|             } | ||||
|         ) | ||||
| 
 | ||||
|         Ok(WavSample { format: WavFormat { channels, sample_rate: samples, bit_depth: bits }, data: buf }) | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -4,8 +4,7 @@ use std::io; | |||
| use std::fmt; | ||||
| 
 | ||||
| pub struct SoundBank { | ||||
|     // FIXME: would prefer Box<[u8; 25600]>
 | ||||
|     pub wave100: Box<[u8]>, | ||||
|     pub wave100: Box<[u8; 25600]>, | ||||
|     
 | ||||
|     pub samples: Vec<wav::WavSample> | ||||
| } | ||||
|  | @ -24,10 +23,9 @@ impl fmt::Display for SoundBank { | |||
| 
 | ||||
| impl SoundBank { | ||||
|     pub fn load_from<R: io::Read>(mut f: R) -> io::Result<SoundBank> { | ||||
|         // no box [0; 25600] yet
 | ||||
|         let mut wave100 = vec![0; 25600].into_boxed_slice(); | ||||
|         let mut wave100 = Box::new([0u8; 25600]); | ||||
|         
 | ||||
|         f.read_exact(&mut *wave100)?; | ||||
|         f.read_exact(wave100.as_mut())?; | ||||
|         
 | ||||
|         let mut samples = Vec::with_capacity(16); | ||||
|         
 | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue