midnote 0.11.1

A terminal application for viewing notes in a MIDI track, with audio.
Documentation
use std::{borrow::Cow, error::Error, fmt, fs};

use crossterm::event::KeyCode;
use serde::{Deserialize, Serialize};

use crate::Command;

#[derive(Serialize, Deserialize)]
#[serde(default)]
pub struct Config {
	pub colors: bool,
	pub keys: Keys,
}

impl Default for Config {
	fn default() -> Self {
		Self {
			colors: true,
			keys: Keys::default(),
		}
	}
}

#[derive(Serialize, Deserialize, Copy, Clone)]
#[serde(default)]
pub struct Keys {
	pub next: KeyCode,
	pub prev: KeyCode,
	pub transpose_up: KeyCode,
	pub transpose_down: KeyCode,
	pub speed_up: KeyCode,
	pub speed_down: KeyCode,
	pub reset: KeyCode,
	pub replay: KeyCode,
	pub solo: KeyCode,
	pub silence: KeyCode,
	pub rewind: KeyCode,
	pub info: KeyCode,
	pub note_style: KeyCode,
	pub exit: KeyCode,
	pub help: KeyCode,
}

impl Default for Keys {
	fn default() -> Self {
		Self {
			next: KeyCode::Right,
			prev: KeyCode::Left,
			transpose_up: KeyCode::Up,
			transpose_down: KeyCode::Down,
			speed_up: KeyCode::Char('2'),
			speed_down: KeyCode::Char('1'),
			reset: KeyCode::Char('x'),
			silence: KeyCode::Char(' '),
			exit: KeyCode::Esc,
			solo: KeyCode::Char('s'),
			rewind: KeyCode::Char('p'),
			replay: KeyCode::Char('r'),
			info: KeyCode::Char('i'),
			note_style: KeyCode::Char('n'),
			help: KeyCode::Char('h'),
		}
	}
}

impl Config {
	pub fn read_from(p: &str) -> Result<Self, Box<dyn Error>> {
		let data = fs::read_to_string(p)?;
		let c: Self = serde_json::from_str(&data)?;
		Ok(c)
	}
}

impl fmt::Display for Keys {
	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
		let keys = &[
			("next", self.next),
			("previous", self.prev),
			("transpose up", self.transpose_up),
			("transpose down", self.transpose_down),
			("speed up", self.speed_up),
			("speed down", self.speed_down),
			("reset transposition", self.reset),
			("replay", self.replay),
			("solo", self.solo),
			("rewind", self.rewind),
			("silence", self.silence),
			("info", self.info),
			("toggle note style", self.note_style),
			("help", self.help),
			("exit", self.exit),
		];

		let w = keys.iter().map(|x| x.0.len()).max().unwrap();

		for (i, (name, key)) in keys.iter().enumerate() {
			if i > 0 {
				f.write_str("\n")?;
			}
			write!(
				f,
				"{name:w$} | {key}",
				name = name,
				w = w,
				key = key_string(*key)
			)?;
		}
		Ok(())
	}
}

fn key_string(k: KeyCode) -> Cow<'static, str> {
	type K = KeyCode;
	Cow::Borrowed(match k {
		K::Null => "null",
		K::BackTab => "backtab",
		K::Backspace => "backspace",
		K::Delete => "del",
		K::Up => "up",
		K::Down => "down",
		K::Left => "left",
		K::Right => "right",
		K::End => "end",
		K::Enter => "enter",
		K::Esc => "esc",
		K::Home => "home",
		K::Insert => "insert",
		K::PageDown => "pagedown",
		K::PageUp => "pageup",
		K::Tab => "tab",
		K::Char(c) => return Cow::Owned(format!("'{}'", c)),
		K::F(n) => return Cow::Owned(format!("F{}", n)),
	})
}

impl Keys {
	pub fn command(&self, k: KeyCode) -> Option<Command> {
		Some(if k == self.next {
			Command::Next
		} else if k == self.prev {
			Command::Prev
		} else if k == self.transpose_up {
			Command::Transpose(1)
		} else if k == self.transpose_down {
			Command::Transpose(-1)
		} else if k == self.reset {
			Command::Transpose(0)
		} else if k == self.replay {
			Command::Replay
		} else if k == self.silence {
			Command::Silence
		} else if k == self.solo {
			Command::Solo
		} else if k == self.rewind {
			Command::Reset
		} else if k == self.info {
			Command::Info
		} else if k == self.note_style {
			Command::NoteStyle
		} else if k == self.speed_up {
			Command::Speed(0.05)
		} else if k == self.speed_down {
			Command::Speed(-0.05)
		} else {
			return None;
		})
	}
}