aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatarLarge Libravatar /dev/urandom <johnfkennedymaili2p>2025-02-15 01:46:19 +0100
committerLibravatarLarge Libravatar /dev/urandom <johnfkennedymaili2p>2025-02-15 01:46:19 +0100
commitf12e249c73f021d57d69d5f93d27e03259982edf (patch)
treed78de03146356df5c965433cc4f7f53b9f62579e
parenta903bb7268fb95ea525a24bc8dd251e00312e149 (diff)
downloadcosin25-invite-mountainbytes-f12e249c73f021d57d69d5f93d27e03259982edf.tar.gz
cosin25-invite-mountainbytes-f12e249c73f021d57d69d5f93d27e03259982edf.tar.bz2
cosin25-invite-mountainbytes-f12e249c73f021d57d69d5f93d27e03259982edf.tar.lz
cosin25-invite-mountainbytes-f12e249c73f021d57d69d5f93d27e03259982edf.zip

feat: Marquee, Lower dither opacity at top, DVD Logo should be separate function, Release Builds should work, advance_width should be f64 to save bytes, Vendor Micromod-RS, Add Timing Steps

-rw-r--r--Cargo.lock7
-rw-r--r--Cargo.toml1
-rwxr-xr-xbuild2
-rw-r--r--build.rs12
-rw-r--r--greetings.txt14
-rw-r--r--src/font.rs10
-rw-r--r--src/interpolation.rs9
-rw-r--r--src/main.rs3
-rw-r--r--src/render.rs273
-rw-r--r--src/vendored/micromod-rs/ATTRIBUTION1
-rw-r--r--src/vendored/micromod-rs/COPYING37
-rw-r--r--src/vendored/micromod-rs/consts.rs11
-rw-r--r--src/vendored/micromod-rs/mod.rs860
-rw-r--r--src/vendored/mod.rs2
14 files changed, 1109 insertions, 133 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 2c7a638..0ca15e4 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -15,6 +15,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
+name = "bytemuck"
+version = "1.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef657dfab802224e671f5818e9a4935f9b1957ed18e58292690cc39e7a4092a3"
+
+[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -48,6 +54,7 @@ dependencies = [
name = "cosin-2025-invite-deck"
version = "0.1.0"
dependencies = [
+ "bytemuck",
"rand",
"rusttype",
"sdl2",
diff --git a/Cargo.toml b/Cargo.toml
index 001ba33..e52fd22 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -4,6 +4,7 @@ version = "0.1.0"
edition = "2021"
[dependencies]
+bytemuck = "1.21.0"
rand = "0.8.5"
sdl2 = { version = "0.37.0" }
diff --git a/build b/build
index 3c2d560..0dff0a9 100755
--- a/build
+++ b/build
@@ -1,4 +1,4 @@
#!/bin/sh
set -eax
-RUSTFLAGS="-Zfmt-debug=none -Zlocation-detail=none" cargo +nightly build -Z build-std=std,panic_abort -Z build-std-features="optimize_for_size" -Z build-std-features=panic_immediate_abort -r
+RUSTFLAGS="-Zlocation-detail=none" cargo +nightly build -Z build-std=std,panic_abort -Z build-std-features="optimize_for_size" -Z build-std-features=panic_immediate_abort -r
upx --brute target/release/cosin-2025-invite-deck
diff --git a/build.rs b/build.rs
index 9292e1e..438cde8 100644
--- a/build.rs
+++ b/build.rs
@@ -51,7 +51,7 @@ impl FontMetadata {
);
image[0] = (width & 0b0000_0000_1111_1111) as u8;
- image[1] = (width & 0b1111_1111_0000_0000 << 8) as u8;
+ image[1] = (width & (0b1111_1111_0000_0000 << 8)) as u8;
if (image[0] as u16) | (image[1] as u16 >> 8) != width as u16 {
panic!("Width missmatch!");
}
@@ -78,7 +78,7 @@ impl FontMetadata {
if i >= 0 {
if i as u16 == width {
i = 0;
- image2.push('\n' as u8);
+ image2.push(b'\n');
}
let v = format!("{:x?}", bit);
let v = if v.len() == 1 { format!("0{}", v) } else { v };
@@ -178,11 +178,9 @@ pub const FONT{}: {name}Struct = {name}Struct{{
fn main() -> Result<(), Box<dyn std::error::Error>> {
fs::create_dir_all("src/generated")?;
- let mut modrs = format!(
- "// Copyright is a sham this is a @generated file.
+ let mut modrs = "// Copyright is a sham this is a @generated file.
use crate::font::BakedFont;
-"
- );
+".to_string();
let fonts = [
FontMetadata {
name: "Galmuri",
@@ -205,6 +203,6 @@ use crate::font::BakedFont;
);
exec(font)?;
}
- File::create(&"src/generated/fonts.rs")?.write_all(modrs.as_bytes())?;
+ File::create("src/generated/fonts.rs")?.write_all(modrs.as_bytes())?;
Ok(())
}
diff --git a/greetings.txt b/greetings.txt
new file mode 100644
index 0000000..fbd8e30
--- /dev/null
+++ b/greetings.txt
@@ -0,0 +1,14 @@
+greetings to use, as discussed with uwuspace creatures & /dev/urandom @ mountainbytes directly:
+- Venty
+- Erdit
+- Kaede
+- Deja
+- dui
+- vimja
+- cy
+- alu
+- sashu
+- expired bread
+- gaben
+
+DELETE THIS FILE BEFORE RELEASING THE SOURCE CODE!
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;