aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Chris Xiong <chirs241097@gmail.com> 2025-03-05 23:13:33 -0500
committerGravatar Chris Xiong <chirs241097@gmail.com> 2025-03-05 23:13:33 -0500
commit8fba20530936d7b55b8cfc7b92ae893f9b06c08f (patch)
treefcd786e892c8c2da9071b1b1cf3e038b4d3d9a04
parent473d6e1d506cb6e0ac3c763871c5c8f9e4395ae5 (diff)
downloadit2midi-8fba20530936d7b55b8cfc7b92ae893f9b06c08f.tar.xz
First steps towards making some noise. New project name.HEADdev
-rw-r--r--README.md9
-rw-r--r--src/player.rs36
-rw-r--r--src/portmod.rs45
3 files changed, 84 insertions, 6 deletions
diff --git a/README.md b/README.md
index 4675265..f8008e0 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,11 @@
-# IT2MIDI
+# ~~IT2MIDI~~
+# IMPRESSIO
+
+A toolkit for modular music from popular DOS-era sample-based trackers.
+
+A work in progress...
+
+Below is the original content of this readme when the project was still "IT2MIDI".
_#Rewrite it in Rust_
diff --git a/src/player.rs b/src/player.rs
index d33b622..6afc17a 100644
--- a/src/player.rs
+++ b/src/player.rs
@@ -1,6 +1,6 @@
use crate::utils::Rational;
use crate::itfile::ITFile;
-use crate::portmod::Effect;
+use crate::portmod::{Effect, NoteCommand};
pub struct PlayerState {
skip_row: Option<u8>,
@@ -53,6 +53,21 @@ pub struct BasePlayer<'itfile> {
impl<'itfile> BasePlayer<'itfile> {
pub fn new(it: &ITFile) -> BasePlayer { BasePlayer{it} }
+ fn process_voice_commands(&self, state: &PlayerState, chmem: [ChannelMemory; 64]) -> [ChannelMemory; 64] {
+ let pat = self.it.orders[state.current_ord as usize] as usize;
+ std::array::from_fn(|ch| {
+ let itcell = self.it.patterns[pat].cell_at(state.current_row, ch as u8);
+ let cell = crate::portmod::Cell::from_it_cell(itcell);
+ let (note, pitch) = if let Some(ncmd) = cell.note {
+ match ncmd {
+ NoteCommand::NoteOn(n) => (Some(n), Some(n.into())),
+ _ => (None, None)
+ }
+ } else { (chmem[ch].note, chmem[ch].pitch) };
+ let inst = if cell.inst.is_some() { cell.inst } else { chmem[ch].inst };
+ ChannelMemory{note, inst, pitch}
+ })
+ }
fn process_transport_commands(&self, state: PlayerState) -> PlayerState {
let pat = self.it.orders[state.current_ord as usize] as usize;
(0..64).fold(state, |state, ch| {
@@ -101,8 +116,17 @@ impl<'itfile> BasePlayer<'itfile> {
}
}
+//TODO: proper voice system in the future
+#[derive(Default)]
+pub struct ChannelMemory {
+ pub note: Option<u8>,
+ pub inst: Option<u8>,
+ pub pitch: Option<Rational>,
+}
+
pub struct BasePlayerStateEx {
- loop_detected: bool
+ loop_detected: bool,
+ channel_mems: [ChannelMemory; 64]
}
impl<'itfile> Player for BasePlayer<'itfile> {
@@ -122,9 +146,11 @@ impl<'itfile> Player for BasePlayer<'itfile> {
loop_ctr: u8::MAX,
row_extension: 0,
in_rep: false
- }, BasePlayerStateEx{ loop_detected: false})
+ }, BasePlayerStateEx{ loop_detected: false, channel_mems: std::array::from_fn(|_| Default::default()) })
}
fn process_row(&self, state: PlayerState, statex: Self::PlayerStateExtension) -> (PlayerState, Self::PlayerStateExtension) {
+ let chmem_next = self.process_voice_commands(&state, statex.channel_mems);
+ let statex = BasePlayerStateEx{channel_mems: chmem_next, ..statex};
let state = self.process_transport_commands(state);
let (state_next, statex) =
if state.current_tick + 1 < state.speed + state.row_extension { // still within the current row
@@ -134,14 +160,14 @@ impl<'itfile> Player for BasePlayer<'itfile> {
let (state, statex) = match (state.skip_row, state.skip_ord) {
(Some(skip_row), Some(skip_ord)) => {
(PlayerState{current_row: skip_row, current_ord: skip_ord, ..state},
- BasePlayerStateEx{loop_detected: skip_ord < state.current_ord && !state.loop_ctr == 0})
+ BasePlayerStateEx{loop_detected: skip_ord < state.current_ord && !state.loop_ctr == 0, ..statex})
},
(Some(skip_row), None) => { // this is also handled in Player::skip_to... only keep one of them?
(PlayerState{current_row: skip_row, current_ord: state.current_ord + 1, ..state}, statex)
},
(None, Some(skip_ord)) => {
(PlayerState{current_row: 0, current_ord: skip_ord, ..state},
- BasePlayerStateEx{loop_detected: skip_ord < state.current_ord && !state.loop_ctr == 0})
+ BasePlayerStateEx{loop_detected: skip_ord < state.current_ord && !state.loop_ctr == 0, ..statex})
},
(None, None) => {
let (row, ord) = if state.current_row + 1 < self.it.patterns[self.it.orders[state.current_ord as usize] as usize].nrows {
diff --git a/src/portmod.rs b/src/portmod.rs
index a271ead..38c353b 100644
--- a/src/portmod.rs
+++ b/src/portmod.rs
@@ -65,6 +65,51 @@ pub enum Effect
NoEffect
}
+pub enum LoopMode {
+ NoLoop,
+ ForwardLoop,
+ PingPongLoop
+}
+
+pub enum NoteCommand {
+ NoteOn(u8),
+ NoteOff,
+ NoteCut,
+ NoteFade,
+}
+
+pub struct Cell {
+ pub note: Option<NoteCommand>,
+ pub inst: Option<u8>,
+ pub vol: Effect,
+ pub efx: Effect
+}
+
+impl Cell
+{
+ pub fn from_it_cell(cell: &crate::itfile::Cell) -> Cell {
+ let note = if cell.mask & 0x11 != 0 {
+ let cmd = match cell.note {
+ 0xff => NoteCommand::NoteOff,
+ 0xfe => NoteCommand::NoteCut,
+ 0x78..0xfe => NoteCommand::NoteFade,
+ _ => NoteCommand::NoteOn(cell.note)
+ };
+ Some(cmd)
+ } else { None };
+ let inst = if cell.mask & 0x22 != 0 {
+ Some(cell.inst)
+ } else { None };
+ let vol = if cell.mask & 0x44 != 0 {
+ Effect::from_it_vol(cell.vol)
+ } else { Effect::NoEffect };
+ let efx = if cell.mask & 0x88 != 0 {
+ Effect::from_it_efx((cell.efx, cell.fxp))
+ } else { Effect::NoEffect };
+ Cell{note, inst, vol, efx}
+ }
+}
+
impl Effect
{
pub fn from_it_efx(f: (u8, u8)) -> Effect