]> git.huck.website - cellseq.git/commitdiff
added: layout outlines
authorHuck Boles <huck@huck.website>
Tue, 30 May 2023 20:33:35 +0000 (15:33 -0500)
committerHuck Boles <huck@huck.website>
Tue, 30 May 2023 20:33:35 +0000 (15:33 -0500)
src/cells.rs
src/graphics.rs
src/graphics/area.rs
src/graphics/map.rs
src/lib.rs
src/main.rs
src/music.rs
src/music/scale.rs
src/music/transport.rs
src/state.rs [new file with mode: 0644]

index 8ca68085055f67d709efc574820e962f91a68cdc..57f14f0cea44390989c71f485981e8d8f64935c6 100644 (file)
@@ -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<Cell<State>>,
+    pub map: Array2<Cell<State>>,
 }
 
 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 }
     }
 
index e7c8e4cbe3de0ab43ba5365ae566cf9dc80e7918..a886a0280c3d632a79e8fbb3bd8ef3d98b497f17 100644 (file)
@@ -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<T>(map: &mut 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());
 
-pub fn draw_frame<T>(map: &mut impl Map<T>, 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<T>(map: &mut impl Map<T>, offset: Point) -> Result<()> {
             }
         }
     }
+
+    area.outline_area()?;
+
     Ok(())
 }
 
-pub fn run_map<T>(map: &mut impl Map<T>, offset: Point, time: Duration) -> Result<()> {
+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_frame(map, offset)?;
+        draw_map(map, area)?;
 
         stdout().flush()?;
 
@@ -80,7 +116,7 @@ pub fn run_map<T>(map: &mut impl Map<T>, offset: Point, time: Duration) -> Resul
 
 pub fn loop_map<T>(
     map: &mut (impl Map<T> + Clone),
-    offset: Point,
+    area: &Area,
     time: Duration,
     steps: usize,
 ) -> Result<()> {
@@ -88,7 +124,7 @@ pub fn loop_map<T>(
         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);
index 9f5b003f0a531923a786b01a878f73bed22809ff..d36e50b0e9b710d7b74a0f9784ba871886a5a6f2 100644 (file)
@@ -14,7 +14,6 @@ use super::*;
 pub struct Area {
     pub origin: Point,
     pub max: Point,
-    pub colors: Option<Colors>,
 }
 
 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,
index 3491ceb089b74678ad1466b1c5a6382885def0f1..79d27cfbabaa93d1b521a6ff02a8b3370e83db1d 100644 (file)
@@ -7,6 +7,8 @@ use crossterm::style::{
 use ndarray::Array2;
 use std::ops::Deref;
 
+use super::*;
+
 pub trait Map<T> {
     fn try_point(&self, point: Point) -> bool;
     fn get_point(&self, point: Point) -> Option<T>;
@@ -20,25 +22,25 @@ pub trait Map<T> {
 }
 
 #[derive(Debug, Clone)]
-pub struct Mask<T: Clone> {
-    pub mask: Array2<T>,
+pub struct Mask {
+    pub mask: Array2<Option<Note>>,
 }
 
-impl<T: Clone> Mask<T> {
-    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<T: Clone> Deref for Mask<T> {
-    type Target = Array2<T>;
+impl Deref for Mask {
+    type Target = Array2<Option<Note>>;
     fn deref(&self) -> &Self::Target {
         &self.mask
     }
 }
 
-impl<T: Clone> Map<T> for Mask<T> {
+impl Map<Option<Note>> for Mask {
     fn x_size(&self) -> usize {
         self.ncols()
     }
@@ -51,8 +53,8 @@ impl<T: Clone> Map<T> for Mask<T> {
         self.get((point.y, point.x)).is_some()
     }
 
-    fn get_point(&self, point: Point) -> Option<T> {
-        self.get((point.y, point.x)).cloned()
+    fn get_point(&self, point: Point) -> Option<Option<Note>> {
+        self.get((point.y, point.x)).copied()
     }
 
     fn characters(&self) -> (char, char) {
index 131c0725f7843faeb3b7c55f6e0da90e6f6004e7..3638b07676ae0fded4c277983b7f2c8c4abbb796 100644 (file)
@@ -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;
index a1c450e67b7bb9c417be10bccccbd91e452061ee..4a5d0d42f891b9fb93dfe6d0142a8b23f77080eb 100644 (file)
@@ -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<bool> = 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()
+        }
+    }
 }
index 472a4b242ab52a05b00cc86d7b2ed1f2003815a9..943d5dd6fc0f9c976ce14b8c3c4656c962d3520e 100644 (file)
@@ -4,7 +4,7 @@ mod transport;
 pub use scale::*;
 pub use transport::*;
 
-pub type NoteMask = [bool; 12];
+pub type NoteMask = [Option<Note>; 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,
 }
index b74b38df005ec051d157060a70d0efef085c63be..a57d8b364be51f383c098eff56309ca757eba6a2 100644 (file)
@@ -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)),
         }
     }
 }
index ad19bdb6a4821857faee182ecb38e6a9176bd23a..59face80ba47a5b1e8a449cd9dab8f34051418ff 100644 (file)
@@ -1,24 +1,12 @@
 use super::*;
 
 pub struct Song {
-    pub bpm: usize,
-    pub time_sig: TimeSignature,
     pub key: Option<Note>,
     pub scale: Option<Scale>,
 }
 
 impl Song {
-    pub fn new(
-        bpm: usize,
-        time_sig: TimeSignature,
-        key: Option<Note>,
-        scale: Option<Scale>,
-    ) -> Self {
-        Self {
-            bpm,
-            time_sig,
-            key,
-            scale,
-        }
+    pub fn new(key: Option<Note>, scale: Option<Scale>) -> Self {
+        Self { key, scale }
     }
 }
diff --git a/src/state.rs b/src/state.rs
new file mode 100644 (file)
index 0000000..7bbf6bc
--- /dev/null
@@ -0,0 +1,59 @@
+use super::*;
+
+pub struct GlobalState {
+    pub world: World,
+    pub layout: Layout,
+    pub transport: Transport,
+    pub song: Song,
+    pub mask: Vec<Mask>,
+    pub channels: Vec<MidiChannel>,
+}
+
+impl GlobalState {
+    pub fn build() -> Result<Self> {
+        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),
+        }
+    }
+}