From 8fba20530936d7b55b8cfc7b92ae893f9b06c08f Mon Sep 17 00:00:00 2001 From: Chris Xiong Date: Wed, 5 Mar 2025 23:13:33 -0500 Subject: First steps towards making some noise. New project name. --- README.md | 9 ++++++++- src/player.rs | 36 +++++++++++++++++++++++++++++++----- src/portmod.rs | 45 +++++++++++++++++++++++++++++++++++++++++++++ 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, @@ -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, + pub inst: Option, + pub pitch: Option, +} + 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, + pub inst: Option, + 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 -- cgit v1.2.3