Skip to main content

jazz_chord/
lib.rs

1
2#![allow(warnings)]
3
4use std::cell::OnceCell;
5use std::cmp;
6use std::cmp::PartialEq;
7use std::collections::HashMap;
8use std::fmt::{Debug, Display, Formatter};
9use std::ops::{Index, IndexMut};
10use std::string::ToString;
11use std::sync::Mutex;
12use std::vec::IntoIter;
13use ezstr::{EzStr, GraphemeMatch};
14use jazz_accidentals::Accidentals;
15use once_cell::sync::Lazy;
16
17use cached::proc_macro::cached;
18
19#[derive(Clone)]
20struct ChordQualityParseResult {
21    input: EzStr,
22    triad: Option<ChordTypeMatch>,
23    extension: Option<ExtensionTypeMatch>,
24    modification: Option<ChordModificationTokens>,
25
26}
27#[derive(Debug,Clone,PartialEq,Hash)]
28pub struct TriadType<'a> {
29    pub name: &'a str,
30    pub triggers: &'a [&'a str],
31    pub notes: Change,
32}
33impl TriadType<'_> {
34
35    pub const MAJOR: Lazy<TriadType<'static>> =  Lazy::new(|| TriadType { name: "Major",
36        notes: Change::from("1 3 5"),
37        triggers: &["maj","ma","Ma", "MA"]});
38    pub const MINOR: Lazy<TriadType<'static>> =  Lazy::new(|| TriadType {  name: "Minor",
39        notes: Change::from("1 b3 5"),
40        triggers: &["minor", "min","mi","m", "-"]});
41    pub const DIMINISHED: Lazy<TriadType<'static>> =  Lazy::new(|| TriadType { name: "Diminished",
42        notes: Change::from("1 b3 b5"),
43            triggers: &["dim", "di", "o","0","O","o","o"]});
44
45
46    pub const HALF_DIMINISHED: Lazy<TriadType<'static>> =  Lazy::new(|| TriadType {  name: "Half Diminished",
47        notes: Change::from("1 b3 b5"),
48            triggers: &["ø","⌀","halfdim", "hdim"]});
49    pub const AUGMENTED: Lazy<TriadType<'static>> =  Lazy::new(|| TriadType {  name: "Augmented", notes: Change::from("1 3 #5"),
50            triggers: &["aug", "+"]});
51    pub const SUS_FOUR: Lazy<TriadType<'static>> =  Lazy::new(|| TriadType {  name: "Suspended 4th", notes: Change::from("1 4 5"),
52            triggers: &["sus4","sus"]});
53    pub const SUS_SHARP_FOUR: Lazy<TriadType<'static>> =  Lazy::new(|| TriadType { name: "Suspended Sharp 4th", notes: Change::from("1 4 #5"),
54            triggers: &["sus♯4","sus#4"]});
55    pub const SUS_TWO: Lazy<TriadType<'static>> =  Lazy::new(|| TriadType { name: "Suspended 2nd", notes: Change::from("1 2 5"),
56            triggers: &["sus2"]});
57    pub const SUS_FLAT_TWO: Lazy<TriadType<'static>> =  Lazy::new(|| TriadType { name: "Suspended ♭2nd",notes: Change::from("1 b2 5"),
58            triggers: &["sus♭2","susb2"]});
59    pub const TRIAD: Lazy<Vec<TriadType<'static>>> = Lazy::new(|| vec![
60        Self::MAJOR.clone(),
61        Self::MINOR.clone(),
62        Self::DIMINISHED.clone(),
63        Self::HALF_DIMINISHED.clone(),
64        Self::AUGMENTED.clone(),
65        Self::SUS_TWO.clone(),
66        Self::SUS_FLAT_TWO.clone(),
67        Self::SUS_FOUR.clone(),
68        Self::SUS_SHARP_FOUR.clone(),
69    ]);
70
71    pub const SUS_TRIADS: Lazy<Vec<TriadType<'static>>> = Lazy::new(|| vec![
72        Self::SUS_FOUR.clone(),
73        Self::SUS_TWO.clone(),
74        Self::SUS_FLAT_TWO.clone(),
75        Self::SUS_SHARP_FOUR.clone()
76    ]);
77
78    pub fn is_sus(&self) -> bool {
79        Self::SUS_TRIADS.contains(self)
80    }
81}
82
83enum BracketState {
84    Unknown,
85    Add,
86    Remove,
87}
88impl PartialEq for &BracketState {
89    fn eq(&self, other: &Self) -> bool {
90        self == other
91    }
92}
93
94#[derive(Clone, Debug, PartialEq)]
95enum ChordModificationTokenType {
96    BracketOpen,
97    BracketClose,
98    Add,
99    Remove,
100    Note,
101    Space,
102    Comma,
103}
104
105#[derive(Debug, Clone)]
106pub struct ChordModificationToken {
107    pub span: GraphemeMatch,
108    pub modification_type: ChordModificationTokenType
109}
110
111
112
113#[derive(Debug, Clone)]
114pub struct ChordModificationTokens {
115    tokens: Vec<ChordModificationToken>,
116}
117
118impl IntoIterator for ChordModificationTokens {
119    type Item = ChordModificationToken;
120    type IntoIter = std::vec::IntoIter<Self::Item>;
121    fn into_iter(self) -> Self::IntoIter {
122        self.tokens.into_iter()
123    }
124}
125
126
127pub struct ChordModificationParseResult {
128    tokens: ChordModificationTokens,
129}
130
131
132#[derive(Debug,Clone,PartialEq,Hash)]
133pub struct ChordTypeMatch {
134    pub chord_type: TriadType<'static>,
135    pub span: GraphemeMatch,
136}
137
138#[derive(Debug, Clone, PartialEq, Hash)]
139pub struct ExtensionType<'a> {
140    pub name: &'static str,
141    pub triggers: &'a [&'a str],
142    pub notes:Change,
143}
144
145impl ExtensionType<'static> {
146    pub const FIFTH: Lazy<ExtensionType<'static>> = Lazy::new(|| ExtensionType {
147        name: "Fifth",
148        notes: Change::from("1 5"),
149        triggers: &["5th","5",]
150    });
151    pub const SIXTH: Lazy<ExtensionType<'static>> = Lazy::new(|| ExtensionType {
152        name: "Major Six",
153        notes: Change::from("1 3 5 6"),
154        triggers: &["6"]
155    });
156    pub const SEVENTH: Lazy<ExtensionType<'static>> = Lazy::new(|| ExtensionType {
157        name: "Dominant Seventh",
158        notes: Change::from("1 3 5 b7"),
159        triggers: &["7","dom","dominant"]
160    });
161    pub const MAJOR_SEVENTH: Lazy<ExtensionType<'static>> = Lazy::new(|| ExtensionType {
162        name: "Major Seventh",
163        notes: Change::from("1 3 5 7"),
164        triggers: &["ma7","Ma7","MA7","triangle7",] //TODO add the real triangle
165    });
166
167    pub const NINTH: Lazy<ExtensionType<'static>> = Lazy::new(|| ExtensionType {
168        name: "Ninth",
169        notes: Change::from("1 3 5 b7 9"),
170        triggers: &["9"] //TODO add the real triangle
171    });
172
173    pub const MAJOR_NINTH: Lazy<ExtensionType<'static>> = Lazy::new(|| ExtensionType {
174        name: "Major Ninth",
175        notes: Change::from("1 3 5 7 9"),
176        triggers: &["ma9","Ma9","MA9","triangle9",] //TODO add the real triangle
177    });
178
179    pub const ELEVENTH: Lazy<ExtensionType<'static>> = Lazy::new(|| ExtensionType {
180        name: "Eleventh",
181        notes: Change::from("1 3 5 b7 9 11"),
182        triggers: &["11",] //TODO add the real triangle
183    });
184
185    pub const MAJOR_ELEVENTH: Lazy<ExtensionType<'static>> = Lazy::new(|| ExtensionType {
186        name: "Major Eleventh",
187        notes: Change::from("1 3 5 7 9 11"),
188        triggers: &["ma11","Ma11","MA11","triangle11",] //TODO add the real triangle
189    });
190
191    pub const THIRTEENTH: Lazy<ExtensionType<'static>> = Lazy::new(|| ExtensionType {
192        name: "Thirteenth",
193        notes: Change::from("1 3 5 b7 9 11 13"),
194        triggers: &["13",] //TODO add the real triangle
195    });
196
197    pub const MAJOR_THIRTEENTH: Lazy<ExtensionType<'static>> = Lazy::new(|| ExtensionType {
198        name: "Major Thirteenth",
199        notes: Change::from("1 3 5 7 9 11 13"),
200        triggers: &["ma13","Ma13","MA13","triangle13",] //TODO add the real triangle
201    });
202
203    pub const SIX_ADD_NINE: Lazy<ExtensionType<'static>> = Lazy::new(|| ExtensionType {
204        name: "Six Add Nine",
205        notes: Change::from("1 3 5 6 9"),
206        triggers: &["69","6/9"] //TODO add the real triangle
207    });
208
209
210    pub const EXTENSIONS: Lazy<Vec<ExtensionType<'static>>> = Lazy::new(|| {
211        vec![
212            Self::FIFTH.clone(),
213            Self::SIX_ADD_NINE.clone(),
214            Self::SIXTH.clone(),
215            Self::MAJOR_SEVENTH.clone(),
216            Self::SEVENTH.clone(),
217            Self::MAJOR_NINTH.clone(),
218            Self::NINTH.clone(),
219            Self::MAJOR_ELEVENTH.clone(),
220            Self::ELEVENTH.clone(),
221            Self::MAJOR_THIRTEENTH.clone(),
222            Self::THIRTEENTH.clone(),
223
224        ]
225    });
226}
227#[derive(Debug,Clone,PartialEq,Hash)]
228pub struct ExtensionTypeMatch {
229    pub extension_type: ExtensionType<'static>,
230    pub span: GraphemeMatch,
231}
232
233#[derive(Debug)]
234#[derive(Clone)]
235pub struct ChordQuality {
236    pub input: EzStr,
237    pub triad: Option<ChordTypeMatch>,
238    pub extension: Option<ExtensionTypeMatch>,
239    pub modification: Option<ChordModificationTokens>,
240}
241
242impl Default for ChordQuality {
243    fn default() -> ChordQuality {
244        ChordQuality {
245            input: "".into(),
246            triad: None,
247            extension: None,
248            modification: None,
249            // accidentals: Lazy::new(|| "".into()),
250        }
251    }
252}
253
254impl Display for ChordQuality {
255    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
256        let mut extension_str = String::new();
257        let mut triad_str = String::new();
258        let mut modification_str = String::new();
259        if let Some(triad) = &self.triad {
260            triad_str = triad.chord_type.triggers[0].parse().unwrap();
261        }
262        if let Some(extension) = &self.extension {
263            extension_str = extension.extension_type.triggers[0].parse().unwrap();
264        }
265        if let Some(modification) = &self.modification {
266            modification_str = modification.tokens.iter().map(|m| m.span.to_string()).collect::<String>();
267            //panic!("self.modification: {:?}",modification);
268        }
269
270
271        if self.is_sus_chord() {
272            write!(f, "{}{}", extension_str, triad_str, );
273        } else {
274            write!(f, "{}{}", triad_str, extension_str, );
275        }
276        write!(f, "{}", modification_str)
277
278    }
279}
280
281
282impl ChordQuality {
283
284    // pub const DIAD: [ChordType<'static>; 12] = [
285    //     ChordType { name: "Minor Second", frets: &[0, 2],
286    //         triggers: triggers: &["mi2nd", "m2nd", "min2nd","minor2nd","-2nd"]},
287    //     ChordType { name: "Minor", frets: &[0, 3, 7],
288    //         triggers: &["mi", "m", "min","minor","-"]},
289    //
290    //
291    // ];
292
293
294
295
296
297    fn new<S: Into<EzStr> + Clone>(s:S) -> Option<Self> {
298        let string = s.clone().into();
299        let result = Self::parse_string(string.data);
300        let mut triad = None;
301        let mut extension = None;
302        if result.is_some() {
303            if result.clone().unwrap().triad.is_some(){
304                triad = result.clone().unwrap().triad;
305            }
306            if result.clone().unwrap().extension.is_some(){
307                extension = result.clone().unwrap().extension;
308            }
309        }
310        if triad.is_none() && extension.is_none() {
311            return None;
312        }
313        Some(ChordQuality {
314            input: s.into(),
315            triad: triad,
316            extension: extension,
317            modification: result.unwrap().modification,
318        })
319    }
320
321    pub fn from_string<S: Into<EzStr>>(s: S) -> Option<Self> {
322        Self::new(s.into())
323    }
324
325    fn parse_string(input: String) -> Option<ChordQualityParseResult> {
326
327        let input = EzStr::from(input);
328        let mut triad_at_start_result = ChordQuality::triad_at_index(input.clone(), 0usize);
329        let mut extension_result = ChordQuality::extension_at_index(input.clone(), 0usize);
330        let mut modifications_result = None;
331        let mut start: usize = 0;
332        let mut end: usize = input.len();
333        if extension_result.is_some() {
334
335            start = extension_result.clone().unwrap().span.text.len();
336            if let Some(triad) = ChordQuality::triad_at_index(&input.data, start){
337                if triad.chord_type.is_sus(){
338                    triad_at_start_result = ChordQuality::triad_at_index(&input.data, start);
339                }
340            }
341        }
342        else if triad_at_start_result.is_some() {
343
344            start = triad_at_start_result.clone().unwrap().span.text.len();
345
346            //print!("start:{} end:{} input:{} triad_result:{}", start, end, input, triad_result.clone().unwrap().span.text);
347
348
349            extension_result = ChordQuality::extension_at_index(input.clone(), start);
350            if extension_result.is_some() {
351                if input == EzStr::from("mima7"){
352                    //panic!("{input} {:?}", extension_result.clone().unwrap());
353                }
354                //println!(" extension_result:{:?}",ChordQuality::extension_at_index(&input, start).unwrap());
355            }
356
357            if input == EzStr::from("mima7") {
358                //panic!("input: {}, triad: {},", input,triad_result.clone().unwrap().span);
359            }
360        } else {
361            //panic!("{:?}",ChordQuality::extension_at_start(input.slice(start as i32, end as i32)));
362            return None;
363        }
364
365        // this is a weird way to handle this
366        if extension_result.is_some() && triad_at_start_result.is_some() {
367            if extension_result.clone().unwrap().span.start == triad_at_start_result.clone().unwrap().span.start {
368                triad_at_start_result = None;
369            }
370        }
371
372        let mut brackets_start = 0usize;
373        if let Some(triad) = triad_at_start_result.clone() {
374            brackets_start = cmp::max(brackets_start,triad.span.end);
375        }
376        if let Some(extension) = extension_result.clone() {
377            brackets_start = cmp::max(brackets_start,extension.span.end);
378        }
379
380        // if input == EzStr::from("5(add ♭6)"){
381        //     let mut panic_str = format!("SHIT input:{input} brackets_start:{brackets_start}");
382        //         if triad_at_start_result.is_some() {
383        //             panic_str += format!("triad: {}",triad_at_start_result.unwrap().span).as_str();
384        //         }
385        //         if extension_result.is_some() {
386        //             panic_str += format!("extension: {}",extension_result.unwrap().span).as_str();
387        //         }
388        //
389        //     panic!("{}", panic_str);
390        // }
391        modifications_result = ChordQuality::brackets_at_index(input.clone(),brackets_start);
392
393        //
394        // // Deal with brackets
395        // let mut brackets_start = 0usize;
396        // if let Some(triad) = triad_at_start_result.clone() {
397        //     brackets_start += triad.span.text.len();
398        // }
399        // if let Some(extension) = extension_result.clone() {
400        //     brackets_start += extension.span.text.len();
401        // }
402        // let remains = &input.slice(brackets_start as i32,input.len() as i32);
403        // if remains.len() > 0 {
404        //
405        //     let using_brackets = &remains[0].value == "(";
406        //     let mut c = brackets_start;
407        //     let remains_without_brackets = remains.data.replace("(","").replace(")","");
408        //     let mut matches: Vec<ChordModificationToken> = Vec::new();
409        //     let words = remains_without_brackets.split_whitespace();
410        //     //panic!("NO {:?}", words.collect::<Vec<&str>>());
411        //     for word in words {
412        //         let start = c;
413        //         if let Some(note) = Note::new(word) {
414        //             let note_text = EzStr::from(note.text);
415        //             let end = start + note_text.len();
416        //
417        //             matches.push(ChordModificationToken{
418        //                 span: GraphemeMatch{start, end, text: note_text.clone().into()},
419        //                 modification_type: ChordModificationTokenType::Note,
420        //             });
421        //             c += note_text.len();
422        //             //panic!("word {} note {} matches {:?}",word, Note::new(word).unwrap(),matches);
423        //
424        //         } else if word == "add" {
425        //             end = start + 3;
426        //             matches.push(ChordModificationToken{
427        //                 span: GraphemeMatch { start, end, text: "add".into()},
428        //                 modification_type: ChordModificationTokenType::Add });
429        //             c += 3;
430        //         } else if word == "no" {
431        //             end = start + 2;
432        //             matches.push(ChordModificationToken{
433        //                 span: GraphemeMatch { start, end, text: "no".into()},
434        //                 modification_type: ChordModificationTokenType::Remove });
435        //             c += 2
436        //         } else {
437        //             panic!("This was not supposed to happen. input: {}, word: {}", input, word);
438        //         }
439        //         let matches_len = matches.len();
440        //         matches[matches_len - 1].span.ensure_is_valid(input.clone());
441        //         c += 1;
442        //
443        //     }
444        //     if remains[remains.len() - 1].value == ")" {
445        //         matches.push(
446        //             ChordModificationToken {
447        //                 span: GraphemeMatch {start: input.len() -1, end: input.len(),
448        //                     text:")".into()},
449        //                 modification_type: ChordModificationTokenType::BracketClose,
450        //             }
451        //         )
452        //     }
453        //
454        //     modifications_result = Some(ChordModificationTokens {
455        //         tokens: matches});
456        //     //panic!("input: {} {:?}",input,modifications_result.unwrap())
457        //
458        //
459        //
460        //
461        //     //panic!("input: {} chord_and_extension: {} remains: {} remains_without_brackets: {}", input, input.slice(0,brackets_start as i32),remains, remains_without_brackets)
462        // }
463        // if input.contains("("){
464        //     //panic!("input: {} {:?}",input,modifications_result.unwrap())
465        // }
466
467
468        Some(ChordQualityParseResult {
469            input: input.clone().into(),
470            triad: triad_at_start_result,
471            extension: extension_result,
472            modification: modifications_result,
473        })
474        //if triad_at_start.is_none()
475
476    }
477
478    fn triad_at_index<S: Into<EzStr> + Clone, I: Into<usize>>(s:S, index:I) -> Option<ChordTypeMatch> {
479        let start = index.into();
480        let s = s.clone().into();
481        for triad in TriadType::TRIAD.iter() {
482            for trigger in triad.triggers.iter() {
483                let trigger = EzStr::from(*trigger);
484                let end = start + trigger.len();
485
486                // if s == "mima7" && *trigger == "mi" {
487                //     panic!("s: {}, trigger: {}, start: {}, end: {}, abool:{}, bbool:{}, cbool:{}, s[start..end]:{}, s:{}", s,trigger,start,end, s.contains(trigger),end <= s.len(),s[start..end] == s,&s[start..end],&s);
488                // }
489                if end <= s.len() && s.slice(start as i32, end as i32) == trigger {
490                    //let found_index = s[start..].find(trigger).map(|idx|idx + start).unwrap();
491                    let grapheme_match = GraphemeMatch{start:start,
492                        end:end,
493                        text: trigger.clone(),
494                    };
495
496                    //grapheme_match.ensure_is_valid(EzStr::from(EzStr::from(s.into())));
497
498                    return Some(ChordTypeMatch{chord_type:triad.clone(), span:
499                        grapheme_match
500                    }
501                    )
502                }
503            }
504        }
505        None
506    }
507
508    pub fn extension_at_index<S: Into<EzStr> + Clone, I: Into<usize>>(s:S, index:I) -> Option<ExtensionTypeMatch> {
509        let start = index.into();
510        let s = s.clone().into();
511        for extension in ExtensionType::EXTENSIONS.iter() {
512            for trigger in extension.triggers.iter() {
513                let trigger = EzStr::from(*trigger);
514                let end = start + trigger.len();
515                //if trigger.len() + index > s.len()
516                //let found_index = s[start..].find(trigger).map(|idx|idx + index).unwrap();
517                //panic!("{}",found_index);
518                if end <= s.len() && s.slice(start as i32,end as i32) == trigger{
519                    let grapheme_match = GraphemeMatch {
520                        start,
521                        end,
522                        text: trigger.clone(),
523                    };
524                    //grapheme_match.ensure_is_valid(s);
525                    return Some(ExtensionTypeMatch {
526                        extension_type: extension.clone(),
527                        span: grapheme_match
528                    });
529                }
530
531            }
532        }
533        None
534    }
535
536    fn brackets_at_index<S: Into<EzStr> + Clone, I: Into<usize>>(s:S, index:I) -> Option<ChordModificationTokens> {
537        let mut tokens:Vec<ChordModificationToken> = Vec::new();
538        let mut start = index.into();
539        let s = s.clone().into();
540        let triggers_to_mod_type = vec![
541            ( vec!["add", "+"], ChordModificationTokenType::Add ),
542            ( vec!["no", "-"], ChordModificationTokenType::Remove ),
543            ( vec!["("], ChordModificationTokenType::BracketOpen ),
544            ( vec![" "], ChordModificationTokenType::Space ),
545            ( vec![","], ChordModificationTokenType::Comma ),
546            ( vec![")"], ChordModificationTokenType::BracketClose ),
547        ];
548        while start < s.len() {
549            let mut trigger_found = false;
550            for token_type in &triggers_to_mod_type {
551                if trigger_found { break; }
552                for trigger in token_type.0.iter() {
553                    if trigger_found { break; }
554                    let trigger = EzStr::from(*trigger);
555                    let end = start + trigger.len();
556
557                    // if s == "mima7" && *trigger == "mi" {
558                    //     panic!("s: {}, trigger: {}, start: {}, end: {}, abool:{}, bbool:{}, cbool:{}, s[start..end]:{}, s:{}", s,trigger,start,end, s.contains(trigger),end <= s.len(),s[start..end] == s,&s[start..end],&s);
559                    // }
560                    if end <= s.len() && s.slice(start as i32, end as i32) == trigger {
561                        //let found_index = s[start..].find(trigger).map(|idx|idx + start).unwrap();
562                        let grapheme_match = GraphemeMatch {
563                            start: start,
564                            end: end,
565                            text: trigger.clone(),
566                        };
567
568                        //grapheme_match.ensure_is_valid(EzStr::from(EzStr::from(s.into())));
569                        tokens.push(ChordModificationToken {
570                            span:
571                            GraphemeMatch { start, end, text: trigger.clone() },
572                            modification_type: token_type.1.clone(),
573                        });
574                        start = end;
575                        trigger_found = true;
576                    }
577                }
578            }
579            if !trigger_found {
580                let mut note_found = false;
581
582
583                if let Some(note) = Note::parse_until_invalid(&s, start){
584                    let end = start + note.text.len();
585                    tokens.push(ChordModificationToken {
586                        span:
587                        GraphemeMatch { start, end, text: note.text.clone() },
588                        modification_type: ChordModificationTokenType::Note,
589                    });
590                    start = end;
591                    trigger_found = true;
592                } else{
593                    start += 1;
594                    break;
595
596                }
597                //panic!("input: {s} trigger not found at start:{start}, which contains {}\ntokens{tokens:?}",s[start])
598            }
599        }
600        if tokens.len() > 0 {
601
602            // if s.contains("Ma7(no 5)") {
603            //     panic!("input: {} found {:?}", s, tokens);
604            // }
605            return Some(ChordModificationTokens {tokens})
606        }
607        None
608    }
609
610    fn brackets_at_indexOLD<S: Into<EzStr> + Clone, I: Into<usize>>(s:S, index:I) -> Option<ChordModificationParseResult> {
611        let mut tokens:Vec<ChordModificationToken> = Vec::new();
612        let s = s.into();
613        let mut index = index.into();
614
615        let triggers_to_mod_type = vec![
616            ( vec!["add", "+"], ChordModificationTokenType::Add ),
617            ( vec!["no", "-"], ChordModificationTokenType::Remove ),
618            ( vec!["("], ChordModificationTokenType::BracketOpen ),
619            ( vec![" "], ChordModificationTokenType::Space ),
620            ( vec![")"], ChordModificationTokenType::BracketClose ),
621        ];
622
623        while index < s.len() - 1 {
624            let mut trigger_found = false;
625            for mod_type in triggers_to_mod_type.iter() {
626                let triggers = &mod_type.0;
627                let token_type = &mod_type.1;
628                for trigger in triggers.iter() {
629                    // if *trigger == "(" {
630                    //     panic!("space {}", EzStr::from(*trigger) == EzStr::from("("))
631                    // }
632                    // print!("{}", trigger);
633                    let trigger = EzStr::from(*trigger);
634                    if trigger == EzStr::from("(") {
635                        let val1 = &trigger;
636                        let val2 = s.slice(index as i32, (index + trigger.len()) as i32);
637                        panic!("inside brackets_at_index(s:{}, index:{}) trigger:{} val1:{} val2:{} bool1:{} bool2:{}\ntokens {:?}", &s, index, trigger,val1,val2,
638                               index + trigger.len() <= s.len(),
639                               *val1 == val2,
640                            tokens
641                        );
642                    }
643
644                    if trigger == EzStr::from("(") {
645
646                    }
647
648                    if index + trigger.len() <= s.len() && trigger == s.slice(index as i32, (index + trigger.len()) as i32) {
649                        trigger_found = true;
650                        tokens.push(ChordModificationToken{span:
651                        GraphemeMatch{start:index,end:index+ trigger.len(),text:trigger.clone()},
652                            modification_type: token_type.clone() });
653                        index += &trigger.len();
654                        break;
655                    }
656                }
657                if trigger_found {
658                    break;
659                } else {
660                    index += 1;
661                    //if s.graphemes().
662                    //panic!("inside brackets_at_index(s:{}, index:{}) tokens: {:?}", &s, index, &tokens);
663                }
664            }
665        }
666        if !tokens.is_empty() {
667            panic!("inside brackets_at_index({}, {}) tokens: {:?}", s, index, tokens);
668        }
669
670
671
672
673
674
675        Some(ChordModificationParseResult{
676            tokens: ChordModificationTokens {tokens}
677        })
678    }
679
680    fn first_triad_in_str(s:&str) -> Option<TriadType> {
681        for triad in TriadType::TRIAD.iter() {
682            for trigger in triad.triggers.iter() {
683                if s.contains(trigger) {
684                    return Some(triad.clone())
685                }
686            }
687        }
688        None
689    }
690
691    pub fn to_change(&self) -> Change {
692        let mut notes:Change = Change::new();
693        if self.extension.is_none() && let Some(triad) = &self.triad {
694            let triad_notes =  &triad.chord_type.notes;
695            notes.extend(triad_notes);
696        }
697        if let Some(extension) = &self.extension {
698            let mut extension_notes =  extension.clone().extension_type.notes;
699            // Change notes in extension to match triad
700            if let Some(triad) = &self.triad {
701                let triad_notes = &triad.chord_type.notes;
702                let triad_third = &triad_notes[1];
703                let triad_fifth = &triad_notes[2];
704                if let Some(extension_third) = extension_notes.first_note_with_degree(3){
705                    extension_notes[extension_third] = triad_third.clone();
706                }
707                if let Some(extension_fifth) = extension_notes.first_note_with_degree(5) {
708                    extension_notes[extension_fifth] = triad_fifth.clone();
709                }
710                //panic!("triad: {} extension: {} triad_third: {}", triad.chord_type.notes, extension_notes, triad_third);
711            }
712            notes.extend(extension_notes);
713        }
714
715        if let Some(mods) = &self.modification {
716            //let mut is_doing_exclusions = false;
717            let mut state = BracketState::Unknown;
718            for token in mods.clone().into_iter() {
719                match token.modification_type {
720                    ChordModificationTokenType::Note =>
721                        match state {
722                            BracketState::Unknown => {
723                                if let Some(mod_note) = Note::new( & token.span) {
724                                    if let Some(target_n) = notes.first_note_with_degree( & mod_note.degree){
725                                        //panic!("Fuck {} {}",self, target_n, );
726                                        notes[target_n] = mod_note;
727                                    } else {
728                                    notes.extend(Change::from_note(mod_note));
729                                    }
730                                    } else {
731                                    panic ! ("Can't parse note: {}", token.span);
732                                    }
733                            },
734                            BracketState::Remove => { //doing exclusions
735
736                                if let Some(mod_note) = Note::new( & token.span) {
737                                    if let Some(target_n) = notes.first_note_with_degree( & mod_note.degree){
738                                    notes.remove(target_n);
739                                    }
740                                }
741                                //panic!("Hey {} {notes}", self.input);
742                            },
743                            BracketState::Add => {
744                                if let Some(mod_note) = Note::new( & token.span) {
745                                    notes.extend(Change::from_note(mod_note));
746                                }
747                            }
748                        },
749                    ChordModificationTokenType::Remove =>
750                        state = BracketState::Remove,
751                    ChordModificationTokenType::Add =>
752                        state = BracketState::Add,
753                    _ => (),
754                }
755            }
756        }
757        // Fix diminished
758        if let Some(triad) = &self.triad {
759            if triad.chord_type == *TriadType::DIMINISHED {
760                if let Some(flat_seven) = notes.position("b7") {
761                    notes[flat_seven] = "bb7".into();
762                    //panic!("{} {}", self.input,notes);
763                }
764            }
765        }
766        notes
767    }
768
769
770    pub fn to_frets(&self) -> Vec<i32> {
771        self.to_change().to_frets()
772    }
773
774    pub fn short_debug(&self) -> String {
775        let mut ret = String::new();
776        ret += "ChordQuality {triad: ";
777        if let Some(triad) = self.triad.clone() {
778            ret.push_str(format!("{:?}", &self.clone().triad.unwrap().span.text).as_str());
779        } else {
780            ret.push_str("None");
781        }
782        ret.push_str(", extension: ");
783        if let Some(extension) = self.extension.clone() {
784            ret.push_str(format!("{:?}", &self.clone().extension.unwrap().span.text).as_str());
785        } else {
786            ret.push_str("None");
787        }
788        ret.push_str(", mods: ");
789        if let Some(modification) = self.modification.clone() {
790            ret.push_str(format!("{:?}", &self.clone().modification.unwrap().tokens).as_str());
791        } else {
792            ret.push_str("None");
793        }
794        ret.push_str("}");
795        ret.push_str(format!(" == {}", self).as_str());
796        ret
797    }
798
799    pub fn is_sus_chord(&self) -> bool {
800        if let Some(triad) = &self.triad {
801            if TriadType::SUS_TRIADS.contains(&triad.chord_type) {
802                return true;
803            }
804        }
805        return false;
806    }
807}
808
809
810
811    // pub fn find_root(&self) -> Option<GraphemeMatch> {
812    //
813    //
814    // }
815
816// use once_cell::unsync::OnceCell;
817#[derive(Clone,PartialEq,Eq,Hash,Debug)]
818pub struct Note {
819    pub text: EzStr,
820    pub accidentals: EzStr,
821    pub degree: EzStr,
822}
823
824static FRETS_FROM_ONE_CACHE: Lazy<Mutex<HashMap<String, i32>>> = Lazy::new(|| Mutex::new(HashMap::with_capacity(256)));
825static PARSE_CACHE: Lazy<Mutex<HashMap<String, Option<(String, String)>>>> = Lazy::new(|| Mutex::new(HashMap::with_capacity(256)));
826
827impl From<&str> for Note {
828    fn from(s:&str) -> Note {
829        Note::new(s).expect("REASON")
830    }
831}
832
833impl Display for Note {
834    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
835        write!(f,"{}",self.unicode_accidentals(false))
836    }
837}
838
839impl Note {
840    pub const fn scale_degree_to_fret_first_octave(degree: u8) -> Option<u8> {
841        match degree {
842            1 => Some(0),
843            2 => Some(2),
844            3 => Some(4),
845            4 => Some(5),
846            5 => Some(7),
847            6 => Some(9),
848            7 => Some(11),
849            _ => None,
850        }
851    }
852    pub const fn fret_to_scale_degree_all_sharps(fret: u8) -> Option<u8> {
853        match fret {
854            0 | 1 => Some(1),
855            2 | 3 => Some(2),
856            4 => Some(3),
857            5 | 6 => Some(4),
858            7 | 8 => Some(5),
859            9 | 10 => Some(6),
860            11 => Some(7),
861            _ => None,
862        }
863    }
864
865    pub const fn fret_to_scale_degree_all_flats(fret: u8) -> Option<u8> {
866        match fret {
867            0 => Some(1),
868            1 | 2 => Some(2),
869            3 | 4 => Some(3),
870            5 => Some(4),
871            6 | 7 => Some(5),
872            8 | 9 => Some(6),
873            10 | 11 => Some(7),
874            _ => None,
875        }
876    }
877    pub fn new<T: Into<EzStr>>(text: T) -> Option<Self> {
878        let text = text.into();
879        let result = Self::parse(&text);
880        if result.is_none() {
881            None
882        } else {
883            Some(
884                Self { text: text,
885                    accidentals: result.clone().unwrap().0.into(),
886                    degree: result.unwrap().1.into() }
887            )
888        }
889    }
890
891    fn parse(text: &EzStr) -> Option<(String, String)> {
892        // memoize
893        {
894            let cache = PARSE_CACHE.lock().unwrap();
895            if let Some(v) = cache.get(&text.data) {
896                return v.clone();
897            }
898        }
899        let mut accidentals = String::new();
900        let mut degree = String::new();
901        let mut is_parsing_accidentals = true;
902        let mut c_inactive_until = 0usize;
903        let mut ret:Option<(String, String)>;
904        for (c, char) in text.graphemes().iter().enumerate() {
905            if c < c_inactive_until {
906                continue;
907            }
908            if is_parsing_accidentals {
909                let mut trigger_found = false;
910                for trigger in Accidentals::ALL_TRIGGERS_LONGEST_FIRST.iter() {
911                    let trigger = EzStr::from(*trigger);
912                    let end = c as i32 + trigger.len() as i32;
913                    if end < text.len() as i32 && trigger == text.slice(c as i32, end) {
914                        accidentals.push_str(&trigger.data);
915                        c_inactive_until = c_inactive_until.wrapping_add(trigger.len());
916                        trigger_found = true;
917                        break;
918                    }
919                }
920                if !trigger_found {
921                    is_parsing_accidentals = false;
922                }
923            }
924            if !is_parsing_accidentals {
925                if !text[c].value.chars().next().unwrap().is_numeric() {
926                    ret = None;
927                    let mut cache = PARSE_CACHE.lock().unwrap();
928                    cache.insert(text.data.clone(), ret);
929                    return None;
930
931
932                } else {
933                    degree.push_str(&text[c].value);
934                }
935            }
936        }
937
938        ret = Some((accidentals, degree));
939        let mut cache = PARSE_CACHE.lock().unwrap();
940        cache.insert(text.data.clone(), ret.clone());
941        ret
942    }
943
944    fn in_first_octave(&self) -> Note {
945        let mut degree = self.degree.data.parse::<u8>().unwrap();
946        degree = (degree - 1 ) % 7 + 1;
947        let text = self.accidentals.as_ref().to_owned() + &*degree.to_string();
948        let note = Note::new(text).unwrap();
949        note
950    }
951
952    pub fn accidentals_dist(&self) -> i32 {
953        Accidentals::get_dist(&*self.accidentals.data)
954    }
955    pub fn degree_to_fret<N: Into<u8>>(degree:N) -> i32 {
956        let degree = degree.into();
957        let octave = (degree - 1) / 7; // floor
958        let degree_first_octave = (degree - 1) % 7 + 1;
959        (octave * 12 + Self::scale_degree_to_fret_first_octave(degree_first_octave).unwrap()) as i32
960    }
961    pub fn fret(&self) -> i32 {
962        // memoize
963        {
964            let cache = FRETS_FROM_ONE_CACHE.lock().unwrap();
965            if let Some(v) = cache.get(&self.text.data) {
966                return *v;
967            }
968        }
969        let accidentals_frets:i32 = Accidentals::get_dist(&self.accidentals.data);
970        let degree_frets:i32 = Note::degree_to_fret((&self.degree.data).parse::<u8>().unwrap());
971
972        let ret = accidentals_frets + degree_frets;
973        // memoize
974        let mut cache = FRETS_FROM_ONE_CACHE.lock().unwrap();
975        cache.insert(self.text.data.clone(), ret);
976        ret
977    }
978
979    pub fn from_fret<F: Into<u8>>(fret: F) -> Note {
980        let fret = fret.into();
981        let octave = fret / 12;
982        let first_octave_fret = fret % 12;
983        let first_octave_degree  = Note::fret_to_scale_degree_all_flats(first_octave_fret).unwrap();
984        let accidental_dist = Self::scale_degree_to_fret_first_octave(first_octave_degree).unwrap() - first_octave_fret;
985        let degree = String::from((first_octave_degree + 7 * octave).to_string());
986        let text = Accidentals::from_dist(accidental_dist as i32,false,false) + &*degree;
987        let note = Note::new(text.clone());
988        if note.is_some(){
989            note.unwrap()
990        } else {
991            panic!("Failed {}", text)
992        }
993    }
994
995    pub fn unicode_accidentals(&self, using_double_accidentals: bool) -> String {
996        format!("{}", Accidentals::replace_with_unicode(&self.text.data, using_double_accidentals))
997    }
998
999    pub fn parse_until_invalid(text: &EzStr,start:usize) -> Option<Note>{
1000        let mut accidentals = String::new();
1001        let mut degree = String::new();
1002        let mut is_parsing_accidentals = true;
1003        let mut c_inactive_until = start;
1004        let mut ret:Option<Note>;
1005        for (c, char) in text.graphemes().iter().enumerate() {
1006            if c < c_inactive_until {
1007                continue;
1008            }
1009            if is_parsing_accidentals {
1010                let mut trigger_found = false;
1011                for trigger in Accidentals::ALL_TRIGGERS_LONGEST_FIRST.iter() {
1012                    let trigger = EzStr::from(*trigger);
1013                    let end = c as i32 + trigger.len() as i32;
1014                    if end < text.len() as i32 && trigger == text.slice(c as i32, end) {
1015                        accidentals.push_str(&trigger.data);
1016                        c_inactive_until = c_inactive_until.wrapping_add(trigger.len());
1017                        trigger_found = true;
1018                        break;
1019                    }
1020                }
1021                if !trigger_found {
1022                    is_parsing_accidentals = false;
1023                    if c == 0 {
1024                        return None;
1025                    }
1026                }
1027            }
1028            if !is_parsing_accidentals {
1029                if !text[c].value.chars().next().unwrap().is_numeric() {
1030                    if degree.len() == 0 {
1031                        return None;
1032                    } else {
1033                        break;
1034                    }
1035                } else {
1036                    degree.push_str(&text[c].value);
1037                }
1038            }
1039        }
1040
1041        ret = Some(Note{ text: EzStr::from(accidentals.clone() + &*degree.clone()),
1042            accidentals:accidentals.into(), degree: EzStr::from(degree)
1043        });
1044        ret
1045    }
1046
1047}
1048
1049#[derive(PartialEq, Debug, Hash, Clone)]
1050pub struct Change {
1051    pub notes: Vec<Note>,
1052}
1053
1054
1055impl Display for Change {
1056    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1057        write!(f,"{}",Accidentals::replace_with_unicode(&*self.join(" "), false))
1058    }
1059}
1060impl From<&str> for Change {
1061    fn from(text: &str) -> Self {
1062        let mut text = String::from(text);
1063        while text.contains("  "){
1064           text = text.replace("  ", " ");
1065        }
1066        text = text.replace(",","");
1067        let note_strs = text.split(' ').collect::<Vec<&str>>();
1068        Self {notes: note_strs.into_iter().map(|arg: &str| Note::from(arg)).collect()}
1069    }
1070}
1071
1072impl From<Vec<&str>> for Change {
1073    fn from(text: Vec<&str>) -> Change {
1074        Change { notes:text.into_iter().map(|arg| Note::from(arg)).collect::<Vec<Note>>() }
1075    }
1076}
1077
1078impl From<Vec<Note>> for Change {
1079    fn from(notes: Vec<Note>) -> Change {
1080        Change { notes }
1081    }
1082}
1083
1084impl IntoIterator for Change {
1085    type Item = Note;
1086    type IntoIter = IntoIter<Self::Item>;
1087
1088    fn into_iter(self) -> Self::IntoIter {
1089        self.notes.into_iter()
1090    }
1091}
1092
1093impl IntoIterator for &Change {
1094    type Item = Note;
1095    type IntoIter = IntoIter<Self::Item>;
1096
1097    fn into_iter(self) -> Self::IntoIter {
1098        self.clone().notes.into_iter()
1099    }
1100}
1101
1102impl Index<usize> for Change {
1103    type Output = Note;
1104    fn index(&self, index: usize) -> &Self::Output {
1105        &self.notes[index]
1106    }
1107}
1108
1109impl IndexMut<usize> for Change {
1110    fn index_mut(&mut self, index: usize) -> &mut Self::Output {
1111        &mut self.notes[index]
1112    }
1113}
1114
1115impl Extend<Note> for Change {
1116    fn extend<T: IntoIterator<Item=Note>>(&mut self, iter: T) {
1117        self.notes.extend(iter);
1118    }
1119}
1120
1121impl Change {
1122    pub fn new() -> Self {
1123        Change {notes: Vec::new()}
1124    }
1125
1126    pub fn from_notes(notes: Vec<Note>) -> Self {
1127        Change {notes: notes}
1128    }
1129
1130    pub fn from_note(note: Note) -> Self {
1131        Change {notes: vec![note]}
1132    }
1133    pub fn first_note_with_degree<I: Into<i32>>(&self,degree:I) -> Option<usize>{
1134        let degree = degree.into();
1135        for (n,note) in self.into_iter().enumerate(){
1136            if note.degree == EzStr::new(degree.to_string()) {
1137                return Some(n)
1138            }
1139        }
1140        None
1141    }
1142    pub fn from_frets<F: IntoIterator<Item = u8>>(frets:F) -> Change {
1143        let mut notes = Vec::new();
1144        for fret in frets {
1145            notes.push(Note::from_fret(fret));
1146        }
1147        Change{notes}
1148    }
1149
1150    pub fn to_frets(&self) -> Vec<i32> {
1151        self.notes.iter().map(|i|i.fret()).collect()
1152    }
1153
1154    pub fn join(&self,sep:&str) -> String {
1155        if self.len() == 0 {
1156            return String::new();
1157        }
1158        let mut ret = String::new();
1159        let notes = self.clone().notes;
1160        for note in notes[0..self.notes.len() - 1].iter(){
1161            ret += &*note.unicode_accidentals(false);
1162            ret += sep.as_ref();
1163        }
1164        ret += &*notes[self.len() - 1].unicode_accidentals(false);
1165        ret
1166    }
1167
1168    pub fn len(&self) -> usize {
1169        self.notes.len()
1170    }
1171
1172    pub fn remove(&mut self, n:usize) {
1173        self.notes.remove(n);
1174    }
1175
1176    pub fn contains<N: Into<Note>>(&self,note:N) -> bool {
1177        return self.notes.contains(&note.into());
1178    }
1179    pub fn position<N: Into<Note> + Clone>(&self,note:N) -> Option<usize> {
1180        return self.notes.iter().position(|n| *n == note.clone().into());
1181    }
1182
1183
1184    pub fn in_first_octave(&self) -> Change {
1185        Change {
1186            notes: self.clone().notes.iter().map(|note|
1187                note.in_first_octave()).collect(),
1188        }
1189    }
1190
1191    pub fn sorted_by_dist(&self) -> Change {
1192        let mut notes = self.clone().notes;
1193        notes.sort_by(|a,b| a.fret().cmp(&b.fret()));
1194        Change {
1195            notes
1196        }
1197    }
1198
1199    pub fn in_first_octave_sorted(&self) -> Change {
1200        Change {
1201            notes: self.in_first_octave().sorted_by_dist().notes.clone(),
1202        }
1203    }
1204
1205}
1206#[derive(Debug)]
1207struct KeyParseResult {
1208    accidentals: EzStr,
1209    letter: EzStr,
1210}
1211#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1212pub struct Key {
1213    pub data: String,
1214    pub accidentals: EzStr,
1215    pub letter: EzStr,
1216}
1217
1218
1219impl Key {
1220    pub const fn letter_to_frets_from_c(letter: char) -> Option<u8> {
1221        match letter {
1222            'C' => Some(0),
1223            'D' => Some(2),
1224            'E' => Some(4),
1225            'F' => Some(5),
1226            'G' => Some(7),
1227            'A' => Some(9),
1228            'B' => Some(11),
1229            _ => None,
1230        }
1231    }
1232
1233    pub fn new(data: &str) -> Option<Self> {
1234        let parse_result = Key::parse(data);
1235        //panic!("data: {:?}, parse_result:{:?}",data,parse_result);
1236        match parse_result {
1237            Some(x) => {
1238                Some(Key {
1239                    data: data.into(),
1240                    accidentals: x.accidentals,
1241                    letter: x.letter,
1242                })
1243            }
1244            _ => None,
1245        }
1246    }
1247
1248    fn parse(data: &str) -> Option<KeyParseResult> {
1249        let letter:char;
1250        let accidentals:&str;
1251        if "ABCDEFG".contains(&data.chars().nth(0).unwrap().to_string()) {
1252            letter = data.chars().nth(0).unwrap()
1253        } else {
1254            return None;
1255        }
1256        let ending = &data[1..];
1257        if Accidentals::is_accidentals_str(ending) {
1258            accidentals = ending;
1259        } else {
1260            return None
1261        }
1262        Some(KeyParseResult {
1263            accidentals: accidentals.into(),
1264            letter: letter.into(), })
1265    }
1266
1267    /// Returns the number of frets (semitones) up from C. Always returns a value from 0-11
1268    pub fn frets_from_c(&self) -> i32{
1269        let letter_dist_result = Key::letter_to_frets_from_c(self.letter.data.chars().nth(0).unwrap());
1270        if letter_dist_result.is_none() {
1271            panic!("{:?} {:?}",self,
1272                   self.letter.data.chars().nth(0).unwrap().to_string());
1273        }
1274        let letter_dist = letter_dist_result.unwrap() as i32;
1275        let accidentals_dist = Accidentals::get_dist(&self.accidentals.data) as i32 ;
1276        //panic!("NOW {} {}",letter_dist,accidentals_dist);
1277        (letter_dist + accidentals_dist).rem_euclid( 12)
1278    }
1279}