extern crate itertools;
extern crate pest;
#[macro_use]
extern crate pest_derive;
extern crate serde;
extern crate serde_json;
#[macro_use]
extern crate serde_derive;
use itertools::Itertools;
use pest::iterators::Pairs;
use pest::Parser;
const _GRAMMAR: &str = include_str!("gabc.pest");
#[derive(Parser)]
#[grammar = "gabc.pest"]
pub struct GABCParser;
#[derive(Debug, Serialize)]
pub struct Note<'a> {
pub prefix: &'a str,
pub position: char,
pub suffix: &'a str,
pub current_clef: &'a str,
}
impl<'a> Note<'a> {
pub fn new<'b>(gabc_input: &'b str, current_clef: &'b str) -> Note<'b> {
let mut parse_result = parse_gabc(gabc_input, Rule::note);
parsed_note_to_struct(parse_result.next().unwrap(), current_clef)
}
pub fn absolute_pitch(&self) -> &str {
let ly_notes = vec![
"a,", "b,", "c", "d", "e", "f", "g", "a", "b", "c'", "d'", "e'", "f'", "g'", "a'",
"b'", "c''", "d''", "e''", "f''", "g''", "a'''",
];
let start_index = match self.current_clef {
"c1" => 6,
"c2" => 4,
"c3" => 2,
"c4" => 0,
"f1" => 9,
"f2" => 7,
"f3" => 5,
"f4" => 3,
x => panic!("invalid clef: {}", x),
};
let position = self.position.to_lowercase().next().unwrap() as usize - 'a' as usize;
assert!(position < ly_notes.len());
return ly_notes.get(position + start_index).unwrap();
}
}
#[derive(Debug, Serialize)]
pub enum NoteElem<'a> {
Spacer(&'a str),
Barline(&'a str),
Note(Note<'a>),
}
impl<'a> NoteElem<'a> {
pub fn to_ly(&self) -> &str {
match self {
NoteElem::Barline(s) => match *s {
"'" => "\\divisioMinima",
";" => "\\divisioMaior",
":" => "\\divisioMaior",
"::" => "\\finalis",
_ => "\\divisioMinima",
},
NoteElem::Note(n) => n.absolute_pitch(),
NoteElem::Spacer(_) => "",
}
}
}
#[derive(Debug, Serialize)]
pub struct Syllable<'a> {
pub text: &'a str,
pub music: Vec<NoteElem<'a>>,
}
impl<'a> Syllable<'a> {
pub fn new<'b>(gabc_input: &'b str, current_clef: &'b str) -> Syllable<'b> {
let mut parse_result = parse_gabc(gabc_input, Rule::syllable);
parsed_syllable_to_struct(parse_result.next().unwrap(), current_clef)
}
pub fn ly_notes(&self) -> String {
let mut result = String::new();
let mut notes_iter = self.music.iter();
match notes_iter.next() {
None => return result,
Some(s) => result.push_str(s.to_ly()),
}
match notes_iter.next() {
None => return result,
Some(s) => {
result.push_str("(");
result.push_str(s.to_ly());
}
}
while let Some(s) = notes_iter.next() {
let t = s.to_ly();
if t.trim() != "" { result.push_str(" "); };
result.push_str(t);
}
result.push_str(")");
result
}
pub fn ly_text(&self) -> String {
let text = sanitize_ly_syllable(self.text);
let mut flag = false;
for ne in &self.music {
if let NoteElem::Note(_) = ne {
flag = true;
}
}
if !flag && !(text.trim() == "") {
return format!(" \\set stanza = \"{}\" ", text);
} else {
return text.to_string();
}
}
}
fn sanitize_ly_syllable(text: &str) -> String {
let start = text.trim_left() != text;
let end = text.trim_right() != text;
let mut t = text.trim().chars().filter(|c| {
match c {
'{' | '}' => false,
_ => true,
}
}).map(|c| match c {
' ' => '_',
x => x,
}).collect::<String>();
if let Some(c) = t.chars().next() {
if c.is_numeric() {
t = format!("\"{}\"", t);
}
}
let mut result = String::new();
if start {result.push(' ')};
result.push_str(&t);
if end {result.push(' ')};
result
}
#[derive(Debug, Serialize)]
pub struct GabcFile<'a> {
pub attributes: Vec<(&'a str, &'a str)>,
pub syllables: Vec<Syllable<'a>>,
}
impl<'a> GabcFile<'a> {
pub fn new(gabc_input: &str) -> GabcFile {
let parse_result = parse_gabc(gabc_input, Rule::file);
parsed_file_to_struct(parse_result)
}
pub fn as_json(&self) -> String {
serde_json::to_string(self).unwrap()
}
pub fn as_lilypond(&self) -> String {
format!("{}{}{}{}{}", LY_1, &self.ly_notes(), LY_2, &self.ly_lyrics(), LY_3)
}
pub fn ly_notes(&self) -> String {
let mut notes = String::new();
for syllable in &self.syllables {
notes.push_str(&syllable.ly_notes());
notes.push_str("\n");
}
notes
}
pub fn ly_lyrics(&self) -> String {
let mut result = String::new();
let syllable_iter = &mut self.syllables.iter().peekable();
while let Some(syll) = syllable_iter.next() {
let s = &syll.ly_text();
result.push_str(&s);
if let Some(next_syll) = syllable_iter.peek() {
let next_s = next_syll.ly_text();
if s.trim_right() == s && next_s.trim_left() == next_s {
result.push_str(" -- ");
}
}
}
result
}
}
pub fn parse_gabc(text: &str, rule: Rule) -> Pairs<Rule> {
let parse_result = GABCParser::parse(rule, &text);
match parse_result {
Err(e) => {
println!("Parse error: {}", e);
std::process::exit(1);
}
Ok(pairs) => {
return pairs;
}
}
}
pub fn debug_print(rules: Pairs<Rule>) -> String {
print_rule_tree(rules, 0)
}
fn print_rule_tree(rules: Pairs<Rule>, tabs: usize) -> String {
let mut output = String::new();
for rule in rules {
for _ in 0..tabs {
output.push_str("\t");
}
output.push_str(format!("{:?}: {}\n", rule.as_rule(), rule.as_str()).as_ref());
output.push_str(print_rule_tree(rule.into_inner(), tabs + 1).as_ref());
}
output
}
fn parsed_file_to_struct<'b>(mut parsed_file: pest::iterators::Pairs<'b, Rule>) -> GabcFile<'b> {
let mut syllables: Vec<Syllable> = Vec::new();
let mut attributes: Vec<(&str, &str)> = Vec::new();
let mut current_clef = "no clef set";
for pair in parsed_file.next().unwrap().into_inner() {
match pair.as_rule() {
Rule::attribute => {
let attribute: (&str, &str) =
pair.into_inner().map(|x| x.as_str()).next_tuple().unwrap();
attributes.push(attribute);
}
Rule::syllable => {
let mut syllable_components = pair.into_inner();
let text = syllable_components.next().unwrap().as_str();
let mut music: Vec<NoteElem> = Vec::new();
while let Some(pair) = syllable_components.next() {
match pair.as_rule() {
Rule::note => {
music.push(NoteElem::Note(parsed_note_to_struct(pair, current_clef)));
}
Rule::barline => {
music.push(NoteElem::Barline(pair.as_str()));
}
Rule::spacer => {
music.push(NoteElem::Spacer(pair.as_str()));
}
Rule::clef => {
current_clef = pair.as_str();
}
_ => unreachable!("impossible syllable sub-rule"),
}
}
syllables.push(Syllable { text, music }); }
_ => {}
}
}
GabcFile {
attributes,
syllables,
}
}
fn parsed_syllable_to_struct<'a>(parsed_syllable: pest::iterators::Pair<'a, Rule>, current_clef: &'a str) -> Syllable<'a> {
let mut syllable_components = parsed_syllable.into_inner();
let text = syllable_components.next().unwrap().as_str();
let mut music: Vec<NoteElem> = Vec::new();
while let Some(pair) = syllable_components.next() {
match pair.as_rule() {
Rule::note => {
music.push(NoteElem::Note(parsed_note_to_struct(pair, current_clef)));
}
Rule::barline => {
music.push(NoteElem::Barline(pair.as_str()));
}
Rule::spacer => {
music.push(NoteElem::Spacer(pair.as_str()));
}
_ => unreachable!("impossible syllable sub-rule"),
}
}
Syllable { text, music }
}
fn parsed_note_to_struct<'b>(parsed_note: pest::iterators::Pair<'b, Rule>, current_clef: &'b str) -> Note<'b> {
let mut prefix = "";
let mut position = 'z';
let mut suffix = "";
for p in parsed_note.into_inner() {
match &p.as_rule() {
Rule::prefix => prefix = p.as_str(),
Rule::position => position = p.as_str().chars().next().unwrap(),
Rule::suffix => suffix = p.as_str(),
_ => unreachable!("impossible note sub-rule"),
}
}
assert!(position != 'z'); Note {
prefix,
position,
suffix,
current_clef,
}
}
static LY_1: &'static str = r#"\include "gregorian.ly"
chant = \absolute { \transpose c c' {
\set Score.timing = ##f
"#;
static LY_2: &'static str = r#"
}}
verba = \lyricmode {
"#;
static LY_3: &'static str = r#"
}
\score {
\new Staff <<
\new Voice = "melody" \chant
\new Lyrics = "one" \lyricsto melody \verba
>>
\layout {
\context {
\Staff
\remove "Time_signature_engraver"
\remove "Bar_engraver"
\hide Stem
}
\context {
\Voice
\override Stem.length = #0
}
\context {
\Score
barAlways = ##t
}
}
}"#;