diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/font.rs | 10 | ||||
-rw-r--r-- | src/interpolation.rs | 9 | ||||
-rw-r--r-- | src/main.rs | 3 | ||||
-rw-r--r-- | src/render.rs | 273 | ||||
-rw-r--r-- | src/vendored/micromod-rs/ATTRIBUTION | 1 | ||||
-rw-r--r-- | src/vendored/micromod-rs/COPYING | 37 | ||||
-rw-r--r-- | src/vendored/micromod-rs/consts.rs | 11 | ||||
-rw-r--r-- | src/vendored/micromod-rs/mod.rs | 860 | ||||
-rw-r--r-- | src/vendored/mod.rs | 2 |
9 files changed, 1081 insertions, 125 deletions
diff --git a/src/font.rs b/src/font.rs index 1dddd20..7041634 100644 --- a/src/font.rs +++ b/src/font.rs @@ -1,5 +1,3 @@ -use std::sync::LazyLock; - use sdl2::{ pixels::{Color, PixelFormatEnum}, rect::{Point, Rect}, @@ -17,8 +15,8 @@ pub struct RenderableCharacter { pub data: &'static [u8], /** The offset to draw the character at */ pub offset: Point, - /** The amount to advance the x position of the cursor when drawing */ - pub advance_width: f32, + /** The amount to advance the x position of the cursor when drawing - f64 due to internally being used as a f64, despite being stored as f32 (only f64 to save some cpu cycles converting f32->f64) */ + pub advance_width: f64, } /** A trait describing a generated font. @@ -34,9 +32,9 @@ pub trait BakedFont { let width = u16::from_le_bytes(bytes[0..2].try_into().unwrap()); let offset_x = i32::from_le_bytes(bytes[2..6].try_into().unwrap()); let offset_y = i32::from_le_bytes(bytes[6..10].try_into().unwrap()); - let advance_width = f32::from_le_bytes(bytes[10..14].try_into().unwrap()); + let advance_width = f64::from(f32::from_le_bytes(bytes[10..14].try_into().unwrap())); let data = &bytes[14..]; - let height = if data.len() == 0 { + let height = if data.is_empty() { 0 } else { data.len() as u16 / width diff --git a/src/interpolation.rs b/src/interpolation.rs index ecb2c85..448ef43 100644 --- a/src/interpolation.rs +++ b/src/interpolation.rs @@ -90,7 +90,7 @@ impl<ValueType: Copy> AnimationTrack<ValueType> { } fn get_first_last(&self) -> Option<(KeyFrame<ValueType>, KeyFrame<ValueType>)> { let okf = self.get_sorted_keyframes(); - if okf.len() == 0 { + if okf.is_empty() { None } else { Some((*okf.first().unwrap(), *okf.last().unwrap())) @@ -175,14 +175,11 @@ impl< timing_function: TwoValueTimingFunction, ) -> Option<ValueType> { let frames = self.get_current_keyframes(sorted_keyframes, time); - match frames { - Some(frames) => Some(KeyFrame::raw_value_at( + frames.map(|frames| KeyFrame::raw_value_at( &frames.0, &frames.1, time, timing_function, - )), - None => None, - } + )) } } diff --git a/src/main.rs b/src/main.rs index 9c65333..7ed8df1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,7 @@ mod font; pub mod generated; mod interpolation; mod render; +pub mod vendored; use std::time::{Duration, SystemTime}; @@ -17,6 +18,8 @@ pub fn main() { let window = &mut video_subsystem .window("Cosin25 Invite", 512, 256) .position_centered() + // works lol: + // .resizable() .build() .unwrap(); diff --git a/src/render.rs b/src/render.rs index dbd3761..bd17e2b 100644 --- a/src/render.rs +++ b/src/render.rs @@ -8,6 +8,31 @@ use sdl2::surface::Surface; use sdl2::video::{Window, WindowContext}; use std::ops::DerefMut; +fn dvd_logo_offset(t: f64, screen_size_x: f64, screen_size_y: f64) -> (f64, f64) { + let offset_x = t % (screen_size_x * 2.0); + let offset_x = if offset_x > screen_size_x { + screen_size_x * 2.0 - offset_x + } else { + offset_x + }; + + let offset_y = t % (screen_size_y * 2.0); + let offset_y = if offset_y > screen_size_y { + screen_size_y * 2.0 - offset_y + } else { + offset_y + }; + + (offset_x, offset_y) +} + +const START_UWUSPACE: f64 = 0.0; +const START_BOUNCE: f64 = 0.5; +const START_SIN: f64 = 1.5; +const START_COMETOCOSIN: f64 = 2.5; +const SCENE_GREETINGS: f64 = 20.0; +const JUST_DVD: f64 = 70.0; + pub fn render( canvas: &mut Canvas<Window>, texture_creator: &TextureCreator<WindowContext>, @@ -19,147 +44,169 @@ pub fn render( } else { i as u8 }; - let sin_offset = time_seconds * 0.1; let win_size = canvas.window().drawable_size(); canvas.set_draw_color(Color::RGB(12, 12, 12)); canvas.clear(); - let mut offset: f32 = 0.0; + let is_dvd = time_seconds < SCENE_GREETINGS || time_seconds >= JUST_DVD; - { - let bounce_time_offset = 1.5; - let bounce_speed = 90.0; - let padding_x = 16.0; - let padding_y = 16.0; - let (offset_x, offset_y) = if time_seconds > bounce_time_offset { - let mut uwu_width = padding_x; - let mut uwu_height: f32 = padding_y * 2.0; - for c in "UwU-Space".chars() { - let char = FONT_CHERRY_BOMB_ONE.get_char(c); - uwu_width += char.advance_width; - if f32::from(char.height) > uwu_height { - uwu_height = char.height.into(); + if is_dvd { + let time_seconds = if time_seconds >= JUST_DVD { + time_seconds - JUST_DVD + 15.0 + } else { + time_seconds + }; + if time_seconds >= START_UWUSPACE { + let bounce_speed = 90.0; + let padding_x = 16.0; + let padding_y = 16.0; + let (offset_x, offset_y) = if time_seconds > START_BOUNCE { + let mut uwu_width = padding_x; + let mut uwu_height: f32 = padding_y; + for c in "UwU-Space".chars() { + let char = FONT_CHERRY_BOMB_ONE.get_char(c); + uwu_width += char.advance_width; + let nh = f32::from(char.height) + padding_y; + if nh > uwu_height { + uwu_height = nh; + } } - } - let virtual_screen_size = ( - f64::from(win_size.0) - f64::from(uwu_width + padding_x), - f64::from(win_size.1) - f64::from(uwu_height + padding_y), - ); - let t = (time_seconds - bounce_time_offset) * bounce_speed; - let offset_x = t % (virtual_screen_size.0 * 2.0); - let offset_x = if offset_x > virtual_screen_size.0 { - virtual_screen_size.0 * 2.0 - offset_x + let virtual_screen_size = ( + f64::from(win_size.0) - (uwu_width + padding_x), + f64::from(win_size.1) - f64::from(uwu_height + padding_y), + ); + let t = (time_seconds - START_BOUNCE) * bounce_speed; + let (offset_x, offset_y) = dvd_logo_offset(t, virtual_screen_size.0, virtual_screen_size.1); + ( + (padding_x + offset_x).round() as i32, + (f64::from(padding_y) + offset_y).round() as i32, + ) } else { - offset_x + (padding_x.round() as i32, padding_y.round() as i32) }; + let mut offset: f64 = 0.0; + for c in "UwU-Space".chars() { + let char = FONT_CHERRY_BOMB_ONE.get_char(c); + canvas + .copy( + &char + .to_texture(texture_creator, Color::RGB(i, 64, 255 - i)) + .unwrap(), + None, + char.to_rect(offset as i32 + offset_x, offset_y), + ) + .unwrap(); + offset += char.advance_width; + } + } + } - let offset_y = t % (virtual_screen_size.1 * 2.0); - let offset_y = if offset_y > virtual_screen_size.1 { - virtual_screen_size.1 * 2.0 - offset_y - } else { - offset_y - }; + if time_seconds >= JUST_DVD { + // + } else if time_seconds >= SCENE_GREETINGS { + let mut offset = 0.0; + let mut rng = rand::thread_rng(); - ( - (f64::from(padding_x) + offset_x).round() as i32, - (f64::from(padding_y) + offset_y).round() as i32, - ) - } else { - (padding_x.round() as i32, padding_y.round() as i32) - }; - for c in "UwU-Space".chars() { - let char = FONT_CHERRY_BOMB_ONE.get_char(c); + for c in "sorry for shit invite we have adhd".chars() { + let char = FONT_GALMURI.get_char(c); canvas .copy( &char - .to_texture(&texture_creator, Color::RGB(i, 64, 255 - i)) + .to_texture(texture_creator, Color::RGB(i, 64, 255 - i)) .unwrap(), None, - char.to_rect(offset as i32 + offset_x, offset_y), + char.to_rect( + offset as i32 + 18 + rng.gen_range(-2..2), + win_size.1 as i32 - 24 + rng.gen_range(-2..2), + ), ) .unwrap(); offset += char.advance_width; } - } - - offset = 0.0; - let mut rng = rand::thread_rng(); - - for c in "sorry for shit invite we have adhd".chars() { - let char = FONT_GALMURI.get_char(c); - canvas - .copy( - &char - .to_texture(&texture_creator, Color::RGB(i, 64, 255 - i)) - .unwrap(), - None, - char.to_rect( - offset as i32 + 18 + rng.gen_range(-2..2), - 16 + 36 + rng.gen_range(-2..2), - ), - ) - .unwrap(); - offset += char.advance_width / 1.0; - } - - { - let mut sin_surface = Surface::new(512, 256, PixelFormatEnum::RGBA32).unwrap(); - - let w = sin_surface.width(); - let h = sin_surface.height(); - let f = &mut sin_surface.deref_mut().without_lock_mut().unwrap(); + } else { + if time_seconds >= START_SIN { + let time_seconds = time_seconds - START_SIN; + let base_sin_offset = time_seconds * 0.1; + let sin_offset = base_sin_offset - 0.75; + let mut sin_surface = Surface::new(win_size.0, win_size.1, PixelFormatEnum::RGBA32).unwrap(); - for x in 0..w { + let w = win_size.0; + let h = win_size.1; let f64_w = f64::from(w); let f64_h = f64::from(h); - let sin_x = { - let mut sin_x = f64::from(x) + (sin_offset * f64_w); - if sin_x > f64_w { - sin_x = sin_x - f64_w; - } - sin_x - }; - let sin_y = - ((f64::sin(sin_x * (3.141 * 2.0) / f64_w) + 1.0) * (f64_h / 2.0)).floor() as usize; - // let sin_idx = (sin_y * w as usize + x as usize) * 4; - - for y in 0..h { - let idx = (y * w + x) as usize * 4; - f[idx] = 122 - (x / 8) as u8; - f[idx + 1] = 255 - (x / 2) as u8; - f[idx + 2] = (x / 2) as u8; - f[idx + 3] = if sin_y < y as usize { - if idx % 5 == 0 { - 255 - } else { - 122 + let f = &mut sin_surface.deref_mut().without_lock_mut().unwrap(); + + let min_x_pos = (if base_sin_offset > 1.0 { + 0.0 + } else { + 1.0 - base_sin_offset + }) * f64_w; + + for x in 0..w { + let f64_x = f64::from(x); + let out_of_frame = f64_x < min_x_pos; + let sin_x = { + let mut sin_x = f64_x + (sin_offset * f64_w); + if sin_x > f64_w { + sin_x -= f64_w; } - } else { - 0 + sin_x }; + let sin_y = + ((f64::sin(sin_x * (3.141 * 2.0) / f64_w) + 1.0) * (f64_h / 2.0)).floor() as usize; + // let sin_idx = (sin_y * w as usize + x as usize) * 4; + + for y in 0..h { + let idx = (y * w + x) as usize * 4; + let cx = x * 512 / w; + f[idx] = (122 - (cx / 8)) as u8; + f[idx + 1] = (255 - (cx / 2)) as u8; + f[idx + 2] = (cx / 2) as u8; + f[idx + 3] = if out_of_frame { + 0 + } else if sin_y < y as usize { + let v: u16 = if idx % 5 == 0 { 255 } else { 122 }; + let v = if (sin_y + 3) < (y as usize) { + v + } else { + v * 2 / 3 + }; + v as u8 + } else { + 0 + }; + } } - } - let sin_texture = Texture::from_surface(&sin_surface, &texture_creator).unwrap(); + let sin_texture = Texture::from_surface(&sin_surface, texture_creator).unwrap(); - canvas - .copy(&sin_texture, None, Rect::new(0, 0, win_size.0, win_size.1)) - .unwrap(); - } - offset = 0.0; - for c in "Come to Cosin25 :3".chars() { - let char = FONT_GALMURI.get_char(c); - canvas - .copy( - &char - .to_texture(&texture_creator, Color::RGB(i, 64, 255 - i)) - .unwrap(), - None, - char.to_rect(offset as i32 + 18, win_size.1 as i32 - 32), - ) - .unwrap(); - offset += char.advance_width / 1.1; + canvas + .copy(&sin_texture, None, Rect::new(0, 0, win_size.0, win_size.1)) + .unwrap(); + } + if time_seconds >= START_COMETOCOSIN { + let time_seconds = time_seconds - START_COMETOCOSIN; + let wrap_width = f64::from(win_size.0); + let mut offset = (18.0 + (time_seconds * 32.0)) % wrap_width; + // WARNING: we wrap this! if the text is wider than the window, this whole thing falls apart + for c in "Come to Cosin25 :3".chars() { + let char = FONT_GALMURI.get_char(c); + canvas + .copy( + &char + .to_texture(texture_creator, Color::RGB(i, 64, 255 - i)) + .unwrap(), + None, + char.to_rect(offset as i32, win_size.1 as i32 - 32), + ) + .unwrap(); + offset += char.advance_width; + if offset > wrap_width { + offset -= wrap_width; + } + } + } } } diff --git a/src/vendored/micromod-rs/ATTRIBUTION b/src/vendored/micromod-rs/ATTRIBUTION new file mode 100644 index 0000000..2f5cc0f --- /dev/null +++ b/src/vendored/micromod-rs/ATTRIBUTION @@ -0,0 +1 @@ +https://github.com/aspizu/micromod-rust/tree/bcf4f62610c2a7b4b417cbde79241efdaa7ed71d
\ No newline at end of file diff --git a/src/vendored/micromod-rs/COPYING b/src/vendored/micromod-rs/COPYING new file mode 100644 index 0000000..f5dc43e --- /dev/null +++ b/src/vendored/micromod-rs/COPYING @@ -0,0 +1,37 @@ +--- +Copyright (c) 2019, Martin Cameron +All rights reserved. + +Redistribution and use in source and binary forms, with or +without modification, are permitted provided that the +following conditions are met: + + * Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + + * Redistributions in binary form must reproduce the + above copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + + * Neither the name of the organization nor the names of + its contributors may be used to endorse or promote + products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND +CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +---
\ No newline at end of file diff --git a/src/vendored/micromod-rs/consts.rs b/src/vendored/micromod-rs/consts.rs new file mode 100644 index 0000000..092766d --- /dev/null +++ b/src/vendored/micromod-rs/consts.rs @@ -0,0 +1,11 @@ +pub static MICROMOD_VERSION: &str = "Micromod Protracker replay 20180625 (c)mumart@gmail.com"; +pub static FINE_TUNING: [u16; 16] = [ + 4340, 4308, 4277, 4247, 4216, 4186, 4156, 4126, 4096, 4067, 4037, 4008, 3979, 3951, 3922, 3894, +]; +pub static ARP_TUNING: [u16; 16] = [ + 4096, 3866, 3649, 3444, 3251, 3069, 2896, 2734, 2580, 2435, 2299, 2170, 2048, 1933, 1825, 1722, +]; +pub static SINE_TABLE: [u8; 32] = [ + 0, 24, 49, 74, 97, 120, 141, 161, 180, 197, 212, 224, 235, 244, 250, 253, 255, 253, 250, 244, + 235, 224, 212, 197, 180, 161, 141, 120, 97, 74, 49, 24, +]; diff --git a/src/vendored/micromod-rs/mod.rs b/src/vendored/micromod-rs/mod.rs new file mode 100644 index 0000000..1d1934a --- /dev/null +++ b/src/vendored/micromod-rs/mod.rs @@ -0,0 +1,860 @@ +//! Experiment with converting [Micromod](https://github.com/martincameron/micromod) with +//! [c2rust](https://c2rust.com/). +//! +//! Safetyfication done manually + +#![warn(missing_docs)] + +mod consts; + +use std::cmp::Ordering; + +#[derive(Copy, Clone, Default)] +struct Channel { + note: Note, + period: u16, + porta_period: u16, + sample_offset: usize, + sample_idx: usize, + step: usize, + volume: u8, + panning: u8, + fine_tune: u8, + ampl: u8, + mute: u8, + id: u8, + instrument: u8, + assigned: u8, + porta_speed: u8, + pl_row: u8, + fx_count: u8, + vibrato_type: u8, + vibrato_phase: u8, + vibrato_speed: u8, + vibrato_depth: u8, + tremolo_type: u8, + tremolo_phase: u8, + tremolo_speed: u8, + tremolo_depth: u8, + tremolo_add: i8, + vibrato_add: i8, + arpeggio_add: i8, +} +#[derive(Copy, Clone, Default)] +struct Note { + key: u16, + instrument: u8, + effect: u8, + param: u8, +} +#[derive(Copy, Clone)] +struct Instrument<'src> { + volume: u8, + fine_tune: u8, + loop_start: usize, + loop_length: usize, + sample_data: &'src [i8], +} + +impl Instrument<'_> { + fn dummy() -> Self { + Self { + volume: 0, + fine_tune: 0, + loop_start: 0, + loop_length: 0, + sample_data: &[], + } + } +} + +/// Immutable source data of the module +struct ModSrc<'src> { + instruments: Vec<Instrument<'src>>, + module_data: &'src [i8], + pattern_data: &'src [u8], + sequence: &'src [u8], + num_patterns: isize, + num_channels: isize, + song_length: isize, +} + +#[derive(Default)] +struct PlaybackState { + gain: isize, + c2_rate: isize, + tick_len: isize, + tick_offset: isize, + pattern: isize, + break_pattern: isize, + row: isize, + next_row: isize, + tick: isize, + speed: isize, + pl_count: isize, + pl_channel: isize, + random_seed: isize, +} + +/// A micromod decoding instance thingy. +pub struct MmC2r<'src> { + sample_rate: isize, + channels: [Channel; 16], + src: ModSrc<'src>, + playback: PlaybackState, +} + +fn calculate_num_patterns(module_header: &[i8]) -> isize { + let mut num_patterns_0; + let mut order_entry; + let mut pattern_0; + num_patterns_0 = 0; + pattern_0 = 0; + while pattern_0 < 128 { + order_entry = (module_header[(952 + pattern_0) as usize] as i32 & 0x7f_i32) as isize; + if order_entry >= num_patterns_0 { + num_patterns_0 = order_entry + 1; + } + pattern_0 += 1; + } + num_patterns_0 +} + +fn calculate_num_channels(module_header: &[i8]) -> Option<isize> { + const MAX_CHANNELS: isize = 16; + let numchan: isize = match ((module_header[1082] as isize) << 8) | module_header[1083] as isize { + // M.K. M!K! N.T. FLT4 + 0x4b2e | 0x4b21 | 0x542e | 0x5434 => 4, + // xCHN + 0x484e => (module_header[1080] - 48) as isize, + // xxCH + 0x4348 => (((module_header[1080] - 48) * 10) + (module_header[1081] - 48)) as isize, + // Not recognised. + _ => 0, + }; + if numchan > MAX_CHANNELS { + None + } else { + Some(numchan) + } +} +fn unsigned_short_big_endian(buf: &[i8], offset: isize) -> isize { + ((buf[offset as usize] as i32 & 0xff_i32) << 8 | buf[(offset + 1) as usize] as i32 & 0xff_i32) + as isize +} +fn set_tempo(tempo: isize, tick_len: &mut isize, sample_rate: isize) { + *tick_len = ((sample_rate << 1) + (sample_rate >> 1)) / tempo; +} +fn update_frequency(chan: &mut Channel, sample_rate: isize, gain: &mut isize, c2_rate: &mut isize) { + let mut period; + let mut volume; + + period = (chan.period as i32 + chan.vibrato_add as i32) as isize; + period = (period * consts::ARP_TUNING[chan.arpeggio_add as usize] as isize) >> 11; + period = (period >> 1) + (period & 1); + if period < 14 { + period = 6848; + } + let freq = (*c2_rate * 428 / period) as usize; + chan.step = (freq << 14).wrapping_div(sample_rate as usize); + volume = (chan.volume as i32 + chan.tremolo_add as i32) as isize; + volume = volume.clamp(0, 64); + chan.ampl = ((volume * *gain) >> 5) as u8; +} +fn tone_portamento(chan: &mut Channel) { + let mut source; + + source = chan.period as isize; + let dest = chan.porta_period as isize; + match source.cmp(&dest) { + Ordering::Less => { + source += chan.porta_speed as isize; + if source > dest { + source = dest; + } + } + Ordering::Equal => { /* Do absolutely nothing */ } + Ordering::Greater => { + source -= chan.porta_speed as isize; + if source < dest { + source = dest; + } + } + } + chan.period = source as u16; +} +fn volume_slide(chan: &mut Channel, param: isize) { + let mut volume; + volume = chan.volume as isize + (param >> 4) - (param & 0xf); + volume = volume.clamp(0, 64); + chan.volume = volume as u8; +} +fn waveform(phase: isize, type_0: isize, random_seed: &mut isize) -> isize { + let mut amplitude: isize = 0; + match type_0 & 0x3 { + 0 => { + amplitude = consts::SINE_TABLE[(phase & 0x1f) as usize] as isize; + if phase & 0x20 > 0 { + amplitude = -amplitude; + } + } + 1 => { + amplitude = 255 - (((phase + 0x20) & 0x3f) << 3); + } + 2 => { + amplitude = 255 - ((phase & 0x20) << 4); + } + 3 => { + amplitude = (*random_seed >> 20) - 255; + *random_seed = (*random_seed * 65 + 17) & 0x1fffffff; + } + _ => {} + } + amplitude +} +fn vibrato(chan: &mut Channel, random_seed: &mut isize) { + chan.vibrato_add = ((waveform( + chan.vibrato_phase as isize, + chan.vibrato_type as isize, + random_seed, + ) * chan.vibrato_depth as isize) + >> 7) as i8; +} +fn tremolo(chan: &mut Channel, random_seed: &mut isize) { + chan.tremolo_add = ((waveform( + chan.tremolo_phase as isize, + chan.tremolo_type as isize, + random_seed, + ) * chan.tremolo_depth as isize) + >> 6) as i8; +} +fn trigger(channel: &mut Channel, instruments: &[Instrument]) { + let period; + + let ins = channel.note.instrument as isize; + if ins > 0 && ins < 32 { + channel.assigned = ins as u8; + channel.sample_offset = 0; + channel.fine_tune = instruments[ins as usize].fine_tune; + channel.volume = instruments[ins as usize].volume; + if instruments[ins as usize].loop_length > 0 && channel.instrument as i32 > 0 { + channel.instrument = ins as u8; + } + } + if channel.note.effect as i32 == 0x9_i32 { + channel.sample_offset = ((channel.note.param as i32 & 0xff_i32) << 8) as usize; + } else if channel.note.effect as i32 == 0x15_i32 { + channel.fine_tune = channel.note.param; + } + if channel.note.key as i32 > 0 { + period = ((channel.note.key as i32 + * consts::FINE_TUNING[(channel.fine_tune as i32 & 0xf_i32) as usize] as i32) + >> 11) as isize; + channel.porta_period = ((period >> 1) + (period & 1)) as u16; + if channel.note.effect as i32 != 0x3_i32 && channel.note.effect as i32 != 0x5_i32 { + channel.instrument = channel.assigned; + channel.period = channel.porta_period; + channel.sample_idx = channel.sample_offset << 14; + if (channel.vibrato_type as i32) < 4 { + channel.vibrato_phase = 0; + } + if (channel.tremolo_type as i32) < 4 { + channel.tremolo_phase = 0; + } + } + } +} +fn channel_row(chan: &mut Channel, sample_rate: isize, src: &ModSrc, playback: &mut PlaybackState) { + let volume; + let period; + let effect = chan.note.effect as isize; + let param = chan.note.param as isize; + let fresh0 = &mut chan.fx_count; + *fresh0 = 0; + let fresh1 = &mut chan.arpeggio_add; + *fresh1 = *fresh0 as i8; + let fresh2 = &mut chan.tremolo_add; + *fresh2 = *fresh1; + chan.vibrato_add = *fresh2; + if !(effect == 0x1d && param > 0) { + trigger(chan, &src.instruments); + } + match effect { + 3 => { + if param > 0 { + chan.porta_speed = param as u8; + } + } + 4 => { + if param & 0xf0 > 0 { + chan.vibrato_speed = (param >> 4) as u8; + } + if param & 0xf > 0 { + chan.vibrato_depth = (param & 0xf) as u8; + } + vibrato(chan, &mut playback.random_seed); + } + 6 => { + vibrato(chan, &mut playback.random_seed); + } + 7 => { + if param & 0xf0 > 0 { + chan.tremolo_speed = (param >> 4) as u8; + } + if param & 0xf > 0 { + chan.tremolo_depth = (param & 0xf) as u8; + } + tremolo(chan, &mut playback.random_seed); + } + 8 => { + if src.num_channels != 4 { + chan.panning = (if param < 128 { param } else { 127 }) as u8; + } + } + 11 => { + if playback.pl_count < 0 { + playback.break_pattern = param; + playback.next_row = 0; + } + } + 12 => { + chan.volume = (if param > 64 { 64 } else { param }) as u8; + } + 13 => { + if playback.pl_count < 0 { + if playback.break_pattern < 0 { + playback.break_pattern = playback.pattern + 1; + } + playback.next_row = (param >> 4) * 10 + (param & 0xf); + if playback.next_row >= 64 { + playback.next_row = 0; + } + } + } + 15 => { + if param > 0 { + if param < 32 { + playback.speed = param; + playback.tick = playback.speed; + } else { + set_tempo(param, &mut playback.tick_len, sample_rate); + } + } + } + 17 => { + period = chan.period as isize - param; + chan.period = (if period < 0 { 0 } else { period }) as u16; + } + 18 => { + period = chan.period as isize + param; + chan.period = (if period > 65535 { 65535 } else { period }) as u16; + } + 20 => { + if param < 8 { + chan.vibrato_type = param as u8; + } + } + 22 => { + if param == 0 { + chan.pl_row = playback.row as u8; + } + if (chan.pl_row as isize) < playback.row && playback.break_pattern < 0 { + if playback.pl_count < 0 { + playback.pl_count = param; + playback.pl_channel = chan.id as isize; + } + if playback.pl_channel == chan.id as isize { + if playback.pl_count == 0 { + chan.pl_row = (playback.row + 1) as u8; + } else { + playback.next_row = chan.pl_row as isize; + } + playback.pl_count -= 1; + } + } + } + 23 => { + if param < 8 { + chan.tremolo_type = param as u8; + } + } + 26 => { + volume = chan.volume as isize + param; + chan.volume = (if volume > 64 { 64 } else { volume }) as u8; + } + 27 => { + volume = chan.volume as isize - param; + chan.volume = (if volume < 0 { 0 } else { volume }) as u8; + } + 28 => { + if param <= 0 { + chan.volume = 0; + } + } + 30 => { + playback.tick = playback.speed + playback.speed * param; + } + _ => {} + } + update_frequency(chan, sample_rate, &mut playback.gain, &mut playback.c2_rate); +} +fn channel_tick( + chan: &mut Channel, + sample_rate: isize, + gain: &mut isize, + c2_rate: &mut isize, + random_seed: &mut isize, + instruments: &[Instrument], +) { + let period; + let effect = chan.note.effect as isize; + let param = chan.note.param as isize; + let fresh3 = &mut chan.fx_count; + *fresh3 = (*fresh3).wrapping_add(1); + match effect { + 1 => { + period = chan.period as isize - param; + chan.period = (if period < 0 { 0 } else { period }) as u16; + } + 2 => { + period = chan.period as isize + param; + chan.period = (if period > 65535 { 65535 } else { period }) as u16; + } + 3 => { + tone_portamento(chan); + } + 4 => { + let fresh4 = &mut chan.vibrato_phase; + *fresh4 = (*fresh4 as i32 + chan.vibrato_speed as i32) as u8; + vibrato(chan, random_seed); + } + 5 => { + tone_portamento(chan); + volume_slide(chan, param); + } + 6 => { + let fresh5 = &mut chan.vibrato_phase; + *fresh5 = (*fresh5 as i32 + chan.vibrato_speed as i32) as u8; + vibrato(chan, random_seed); + volume_slide(chan, param); + } + 7 => { + let fresh6 = &mut chan.tremolo_phase; + *fresh6 = (*fresh6 as i32 + chan.tremolo_speed as i32) as u8; + tremolo(chan, random_seed); + } + 10 => { + volume_slide(chan, param); + } + 14 => { + if chan.fx_count as i32 > 2 { + chan.fx_count = 0; + } + if chan.fx_count as i32 == 0 { + chan.arpeggio_add = 0; + } + if chan.fx_count as i32 == 1 { + chan.arpeggio_add = (param >> 4) as i8; + } + if chan.fx_count as i32 == 2 { + chan.arpeggio_add = (param & 0xf) as i8; + } + } + 25 => { + if chan.fx_count as isize >= param { + chan.fx_count = 0; + chan.sample_idx = 0; + } + } + 28 => { + if param == chan.fx_count as isize { + chan.volume = 0; + } + } + 29 => { + if param == chan.fx_count as isize { + trigger(chan, instruments); + } + } + _ => {} + } + if effect > 0 { + update_frequency(chan, sample_rate, gain, c2_rate); + } +} +fn sequence_row(state: &mut MmC2r) -> bool { + let mut song_end = false; + let mut chan_idx; + let mut pat_offset; + let mut effect; + let mut param; + let mut note; + if state.playback.next_row < 0 { + state.playback.break_pattern = state.playback.pattern + 1; + state.playback.next_row = 0; + } + if state.playback.break_pattern >= 0 { + if state.playback.break_pattern >= state.src.song_length { + state.playback.next_row = 0; + state.playback.break_pattern = state.playback.next_row; + } + if state.playback.break_pattern <= state.playback.pattern { + song_end = true; + } + state.playback.pattern = state.playback.break_pattern; + chan_idx = 0; + while chan_idx < state.src.num_channels { + state.channels[chan_idx as usize].pl_row = 0; + chan_idx += 1; + } + state.playback.break_pattern = -1; + } + state.playback.row = state.playback.next_row; + state.playback.next_row = state.playback.row + 1; + if state.playback.next_row >= 64 { + state.playback.next_row = -1; + } + pat_offset = ((state.src.sequence[state.playback.pattern as usize] as i32 * 64) as isize + + state.playback.row) + * state.src.num_channels + * 4; + chan_idx = 0; + while chan_idx < state.src.num_channels { + note = &mut (state.channels[chan_idx as usize]).note; + let pattern_data = state.src.pattern_data; + note.key = ((pattern_data[pat_offset as usize] as i32 & 0xf_i32) << 8) as u16; + let fresh7 = &mut note.key; + *fresh7 = (*fresh7 as i32 | pattern_data[(pat_offset + 1) as usize] as i32) as u16; + note.instrument = (pattern_data[(pat_offset + 2) as usize] as i32 >> 4) as u8; + let fresh8 = &mut note.instrument; + *fresh8 = (*fresh8 as i32 | pattern_data[pat_offset as usize] as i32 & 0x10_i32) as u8; + effect = (pattern_data[(pat_offset + 2) as usize] as i32 & 0xf_i32) as isize; + param = pattern_data[(pat_offset + 3) as usize] as isize; + pat_offset += 4; + if effect == 0xe { + effect = 0x10 | param >> 4; + param &= 0xf; + } + if effect == 0 && param > 0 { + effect = 0xe; + } + note.effect = effect as u8; + note.param = param as u8; + channel_row( + &mut state.channels[chan_idx as usize], + state.sample_rate, + &state.src, + &mut state.playback, + ); + chan_idx += 1; + } + song_end +} +fn sequence_tick(state: &mut MmC2r) -> bool { + let mut song_end = false; + let mut chan_idx; + state.playback.tick -= 1; + if state.playback.tick <= 0 { + state.playback.tick = state.playback.speed; + song_end = sequence_row(state); + } else { + chan_idx = 0; + while chan_idx < state.src.num_channels { + channel_tick( + &mut state.channels[chan_idx as usize], + state.sample_rate, + &mut state.playback.gain, + &mut state.playback.c2_rate, + &mut state.playback.random_seed, + &state.src.instruments, + ); + chan_idx += 1; + } + } + song_end +} +fn resample( + chan: &mut Channel, + buf: &mut [i16], + offset: isize, + count: isize, + instruments: &[Instrument], +) { + let mut epos; + let mut buf_idx: usize = (offset << 1) as usize; + let buf_end: usize = ((offset + count) << 1) as usize; + let mut sidx: usize = chan.sample_idx; + let step: usize = chan.step; + let llen: usize = instruments[chan.instrument as usize].loop_length; + let lep1: usize = (instruments[chan.instrument as usize].loop_start).wrapping_add(llen); + let sdat = instruments[chan.instrument as usize].sample_data; + let mut ampl: i16 = (if chan.mute == 0 { chan.ampl as i32 } else { 0 }) as i16; + let lamp: i16 = ((ampl as i32 * (127_i32 - chan.panning as i32)) >> 5) as i16; + let ramp: i16 = ((ampl as i32 * chan.panning as i32) >> 5) as i16; + while buf_idx < buf_end { + if sidx >= lep1 { + if llen <= 16384 { + sidx = lep1; + break; + } else { + while sidx >= lep1 { + sidx = sidx.wrapping_sub(llen); + } + } + } + epos = sidx.wrapping_add((buf_end.wrapping_sub(buf_idx) >> 1).wrapping_mul(step)); + if lamp as i32 != 0 || ramp as i32 != 0 { + if epos > lep1 { + epos = lep1; + } + if lamp as i32 != 0 && ramp as i32 != 0 { + while sidx < epos { + ampl = sdat[(sidx >> 14) as usize] as i16; + let fresh9 = buf_idx; + buf_idx = buf_idx.wrapping_add(1); + let fresh10 = &mut (buf[fresh9 as usize]); + *fresh10 = (*fresh10 as i32 + ((ampl as i32 * lamp as i32) >> 2)) as i16; + let fresh11 = buf_idx; + buf_idx = buf_idx.wrapping_add(1); + let fresh12 = &mut (buf[fresh11 as usize]); + *fresh12 = (*fresh12 as i32 + ((ampl as i32 * ramp as i32) >> 2)) as i16; + sidx = sidx.wrapping_add(step); + } + } else { + if ramp != 0 { + buf_idx = buf_idx.wrapping_add(1); + } + while sidx < epos { + let fresh13 = &mut (buf[buf_idx as usize]); + *fresh13 = (*fresh13 as i32 + sdat[(sidx >> 14) as usize] as i32 * ampl as i32) as i16; + buf_idx = buf_idx.wrapping_add(2); + sidx = sidx.wrapping_add(step); + } + buf_idx &= -2_i32 as usize; + } + } else { + buf_idx = buf_end; + sidx = epos; + } + } + chan.sample_idx = sidx; +} + +/// Get a nice version string. I guess. +pub fn version() -> &'static str { + consts::MICROMOD_VERSION +} + +/// An error that can happen when trying to initialize micromod +#[derive(Debug)] +pub enum InitError { + /// Number of channels is incorrect + ChannelNumIncorrect, + /// Sampling rate is incorrect (below 8khz?) + SamplingRateIncorrect, +} + +impl MmC2r<'_> { + /// Create a new micromod decoder apparatus. + pub fn new(data: &[u8], sample_rate: isize) -> Result<MmC2r, InitError> { + let num_channels = match calculate_num_channels(bytemuck::cast_slice(data)) { + Some(num_channels) => num_channels, + None => return Err(InitError::ChannelNumIncorrect), + }; + if sample_rate < 8000 { + return Err(InitError::SamplingRateIncorrect); + } + let song_length = (data[950] as i32 & 0x7f_i32) as isize; + let sequence = &data[952..]; + let pattern_data = &data[1084..]; + let mut state = MmC2r { + sample_rate, + channels: Default::default(), + src: ModSrc { + instruments: Vec::new(), + module_data: bytemuck::cast_slice(data), + pattern_data, + sequence, + num_patterns: Default::default(), + num_channels, + song_length, + }, + playback: PlaybackState::default(), + }; + state.src.num_patterns = calculate_num_patterns(state.src.module_data); + let mut sample_data_offset = 1084 + state.src.num_patterns * 64 * state.src.num_channels * 4; + let mut inst_idx = 1; + // First instrument is an unused dummy instrument + state.src.instruments.push(Instrument::dummy()); + while inst_idx < 32 { + let sample_length = unsigned_short_big_endian(state.src.module_data, inst_idx * 30 + 12) * 2; + let fine_tune = + (state.src.module_data[(inst_idx * 30 + 14) as usize] as i32 & 0xf_i32) as isize; + let fine_tune = ((fine_tune & 0x7) - (fine_tune & 0x8) + 8) as u8; + let volume = + (state.src.module_data[(inst_idx * 30 + 15) as usize] as i32 & 0x7f_i32) as isize; + let volume = (if volume > 64 { 64 } else { volume }) as u8; + let mut loop_start = unsigned_short_big_endian(state.src.module_data, inst_idx * 30 + 16) * 2; + let mut loop_length = + unsigned_short_big_endian(state.src.module_data, inst_idx * 30 + 18) * 2; + if loop_start + loop_length > sample_length { + if loop_start / 2 + loop_length <= sample_length { + loop_start /= 2; + } else { + loop_length = sample_length - loop_start; + } + } + if loop_length < 4 { + loop_start = sample_length; + loop_length = 0; + } + let loop_start = (loop_start << 14) as usize; + let loop_length = (loop_length << 14) as usize; + let sample_data = &bytemuck::cast_slice::<u8, i8>(data)[sample_data_offset as usize..]; + sample_data_offset += sample_length; + inst_idx += 1; + state.src.instruments.push(Instrument { + volume, + fine_tune, + loop_start, + loop_length, + sample_data, + }); + } + state.playback.c2_rate = (if state.src.num_channels > 4 { + 8363 + } else { + 8287 + }) as isize; + state.playback.gain = (if state.src.num_channels > 4 { 32 } else { 64 }) as isize; + state.mute_channel(-1); + micromod_set_position(0, &mut state); + Ok(state) + } + /// Fill a buffer with delicious samples + pub fn get_audio(&mut self, output_buffer: &mut [i16], mut count: isize) -> bool { + let mut offset; + let mut remain; + let mut chan_idx; + if self.src.num_channels <= 0 { + return false; + } + offset = 0; + let mut cnt = true; + while count > 0 { + remain = self.playback.tick_len - self.playback.tick_offset; + if remain > count { + remain = count; + } + chan_idx = 0; + while chan_idx < self.src.num_channels { + resample( + &mut self.channels[chan_idx as usize], + output_buffer, + offset, + remain, + &self.src.instruments, + ); + chan_idx += 1; + } + self.playback.tick_offset += remain; + if self.playback.tick_offset == self.playback.tick_len { + if sequence_tick(self) { + cnt = false; + } + self.playback.tick_offset = 0; + } + offset += remain; + count -= remain; + } + cnt + } + /// Calculate the length of the module file... In samples. Presumably. + pub fn calculate_mod_file_len(&self) -> Option<isize> { + let module_header = self.src.module_data; + let mut length; + + let mut inst_idx; + let numchan = calculate_num_channels(module_header)?; + length = 1084 + 4 * numchan * 64 * calculate_num_patterns(module_header); + inst_idx = 1; + while inst_idx < 32 { + length += unsigned_short_big_endian(module_header, inst_idx * 30 + 12) * 2; + inst_idx += 1; + } + Some(length) + } + /// Calculate the song duration... Okay. + pub fn calculate_song_duration(&mut self) -> isize { + let mut duration; + duration = 0; + if self.src.num_channels > 0 { + micromod_set_position(0, self); + let mut song_end = false; + while !song_end { + duration += self.playback.tick_len; + song_end = sequence_tick(self); + } + micromod_set_position(0, self); + } + duration + } + /// Mute a channel. + pub fn mute_channel(&mut self, channel: isize) -> isize { + let mut chan_idx; + if channel < 0 { + chan_idx = 0; + while chan_idx < self.src.num_channels { + self.channels[chan_idx as usize].mute = 0; + chan_idx += 1; + } + } else if channel < self.src.num_channels { + self.channels[channel as usize].mute = 1; + } + self.src.num_channels + } + /// Set some gainz. + pub fn set_gain(&mut self, value: isize) { + self.playback.gain = value; + } +} + +fn micromod_set_position(mut pos: isize, state: &mut MmC2r) { + let mut chan_idx; + let mut chan; + if state.src.num_channels <= 0 { + return; + } + if pos >= state.src.song_length { + pos = 0; + } + state.playback.break_pattern = pos; + state.playback.next_row = 0; + state.playback.tick = 1; + state.playback.speed = 6; + set_tempo(125, &mut state.playback.tick_len, state.sample_rate); + state.playback.pl_channel = -1; + state.playback.pl_count = state.playback.pl_channel; + state.playback.random_seed = 0xabcdef; + chan_idx = 0; + while chan_idx < state.src.num_channels { + chan = &mut state.channels[chan_idx as usize]; + chan.id = chan_idx as u8; + let fresh15 = &mut chan.assigned; + *fresh15 = 0; + chan.instrument = *fresh15; + chan.volume = 0; + match chan_idx & 0x3 { + 0 | 3 => { + chan.panning = 0; + } + 1 | 2 => { + chan.panning = 127; + } + _ => {} + } + chan_idx += 1; + } + sequence_tick(state); + state.playback.tick_offset = 0; +} diff --git a/src/vendored/mod.rs b/src/vendored/mod.rs new file mode 100644 index 0000000..651edd9 --- /dev/null +++ b/src/vendored/mod.rs @@ -0,0 +1,2 @@ +#[path = "./micromod-rs/mod.rs"] +pub mod micromod; |