#![allow(warnings)]
use std::cell::OnceCell;
use std::cmp;
use std::cmp::PartialEq;
use std::collections::HashMap;
use std::fmt::{Debug, Display, Formatter};
use std::ops::{Index, IndexMut};
use std::string::ToString;
use std::sync::Mutex;
use std::vec::IntoIter;
use ezstr::{EzStr, GraphemeMatch};
use jazz_accidentals::Accidentals;
use once_cell::sync::Lazy;
use cached::proc_macro::cached;
#[derive(Clone)]
struct ChordQualityParseResult {
input: EzStr,
triad: Option<ChordTypeMatch>,
extension: Option<ExtensionTypeMatch>,
modification: Option<ChordModificationTokens>,
}
#[derive(Debug,Clone,PartialEq,Hash)]
pub struct TriadType<'a> {
pub name: &'a str,
pub triggers: &'a [&'a str],
pub notes: Change,
}
impl TriadType<'_> {
pub const MAJOR: Lazy<TriadType<'static>> = Lazy::new(|| TriadType { name: "Major",
notes: Change::from("1 3 5"),
triggers: &["maj","ma","Ma", "MA"]});
pub const MINOR: Lazy<TriadType<'static>> = Lazy::new(|| TriadType { name: "Minor",
notes: Change::from("1 b3 5"),
triggers: &["minor", "min","mi","m", "-"]});
pub const DIMINISHED: Lazy<TriadType<'static>> = Lazy::new(|| TriadType { name: "Diminished",
notes: Change::from("1 b3 b5"),
triggers: &["dim", "di", "o","0","O","o","o"]});
pub const HALF_DIMINISHED: Lazy<TriadType<'static>> = Lazy::new(|| TriadType { name: "Half Diminished",
notes: Change::from("1 b3 b5"),
triggers: &["ø","⌀","halfdim", "hdim"]});
pub const AUGMENTED: Lazy<TriadType<'static>> = Lazy::new(|| TriadType { name: "Augmented", notes: Change::from("1 3 #5"),
triggers: &["aug", "+"]});
pub const SUS_FOUR: Lazy<TriadType<'static>> = Lazy::new(|| TriadType { name: "Suspended 4th", notes: Change::from("1 4 5"),
triggers: &["sus4","sus"]});
pub const SUS_SHARP_FOUR: Lazy<TriadType<'static>> = Lazy::new(|| TriadType { name: "Suspended Sharp 4th", notes: Change::from("1 4 #5"),
triggers: &["sus♯4","sus#4"]});
pub const SUS_TWO: Lazy<TriadType<'static>> = Lazy::new(|| TriadType { name: "Suspended 2nd", notes: Change::from("1 2 5"),
triggers: &["sus2"]});
pub const SUS_FLAT_TWO: Lazy<TriadType<'static>> = Lazy::new(|| TriadType { name: "Suspended â™2nd",notes: Change::from("1 b2 5"),
triggers: &["susâ™2","susb2"]});
pub const TRIAD: Lazy<Vec<TriadType<'static>>> = Lazy::new(|| vec![
Self::MAJOR.clone(),
Self::MINOR.clone(),
Self::DIMINISHED.clone(),
Self::HALF_DIMINISHED.clone(),
Self::AUGMENTED.clone(),
Self::SUS_TWO.clone(),
Self::SUS_FLAT_TWO.clone(),
Self::SUS_FOUR.clone(),
Self::SUS_SHARP_FOUR.clone(),
]);
pub const SUS_TRIADS: Lazy<Vec<TriadType<'static>>> = Lazy::new(|| vec![
Self::SUS_FOUR.clone(),
Self::SUS_TWO.clone(),
Self::SUS_FLAT_TWO.clone(),
Self::SUS_SHARP_FOUR.clone()
]);
pub fn is_sus(&self) -> bool {
Self::SUS_TRIADS.contains(self)
}
}
enum BracketState {
Unknown,
Add,
Remove,
}
impl PartialEq for &BracketState {
fn eq(&self, other: &Self) -> bool {
self == other
}
}
#[derive(Clone, Debug, PartialEq)]
enum ChordModificationTokenType {
BracketOpen,
BracketClose,
Add,
Remove,
Note,
Space,
Comma,
}
#[derive(Debug, Clone)]
pub struct ChordModificationToken {
pub span: GraphemeMatch,
pub modification_type: ChordModificationTokenType
}
#[derive(Debug, Clone)]
pub struct ChordModificationTokens {
tokens: Vec<ChordModificationToken>,
}
impl IntoIterator for ChordModificationTokens {
type Item = ChordModificationToken;
type IntoIter = std::vec::IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
self.tokens.into_iter()
}
}
pub struct ChordModificationParseResult {
tokens: ChordModificationTokens,
}
#[derive(Debug,Clone,PartialEq,Hash)]
pub struct ChordTypeMatch {
pub chord_type: TriadType<'static>,
pub span: GraphemeMatch,
}
#[derive(Debug, Clone, PartialEq, Hash)]
pub struct ExtensionType<'a> {
pub name: &'static str,
pub triggers: &'a [&'a str],
pub notes:Change,
}
impl ExtensionType<'static> {
pub const FIFTH: Lazy<ExtensionType<'static>> = Lazy::new(|| ExtensionType {
name: "Fifth",
notes: Change::from("1 5"),
triggers: &["5th","5",]
});
pub const SIXTH: Lazy<ExtensionType<'static>> = Lazy::new(|| ExtensionType {
name: "Major Six",
notes: Change::from("1 3 5 6"),
triggers: &["6"]
});
pub const SEVENTH: Lazy<ExtensionType<'static>> = Lazy::new(|| ExtensionType {
name: "Dominant Seventh",
notes: Change::from("1 3 5 b7"),
triggers: &["7","dom","dominant"]
});
pub const MAJOR_SEVENTH: Lazy<ExtensionType<'static>> = Lazy::new(|| ExtensionType {
name: "Major Seventh",
notes: Change::from("1 3 5 7"),
triggers: &["ma7","Ma7","MA7","triangle7",] });
pub const NINTH: Lazy<ExtensionType<'static>> = Lazy::new(|| ExtensionType {
name: "Ninth",
notes: Change::from("1 3 5 b7 9"),
triggers: &["9"] });
pub const MAJOR_NINTH: Lazy<ExtensionType<'static>> = Lazy::new(|| ExtensionType {
name: "Major Ninth",
notes: Change::from("1 3 5 7 9"),
triggers: &["ma9","Ma9","MA9","triangle9",] });
pub const ELEVENTH: Lazy<ExtensionType<'static>> = Lazy::new(|| ExtensionType {
name: "Eleventh",
notes: Change::from("1 3 5 b7 9 11"),
triggers: &["11",] });
pub const MAJOR_ELEVENTH: Lazy<ExtensionType<'static>> = Lazy::new(|| ExtensionType {
name: "Major Eleventh",
notes: Change::from("1 3 5 7 9 11"),
triggers: &["ma11","Ma11","MA11","triangle11",] });
pub const THIRTEENTH: Lazy<ExtensionType<'static>> = Lazy::new(|| ExtensionType {
name: "Thirteenth",
notes: Change::from("1 3 5 b7 9 11 13"),
triggers: &["13",] });
pub const MAJOR_THIRTEENTH: Lazy<ExtensionType<'static>> = Lazy::new(|| ExtensionType {
name: "Major Thirteenth",
notes: Change::from("1 3 5 7 9 11 13"),
triggers: &["ma13","Ma13","MA13","triangle13",] });
pub const SIX_ADD_NINE: Lazy<ExtensionType<'static>> = Lazy::new(|| ExtensionType {
name: "Six Add Nine",
notes: Change::from("1 3 5 6 9"),
triggers: &["69","6/9"] });
pub const EXTENSIONS: Lazy<Vec<ExtensionType<'static>>> = Lazy::new(|| {
vec![
Self::FIFTH.clone(),
Self::SIX_ADD_NINE.clone(),
Self::SIXTH.clone(),
Self::MAJOR_SEVENTH.clone(),
Self::SEVENTH.clone(),
Self::MAJOR_NINTH.clone(),
Self::NINTH.clone(),
Self::MAJOR_ELEVENTH.clone(),
Self::ELEVENTH.clone(),
Self::MAJOR_THIRTEENTH.clone(),
Self::THIRTEENTH.clone(),
]
});
}
#[derive(Debug,Clone,PartialEq,Hash)]
pub struct ExtensionTypeMatch {
pub extension_type: ExtensionType<'static>,
pub span: GraphemeMatch,
}
#[derive(Debug)]
#[derive(Clone)]
pub struct ChordQuality {
pub input: EzStr,
pub triad: Option<ChordTypeMatch>,
pub extension: Option<ExtensionTypeMatch>,
pub modification: Option<ChordModificationTokens>,
}
impl Default for ChordQuality {
fn default() -> ChordQuality {
ChordQuality {
input: "".into(),
triad: None,
extension: None,
modification: None,
}
}
}
impl Display for ChordQuality {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let mut extension_str = String::new();
let mut triad_str = String::new();
let mut modification_str = String::new();
if let Some(triad) = &self.triad {
triad_str = triad.chord_type.triggers[0].parse().unwrap();
}
if let Some(extension) = &self.extension {
extension_str = extension.extension_type.triggers[0].parse().unwrap();
}
if let Some(modification) = &self.modification {
modification_str = modification.tokens.iter().map(|m| m.span.to_string()).collect::<String>();
}
if self.is_sus_chord() {
write!(f, "{}{}", extension_str, triad_str, );
} else {
write!(f, "{}{}", triad_str, extension_str, );
}
write!(f, "{}", modification_str)
}
}
impl ChordQuality {
fn new<S: Into<EzStr> + Clone>(s:S) -> Option<Self> {
let string = s.clone().into();
let result = Self::parse_string(string.data);
let mut triad = None;
let mut extension = None;
if result.is_some() {
if result.clone().unwrap().triad.is_some(){
triad = result.clone().unwrap().triad;
}
if result.clone().unwrap().extension.is_some(){
extension = result.clone().unwrap().extension;
}
}
if triad.is_none() && extension.is_none() {
return None;
}
Some(ChordQuality {
input: s.into(),
triad: triad,
extension: extension,
modification: result.unwrap().modification,
})
}
pub fn from_string<S: Into<EzStr>>(s: S) -> Option<Self> {
Self::new(s.into())
}
fn parse_string(input: String) -> Option<ChordQualityParseResult> {
let input = EzStr::from(input);
let mut triad_at_start_result = ChordQuality::triad_at_index(input.clone(), 0usize);
let mut extension_result = ChordQuality::extension_at_index(input.clone(), 0usize);
let mut modifications_result = None;
let mut start: usize = 0;
let mut end: usize = input.len();
if extension_result.is_some() {
start = extension_result.clone().unwrap().span.text.len();
if let Some(triad) = ChordQuality::triad_at_index(&input.data, start){
if triad.chord_type.is_sus(){
triad_at_start_result = ChordQuality::triad_at_index(&input.data, start);
}
}
}
else if triad_at_start_result.is_some() {
start = triad_at_start_result.clone().unwrap().span.text.len();
extension_result = ChordQuality::extension_at_index(input.clone(), start);
if extension_result.is_some() {
if input == EzStr::from("mima7"){
}
}
if input == EzStr::from("mima7") {
}
} else {
return None;
}
if extension_result.is_some() && triad_at_start_result.is_some() {
if extension_result.clone().unwrap().span.start == triad_at_start_result.clone().unwrap().span.start {
triad_at_start_result = None;
}
}
let mut brackets_start = 0usize;
if let Some(triad) = triad_at_start_result.clone() {
brackets_start = cmp::max(brackets_start,triad.span.end);
}
if let Some(extension) = extension_result.clone() {
brackets_start = cmp::max(brackets_start,extension.span.end);
}
modifications_result = ChordQuality::brackets_at_index(input.clone(),brackets_start);
Some(ChordQualityParseResult {
input: input.clone().into(),
triad: triad_at_start_result,
extension: extension_result,
modification: modifications_result,
})
}
fn triad_at_index<S: Into<EzStr> + Clone, I: Into<usize>>(s:S, index:I) -> Option<ChordTypeMatch> {
let start = index.into();
let s = s.clone().into();
for triad in TriadType::TRIAD.iter() {
for trigger in triad.triggers.iter() {
let trigger = EzStr::from(*trigger);
let end = start + trigger.len();
if end <= s.len() && s.slice(start as i32, end as i32) == trigger {
let grapheme_match = GraphemeMatch{start:start,
end:end,
text: trigger.clone(),
};
return Some(ChordTypeMatch{chord_type:triad.clone(), span:
grapheme_match
}
)
}
}
}
None
}
pub fn extension_at_index<S: Into<EzStr> + Clone, I: Into<usize>>(s:S, index:I) -> Option<ExtensionTypeMatch> {
let start = index.into();
let s = s.clone().into();
for extension in ExtensionType::EXTENSIONS.iter() {
for trigger in extension.triggers.iter() {
let trigger = EzStr::from(*trigger);
let end = start + trigger.len();
if end <= s.len() && s.slice(start as i32,end as i32) == trigger{
let grapheme_match = GraphemeMatch {
start,
end,
text: trigger.clone(),
};
return Some(ExtensionTypeMatch {
extension_type: extension.clone(),
span: grapheme_match
});
}
}
}
None
}
fn brackets_at_index<S: Into<EzStr> + Clone, I: Into<usize>>(s:S, index:I) -> Option<ChordModificationTokens> {
let mut tokens:Vec<ChordModificationToken> = Vec::new();
let mut start = index.into();
let s = s.clone().into();
let triggers_to_mod_type = vec![
( vec!["add", "+"], ChordModificationTokenType::Add ),
( vec!["no", "-"], ChordModificationTokenType::Remove ),
( vec!["("], ChordModificationTokenType::BracketOpen ),
( vec![" "], ChordModificationTokenType::Space ),
( vec![","], ChordModificationTokenType::Comma ),
( vec![")"], ChordModificationTokenType::BracketClose ),
];
while start < s.len() {
let mut trigger_found = false;
for token_type in &triggers_to_mod_type {
if trigger_found { break; }
for trigger in token_type.0.iter() {
if trigger_found { break; }
let trigger = EzStr::from(*trigger);
let end = start + trigger.len();
if end <= s.len() && s.slice(start as i32, end as i32) == trigger {
let grapheme_match = GraphemeMatch {
start: start,
end: end,
text: trigger.clone(),
};
tokens.push(ChordModificationToken {
span:
GraphemeMatch { start, end, text: trigger.clone() },
modification_type: token_type.1.clone(),
});
start = end;
trigger_found = true;
}
}
}
if !trigger_found {
let mut note_found = false;
if let Some(note) = Note::parse_until_invalid(&s, start){
let end = start + note.text.len();
tokens.push(ChordModificationToken {
span:
GraphemeMatch { start, end, text: note.text.clone() },
modification_type: ChordModificationTokenType::Note,
});
start = end;
trigger_found = true;
} else{
start += 1;
break;
}
}
}
if tokens.len() > 0 {
return Some(ChordModificationTokens {tokens})
}
None
}
fn brackets_at_indexOLD<S: Into<EzStr> + Clone, I: Into<usize>>(s:S, index:I) -> Option<ChordModificationParseResult> {
let mut tokens:Vec<ChordModificationToken> = Vec::new();
let s = s.into();
let mut index = index.into();
let triggers_to_mod_type = vec![
( vec!["add", "+"], ChordModificationTokenType::Add ),
( vec!["no", "-"], ChordModificationTokenType::Remove ),
( vec!["("], ChordModificationTokenType::BracketOpen ),
( vec![" "], ChordModificationTokenType::Space ),
( vec![")"], ChordModificationTokenType::BracketClose ),
];
while index < s.len() - 1 {
let mut trigger_found = false;
for mod_type in triggers_to_mod_type.iter() {
let triggers = &mod_type.0;
let token_type = &mod_type.1;
for trigger in triggers.iter() {
let trigger = EzStr::from(*trigger);
if trigger == EzStr::from("(") {
let val1 = &trigger;
let val2 = s.slice(index as i32, (index + trigger.len()) as i32);
panic!("inside brackets_at_index(s:{}, index:{}) trigger:{} val1:{} val2:{} bool1:{} bool2:{}\ntokens {:?}", &s, index, trigger,val1,val2,
index + trigger.len() <= s.len(),
*val1 == val2,
tokens
);
}
if trigger == EzStr::from("(") {
}
if index + trigger.len() <= s.len() && trigger == s.slice(index as i32, (index + trigger.len()) as i32) {
trigger_found = true;
tokens.push(ChordModificationToken{span:
GraphemeMatch{start:index,end:index+ trigger.len(),text:trigger.clone()},
modification_type: token_type.clone() });
index += &trigger.len();
break;
}
}
if trigger_found {
break;
} else {
index += 1;
}
}
}
if !tokens.is_empty() {
panic!("inside brackets_at_index({}, {}) tokens: {:?}", s, index, tokens);
}
Some(ChordModificationParseResult{
tokens: ChordModificationTokens {tokens}
})
}
fn first_triad_in_str(s:&str) -> Option<TriadType> {
for triad in TriadType::TRIAD.iter() {
for trigger in triad.triggers.iter() {
if s.contains(trigger) {
return Some(triad.clone())
}
}
}
None
}
pub fn to_change(&self) -> Change {
let mut notes:Change = Change::new();
if self.extension.is_none() && let Some(triad) = &self.triad {
let triad_notes = &triad.chord_type.notes;
notes.extend(triad_notes);
}
if let Some(extension) = &self.extension {
let mut extension_notes = extension.clone().extension_type.notes;
if let Some(triad) = &self.triad {
let triad_notes = &triad.chord_type.notes;
let triad_third = &triad_notes[1];
let triad_fifth = &triad_notes[2];
if let Some(extension_third) = extension_notes.first_note_with_degree(3){
extension_notes[extension_third] = triad_third.clone();
}
if let Some(extension_fifth) = extension_notes.first_note_with_degree(5) {
extension_notes[extension_fifth] = triad_fifth.clone();
}
}
notes.extend(extension_notes);
}
if let Some(mods) = &self.modification {
let mut state = BracketState::Unknown;
for token in mods.clone().into_iter() {
match token.modification_type {
ChordModificationTokenType::Note =>
match state {
BracketState::Unknown => {
if let Some(mod_note) = Note::new( & token.span) {
if let Some(target_n) = notes.first_note_with_degree( & mod_note.degree){
notes[target_n] = mod_note;
} else {
notes.extend(Change::from_note(mod_note));
}
} else {
panic ! ("Can't parse note: {}", token.span);
}
},
BracketState::Remove => {
if let Some(mod_note) = Note::new( & token.span) {
if let Some(target_n) = notes.first_note_with_degree( & mod_note.degree){
notes.remove(target_n);
}
}
},
BracketState::Add => {
if let Some(mod_note) = Note::new( & token.span) {
notes.extend(Change::from_note(mod_note));
}
}
},
ChordModificationTokenType::Remove =>
state = BracketState::Remove,
ChordModificationTokenType::Add =>
state = BracketState::Add,
_ => (),
}
}
}
if let Some(triad) = &self.triad {
if triad.chord_type == *TriadType::DIMINISHED {
if let Some(flat_seven) = notes.position("b7") {
notes[flat_seven] = "bb7".into();
}
}
}
notes
}
pub fn to_frets(&self) -> Vec<i32> {
self.to_change().to_frets()
}
pub fn short_debug(&self) -> String {
let mut ret = String::new();
ret += "ChordQuality {triad: ";
if let Some(triad) = self.triad.clone() {
ret.push_str(format!("{:?}", &self.clone().triad.unwrap().span.text).as_str());
} else {
ret.push_str("None");
}
ret.push_str(", extension: ");
if let Some(extension) = self.extension.clone() {
ret.push_str(format!("{:?}", &self.clone().extension.unwrap().span.text).as_str());
} else {
ret.push_str("None");
}
ret.push_str(", mods: ");
if let Some(modification) = self.modification.clone() {
ret.push_str(format!("{:?}", &self.clone().modification.unwrap().tokens).as_str());
} else {
ret.push_str("None");
}
ret.push_str("}");
ret.push_str(format!(" == {}", self).as_str());
ret
}
pub fn is_sus_chord(&self) -> bool {
if let Some(triad) = &self.triad {
if TriadType::SUS_TRIADS.contains(&triad.chord_type) {
return true;
}
}
return false;
}
}
#[derive(Clone,PartialEq,Eq,Hash,Debug)]
pub struct Note {
pub text: EzStr,
pub accidentals: EzStr,
pub degree: EzStr,
}
static FRETS_FROM_ONE_CACHE: Lazy<Mutex<HashMap<String, i32>>> = Lazy::new(|| Mutex::new(HashMap::with_capacity(256)));
static PARSE_CACHE: Lazy<Mutex<HashMap<String, Option<(String, String)>>>> = Lazy::new(|| Mutex::new(HashMap::with_capacity(256)));
impl From<&str> for Note {
fn from(s:&str) -> Note {
Note::new(s).expect("REASON")
}
}
impl Display for Note {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f,"{}",self.unicode_accidentals(false))
}
}
impl Note {
pub const fn scale_degree_to_fret_first_octave(degree: u8) -> Option<u8> {
match degree {
1 => Some(0),
2 => Some(2),
3 => Some(4),
4 => Some(5),
5 => Some(7),
6 => Some(9),
7 => Some(11),
_ => None,
}
}
pub const fn fret_to_scale_degree_all_sharps(fret: u8) -> Option<u8> {
match fret {
0 | 1 => Some(1),
2 | 3 => Some(2),
4 => Some(3),
5 | 6 => Some(4),
7 | 8 => Some(5),
9 | 10 => Some(6),
11 => Some(7),
_ => None,
}
}
pub const fn fret_to_scale_degree_all_flats(fret: u8) -> Option<u8> {
match fret {
0 => Some(1),
1 | 2 => Some(2),
3 | 4 => Some(3),
5 => Some(4),
6 | 7 => Some(5),
8 | 9 => Some(6),
10 | 11 => Some(7),
_ => None,
}
}
pub fn new<T: Into<EzStr>>(text: T) -> Option<Self> {
let text = text.into();
let result = Self::parse(&text);
if result.is_none() {
None
} else {
Some(
Self { text: text,
accidentals: result.clone().unwrap().0.into(),
degree: result.unwrap().1.into() }
)
}
}
fn parse(text: &EzStr) -> Option<(String, String)> {
{
let cache = PARSE_CACHE.lock().unwrap();
if let Some(v) = cache.get(&text.data) {
return v.clone();
}
}
let mut accidentals = String::new();
let mut degree = String::new();
let mut is_parsing_accidentals = true;
let mut c_inactive_until = 0usize;
let mut ret:Option<(String, String)>;
for (c, char) in text.graphemes().iter().enumerate() {
if c < c_inactive_until {
continue;
}
if is_parsing_accidentals {
let mut trigger_found = false;
for trigger in Accidentals::ALL_TRIGGERS_LONGEST_FIRST.iter() {
let trigger = EzStr::from(*trigger);
let end = c as i32 + trigger.len() as i32;
if end < text.len() as i32 && trigger == text.slice(c as i32, end) {
accidentals.push_str(&trigger.data);
c_inactive_until = c_inactive_until.wrapping_add(trigger.len());
trigger_found = true;
break;
}
}
if !trigger_found {
is_parsing_accidentals = false;
}
}
if !is_parsing_accidentals {
if !text[c].value.chars().next().unwrap().is_numeric() {
ret = None;
let mut cache = PARSE_CACHE.lock().unwrap();
cache.insert(text.data.clone(), ret);
return None;
} else {
degree.push_str(&text[c].value);
}
}
}
ret = Some((accidentals, degree));
let mut cache = PARSE_CACHE.lock().unwrap();
cache.insert(text.data.clone(), ret.clone());
ret
}
fn in_first_octave(&self) -> Note {
let mut degree = self.degree.data.parse::<u8>().unwrap();
degree = (degree - 1 ) % 7 + 1;
let text = self.accidentals.as_ref().to_owned() + &*degree.to_string();
let note = Note::new(text).unwrap();
note
}
pub fn accidentals_dist(&self) -> i32 {
Accidentals::get_dist(&*self.accidentals.data)
}
pub fn degree_to_fret<N: Into<u8>>(degree:N) -> i32 {
let degree = degree.into();
let octave = (degree - 1) / 7; let degree_first_octave = (degree - 1) % 7 + 1;
(octave * 12 + Self::scale_degree_to_fret_first_octave(degree_first_octave).unwrap()) as i32
}
pub fn fret(&self) -> i32 {
{
let cache = FRETS_FROM_ONE_CACHE.lock().unwrap();
if let Some(v) = cache.get(&self.text.data) {
return *v;
}
}
let accidentals_frets:i32 = Accidentals::get_dist(&self.accidentals.data);
let degree_frets:i32 = Note::degree_to_fret((&self.degree.data).parse::<u8>().unwrap());
let ret = accidentals_frets + degree_frets;
let mut cache = FRETS_FROM_ONE_CACHE.lock().unwrap();
cache.insert(self.text.data.clone(), ret);
ret
}
pub fn from_fret<F: Into<u8>>(fret: F) -> Note {
let fret = fret.into();
let octave = fret / 12;
let first_octave_fret = fret % 12;
let first_octave_degree = Note::fret_to_scale_degree_all_flats(first_octave_fret).unwrap();
let accidental_dist = Self::scale_degree_to_fret_first_octave(first_octave_degree).unwrap() - first_octave_fret;
let degree = String::from((first_octave_degree + 7 * octave).to_string());
let text = Accidentals::from_dist(accidental_dist as i32,false,false) + &*degree;
let note = Note::new(text.clone());
if note.is_some(){
note.unwrap()
} else {
panic!("Failed {}", text)
}
}
pub fn unicode_accidentals(&self, using_double_accidentals: bool) -> String {
format!("{}", Accidentals::replace_with_unicode(&self.text.data, using_double_accidentals))
}
pub fn parse_until_invalid(text: &EzStr,start:usize) -> Option<Note>{
let mut accidentals = String::new();
let mut degree = String::new();
let mut is_parsing_accidentals = true;
let mut c_inactive_until = start;
let mut ret:Option<Note>;
for (c, char) in text.graphemes().iter().enumerate() {
if c < c_inactive_until {
continue;
}
if is_parsing_accidentals {
let mut trigger_found = false;
for trigger in Accidentals::ALL_TRIGGERS_LONGEST_FIRST.iter() {
let trigger = EzStr::from(*trigger);
let end = c as i32 + trigger.len() as i32;
if end < text.len() as i32 && trigger == text.slice(c as i32, end) {
accidentals.push_str(&trigger.data);
c_inactive_until = c_inactive_until.wrapping_add(trigger.len());
trigger_found = true;
break;
}
}
if !trigger_found {
is_parsing_accidentals = false;
if c == 0 {
return None;
}
}
}
if !is_parsing_accidentals {
if !text[c].value.chars().next().unwrap().is_numeric() {
if degree.len() == 0 {
return None;
} else {
break;
}
} else {
degree.push_str(&text[c].value);
}
}
}
ret = Some(Note{ text: EzStr::from(accidentals.clone() + &*degree.clone()),
accidentals:accidentals.into(), degree: EzStr::from(degree)
});
ret
}
}
#[derive(PartialEq, Debug, Hash, Clone)]
pub struct Change {
pub notes: Vec<Note>,
}
impl Display for Change {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f,"{}",Accidentals::replace_with_unicode(&*self.join(" "), false))
}
}
impl From<&str> for Change {
fn from(text: &str) -> Self {
let mut text = String::from(text);
while text.contains(" "){
text = text.replace(" ", " ");
}
text = text.replace(",","");
let note_strs = text.split(' ').collect::<Vec<&str>>();
Self {notes: note_strs.into_iter().map(|arg: &str| Note::from(arg)).collect()}
}
}
impl From<Vec<&str>> for Change {
fn from(text: Vec<&str>) -> Change {
Change { notes:text.into_iter().map(|arg| Note::from(arg)).collect::<Vec<Note>>() }
}
}
impl From<Vec<Note>> for Change {
fn from(notes: Vec<Note>) -> Change {
Change { notes }
}
}
impl IntoIterator for Change {
type Item = Note;
type IntoIter = IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
self.notes.into_iter()
}
}
impl IntoIterator for &Change {
type Item = Note;
type IntoIter = IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
self.clone().notes.into_iter()
}
}
impl Index<usize> for Change {
type Output = Note;
fn index(&self, index: usize) -> &Self::Output {
&self.notes[index]
}
}
impl IndexMut<usize> for Change {
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
&mut self.notes[index]
}
}
impl Extend<Note> for Change {
fn extend<T: IntoIterator<Item=Note>>(&mut self, iter: T) {
self.notes.extend(iter);
}
}
impl Change {
pub fn new() -> Self {
Change {notes: Vec::new()}
}
pub fn from_notes(notes: Vec<Note>) -> Self {
Change {notes: notes}
}
pub fn from_note(note: Note) -> Self {
Change {notes: vec![note]}
}
pub fn first_note_with_degree<I: Into<i32>>(&self,degree:I) -> Option<usize>{
let degree = degree.into();
for (n,note) in self.into_iter().enumerate(){
if note.degree == EzStr::new(degree.to_string()) {
return Some(n)
}
}
None
}
pub fn from_frets<F: IntoIterator<Item = u8>>(frets:F) -> Change {
let mut notes = Vec::new();
for fret in frets {
notes.push(Note::from_fret(fret));
}
Change{notes}
}
pub fn to_frets(&self) -> Vec<i32> {
self.notes.iter().map(|i|i.fret()).collect()
}
pub fn join(&self,sep:&str) -> String {
if self.len() == 0 {
return String::new();
}
let mut ret = String::new();
let notes = self.clone().notes;
for note in notes[0..self.notes.len() - 1].iter(){
ret += &*note.unicode_accidentals(false);
ret += sep.as_ref();
}
ret += &*notes[self.len() - 1].unicode_accidentals(false);
ret
}
pub fn len(&self) -> usize {
self.notes.len()
}
pub fn remove(&mut self, n:usize) {
self.notes.remove(n);
}
pub fn contains<N: Into<Note>>(&self,note:N) -> bool {
return self.notes.contains(¬e.into());
}
pub fn position<N: Into<Note> + Clone>(&self,note:N) -> Option<usize> {
return self.notes.iter().position(|n| *n == note.clone().into());
}
pub fn in_first_octave(&self) -> Change {
Change {
notes: self.clone().notes.iter().map(|note|
note.in_first_octave()).collect(),
}
}
pub fn sorted_by_dist(&self) -> Change {
let mut notes = self.clone().notes;
notes.sort_by(|a,b| a.fret().cmp(&b.fret()));
Change {
notes
}
}
pub fn in_first_octave_sorted(&self) -> Change {
Change {
notes: self.in_first_octave().sorted_by_dist().notes.clone(),
}
}
}
#[derive(Debug)]
struct KeyParseResult {
accidentals: EzStr,
letter: EzStr,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Key {
pub data: String,
pub accidentals: EzStr,
pub letter: EzStr,
}
impl Key {
pub const fn letter_to_frets_from_c(letter: char) -> Option<u8> {
match letter {
'C' => Some(0),
'D' => Some(2),
'E' => Some(4),
'F' => Some(5),
'G' => Some(7),
'A' => Some(9),
'B' => Some(11),
_ => None,
}
}
pub fn new(data: &str) -> Option<Self> {
let parse_result = Key::parse(data);
match parse_result {
Some(x) => {
Some(Key {
data: data.into(),
accidentals: x.accidentals,
letter: x.letter,
})
}
_ => None,
}
}
fn parse(data: &str) -> Option<KeyParseResult> {
let letter:char;
let accidentals:&str;
if "ABCDEFG".contains(&data.chars().nth(0).unwrap().to_string()) {
letter = data.chars().nth(0).unwrap()
} else {
return None;
}
let ending = &data[1..];
if Accidentals::is_accidentals_str(ending) {
accidentals = ending;
} else {
return None
}
Some(KeyParseResult {
accidentals: accidentals.into(),
letter: letter.into(), })
}
pub fn frets_from_c(&self) -> i32{
let letter_dist_result = Key::letter_to_frets_from_c(self.letter.data.chars().nth(0).unwrap());
if letter_dist_result.is_none() {
panic!("{:?} {:?}",self,
self.letter.data.chars().nth(0).unwrap().to_string());
}
let letter_dist = letter_dist_result.unwrap() as i32;
let accidentals_dist = Accidentals::get_dist(&self.accidentals.data) as i32 ;
(letter_dist + accidentals_dist).rem_euclid( 12)
}
}