use std::fmt::{self, Write};
use std::iter;
use combine::Parser;
use parser::{parse_chord, parse_polychord};
pub use combine::ParseError;
pub type ParseResult<'a, T> = Result<T, ParseError<&'a str>>;
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum NoteClass {
A, B, C, D, E, F, G
}
pub const NOTE_CLASS_COUNT: usize = 7;
impl NoteClass {
pub fn from_char(input: char) -> Option<NoteClass> {
use self::NoteClass::*;
match input {
'A' => Some(A),
'B' => Some(B),
'C' => Some(C),
'D' => Some(D),
'E' => Some(E),
'F' => Some(F),
'G' => Some(G),
_ => None
}
}
pub fn from_int(input: usize) -> Option<NoteClass> {
use self::NoteClass::*;
match input {
0 => Some(A),
1 => Some(B),
2 => Some(C),
3 => Some(D),
4 => Some(E),
5 => Some(F),
6 => Some(G),
_ => None
}
}
pub fn to_int(&self) -> usize {
use self::NoteClass::*;
match *self {
A => 0,
B => 1,
C => 2,
D => 3,
E => 4,
F => 5,
G => 6
}
}
pub fn difference(&self, other: &NoteClass) -> usize {
const OFFSETS: [usize; NOTE_CLASS_COUNT] = [
0, 2, 3, 5, 7, 8, 10,
];
let upper = OFFSETS[other.to_int()] + 12;
let lower = OFFSETS[self.to_int()];
(upper - lower) % 12
}
}
impl fmt::Display for NoteClass {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self)
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum PitchClass {
N1, N2, N3, N4, N5, N6, N7, N9, N11, N13
}
pub const PITCH_CLASS_COUNT: usize = 10;
impl PitchClass {
pub fn from_int(input: usize) -> Option<Self> {
use self::PitchClass::*;
match input {
0 => Some(N1),
1 => Some(N2),
2 => Some(N3),
3 => Some(N4),
4 => Some(N5),
5 => Some(N6),
6 => Some(N7),
7 => Some(N9),
8 => Some(N11),
9 => Some(N13),
_ => None,
}
}
pub fn index(&self) -> usize {
use self::PitchClass::*;
match *self {
N1 => 0,
N2 => 1,
N3 => 2,
N4 => 3,
N5 => 4,
N6 => 5,
N7 => 6,
N9 => 7,
N11 => 8,
N13 => 9,
}
}
pub fn to_int(&self) -> usize {
use self::PitchClass::*;
match *self {
N1 => 0,
N2 | N9 => 1,
N3 => 2,
N4 | N11 => 3,
N5 => 4,
N6 | N13 => 5,
N7 => 6,
}
}
pub fn to_relative_difference(&self) -> usize {
use self::PitchClass::*;
match *self {
N1 => 0,
N2 => 2,
N3 => 4,
N4 => 5,
N5 => 7,
N6 => 9,
N7 => 11,
N9 => 14,
N11 => 17,
N13 => 21,
}
}
pub fn extended_intervals(&self) -> &'static [ChordComponent] {
use self::PitchClass::*;
static CLASSES: [ChordComponent; 4] = [
(N7, 0), (N9, 0), (N11, 0), (N13, 0),
];
match *self {
N7 => &CLASSES[..1],
N9 => &CLASSES[..2],
N11 => &CLASSES[..3],
N13 => &CLASSES[..4],
_ => &[]
}
}
}
pub type PitchOffset = i8;
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Note {
pub root: NoteClass,
pub offset: PitchOffset
}
impl Note {
pub fn new(root: NoteClass, offset: PitchOffset) -> Note {
Note { root, offset }
}
pub fn get_relative(&self, (class, offset): ChordComponent) -> Note {
let root_val = (self.root.to_int() + class.to_int()) % NOTE_CLASS_COUNT;
let root_note = NoteClass::from_int(root_val).unwrap();
let rel_offset = class.to_relative_difference() as i8
- self.root.difference(&root_note) as i8;
Note {
root: root_note,
offset: self.offset + offset + rel_offset
}
}
}
impl fmt::Display for Note {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.root)?;
let accidental = if self.offset.is_positive() { '#' } else { 'b' };
for _ in 0..self.offset.abs() {
f.write_char(accidental)?;
}
Ok(())
}
}
pub type ChordComponent = (PitchClass, PitchOffset);
#[derive(Clone, Debug, Default, PartialEq)]
pub struct ChordStructure([Option<PitchOffset>; PITCH_CLASS_COUNT]);
impl ChordStructure {
pub fn new() -> ChordStructure {
let mut classes = [None; PITCH_CLASS_COUNT];
classes[PitchClass::N1.index()] = Some(0);
ChordStructure(classes)
}
pub fn from_component(component: ChordComponent) -> ChordStructure {
let mut classes = [None; PITCH_CLASS_COUNT];
classes[component.0.index()] = Some(component.1);
ChordStructure(classes)
}
pub fn insert(mut self, component: ChordComponent) -> ChordStructure {
self.0[component.0.index()] = Some(component.1);
self
}
pub fn insert_many(mut self, components: &[ChordComponent]) -> ChordStructure {
for &component in components {
self.0[component.0.index()] = Some(component.1);
}
self
}
pub fn merge(mut self, other: &ChordStructure) -> ChordStructure {
for i in 0..PITCH_CLASS_COUNT {
if other.0[i].is_some() {
self.0[i] = other.0[i];
}
}
self
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct Chord {
pub slash_root: Option<Note>,
pub root: Note,
pub structure: ChordStructure
}
impl Chord {
pub fn new(root: Note, structure: ChordStructure) -> Chord {
Chord { slash_root: None, root, structure }
}
pub fn new_slash(slash_root: Note, root: Note, structure: ChordStructure)
-> Chord
{
Chord { slash_root: Some(slash_root), root, structure }
}
pub fn from_shorthand(input: &str) -> ParseResult<Chord> {
parse_chord().parse(input).map(|c| c.0)
}
pub fn iter(&self) -> NoteIterator {
NoteIterator {
chord: self,
state: NoteIteratorState::Slash,
}
}
}
#[derive(Clone, Debug)]
pub struct NoteIterator<'a> {
pub chord: &'a Chord,
state: NoteIteratorState,
}
#[derive(Clone, Copy, Debug)]
enum NoteIteratorState {
Slash,
Structure(usize),
Exhausted,
}
impl<'a> Iterator for NoteIterator<'a> {
type Item = Note;
fn next(&mut self) -> Option<Self::Item> {
use self::NoteIteratorState::*;
'retry: loop {
return match self.state {
Slash => {
self.state = Structure(0);
if let Some(note) = self.chord.slash_root {
Some(note)
} else {
continue 'retry;
}
},
Structure(ii) => {
let mut i = ii;
while i < PITCH_CLASS_COUNT {
if let Some(offset) = self.chord.structure.0[i] {
let pc = PitchClass::from_int(i).unwrap();
self.state = Structure(i + 1);
return Some(self.chord.root.get_relative((pc, offset)));
}
i += 1;
}
self.state = Exhausted;
None
},
Exhausted => {
None
},
};
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct PolyChord {
pub upper: Chord,
pub lower: Chord
}
impl PolyChord {
pub fn new(upper: Chord, lower: Chord) -> PolyChord {
PolyChord { upper, lower }
}
pub fn from_shorthand(input: &str) -> ParseResult<PolyChord> {
parse_polychord().parse(input).map(|c| c.0)
}
pub fn iter(&self) -> iter::Chain<NoteIterator, NoteIterator> {
self.lower.iter().chain(self.upper.iter())
}
}
#[cfg(test)]
mod tests {
use super::*;
use chord::NoteClass::*;
use chord::PitchClass::*;
#[test]
fn offset_calculation() {
assert_eq!(Note::new(A, 0).get_relative((N5, 0)), Note::new(E, 0));
assert_eq!(Note::new(A, 0).get_relative((N5, -1)), Note::new(E, -1));
assert_eq!(Note::new(F, -1).get_relative((N2, -2)), Note::new(G, -3));
assert_eq!(Note::new(D, 0).get_relative((N3, 0)), Note::new(F, 1));
assert_eq!(Note::new(A, 0).get_relative((N3, 0)), Note::new(C, 1));
}
#[test]
fn chord_notes() {
let chord = Chord::new_slash(
Note::new(C, 1),
Note::new(A, 0),
ChordStructure::new()
.insert_many(&[(N3, 0), (N5, 0)])
);
let notes = vec![
Note::new(C, 1),
Note::new(A, 0),
Note::new(C, 1),
Note::new(E, 0),
];
assert_eq!(chord.iter().collect::<Vec<_>>(), notes);
}
#[test]
fn polychord_notes() {
let chord = PolyChord::new(
Chord::new(
Note::new(F, 1),
ChordStructure::new()
.insert_many(&[(N3, 0), (N5, 1)])
),
Chord::new(
Note::new(B, 0),
ChordStructure::new()
.insert_many(&[(N3, -1), (N5, 0)])
)
);
let notes = vec![
Note::new(B, 0),
Note::new(D, 0),
Note::new(F, 1),
Note::new(F, 1),
Note::new(A, 1),
Note::new(C, 2),
];
assert_eq!(chord.iter().collect::<Vec<_>>(), notes);
}
#[test]
fn chord_from_shorthand() {
let chord = Chord::from_shorthand("C").unwrap();
let expected = Chord::new(
Note::new(C, 0),
ChordStructure::new()
.insert_many(&[(N3, 0), (N5, 0)])
);
assert_eq!(chord, expected);
}
#[test]
fn polychord_from_shorthand() {
let chord = PolyChord::from_shorthand("C|Am").unwrap();
let upper = Chord::new(
Note::new(C, 0),
ChordStructure::new()
.insert_many(&[(N3, 0), (N5, 0)])
);
let lower = Chord::new(
Note::new(A, 0),
ChordStructure::new()
.insert_many(&[(N3, -1), (N5, 0)])
);
let expected = PolyChord::new(upper, lower);
assert_eq!(chord, expected);
}
}