From 8ea912c0620867bec5d040d1c44461ae46bb2a35 Mon Sep 17 00:00:00 2001 From: Huck Boles Date: Tue, 30 May 2023 15:33:35 -0500 Subject: [PATCH] added: layout outlines --- src/cells.rs | 6 ++-- src/graphics.rs | 54 +++++++++++++++++++++++++------ src/graphics/area.rs | 21 ++++++------ src/graphics/map.rs | 22 +++++++------ src/lib.rs | 2 ++ src/main.rs | 36 ++++++--------------- src/music.rs | 8 +++-- src/music/scale.rs | 72 ++++++++++++++++++++++++++++-------------- src/music/transport.rs | 16 ++-------- src/state.rs | 59 ++++++++++++++++++++++++++++++++++ 10 files changed, 197 insertions(+), 99 deletions(-) create mode 100644 src/state.rs diff --git a/src/cells.rs b/src/cells.rs index 8ca6808..57f14f0 100644 --- a/src/cells.rs +++ b/src/cells.rs @@ -1,4 +1,4 @@ -use crate::{Map, Mask, Point}; +use crate::{Map, Point}; use crossterm::style::{ Attribute, Attributes, Color::{Black, Green, Grey}, @@ -16,12 +16,12 @@ pub enum State { #[derive(Clone, Debug)] pub struct World { - pub map: Mask>, + pub map: Array2>, } impl World { pub fn new(x: usize, y: usize) -> Self { - let map = Mask::new(x, y, Cell::from(State::Dead)); + let map = Array2::from_elem((x, y), Cell::from(State::Dead)); Self { map } } diff --git a/src/graphics.rs b/src/graphics.rs index e7c8e4c..a886a02 100644 --- a/src/graphics.rs +++ b/src/graphics.rs @@ -14,6 +14,8 @@ pub use map::*; pub use point::*; pub use selector::*; +use super::*; + use crossterm::{ cursor::{Hide, MoveTo}, execute, queue, @@ -27,18 +29,49 @@ use std::{ time::Duration, }; -pub struct Cursor {} +pub fn render_loop(state: &mut GlobalState) -> Result<()> { + execute!(stdout(), terminal::Clear(terminal::ClearType::All))?; + + state.layout.draw_outlines()?; + + loop { + let tick = state.transport.tick; + let timer = std::thread::spawn(move || thread::sleep(tick)); + + if !state.transport.running { + timer.join().unwrap(); + continue; + } + + state.world.update(); + + draw_map(&mut state.world, &state.layout.cells)?; + // draw_map(&mut state.mask[0], &state.layout.mask)?; + + timer.join().unwrap(); + + stdout().flush()?; + } +} + +pub fn draw_map(map: &mut impl Map, area: &Area) -> Result<()> { + let ((x_zero, y_zero), (x_max, y_max)) = area.to_u16()?; + + let origin = Point::new(x_zero.into(), y_zero.into()); -pub fn draw_frame(map: &mut impl Map, offset: Point) -> Result<()> { let (char_on, char_off) = map.characters(); let on_colors = map.on_colors(); let off_colors = map.off_colors(); let (style_on, style_off) = map.styles(); - for x in 1..(map.x_size() - 1) { - for y in 1..(map.y_size() - 1) { + for x in 0..=(map.x_size()) { + for y in 0..=(map.y_size()) { let point = Point::new(x, y); - let (x_off, y_off) = point.u16_offset(offset)?; + let (x_off, y_off) = origin.u16_offset(point)?; + + if x_off <= x_zero || x_off >= x_max || y_off <= y_zero || y_off >= y_max { + continue; + } if map.try_point(point) { queue!( @@ -61,16 +94,19 @@ pub fn draw_frame(map: &mut impl Map, offset: Point) -> Result<()> { } } } + + area.outline_area()?; + Ok(()) } -pub fn run_map(map: &mut impl Map, offset: Point, time: Duration) -> Result<()> { +pub fn run_map(map: &mut impl Map, area: &Area, time: Duration) -> Result<()> { loop { map.update(); execute!(stdout(), terminal::Clear(terminal::ClearType::All))?; - draw_frame(map, offset)?; + draw_map(map, area)?; stdout().flush()?; @@ -80,7 +116,7 @@ pub fn run_map(map: &mut impl Map, offset: Point, time: Duration) -> Resul pub fn loop_map( map: &mut (impl Map + Clone), - offset: Point, + area: &Area, time: Duration, steps: usize, ) -> Result<()> { @@ -88,7 +124,7 @@ pub fn loop_map( let mut tmp = map.clone(); for _ in 0..steps { execute!(stdout(), terminal::Clear(terminal::ClearType::All))?; - draw_frame(&mut tmp, offset)?; + draw_map(&mut tmp, area)?; stdout().flush()?; tmp.update(); thread::sleep(time); diff --git a/src/graphics/area.rs b/src/graphics/area.rs index 9f5b003..d36e50b 100644 --- a/src/graphics/area.rs +++ b/src/graphics/area.rs @@ -14,7 +14,6 @@ use super::*; pub struct Area { pub origin: Point, pub max: Point, - pub colors: Option, } impl From<(Point, Point)> for Area { @@ -22,7 +21,6 @@ impl From<(Point, Point)> for Area { Self { origin: value.0, max: value.1, - colors: None, } } } @@ -32,14 +30,9 @@ impl Area { Self { origin: Point::new(x_zero, y_zero), max: Point::new(x_max, y_max), - colors: None, } } - pub fn set_colors(&mut self, colors: Colors) { - self.colors = Some(colors); - } - pub fn to_u16(&self) -> Result<((u16, u16), (u16, u16))> { Ok(( (self.origin.x.try_into()?, self.origin.y.try_into()?), @@ -47,11 +40,19 @@ impl Area { )) } + pub fn width(&self) -> usize { + self.max.y - self.origin.y + } + + pub fn height(&self) -> usize { + self.max.x - self.origin.x + } + pub fn outline_area(&self) -> Result<()> { - let colors = self.colors.unwrap_or(Colors::new(White, Black)); + let colors = Colors::new(White, Black); let ((x_zero, y_zero), (x_max, y_max)) = self.to_u16()?; - for y in y_zero..=y_max { + for y in y_zero + 1..y_max { queue!( stdout(), Hide, @@ -64,7 +65,7 @@ impl Area { )?; } - for x in x_zero..=x_max { + for x in x_zero + 1..x_max { queue!( stdout(), Hide, diff --git a/src/graphics/map.rs b/src/graphics/map.rs index 3491ceb..79d27cf 100644 --- a/src/graphics/map.rs +++ b/src/graphics/map.rs @@ -7,6 +7,8 @@ use crossterm::style::{ use ndarray::Array2; use std::ops::Deref; +use super::*; + pub trait Map { fn try_point(&self, point: Point) -> bool; fn get_point(&self, point: Point) -> Option; @@ -20,25 +22,25 @@ pub trait Map { } #[derive(Debug, Clone)] -pub struct Mask { - pub mask: Array2, +pub struct Mask { + pub mask: Array2>, } -impl Mask { - pub fn new(x: usize, y: usize, default: T) -> Self { - let mask = Array2::from_elem((y, x), default); +impl Mask { + pub fn new(x: usize, y: usize) -> Self { + let mask = Array2::from_elem((y, x), None); Self { mask } } } -impl Deref for Mask { - type Target = Array2; +impl Deref for Mask { + type Target = Array2>; fn deref(&self) -> &Self::Target { &self.mask } } -impl Map for Mask { +impl Map> for Mask { fn x_size(&self) -> usize { self.ncols() } @@ -51,8 +53,8 @@ impl Map for Mask { self.get((point.y, point.x)).is_some() } - fn get_point(&self, point: Point) -> Option { - self.get((point.y, point.x)).cloned() + fn get_point(&self, point: Point) -> Option> { + self.get((point.y, point.x)).copied() } fn characters(&self) -> (char, char) { diff --git a/src/lib.rs b/src/lib.rs index 131c072..3638b07 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,10 +1,12 @@ mod cells; mod graphics; mod music; +mod state; pub use cells::*; pub use graphics::*; pub use music::*; +pub use state::*; use eyre::Result; use std::time::Duration; diff --git a/src/main.rs b/src/main.rs index a1c450e..4a5d0d4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,35 +1,19 @@ use cellseq::*; +use crossterm::terminal; use eyre::Result; -use crossterm::{ - event::{poll, read, Event}, - terminal, -}; - fn main() -> Result<()> { terminal::enable_raw_mode()?; - action_loop(10)?; - // let mut mask: Mask = Mask::new(48, 24, false); - // - // let mut map = World::new(74, 32); - // map.randomize(0.75); - - // let time = bpm_to_ms(100); + let mut state = GlobalState::build()?; - // loop { - // if poll(std::time::Duration::from_millis(50))? { - // // It's guaranteed that the `read()` won't block when the `poll()` - // // function returns `true` - // match read()? { - // Event::Key(event) => println!("\n{:?}", event), - // _ => continue, - // } - // } - // } + state.world.randomize(0.75); - // run_map(&mut map, Point::new(10, 2), time)?; - // edit_mask(&mask, (10, 5))?; - // move_cursor()?; - Ok(()) + match render_loop(&mut state) { + Ok(_) => exit(), + Err(e) => { + eprintln!("{}", e); + exit() + } + } } diff --git a/src/music.rs b/src/music.rs index 472a4b2..943d5dd 100644 --- a/src/music.rs +++ b/src/music.rs @@ -4,7 +4,7 @@ mod transport; pub use scale::*; pub use transport::*; -pub type NoteMask = [bool; 12]; +pub type NoteMask = [Option; 12]; pub struct TimeSignature { pub top: usize, @@ -20,6 +20,7 @@ impl From<(usize, usize)> for TimeSignature { } } +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub enum Note { A(Acc), B(Acc), @@ -30,8 +31,9 @@ pub enum Note { G(Acc), } +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub enum Acc { - Shp, - Nat, Flt, + Nat, + Shp, } diff --git a/src/music/scale.rs b/src/music/scale.rs index b74b38d..a57d8b3 100644 --- a/src/music/scale.rs +++ b/src/music/scale.rs @@ -1,5 +1,10 @@ use super::*; +use super::{ + Acc::{Nat, Shp}, + Note::{A, B, C, D, E, F, G}, +}; + pub enum Scale { Ionian, Dorian, @@ -19,38 +24,57 @@ pub enum Scale { impl Scale { pub fn to_notes(&self) -> NoteMask { - let mut diatonic: NoteMask = [ - true, false, true, false, true, true, false, true, false, true, false, true, + let octave = [ + C(Nat), + C(Shp), + D(Nat), + D(Shp), + E(Nat), + F(Nat), + F(Shp), + G(Nat), + G(Shp), + A(Nat), + A(Shp), + B(Nat), ]; - let mut pentatonic: NoteMask = [ - true, false, true, false, true, false, false, true, false, true, false, false, - ]; + let diatonic = [1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1]; + let pentatonic = [1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0]; + let whole_tone = [1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0]; + + fn mask_scale(notes: &[Note; 12], mask: [u8; 12]) -> NoteMask { + let mut output = [None; 12]; + for (i, (note, mask)) in notes.iter().zip(mask.iter()).enumerate() { + if *mask == 1 { + output[i] = Some(*note) + } + } + output + } macro_rules! rotate { - ($i:ident,$e:expr) => {{ - $i.rotate_left($e); - $i + ($s:expr,$n:expr) => {{ + $s.rotate_left($n); + $s }}; } match self { - Scale::Ionian => diatonic, - Scale::Dorian => rotate!(diatonic, 2), - Scale::Phrygian => rotate!(diatonic, 4), - Scale::Lydian => rotate!(diatonic, 5), - Scale::Mixolydian => rotate!(diatonic, 7), - Scale::Aeolian => rotate!(diatonic, 9), - Scale::Locrian => rotate!(diatonic, 11), - Scale::MajPent => pentatonic, - Scale::SusEgypt => rotate!(pentatonic, 2), - Scale::BlueMinPen => rotate!(pentatonic, 4), - Scale::BlueMajPent => rotate!(pentatonic, 7), - Scale::MinPent => rotate!(pentatonic, 9), - Scale::Chromatic => [true; 12], - Scale::WholeTone => [ - true, false, true, false, true, false, true, false, true, false, true, false, - ], + Scale::Ionian => mask_scale(&octave, diatonic), + Scale::Dorian => rotate!(mask_scale(&octave, diatonic), 2), + Scale::Phrygian => rotate!(mask_scale(&octave, diatonic), 4), + Scale::Lydian => rotate!(mask_scale(&octave, diatonic), 5), + Scale::Mixolydian => rotate!(mask_scale(&octave, diatonic), 7), + Scale::Aeolian => rotate!(mask_scale(&octave, diatonic), 9), + Scale::Locrian => rotate!(mask_scale(&octave, diatonic), 11), + Scale::MajPent => mask_scale(&octave, pentatonic), + Scale::SusEgypt => rotate!(mask_scale(&octave, pentatonic), 2), + Scale::BlueMinPen => rotate!(mask_scale(&octave, pentatonic), 4), + Scale::BlueMajPent => rotate!(mask_scale(&octave, pentatonic), 7), + Scale::MinPent => rotate!(mask_scale(&octave, pentatonic), 9), + Scale::WholeTone => mask_scale(&octave, whole_tone), + Scale::Chromatic => octave.map(|n| Some(n)), } } } diff --git a/src/music/transport.rs b/src/music/transport.rs index ad19bdb..59face8 100644 --- a/src/music/transport.rs +++ b/src/music/transport.rs @@ -1,24 +1,12 @@ use super::*; pub struct Song { - pub bpm: usize, - pub time_sig: TimeSignature, pub key: Option, pub scale: Option, } impl Song { - pub fn new( - bpm: usize, - time_sig: TimeSignature, - key: Option, - scale: Option, - ) -> Self { - Self { - bpm, - time_sig, - key, - scale, - } + pub fn new(key: Option, scale: Option) -> Self { + Self { key, scale } } } diff --git a/src/state.rs b/src/state.rs new file mode 100644 index 0000000..7bbf6bc --- /dev/null +++ b/src/state.rs @@ -0,0 +1,59 @@ +use super::*; + +pub struct GlobalState { + pub world: World, + pub layout: Layout, + pub transport: Transport, + pub song: Song, + pub mask: Vec, + pub channels: Vec, +} + +impl GlobalState { + pub fn build() -> Result { + let layout = Layout::build()?; + + let world = World::new(layout.cells.width(), layout.cells.height() + 1); + + let channels = Vec::new(); + let mask = Vec::new(); + + let transport = Transport::new(4, 4, 120); + + let song = Song::new(None, None); + + Ok(Self { + world, + layout, + transport, + song, + mask, + channels, + }) + } +} + +pub struct MidiChannel { + pub num: usize, + pub poly_num: usize, +} + +pub struct Transport { + pub running: bool, + pub bpm: usize, + pub sig: TimeSignature, + pub tick: Duration, + pub repeat: usize, +} + +impl Transport { + pub fn new(top: usize, bottom: usize, bpm: usize) -> Self { + Self { + sig: TimeSignature { top, bottom }, + bpm, + running: true, + repeat: 0, + tick: bpm_to_ms(bpm), + } + } +} -- 2.45.2