]> git.huck.website - cellseq.git/commitdiff
added: mask map rendering
authorHuck Boles <huck@huck.website>
Fri, 2 Jun 2023 02:04:29 +0000 (21:04 -0500)
committerHuck Boles <huck@huck.website>
Fri, 2 Jun 2023 02:04:29 +0000 (21:04 -0500)
18 files changed:
Cargo.lock
src/cells.rs
src/graphics.rs
src/graphics/actions.rs
src/graphics/area.rs
src/graphics/keys.rs
src/graphics/layout.rs
src/graphics/map.rs
src/graphics/point.rs
src/graphics/selector.rs
src/keys.rs [deleted file]
src/lib.rs
src/main.rs
src/music.rs
src/music/scale.rs
src/state.rs
src/state/transport.rs [new file with mode: 0644]
src/transport.rs [new file with mode: 0644]

index 53878fe3ffe2603d3d7535961493bc265acd61b4..4d18abd8ddabdd14d98f8abbddc13da3941364ac 100644 (file)
@@ -100,12 +100,9 @@ dependencies = [
 
 [[package]]
 name = "log"
-version = "0.4.17"
+version = "0.4.18"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
-dependencies = [
- "cfg-if",
-]
+checksum = "518ef76f2f87365916b142844c16d8fefd85039bc5699050210a7778ee1cd1de"
 
 [[package]]
 name = "matrixmultiply"
@@ -119,14 +116,14 @@ dependencies = [
 
 [[package]]
 name = "mio"
-version = "0.8.6"
+version = "0.8.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9"
+checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2"
 dependencies = [
  "libc",
  "log",
  "wasi",
- "windows-sys",
+ "windows-sys 0.48.0",
 ]
 
 [[package]]
@@ -172,9 +169,9 @@ dependencies = [
 
 [[package]]
 name = "once_cell"
-version = "1.17.1"
+version = "1.17.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
+checksum = "9670a07f94779e00908f3e686eab508878ebb390ba6e604d3a284c00e8d0487b"
 
 [[package]]
 name = "parking_lot"
@@ -196,7 +193,7 @@ dependencies = [
  "libc",
  "redox_syscall",
  "smallvec",
- "windows-sys",
+ "windows-sys 0.45.0",
 ]
 
 [[package]]
@@ -326,7 +323,16 @@ version = "0.45.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
 dependencies = [
- "windows-targets",
+ "windows-targets 0.42.2",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
+dependencies = [
+ "windows-targets 0.48.0",
 ]
 
 [[package]]
@@ -335,13 +341,28 @@ version = "0.42.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
 dependencies = [
- "windows_aarch64_gnullvm",
- "windows_aarch64_msvc",
- "windows_i686_gnu",
- "windows_i686_msvc",
- "windows_x86_64_gnu",
- "windows_x86_64_gnullvm",
- "windows_x86_64_msvc",
+ "windows_aarch64_gnullvm 0.42.2",
+ "windows_aarch64_msvc 0.42.2",
+ "windows_i686_gnu 0.42.2",
+ "windows_i686_msvc 0.42.2",
+ "windows_x86_64_gnu 0.42.2",
+ "windows_x86_64_gnullvm 0.42.2",
+ "windows_x86_64_msvc 0.42.2",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5"
+dependencies = [
+ "windows_aarch64_gnullvm 0.48.0",
+ "windows_aarch64_msvc 0.48.0",
+ "windows_i686_gnu 0.48.0",
+ "windows_i686_msvc 0.48.0",
+ "windows_x86_64_gnu 0.48.0",
+ "windows_x86_64_gnullvm 0.48.0",
+ "windows_x86_64_msvc 0.48.0",
 ]
 
 [[package]]
@@ -350,38 +371,80 @@ version = "0.42.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
 
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
+
 [[package]]
 name = "windows_aarch64_msvc"
 version = "0.42.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
 
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
+
 [[package]]
 name = "windows_i686_gnu"
 version = "0.42.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
 
+[[package]]
+name = "windows_i686_gnu"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
+
 [[package]]
 name = "windows_i686_msvc"
 version = "0.42.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
 
+[[package]]
+name = "windows_i686_msvc"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
+
 [[package]]
 name = "windows_x86_64_gnu"
 version = "0.42.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
 
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
+
 [[package]]
 name = "windows_x86_64_gnullvm"
 version = "0.42.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
 
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
+
 [[package]]
 name = "windows_x86_64_msvc"
 version = "0.42.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
index 57f14f0cea44390989c71f485981e8d8f64935c6..36d34732b2c190b1cf59beb9616a1c2e5c915236 100644 (file)
@@ -8,6 +8,8 @@ use ndarray::Array2;
 use rand::{thread_rng, Rng};
 use std::{cell::Cell, ops::Deref};
 
+use super::*;
+
 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
 pub enum State {
     Dead,
@@ -20,8 +22,8 @@ pub struct World {
 }
 
 impl World {
-    pub fn new(x: usize, y: usize) -> Self {
-        let map = Array2::from_elem((x, y), Cell::from(State::Dead));
+    pub fn new(area: Area) -> Self {
+        let map = Array2::from_elem((area.width(), area.height()), Cell::from(State::Dead));
         Self { map }
     }
 
@@ -72,7 +74,7 @@ impl Map<Cell<State>> for World {
                     }
                 }
                 State::Alive => {
-                    if count < 3 || count > 4 {
+                    if !(3..=4).contains(&count) {
                         cell.set(State::Dead);
                     }
                 }
@@ -92,12 +94,8 @@ impl Map<Cell<State>> for World {
         ('●', '◌')
     }
 
-    fn on_colors(&self) -> Colors {
-        Colors::new(Green, Black)
-    }
-
-    fn off_colors(&self) -> Colors {
-        Colors::new(Grey, Black)
+    fn colors(&self) -> (Colors, Colors) {
+        (Colors::new(Green, Black), Colors::new(Grey, Black))
     }
 
     fn styles(&self) -> (Attributes, Attributes) {
index a886a0280c3d632a79e8fbb3bd8ef3d98b497f17..b35f082b7bc87d0c3e5da7e1869b2a1481ac8248 100644 (file)
@@ -17,7 +17,8 @@ pub use selector::*;
 use super::*;
 
 use crossterm::{
-    cursor::{Hide, MoveTo},
+    cursor::MoveTo,
+    event::{poll, read, Event},
     execute, queue,
     style::{Print, SetAttributes, SetColors},
     terminal,
@@ -25,6 +26,7 @@ use crossterm::{
 use eyre::Result;
 use std::{
     io::{stdout, Write},
+    sync::{Arc, Mutex},
     thread,
     time::Duration,
 };
@@ -34,34 +36,64 @@ pub fn render_loop(state: &mut GlobalState) -> Result<()> {
 
     state.layout.draw_outlines()?;
 
+    let world = Arc::new(Mutex::new(state.world.clone()));
+    let state_arc = Arc::new(Mutex::new(state));
+
     loop {
-        let tick = state.transport.tick;
+        let arc = Arc::clone(&state_arc);
+        let tick = arc.lock().unwrap().tick();
         let timer = std::thread::spawn(move || thread::sleep(tick));
 
-        if !state.transport.running {
-            timer.join().unwrap();
-            continue;
+        let event = std::thread::spawn(move || {
+            if poll(tick).unwrap() {
+                match read().unwrap() {
+                    Event::Key(key) => key_event(key),
+                    _ => Action::None,
+                }
+            } else {
+                Action::None
+            }
+        });
+
+        let mut maps = std::thread::spawn(|| {});
+        if arc.lock().unwrap().transport.running {
+            let map = world.clone();
+            let mut mask = arc.lock().unwrap().mask.clone();
+            let cell_area = arc.lock().unwrap().layout.cells;
+            let mask_area = arc.lock().unwrap().layout.mask;
+
+            maps = std::thread::spawn(move || {
+                let mut map = map.lock().unwrap();
+                map.update();
+                let tmp = map.clone();
+                draw_map(&tmp, &cell_area).unwrap();
+                mask[0].update();
+                let tmp = mask[0].clone();
+                draw_map(&tmp, &mask_area).unwrap();
+            });
         }
 
-        state.world.update();
+        let area = arc.lock().unwrap().layout.transport;
 
-        draw_map(&mut state.world, &state.layout.cells)?;
-        // draw_map(&mut state.mask[0], &state.layout.mask)?;
+        arc.lock().unwrap().transport.render(area)?;
 
-        timer.join().unwrap();
+        action(event.join().unwrap(), arc.clone())?;
+        maps.join().unwrap();
 
+        arc.lock().unwrap().cursor.render()?;
+
+        timer.join().unwrap();
         stdout().flush()?;
     }
 }
 
-pub fn draw_map<T>(map: &mut impl Map<T>, area: &Area) -> Result<()> {
+pub fn draw_map<T>(map: &impl Map<T>, 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());
 
     let (char_on, char_off) = map.characters();
-    let on_colors = map.on_colors();
-    let off_colors = map.off_colors();
+    let (on_colors, off_colors) = map.colors();
     let (style_on, style_off) = map.styles();
 
     for x in 0..=(map.x_size()) {
@@ -69,14 +101,13 @@ pub fn draw_map<T>(map: &mut impl Map<T>, area: &Area) -> Result<()> {
             let point = Point::new(x, y);
             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 {
+            if x_off <= x_zero || x_off >= x_max || y_off <= y_zero || y_off >= y_max - 1 {
                 continue;
             }
 
             if map.try_point(point) {
                 queue!(
                     stdout(),
-                    Hide,
                     MoveTo(x_off, y_off),
                     SetAttributes(style_on),
                     SetColors(on_colors),
@@ -85,7 +116,6 @@ pub fn draw_map<T>(map: &mut impl Map<T>, area: &Area) -> Result<()> {
             } else {
                 queue!(
                     stdout(),
-                    Hide,
                     MoveTo(x_off, y_off),
                     SetAttributes(style_off),
                     SetColors(off_colors),
@@ -100,22 +130,8 @@ pub fn draw_map<T>(map: &mut impl Map<T>, area: &Area) -> Result<()> {
     Ok(())
 }
 
-pub fn run_map<T>(map: &mut impl Map<T>, area: &Area, time: Duration) -> Result<()> {
-    loop {
-        map.update();
-
-        execute!(stdout(), terminal::Clear(terminal::ClearType::All))?;
-
-        draw_map(map, area)?;
-
-        stdout().flush()?;
-
-        thread::sleep(time);
-    }
-}
-
 pub fn loop_map<T>(
-    map: &mut (impl Map<T> + Clone),
+    map: &(impl Map<T> + Clone),
     area: &Area,
     time: Duration,
     steps: usize,
@@ -124,7 +140,7 @@ pub fn loop_map<T>(
         let mut tmp = map.clone();
         for _ in 0..steps {
             execute!(stdout(), terminal::Clear(terminal::ClearType::All))?;
-            draw_map(&mut tmp, area)?;
+            draw_map(&tmp, area)?;
             stdout().flush()?;
             tmp.update();
             thread::sleep(time);
index 8db825e8751e8627cb49d7bda02b2f6f13e75ca1..3c67dbbf87b71b6e92f74c6b101fa5ca075a9cc1 100644 (file)
@@ -1,14 +1,14 @@
 use super::*;
-use crossterm::event::{poll, read, Event};
 
 #[derive(Debug)]
 pub enum Action {
     None,
     Move(Direction),
-    Transport(Clock),
+    Clock(Clock),
     Channel(usize),
     Select,
     SelectArea,
+    SwitchPanel,
     Reload,
     Randomize,
     Exit,
@@ -33,24 +33,60 @@ pub enum Direction {
     Right,
 }
 
-pub fn action_loop(speed: usize) -> Result<()> {
-    loop {
-        if poll(Duration::from_millis(speed.try_into()?))? {
-            if let Event::Key(key) = read()? {
-                match key_event(key) {
-                    Action::None => continue,
-                    Action::Exit => crate::exit()?,
-                    Action::Move(_direction) => todo!(),
-                    Action::Channel(_channel) => todo!(),
-                    Action::Transport(_clock) => todo!(),
-                    Action::Edit => todo!(),
-                    Action::Select => todo!(),
-                    Action::SelectArea => todo!(),
-                    Action::Reload => todo!(),
-                    Action::Randomize => todo!(),
-                    Action::Help => todo!(),
-                }
+type AMmGS<'a> = Arc<Mutex<&'a mut GlobalState>>;
+
+pub fn action(action: Action, state: AMmGS) -> Result<()> {
+    match action {
+        Action::None => Ok(()),
+        Action::Exit => exit(),
+        Action::Move(direction) => move_cursor(direction, state),
+        Action::Clock(clock) => transport_action(clock, state),
+        Action::Channel(_) => Ok(()),
+        Action::Select => Ok(()),
+        Action::SelectArea => Ok(()),
+        Action::Reload => Ok(()),
+        Action::Randomize => Ok(()),
+        Action::Help => Ok(()),
+        Action::Edit => Ok(()),
+        Action::SwitchPanel => Ok(()),
+    }
+}
+
+fn transport_action(clock: Clock, state: AMmGS) -> Result<()> {
+    let mut state = state.lock().unwrap();
+    match clock {
+        Clock::Stop => state.transport.running = false,
+        Clock::Start => state.transport.running = true,
+        Clock::Pause => state.transport.running = !state.transport.running,
+        Clock::Faster(n) => state.transport.bpm += n,
+        Clock::Slower(n) => state.transport.bpm -= n,
+    };
+    Ok(())
+}
+
+fn move_cursor(direction: Direction, state: AMmGS) -> Result<()> {
+    let mut state = state.lock().unwrap();
+    match direction {
+        Direction::Up => {
+            if state.cursor.position.y > 1 {
+                state.cursor.position.y -= 1
+            }
+        }
+        Direction::Down => {
+            if state.cursor.position.y < state.cursor.area.height() {
+                state.cursor.position.y += 1
+            }
+        }
+        Direction::Left => {
+            if state.cursor.position.x > 1 {
+                state.cursor.position.x -= 1
+            }
+        }
+        Direction::Right => {
+            if state.cursor.position.x < state.cursor.area.width() {
+                state.cursor.position.x += 1
             }
         }
     }
+    Ok(())
 }
index d36e50b0e9b710d7b74a0f9784ba871886a5a6f2..b9b12f02ea8ed7de97fd910acf13d3748ba8ac32 100644 (file)
@@ -1,5 +1,5 @@
 use crossterm::{
-    cursor::{Hide, MoveTo},
+    cursor::MoveTo,
     queue,
     style::{
         Color::{Black, White},
@@ -10,7 +10,7 @@ use eyre::Result;
 
 use super::*;
 
-#[derive(Clone, Debug, PartialEq, Eq)]
+#[derive(Clone, Debug, PartialEq, Eq, Copy)]
 pub struct Area {
     pub origin: Point,
     pub max: Point,
@@ -52,10 +52,9 @@ impl Area {
         let colors = Colors::new(White, Black);
         let ((x_zero, y_zero), (x_max, y_max)) = self.to_u16()?;
 
-        for y in y_zero + 1..y_max {
+        for y in y_zero + 1..y_max - 1 {
             queue!(
                 stdout(),
-                Hide,
                 MoveTo(x_zero, y),
                 SetColors(colors),
                 Print("┃"),
@@ -68,11 +67,10 @@ impl Area {
         for x in x_zero + 1..x_max {
             queue!(
                 stdout(),
-                Hide,
                 MoveTo(x, y_zero),
                 SetColors(colors),
                 Print("━"),
-                MoveTo(x, y_max),
+                MoveTo(x, y_max - 1),
                 SetColors(colors),
                 Print("━")
             )?;
@@ -81,8 +79,8 @@ impl Area {
         for ((x, y), c) in [
             (x_zero, y_zero),
             (x_max, y_zero),
-            (x_zero, y_max),
-            (x_max, y_max),
+            (x_zero, y_max - 1),
+            (x_max, y_max - 1),
         ]
         .iter()
         .zip(['┏', '┓', '┗', '┛'].iter())
index 36b13e991bdc39965e8ce176f4c74f802985c417..ee3f4c20cecbdeaae940caa75fba4a056fe07476 100644 (file)
@@ -1,12 +1,12 @@
 use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
 
 use super::*;
+use crate::Clock;
 
 pub fn key_event(event: KeyEvent) -> Action {
     match event.modifiers {
         KeyModifiers::NONE => match_normal_key(event.code),
         KeyModifiers::CONTROL => match_ctl_key(event.code),
-        KeyModifiers::ALT => match_alt_key(event.code),
         KeyModifiers::SHIFT => match_shift_key(event.code),
         _ => Action::None,
     }
@@ -14,8 +14,9 @@ pub fn key_event(event: KeyEvent) -> Action {
 
 pub fn match_normal_key(key: KeyCode) -> Action {
     match key {
-        KeyCode::Enter => Action::Transport(Clock::Pause),
+        KeyCode::Enter => Action::Clock(Clock::Pause),
         KeyCode::Esc => Action::Exit,
+        KeyCode::Tab => Action::SwitchPanel,
         KeyCode::Up => Action::Move(Direction::Up),
         KeyCode::Down => Action::Move(Direction::Down),
         KeyCode::Right => Action::Move(Direction::Right),
@@ -23,26 +24,16 @@ pub fn match_normal_key(key: KeyCode) -> Action {
         KeyCode::Char(c) => match c {
             '?' => Action::Help,
             ' ' => Action::Select,
+            '[' => Action::Clock(Clock::Slower(1)),
+            ']' => Action::Clock(Clock::Faster(1)),
+            '{' => Action::Clock(Clock::Slower(5)),
+            '}' => Action::Clock(Clock::Faster(5)),
             'q' => Action::Exit,
+            'r' => Action::Reload,
             'j' => Action::Move(Direction::Down),
             'k' => Action::Move(Direction::Up),
             'h' => Action::Move(Direction::Left),
             'l' => Action::Move(Direction::Right),
-            'r' => Action::Reload,
-            '[' => Action::Transport(Clock::Slower(1)),
-            ']' => Action::Transport(Clock::Faster(1)),
-            '{' => Action::Transport(Clock::Slower(5)),
-            '}' => Action::Transport(Clock::Faster(5)),
-            '0' => Action::Channel(0),
-            '1' => Action::Channel(1),
-            '2' => Action::Channel(2),
-            '3' => Action::Channel(3),
-            '4' => Action::Channel(4),
-            '5' => Action::Channel(5),
-            '6' => Action::Channel(6),
-            '7' => Action::Channel(7),
-            '8' => Action::Channel(8),
-            '9' => Action::Channel(9),
             _ => Action::None,
         },
         _ => Action::None,
@@ -51,27 +42,17 @@ pub fn match_normal_key(key: KeyCode) -> Action {
 
 pub fn match_ctl_key(key: KeyCode) -> Action {
     match key {
-        KeyCode::Char(c) => match c {
-            'c' => Action::Exit,
-            _ => Action::None,
-        },
+        KeyCode::Char('c') => Action::Exit,
         _ => Action::None,
     }
 }
 
 pub fn match_shift_key(key: KeyCode) -> Action {
     match key {
+        KeyCode::Enter => Action::Clock(Clock::Stop),
         KeyCode::Char(c) => match c {
-            'R' => Action::Randomize,
-            _ => Action::None,
-        },
-        _ => Action::None,
-    }
-}
-
-pub fn match_alt_key(key: KeyCode) -> Action {
-    match key {
-        KeyCode::Char(c) => match c {
+            ' ' => Action::SelectArea,
+            'r' => Action::Randomize,
             _ => Action::None,
         },
         _ => Action::None,
index 014c4ba7d4e2efee2e0806caee255014de0806bc..2f5fc7d00f686cc9970c6451ae359e698b5778e8 100644 (file)
@@ -20,10 +20,10 @@ impl Layout {
         let row: usize = row.into();
 
         let screen = Area::new(0, 0, col, row);
-        let cells = Area::new(1, 1, col / 2 - 5, row / 2 - 2);
-        let mask = Area::new(col / 2 + 5, 1, col - 2, row / 2 - 2);
-        let channels = Area::new(1, row / 2 + 2, col - 2, row - 2);
-        let transport = Area::new(col / 2 - 4, 4, col / 2 + 4, 8);
+        let cells = Area::new(1, 0, col / 2 - 6, row / 2 - 1);
+        let mask = Area::new(col / 2 + 6, 0, col - 2, row / 2 - 1);
+        let channels = Area::new(1, row / 2 + 1, col - 2, row - 1);
+        let transport = Area::new(col / 2 - 5, 4, col / 2 + 5, 9);
 
         Ok(Self {
             screen,
index 79d27cfbabaa93d1b521a6ff02a8b3370e83db1d..c73646be9bf9198347620919d0f7c7d0825e67e8 100644 (file)
@@ -1,11 +1,12 @@
 use crate::Point;
 use crossterm::style::{
     Attribute, Attributes,
-    Color::{Black, Grey, White},
+    Color::{Black, White},
     Colors,
 };
 use ndarray::Array2;
-use std::ops::Deref;
+use rand::{thread_rng, Rng};
+use std::ops::{Deref, DerefMut};
 
 use super::*;
 
@@ -15,32 +16,51 @@ pub trait Map<T> {
     fn x_size(&self) -> usize;
     fn y_size(&self) -> usize;
     fn characters(&self) -> (char, char);
-    fn on_colors(&self) -> Colors;
-    fn off_colors(&self) -> Colors;
+    fn colors(&self) -> (Colors, Colors);
     fn styles(&self) -> (Attributes, Attributes);
     fn update(&mut self);
 }
 
 #[derive(Debug, Clone)]
 pub struct Mask {
-    pub mask: Array2<Option<Note>>,
+    pub mask: Array2<Note>,
+    pub colors: (Colors, Colors),
 }
 
 impl Mask {
-    pub fn new(x: usize, y: usize) -> Self {
-        let mask = Array2::from_elem((y, x), None);
-        Self { mask }
+    pub fn new(area: Area) -> Self {
+        let mask = Array2::from_elem((area.width(), area.height()), Note::Off);
+        Self {
+            mask,
+            colors: (Colors::new(White, Black), Colors::new(Black, Black)),
+        }
+    }
+
+    pub fn randomize(&mut self, val: f64, scale: Scale) {
+        let mut rng = thread_rng();
+        for f in self.iter_mut() {
+            if rng.gen::<f64>() > val {
+                let notes = scale.to_notes();
+                *f = notes[rng.gen::<usize>() % notes.len()];
+            }
+        }
     }
 }
 
 impl Deref for Mask {
-    type Target = Array2<Option<Note>>;
+    type Target = Array2<Note>;
     fn deref(&self) -> &Self::Target {
         &self.mask
     }
 }
 
-impl Map<Option<Note>> for Mask {
+impl DerefMut for Mask {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.mask
+    }
+}
+
+impl Map<Note> for Mask {
     fn x_size(&self) -> usize {
         self.ncols()
     }
@@ -50,10 +70,14 @@ impl Map<Option<Note>> for Mask {
     }
 
     fn try_point(&self, point: Point) -> bool {
-        self.get((point.y, point.x)).is_some()
+        if let Some(note) = self.get((point.y, point.x)) {
+            *note != Note::Off
+        } else {
+            false
+        }
     }
 
-    fn get_point(&self, point: Point) -> Option<Option<Note>> {
+    fn get_point(&self, point: Point) -> Option<Note> {
         self.get((point.y, point.x)).copied()
     }
 
@@ -61,12 +85,8 @@ impl Map<Option<Note>> for Mask {
         ('■', '□')
     }
 
-    fn on_colors(&self) -> Colors {
-        Colors::new(White, Black)
-    }
-
-    fn off_colors(&self) -> Colors {
-        Colors::new(Grey, Black)
+    fn colors(&self) -> (Colors, Colors) {
+        self.colors
     }
 
     fn styles(&self) -> (Attributes, Attributes) {
index 0acc2476e396d9dcc71e809011104956f3935bfd..dcb78fd02d5d2bd3c4ca310762b00642a3f25625 100644 (file)
@@ -11,7 +11,7 @@ pub struct Pixel {
     pub style: Attribute,
 }
 
-#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
 pub struct Point {
     pub x: usize,
     pub y: usize,
index 7edf45b50961df6bcb0c646b661263c7aba5e908..8e7785fc903ae8958d02607d309d3cfa1601c995 100644 (file)
@@ -1,7 +1,7 @@
 use crate::Point;
 use crossterm::{
     cursor, queue,
-    style::{self, Attribute, Color},
+    style::{self, Attribute, Attributes, Color, Colors},
 };
 use eyre::Result;
 use std::{
@@ -9,12 +9,15 @@ use std::{
     ops::{Deref, DerefMut},
 };
 
+use super::*;
+
+#[derive(Debug)]
 pub struct Cursor {
     pub position: Point,
-    pub fg: Color,
-    pub bg: Color,
-    pub style: Attribute,
+    pub color: Colors,
+    pub style: Attributes,
     pub char: char,
+    pub area: Area,
 }
 
 impl Deref for Cursor {
@@ -30,25 +33,31 @@ impl DerefMut for Cursor {
     }
 }
 
+impl Default for Cursor {
+    fn default() -> Self {
+        Self::new(Area::new(0, 0, 0, 0))
+    }
+}
+
 impl Cursor {
-    pub fn new() -> Self {
+    pub fn new(area: Area) -> Self {
         Self {
-            position: Point::new(0, 0),
-            fg: Color::White,
-            bg: Color::Black,
-            char: '*',
-            style: Attribute::Reset,
+            position: Point::new(1, 1),
+            color: Colors::new(Color::White, Color::Black),
+            char: '█',
+            style: Attributes::from(Attribute::Bold),
+            area,
         }
     }
 
     pub fn render(&self) -> Result<()> {
+        let offset = self.area.origin + self.position;
         queue!(
             stdout(),
             cursor::Hide,
-            cursor::MoveTo(self.x.try_into().unwrap(), self.y.try_into().unwrap()),
-            style::SetAttribute(self.style),
-            style::SetForegroundColor(self.fg),
-            style::SetBackgroundColor(self.bg),
+            cursor::MoveTo(offset.x.try_into().unwrap(), offset.y.try_into().unwrap()),
+            style::SetAttributes(self.style),
+            style::SetColors(self.color),
             style::Print(self.char.to_string())
         )?;
         Ok(())
diff --git a/src/keys.rs b/src/keys.rs
deleted file mode 100644 (file)
index f110cd8..0000000
+++ /dev/null
@@ -1,100 +0,0 @@
-use crossterm::{
-    event::{poll, read, Event, KeyCode, KeyEvent, KeyModifiers},
-    execute, queue, terminal,
-};
-
-use super::*;
-
-pub enum Action {
-    None,
-    Move(Direction),
-    Resize(Direction),
-    Transport(Transport),
-    Select,
-    UnSelect,
-    Exit,
-}
-
-pub enum Transport {
-    Stop,
-    Start,
-    Pause,
-    Faster(usize),
-    Slower(usize),
-}
-
-pub enum Direction {
-    Up,
-    Down,
-    Left,
-    Right,
-}
-
-pub fn key_event(event: KeyEvent) -> Action {
-    match event.modifiers {
-        KeyModifiers::NONE => match_normal_key(event.code),
-        KeyModifiers::CONTROL => match_ctl_key(event.code),
-        KeyModifiers::ALT => match_alt_key(event.code),
-        KeyModifiers::SHIFT => match_shift_key(event.code),
-        _ => Action::None,
-    }
-}
-
-pub fn match_normal_key(key: KeyCode) -> Action {
-    match key {
-        KeyCode::Enter => Action::Transport(Transport::Pause),
-        KeyCode::Esc => Action::Exit,
-        KeyCode::Up => Action::Move(Direction::Up),
-        KeyCode::Down => Action::Move(Direction::Down),
-        KeyCode::Right => Action::Move(Direction::Right),
-        KeyCode::Left => Action::Move(Direction::Left),
-        KeyCode::Char(c) => match c {
-            ' ' => Action::Select,
-            'q' => Action::Exit,
-            'j' => Action::Move(Direction::Down),
-            'k' => Action::Move(Direction::Up),
-            'h' => Action::Move(Direction::Left),
-            'l' => Action::Move(Direction::Right),
-            _ => Action::None,
-        },
-        _ => Action::None,
-    }
-}
-
-pub fn match_ctl_key(key: KeyCode) -> Action {
-    match key {
-        KeyCode::Char(c) => match c {
-            'c' => Action::Exit,
-            _ => Action::None,
-        },
-        _ => Action::None,
-    }
-}
-
-pub fn match_shift_key(key: KeyCode) -> Action {
-    match key {
-        KeyCode::Enter => Action::Transport(Transport::Stop),
-        KeyCode::Up => Action::Resize(Direction::Up),
-        KeyCode::Down => Action::Resize(Direction::Down),
-        KeyCode::Right => Action::Resize(Direction::Right),
-        KeyCode::Left => Action::Resize(Direction::Left),
-        KeyCode::Char(c) => match c {
-            ' ' => Action::UnSelect,
-            'j' => Action::Resize(Direction::Down),
-            'k' => Action::Resize(Direction::Up),
-            'h' => Action::Resize(Direction::Left),
-            'l' => Action::Resize(Direction::Right),
-            _ => Action::None,
-        },
-        _ => Action::None,
-    }
-}
-
-pub fn match_alt_key(key: KeyCode) -> Action {
-    match key {
-        KeyCode::Char(c) => match c {
-            _ => Action::None,
-        },
-        _ => Action::None,
-    }
-}
index 3638b07676ae0fded4c277983b7f2c8c4abbb796..97a4a32f02fba63b52443b508cd39d7f3ef44982 100644 (file)
@@ -13,6 +13,7 @@ use std::time::Duration;
 
 pub fn exit() -> Result<()> {
     crossterm::terminal::disable_raw_mode()?;
+    crossterm::execute!(std::io::stdout(), crossterm::cursor::Show)?;
     std::process::exit(0);
 }
 
index 4a5d0d42f891b9fb93dfe6d0142a8b23f77080eb..4e9ff64e76dbfd1a40c65c539c19f32a2eeecda9 100644 (file)
@@ -1,13 +1,15 @@
 use cellseq::*;
-use crossterm::terminal;
+use crossterm::{cursor, execute, terminal};
 use eyre::Result;
 
 fn main() -> Result<()> {
     terminal::enable_raw_mode()?;
+    execute!(std::io::stdout(), cursor::Hide)?;
 
     let mut state = GlobalState::build()?;
 
     state.world.randomize(0.75);
+    state.mask[0].randomize(0.75, Scale::Aeolian);
 
     match render_loop(&mut state) {
         Ok(_) => exit(),
index 943d5dd6fc0f9c976ce14b8c3c4656c962d3520e..a5e91c6ed39e2e6b87705357846eab1168d81f90 100644 (file)
@@ -4,7 +4,7 @@ mod transport;
 pub use scale::*;
 pub use transport::*;
 
-pub type NoteMask = [Option<Note>; 12];
+use std::fmt::Display;
 
 pub struct TimeSignature {
     pub top: usize,
@@ -22,6 +22,7 @@ impl From<(usize, usize)> for TimeSignature {
 
 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
 pub enum Note {
+    Off,
     A(Acc),
     B(Acc),
     C(Acc),
@@ -31,9 +32,38 @@ pub enum Note {
     G(Acc),
 }
 
+impl Display for Note {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        let str = match self {
+            Note::Off => "".to_string(),
+            Note::A(a) => format!("A{a}"),
+            Note::B(a) => format!("B{a}"),
+            Note::C(a) => format!("C{a}"),
+            Note::D(a) => format!("D{a}"),
+            Note::E(a) => format!("E{a}"),
+            Note::F(a) => format!("F{a}"),
+            Note::G(a) => format!("G{a}"),
+        };
+
+        write!(f, "{str}")
+    }
+}
+
 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
 pub enum Acc {
     Flt,
     Nat,
     Shp,
 }
+
+impl Display for Acc {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        let str = match self {
+            Acc::Flt => "b",
+            Acc::Nat => "",
+            Acc::Shp => "#",
+        };
+
+        write!(f, "{str}")
+    }
+}
index a57d8b364be51f383c098eff56309ca757eba6a2..33f8773160779c3e7ccf9f16e8e182dc7ad974a4 100644 (file)
@@ -23,7 +23,7 @@ pub enum Scale {
 }
 
 impl Scale {
-    pub fn to_notes(&self) -> NoteMask {
+    pub fn to_notes(&self) -> Vec<Note> {
         let octave = [
             C(Nat),
             C(Shp),
@@ -43,18 +43,20 @@ impl Scale {
         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
+        fn mask_scale(notes: &[Note; 12], mask: [u8; 12]) -> Vec<Note> {
+            notes
+                .iter()
+                .zip(mask.iter())
+                .filter_map(|(note, mask)| if *mask == 1 { Some(*note) } else { None })
+                .collect()
         }
 
         macro_rules! rotate {
-            ($s:expr,$n:expr) => {{
+            ($s:expr,r,$n:expr) => {{
+                $s.rotate_right($n);
+                $s
+            }};
+            ($s:expr,l,$n:expr) => {{
                 $s.rotate_left($n);
                 $s
             }};
@@ -62,19 +64,19 @@ impl Scale {
 
         match self {
             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::Dorian => rotate!(mask_scale(&octave, diatonic), l, 2),
+            Scale::Phrygian => rotate!(mask_scale(&octave, diatonic), l, 4),
+            Scale::Lydian => rotate!(mask_scale(&octave, diatonic), l, 5),
+            Scale::Mixolydian => rotate!(mask_scale(&octave, diatonic), r, 5),
+            Scale::Aeolian => rotate!(mask_scale(&octave, diatonic), r, 3),
+            Scale::Locrian => rotate!(mask_scale(&octave, diatonic), r, 1),
             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::SusEgypt => rotate!(mask_scale(&octave, pentatonic), l, 2),
+            Scale::BlueMinPen => rotate!(mask_scale(&octave, pentatonic), l, 4),
+            Scale::BlueMajPent => rotate!(mask_scale(&octave, pentatonic), r, 5),
+            Scale::MinPent => rotate!(mask_scale(&octave, pentatonic), r, 3),
             Scale::WholeTone => mask_scale(&octave, whole_tone),
-            Scale::Chromatic => octave.map(|n| Some(n)),
+            Scale::Chromatic => octave.to_vec(),
         }
     }
 }
index 7bbf6bc92ef907b162825277b6cec272856775b7..7b9d5e4e190e36c85f50c5492fc716e9e9d852bb 100644 (file)
@@ -1,3 +1,7 @@
+mod transport;
+
+pub use transport::*;
+
 use super::*;
 
 pub struct GlobalState {
@@ -7,19 +11,26 @@ pub struct GlobalState {
     pub song: Song,
     pub mask: Vec<Mask>,
     pub channels: Vec<MidiChannel>,
+    pub cursor: Cursor,
 }
 
 impl GlobalState {
     pub fn build() -> Result<Self> {
         let layout = Layout::build()?;
 
-        let world = World::new(layout.cells.width(), layout.cells.height() + 1);
+        let world = World::new(layout.cells);
 
-        let channels = Vec::new();
-        let mask = Vec::new();
+        let channels = Vec::with_capacity(10);
+        let mut mask: Vec<Mask> = Vec::with_capacity(10);
+
+        for _ in 0..10 {
+            mask.push(Mask::new(layout.mask));
+        }
 
         let transport = Transport::new(4, 4, 120);
 
+        let cursor = Cursor::new(layout.mask);
+
         let song = Song::new(None, None);
 
         Ok(Self {
@@ -29,31 +40,16 @@ impl GlobalState {
             song,
             mask,
             channels,
+            cursor,
         })
     }
+
+    pub fn tick(&self) -> Duration {
+        bpm_to_ms(self.transport.bpm)
+    }
 }
 
 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),
-        }
-    }
-}
diff --git a/src/state/transport.rs b/src/state/transport.rs
new file mode 100644 (file)
index 0000000..f544fe4
--- /dev/null
@@ -0,0 +1,52 @@
+use super::*;
+
+use crossterm::{
+    cursor::MoveTo,
+    queue,
+    style::{Attribute, Color, Colors, Print, SetAttributes, SetColors},
+};
+use std::io::stdout;
+
+pub struct Transport {
+    pub running: bool,
+    pub bpm: usize,
+    pub sig: TimeSignature,
+    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,
+        }
+    }
+
+    pub fn render(&self, area: Area) -> Result<()> {
+        let ((x_zero, y_zero), (_, _)) = area.to_u16()?;
+
+        let bpm = format!("bpm: {}", self.bpm);
+        let sig = format!("sig: {}/{}", self.sig.top, self.sig.bottom);
+        let rep = format!("rep: {}", self.repeat);
+
+        queue!(
+            stdout(),
+            MoveTo(x_zero + 1, y_zero + 1),
+            SetAttributes(Attribute::Bold.into()),
+            SetColors(Colors::new(Color::White, Color::Black)),
+            Print(bpm),
+            MoveTo(x_zero + 1, y_zero + 2),
+            SetAttributes(Attribute::Bold.into()),
+            SetColors(Colors::new(Color::White, Color::Black)),
+            Print(sig),
+            MoveTo(x_zero + 1, y_zero + 3),
+            SetAttributes(Attribute::Bold.into()),
+            SetColors(Colors::new(Color::White, Color::Black)),
+            Print(rep),
+        )?;
+
+        Ok(())
+    }
+}
diff --git a/src/transport.rs b/src/transport.rs
new file mode 100644 (file)
index 0000000..a9c46cb
--- /dev/null
@@ -0,0 +1,19 @@
+use super::*;
+
+pub struct Transport {
+    pub running: bool,
+    pub bpm: usize,
+    pub sig: TimeSignature,
+    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,
+        }
+    }
+}