Skip to content
Permalink
Browse files
Port to Rust
  • Loading branch information
twe4ked committed Nov 14, 2019
1 parent 8de8a3f commit 106a72462c8b4861305c08e7a7c3124bfaff4d85
Showing 6 changed files with 218 additions and 80 deletions.
@@ -1,2 +1,2 @@
tin-whistle
.crystal
/target
**/*.rs.bk

Some generated files are not rendered by default. Learn more.

@@ -0,0 +1,7 @@
[package]
name = "tin-whistle"
version = "0.1.0"
authors = ["Odin Dutton <odindutton@gmail.com>"]
edition = "2018"

[dependencies]
@@ -4,7 +4,7 @@ Takes ABC notation from STDIN and prints out tin whistle diagrams.

## Example

$ crystal build tin-whistle.cr && ./tin-whistle < examples/scale.abc
$ cargo run < examples/scale.abc
D E F# G | A B C# d | e f# g a | b
| . | . . . . | .
● ● ● ● | ● ● ○ ● | ● ● ● ● | ●
@@ -0,0 +1,202 @@
use std::fmt;
use std::io;
use std::io::prelude::*;

#[derive(Debug)]
enum Note {
D1,
E1,
F1Sharp,
G1,
A1,
B1,
C1Sharp,

D2,
E2,
F2Sharp,
G2,
A2,
B2,
C2Sharp,
}

#[derive(Debug)]
enum Item {
Note(Note),
Bar,
Space,
Gap,
LineBreak,
}

impl From<char> for Item {
fn from(input: char) -> Self {
match input {
'd' => Item::Note(Note::D1),
'e' => Item::Note(Note::E1),
'f' => Item::Note(Note::F1Sharp),
'g' => Item::Note(Note::G1),
'a' => Item::Note(Note::A1),
'b' => Item::Note(Note::B1),
'c' => Item::Note(Note::C1Sharp),

'D' => Item::Note(Note::D2),
'E' => Item::Note(Note::E2),
'F' => Item::Note(Note::F2Sharp),
'G' => Item::Note(Note::G2),
'A' => Item::Note(Note::A2),
'B' => Item::Note(Note::B2),
'C' => Item::Note(Note::C2Sharp),

'\n' => Item::LineBreak,
'|' => Item::Bar,
' ' => Item::Space,
'-' => Item::Gap,

_ => unimplemented!("bad char \"{}\"", input),
}
}
}

impl fmt::Display for Item {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let x = match self {
Item::Note(note) => match note {
Note::D1 => "d ",
Note::E1 => "e ",
Note::F1Sharp => "f# ",
Note::G1 => "g ",
Note::A1 => "a ",
Note::B1 => "b ",
Note::C1Sharp => "c# ",

Note::D2 => "D ",
Note::E2 => "E ",
Note::F2Sharp => "F# ",
Note::G2 => "G ",
Note::A2 => "A ",
Note::B2 => "B ",
Note::C2Sharp => "C# ",
},
Item::Bar => "| ",
Item::LineBreak => "",
Item::Gap => "- ",
Item::Space => " ",
};

write!(f, "{}", x)
}
}

const D1: [bool; 6] = [true, true, true, true, true, true];
const E: [bool; 6] = [true, true, true, true, true, false];
const F: [bool; 6] = [true, true, true, true, false, false];
const G: [bool; 6] = [true, true, true, false, false, false];
const A: [bool; 6] = [true, true, false, false, false, false];
const B: [bool; 6] = [true, false, false, false, false, false];
const C: [bool; 6] = [false, false, false, false, false, false];
const D2: [bool; 6] = [false, true, true, true, true, true];

impl Item {
fn is_high_octave(&self) -> bool {
match self {
Item::Note(note) => match note {
Note::D1
| Note::E1
| Note::F1Sharp
| Note::G1
| Note::A1
| Note::B1
| Note::C1Sharp => true,
_ => false,
},
_ => false,
}
}

fn hole_covered(&self, i: usize) -> bool {
match self {
Item::Note(note) => match note {
Note::D1 => D1[i],
Note::E1 | Note::E2 => E[i],
Note::F1Sharp | Note::F2Sharp => F[i],
Note::G1 | Note::G2 => G[i],
Note::A1 | Note::A2 => A[i],
Note::B1 | Note::B2 => B[i],
Note::C1Sharp | Note::C2Sharp => C[i],
Note::D2 => D2[i],
},
_ => unreachable!(),
}
}
}

fn read_stdin() -> String {
let mut input = Vec::new();
io::stdin().read_to_end(&mut input).unwrap();
String::from_utf8(input).expect("invalid input")
}

fn parse_items(input: String) -> Vec<Item> {
let mut ignore = false;
let items: Vec<Item> = input
.chars()
.filter_map(|c| {
if ignore && c == '\n' {
ignore = false;
None
} else if c == '#' {
// TODO: Ignore "T:" comment lines /^T:.*\n/
ignore = true;
None
} else if ignore {
None
} else if c == '\n' {
None
} else {
Some(Item::from(c))
}
})
.collect();
items
}

fn main() {
let input = read_stdin();
let items = parse_items(input);

for item in &items {
print!("{}", item);
}
println!();

for item in &items {
match item {
Item::Bar => print!("| "),
_ => {
if item.is_high_octave() {
print!(". ");
} else {
print!(" ");
}
}
}
}
println!();

(0..6).for_each(|hole| {
&items.iter().for_each(|item| match item {
Item::Note(_) => {
if item.hole_covered(hole) {
print!("● ");
} else {
print!("○ ");
}
}
Item::LineBreak | Item::Space | Item::Bar => print!("{}", item),
Item::Gap => print!(" "),
});
println!();
});
}

This file was deleted.

0 comments on commit 106a724

Please sign in to comment.