-use crate::{Map, Mask, Point};
+use crate::{Map, Point};
use crossterm::style::{
Attribute, Attributes,
Color::{Black, Green, Grey},
#[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 }
}
pub use point::*;
pub use selector::*;
+use super::*;
+
use crossterm::{
cursor::{Hide, MoveTo},
execute, queue,
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!(
}
}
}
+
+ 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()?;
pub fn loop_map<T>(
map: &mut (impl Map<T> + Clone),
- offset: Point,
+ area: &Area,
time: Duration,
steps: usize,
) -> Result<()> {
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);
pub struct Area {
pub origin: Point,
pub max: Point,
- pub colors: Option<Colors>,
}
impl From<(Point, Point)> for Area {
Self {
origin: value.0,
max: value.1,
- colors: None,
}
}
}
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()?),
))
}
+ 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,
)?;
}
- for x in x_zero..=x_max {
+ for x in x_zero + 1..x_max {
queue!(
stdout(),
Hide,
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>;
}
#[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()
}
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) {
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;
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()
+ }
+ }
}
pub use scale::*;
pub use transport::*;
-pub type NoteMask = [bool; 12];
+pub type NoteMask = [Option<Note>; 12];
pub struct TimeSignature {
pub top: usize,
}
}
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Note {
A(Acc),
B(Acc),
G(Acc),
}
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Acc {
- Shp,
- Nat,
Flt,
+ Nat,
+ Shp,
}
use super::*;
+use super::{
+ Acc::{Nat, Shp},
+ Note::{A, B, C, D, E, F, G},
+};
+
pub enum Scale {
Ionian,
Dorian,
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)),
}
}
}
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 }
}
}
--- /dev/null
+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),
+ }
+ }
+}