use std::mem::MaybeUninit; use crate::sound::organya::{Song as Organya, Version}; use crate::sound::stuff::*; use crate::sound::wav::*; use crate::sound::wave_bank::SoundBank; pub(crate) struct OrgPlaybackEngine { song: Organya, lengths: [u8; 8], swaps: [usize; 8], keys: [u8; 8], /// Octave 0 Track 0 Swap 0 /// Octave 0 Track 1 Swap 0 /// ... /// Octave 1 Track 0 Swap 0 /// ... /// Octave 0 Track 0 Swap 1 /// octave * 8 + track + swap /// 128..136: Drum Tracks track_buffers: [RenderBuffer; 136], output_format: WavFormat, play_pos: i32, frames_this_tick: usize, frames_per_tick: usize, pub loops: usize, } pub struct SavedOrganyaPlaybackState { song: Organya, play_pos: i32, } impl Clone for SavedOrganyaPlaybackState { fn clone(&self) -> SavedOrganyaPlaybackState { SavedOrganyaPlaybackState { song: self.song.clone(), play_pos: self.play_pos, } } } impl OrgPlaybackEngine { pub fn new(samples: &SoundBank) -> Self { let mut buffers: [MaybeUninit; 136] = unsafe { MaybeUninit::uninit().assume_init() }; for buffer in buffers.iter_mut() { *buffer = MaybeUninit::new(RenderBuffer::empty()); } let song = Organya::empty(); let frames_per_tick = (44100 / 1000) * song.time.wait as usize; OrgPlaybackEngine { song, lengths: [0; 8], swaps: [0; 8], keys: [255; 8], track_buffers: unsafe { std::mem::transmute(buffers) }, play_pos: 0, output_format: WavFormat { channels: 2, sample_rate: 44100, bit_depth: 16, }, frames_this_tick: 0, frames_per_tick, loops: 1, } } pub fn set_sample_rate(&mut self, sample_rate: usize) { self.frames_this_tick = (self.frames_this_tick as f32 * (self.output_format.sample_rate as f32 / sample_rate as f32)) as usize; self.output_format.sample_rate = sample_rate as u32; self.frames_per_tick = (sample_rate / 1000) * self.song.time.wait as usize; if self.frames_this_tick >= self.frames_per_tick { self.frames_this_tick = 0; } } pub fn get_state(&self) -> SavedOrganyaPlaybackState { SavedOrganyaPlaybackState { song: self.song.clone(), play_pos: self.play_pos, } } pub fn set_state(&mut self, state: SavedOrganyaPlaybackState, samples: &SoundBank) { self.start_song(state.song, samples); self.play_pos = state.play_pos; } pub fn start_song(&mut self, song: Organya, samples: &SoundBank) { for i in 0..8 { 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 rbuf = RenderBuffer::new_organya(WavSample { format, data: sound }); for j in 0..8 { for &k in &[0, 64] { self.track_buffers[i + (j * 8) + k] = rbuf.clone(); } } } 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 { *buf = RenderBuffer::new(samples.samples[idx].clone()); } } self.song = song; self.play_pos = 0; self.frames_per_tick = (self.output_format.sample_rate as usize / 1000) * self.song.time.wait as usize; self.frames_this_tick = 0; for i in self.lengths.iter_mut() { *i = 0 } for i in self.swaps.iter_mut() { *i = 0 } for i in self.keys.iter_mut() { *i = 255 } } pub fn set_position(&mut self, position: i32) { self.play_pos = position; } pub fn rewind(&mut self) { self.set_position(0); } #[allow(unused)] pub fn get_total_samples(&self) -> u32 { let ticks_intro = self.song.time.loop_range.start; let ticks_loop = self.song.time.loop_range.end - self.song.time.loop_range.start; let ticks_total = ticks_intro + ticks_loop + (ticks_loop * self.loops as i32); self.frames_per_tick as u32 * ticks_total as u32 } fn update_play_state(&mut self) { for track in 0..8 { if let Some(note) = self.song.tracks[track].notes.iter().find(|x| x.pos == self.play_pos) { // New note //eprintln!("{:?}", &self.keys); if note.key != 255 { if self.keys[track] == 255 { // New let octave = (note.key / 12) * 8; let j = octave as usize + track + self.swaps[track]; for k in 0..16 { let swap = if k >= 8 { 64 } else { 0 }; let key = note.key % 12; let p_oct = k % 8; let freq = org_key_to_freq(key + p_oct * 12, self.song.tracks[track].inst.freq as i16); let l = p_oct as usize * 8 + track + swap; self.track_buffers[l].set_frequency(freq as u32); self.track_buffers[l] .organya_select_octave(p_oct as usize, self.song.tracks[track].inst.pipi != 0); } self.track_buffers[j].looping = true; self.track_buffers[j].playing = true; // last playing key self.keys[track] = note.key; } else if self.keys[track] == note.key { // Same //assert!(self.lengths[track] == 0); let octave = (self.keys[track] / 12) * 8; let j = octave as usize + track + self.swaps[track]; if self.song.tracks[track].inst.pipi == 0 { self.track_buffers[j].looping = false; } self.swaps[track] += 64; self.swaps[track] %= 128; let j = octave as usize + track + self.swaps[track]; self.track_buffers[j] .organya_select_octave(note.key as usize / 12, self.song.tracks[track].inst.pipi != 0); self.track_buffers[j].looping = true; self.track_buffers[j].playing = true; } else { // change let octave = (self.keys[track] / 12) * 8; let j = octave as usize + track + self.swaps[track]; if self.song.tracks[track].inst.pipi == 0 { self.track_buffers[j].looping = false; } self.swaps[track] += 64; self.swaps[track] %= 128; let octave = (note.key / 12) * 8; let j = octave as usize + track + self.swaps[track]; for k in 0..16 { let swap = if k >= 8 { 64 } else { 0 }; let key = note.key % 12; let p_oct = k % 8; let freq = org_key_to_freq(key + p_oct * 12, self.song.tracks[track].inst.freq as i16); let l = p_oct as usize * 8 + track + swap; self.track_buffers[l].set_frequency(freq as u32); self.track_buffers[l] .organya_select_octave(p_oct as usize, self.song.tracks[track].inst.pipi != 0); } self.track_buffers[j].looping = true; self.track_buffers[j].playing = true; self.keys[track] = note.key; } self.lengths[track] = note.len; } if self.keys[track] != 255 { let octave = (self.keys[track] / 12) * 8; let j = octave as usize + track + self.swaps[track]; if note.vol != 255 { let vol = org_vol_to_vol(note.vol); self.track_buffers[j].set_volume(vol); } if note.pan != 255 { let pan = org_pan_to_pan(note.pan); self.track_buffers[j].set_pan(pan); } } } if self.lengths[track] == 0 { if self.keys[track] != 255 { let octave = (self.keys[track] / 12) * 8; let j = octave as usize + track + self.swaps[track]; if self.song.tracks[track].inst.pipi == 0 { self.track_buffers[j].looping = false; } self.keys[track] = 255; } } self.lengths[track] = self.lengths[track].saturating_sub(1); } for i in 8..16 { let j = i + 120; let notes = &self.song.tracks[i].notes; // start a new note // note (hah) that drums are unaffected by length and pi values. This is the only case we have to handle. if let Some(note) = notes.iter().find(|x| x.pos == self.play_pos) { // FIXME: Add constants for dummy values if note.key != 255 { let freq = org_key_to_drum_freq(note.key); self.track_buffers[j].set_frequency(freq as u32); self.track_buffers[j].set_position(0); self.track_buffers[j].playing = true; } if note.vol != 255 { let vol = org_vol_to_vol(note.vol); self.track_buffers[j].set_volume(vol); } if note.pan != 255 { let pan = org_pan_to_pan(note.pan); self.track_buffers[j].set_pan(pan); } } } } pub fn render_to(&mut self, buf: &mut [u16]) -> usize { for (i, frame) in buf.iter_mut().enumerate() { if self.frames_this_tick == 0 { self.update_play_state() } mix(std::slice::from_mut(frame), self.output_format, &mut self.track_buffers); self.frames_this_tick += 1; if self.frames_this_tick == self.frames_per_tick { self.play_pos += 1; if self.play_pos == self.song.time.loop_range.end { self.play_pos = self.song.time.loop_range.start; if self.loops == 0 { return i + 1; } self.loops -= 1; } self.frames_this_tick = 0; } } buf.len() } } // TODO: Create a MixingBuffer or something... pub fn mix(dst: &mut [u16], dst_fmt: WavFormat, srcs: &mut [RenderBuffer]) { let freq = dst_fmt.sample_rate as f64; for buf in srcs { if buf.playing { // index into sound samples let advance = buf.frequency as f64 / freq; let vol = centibel_to_scale(buf.volume); let (pan_l, pan_r) = match buf.pan.signum() { 0 => (1.0, 1.0), 1 => (centibel_to_scale(-buf.pan), 1.0), -1 => (1.0, centibel_to_scale(buf.pan)), _ => unsafe { std::hint::unreachable_unchecked() }, }; fn clamp(v: T, limit: T) -> T { if v > limit { limit } else { v } } #[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; use std::f32::consts::PI; let r1 = buf.position.fract() as f32; let r2 = (1.0 - f32::cos(r1 * PI)) / 2.0; //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. // -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_le_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_le_bytes([l, r]); } } } } pub fn centibel_to_scale(a: i32) -> f32 { f32::powf(10.0, a as f32 / 2000.0) } #[derive(Clone)] pub struct RenderBuffer { pub position: f64, pub frequency: u32, pub volume: i32, pub pan: i32, pub sample: WavSample, pub playing: bool, pub looping: bool, pub base_pos: usize, pub len: usize, // -1 = infinite pub nloops: i32, } impl RenderBuffer { pub fn new(sample: WavSample) -> RenderBuffer { RenderBuffer { position: 0.0, frequency: sample.format.sample_rate, volume: 0, pan: 0, len: sample.data.len(), sample, playing: false, looping: false, base_pos: 0, nloops: -1, } } pub fn empty() -> RenderBuffer { RenderBuffer { position: 0.0, frequency: 22050, volume: 0, pan: 0, len: 0, sample: WavSample { format: WavFormat { channels: 2, sample_rate: 22050, bit_depth: 16, }, data: vec![], }, playing: false, looping: false, base_pos: 0, nloops: -1, } } pub fn new_organya(mut sample: WavSample) -> RenderBuffer { let wave = sample.data.clone(); sample.data.clear(); for size in &[256_usize, 256, 128, 128, 64, 32, 16, 8] { let step = 256 / size; let mut acc = 0; for _ in 0..*size { sample.data.push(wave[acc]); acc += step; if acc >= 256 { acc = 0; } } } RenderBuffer::new(sample) } #[inline] pub fn organya_select_octave(&mut self, octave: usize, pipi: bool) { const OFFS: &[usize] = &[0x000, 0x100, 0x200, 0x280, 0x300, 0x340, 0x360, 0x370]; const LENS: &[usize] = &[256_usize, 256, 128, 128, 64, 32, 16, 8]; self.base_pos = OFFS[octave]; self.len = LENS[octave]; self.position %= self.len as f64; if pipi && !self.playing { self.nloops = ((octave + 1) * 4) as i32; } } #[inline] pub fn set_frequency(&mut self, frequency: u32) { //assert!(frequency >= 100 && frequency <= 100000); //dbg!(frequency); self.frequency = frequency; } #[inline] pub fn set_volume(&mut self, volume: i32) { assert!(volume >= -10000 && volume <= 0); self.volume = volume; } #[inline] pub fn set_pan(&mut self, pan: i32) { assert!(pan >= -10000 && pan <= 10000); self.pan = pan; } #[inline] #[allow(unused)] pub fn set_position(&mut self, position: u32) { assert!(position < self.sample.data.len() as u32 / self.sample.format.bit_depth as u32); self.position = position as f64; } }