charter_rs/
lib.rs

1
2
3use regex::Regex;
4use std::fs::read_to_string;
5use std::io;
6use std::path::PathBuf;
7use unicode_segmentation::UnicodeSegmentation;
8
9#[derive(PartialEq, Eq, Debug, Clone, Default)]
10struct Match<'a> {
11    start: usize,
12    end: usize,
13    text: String,
14    source: &'a str,
15}
16
17impl<'a> Match<'a> {
18    pub fn new<T, S>(start: usize, end: usize, text: T, source: S) -> Self
19    where
20        T: Into<String>,
21        S: Into<&'a str>,
22    {
23        let span = Match {
24            start,
25            end,
26            text: text.into(),
27            source: source.into(),
28        };
29        span.ensure_is_valid();
30        span
31    }
32
33    fn is_valid(&self) -> bool {
34        //let a = self.source.chars().collect::<Vec<_>>()[self.start..self.end].to_vec();
35        let a = &self.source[self.start..self.end].to_string();
36        let b = &self.text;
37        a == b
38    }
39
40    fn ensure_is_valid(&self) -> () {
41        if !self.is_valid() {
42            panic!(
43                "Invalid span. {:?} not at self.source[{}, {}], which == {:?}",
44                self.text,
45                self.start,
46                self.end,
47                &self.source[self.start..self.end],
48            );
49        }
50    }
51}
52
53#[derive(PartialEq, Eq, Debug)]
54pub struct Measure<'a> {
55    pub chart: &'a Chart,
56    pub index: i32,
57    pub line_c: i32,
58    pub line_index: i32,
59    pub line_measure_count: i32,
60    pub rhythmic: Option<Match<'a>>,
61    pub harmonic: Match<'a>,
62    pub lyrical: Option<Match<'a>>,
63}
64
65impl<'a> Measure<'a> {
66    fn new(
67        chart: &'a Chart,
68        index: i32,
69        line_c: i32,
70        line_index: i32,
71        line_measure_count: i32,
72        rhythmic: Option<Match<'a>>,
73        harmonic: Match<'a>,
74        lyrical: Option<Match<'a>>,
75    ) -> Self {
76        Measure {
77            chart,
78            index,
79            line_c,
80            line_index,
81            line_measure_count,
82            rhythmic,
83            harmonic,
84            lyrical,
85        }
86    }
87
88    #[inline]
89    pub fn short_debug(&self) -> String {
90        format!(
91            "Measure{{ index: {}, line_c: {}, line_index: {}, line_measure_count: {} , self.is_last_in_line: {}\nrhythmic: {:?},\nharmonic: {:?},\nlyrical: {:?}}}",
92            self.index,
93            self.line_c,
94            self.line_index,
95            self.line_measure_count,
96            self.is_last_in_line(),
97            self.rhythmic.clone().unwrap_or_default().text,
98            self.harmonic.text,
99            self.lyrical.clone().unwrap_or_default().text
100        )
101    }
102
103    #[inline]
104    fn is_last_in_line(&self) -> bool {
105        self.line_index + 1 == self.line_measure_count
106    }
107}
108
109#[derive(PartialEq, Eq, Debug)]
110struct Section<'a> {
111    chart: &'a Chart,
112    measure_start: usize,
113    measure_end: usize,
114    section_indicator: Match<'a>,
115}
116
117impl<'a> Section<'a> {
118    fn new(
119        chart: &'a Chart,
120        measure_start: usize,
121        measure_end: usize,
122        section_indicator: Match<'a>,
123    ) -> Section<'a> {
124        Section {
125            chart,
126            measure_start,
127            measure_end,
128            section_indicator,
129        }
130    }
131}
132
133#[derive(PartialEq, Eq, Debug)]
134pub struct Chart {
135    pub path: PathBuf,
136    pub contents: String,
137}
138
139//https://www.reddit.com/r/rust/comments/2j0k21/rust_option_vs_result/
140impl Chart {
141    const RHYTHMIC_TRIGGERS: [&'static str; 14] = [
142        "𝅜", "𝅝", "𝅗𝅥", "𝅘𝅥", "𝅘𝅥𝅮", "𝅘𝅥𝅯", "𝅘𝅥𝅰", "𝅘𝅥𝅱", "𝅘𝅥𝅲", "♪", "♫", "♩", "𝅗𝅥", "𝅝",
143    ];
144
145    pub fn new<S: Into<PathBuf>>(path: S) -> Result<Self, io::Error> {
146        let path_buf = path.into();
147        let contents = read_to_string(&path_buf)?;
148        Ok(Chart {
149            path: path_buf,
150            contents: Chart::normalize_input(contents),
151        })
152    }
153
154    #[inline]
155    pub fn normalize_input<T: AsRef<str>>(input: T) -> T
156    where
157        T: From<String>,
158    {
159        let normalized = input
160            .as_ref()
161            .replace("\r\n", "\n") // Convert Windows newlines to Unix
162            .lines()
163            .map(|line| line.trim_end()) // Strip trailing whitespace from each line
164            .collect::<Vec<_>>()
165            .join("\n");
166
167        T::from(normalized)
168    }
169
170    fn contains_measure_lines(s: &str) -> bool {
171        for c in s.chars() {
172            if c == '|' {
173                return true;
174            }
175        }
176        false
177    }
178
179    fn contains_rhythmic(s: &str) -> bool {
180        for grapheme in s.graphemes(true) {
181            if Chart::RHYTHMIC_TRIGGERS.contains(&grapheme) {
182                return true;
183            }
184        }
185        false
186    }
187
188    fn chord_line_indexes(&self) -> Vec<usize> {
189        let mut indexes = Vec::new();
190        for (l, line) in self.contents.lines().into_iter().enumerate() {
191            if line.contains('|') {
192                indexes.push(l);
193            }
194        }
195        indexes
196    }
197
198    fn chord_lines(&self) -> Vec<String> {
199        let mut lines = Vec::new();
200        for (l, line) in self.contents.lines().into_iter().enumerate() {
201            if self.chord_line_indexes().contains(&l) {
202                lines.push(line.to_string());
203            }
204        }
205        lines
206    }
207
208    pub fn sections(&self) -> Vec<Section> {
209        let mut sections: Vec<Section> = Vec::new();
210        let section_indicators = self.section_indicators();
211        let m = 0;
212        for section_indicator in section_indicators {
213            sections.push(Section {
214                chart: &self,
215                measure_start: m,
216                measure_end: m,
217                section_indicator: section_indicator,
218            })
219        }
220        sections
221    }
222
223    pub fn measures(&self) -> Vec<Measure> {
224        let mut measures: Vec<Measure> = Vec::new();
225        let mut m = 0;
226        let chord_line_indexes = self.chord_line_indexes();
227        let lines: Vec<&str> = self.contents.lines().collect(); // collect into a Vec for indexing
228        let line_start_c = 0usize;
229        let prev_line_start_c = 0usize;
230        let next_line_start_c = 0usize;
231
232        for (l, &line) in lines.iter().enumerate() {
233            if chord_line_indexes.contains(&l) {
234                if l == 0 {
235                    continue; // avoid out-of-bounds when l == 0
236                }
237
238                let prev_line = lines[l - 1usize];
239                let next_line = lines[l + 1usize];
240                let harmonic_line: &str = line;
241                let mut lyrical_line: Option<&str> = None;
242                let mut rhythmic_line: Option<&str> = None;
243                let mut line_measure_indexes: Vec<usize> = Vec::new();
244                let prev_line_is_rhythmic = Chart::contains_rhythmic(prev_line);
245                let next_line_is_lyrical = l + 1usize < lines.len()
246                    && !Chart::contains_measure_lines(next_line)
247                    && !Chart::contains_rhythmic(next_line);
248
249                if prev_line_is_rhythmic {
250                    rhythmic_line = Some(prev_line);
251                } else {
252                    rhythmic_line = None;
253                }
254                if next_line_is_lyrical {
255                    lyrical_line = Some(next_line);
256                } else {
257                    lyrical_line = None;
258                }
259                for (c, char) in harmonic_line.graphemes(true).enumerate() {
260                    if char == "|" {
261                        line_measure_indexes.push(c)
262                    }
263                }
264
265                let max_len = [
266                    harmonic_line,
267                    lyrical_line.unwrap_or_default(),
268                    rhythmic_line.unwrap_or_default(),
269                ]
270                .iter()
271                .map(|line| line.graphemes(true).count())
272                .max()
273                .unwrap_or(0); // fallback if lines is empty
274
275                for (i, line_measure_index) in line_measure_indexes.iter().enumerate() {
276                    let mut slice_len: usize;
277                    let mut slice_start: usize;
278                    let mut next_line_measure_index: usize;
279
280                    if i + 2usize < line_measure_indexes.len() {
281                        slice_start = line_measure_indexes[i];
282                        slice_len = line_measure_indexes[i + 1usize] - slice_start;
283                        //   print!("*");
284                    } else if i + 1usize < line_measure_indexes.len() {
285                        //panic!("Happend");
286                        slice_start = line_measure_indexes[i];
287                        slice_len = max_len - slice_start; // - *line_measure_index;
288                    //  print!("!");
289                    } else {
290                        slice_start = line.len();
291                        slice_len = 1usize;
292                        // print!("?");
293                    }
294                    //println!("m: {} slice_start:{:?} slice_len:{:?} line:{:?}", m,slice_start, slice_len, line);
295                    //panic!("{}",rhythmic_line.graphemes(true));
296                    //panic!("{:?}",  rhythmic_line.unwrap_or_default().graphemes(true));
297                    let mut rhythm_slice: Option<Match> = None;
298                    let mut lyric_slice: Option<Match> = None;
299
300                    if prev_line_is_rhythmic {
301                        rhythm_slice = Some(Match {
302                            start: 0usize,
303                            end: 0usize,
304                            text: rhythmic_line
305                                .unwrap()
306                                .chars()
307                                .skip(slice_start)
308                                .take(slice_len)
309                                .collect(),
310                            source: &*self.contents,
311                        });
312                    }
313                    if next_line_is_lyrical {
314                        //panic!("{:?}",rhythm_slice);
315                        lyric_slice = Some(Match {
316                            start: 0usize,
317                            end: 0usize,
318                            text: lyrical_line
319                                .unwrap()
320                                .chars()
321                                .skip(slice_start)
322                                .take(slice_len)
323                                .collect(),
324                            source: &*self.contents,
325                        });
326                    }
327
328                    let harmonic_slice = Match {
329                        start: 0usize,
330                        end: 0usize,
331                        text: harmonic_line
332                            .chars()
333                            .skip(slice_start)
334                            .take(slice_len)
335                            .collect(),
336                        source: &*self.contents,
337                    };
338
339                    //panic!("harmonic_slice: {}", harmonic_slice);
340
341                    let measure = Measure {
342                        chart: self,
343                        index: m,
344                        line_c: slice_start as i32,
345                        line_index: i as i32,
346                        line_measure_count: line_measure_indexes.len() as i32 - 1,
347                        rhythmic: rhythm_slice,
348                        harmonic: harmonic_slice,
349                        lyrical: lyric_slice,
350                    };
351                    if measure.line_index == measure.line_measure_count {
352                    } else {
353                        measures.push(measure);
354                        m += 1;
355                    }
356                }
357
358                //panic!("path:{:?}\nline_measure_indexes{:?}\nline: {}\nl:{}\nrhythmic_line:{:?}\nharmonic_line:{:?}\nlyrical_line: {:?}",
359                //       self.path,line_measure_indexes, line, l,rhythmic_line.unwrap_or_default(), harmonic_line, lyrical_line.unwrap_or_default())
360
361                //measures.push(Measure::new(self,))
362                // You can now use chord_line, rhythm_line, etc.
363            }
364        }
365
366        measures
367    }
368
369    pub fn section_indicators(&self) -> Vec<Match> {
370        let re = Regex::new(r"\[([^\]]+)\]").unwrap();
371        //panic!("OMG {:?}",re.find_iter(&self.contents).collect::<Vec<_>>()[0].)
372        let re_matches = re.find_iter(&self.contents);
373        let mut matches: Vec<Match> = Vec::<Match>::new();
374        for re_match in re_matches {
375            matches.push(Match::new(
376                re_match.start(),
377                re_match.end(),
378                re_match.as_str(),
379                &*self.contents,
380            ))
381        }
382        matches
383    }
384
385    pub fn join_all_measures(
386        &self,
387        include_harmonic: bool,
388        include_rhythmic: bool,
389        include_lyrical: bool,
390    ) -> String {
391        let mut ret = String::new();
392        let mut multiline_block: Vec<String> = Vec::new();
393        let mut rhythmic_line = "".to_string();
394        let mut harmonic_line = "".to_string();
395        let mut lyrical_line = "".to_string();
396        let section_indicators = self.section_indicators();
397        // lines are rhythmic, harmonic, lyrical
398        for (m, measure) in self.measures().iter().enumerate() {
399            //ret += &*format!("#\"{}\"", measure.index).to_string();
400            //ret += "\n";
401            if measure.line_index == 0 {
402                multiline_block = Vec::new();
403                rhythmic_line = "".to_string();
404                harmonic_line = "".to_string();
405                lyrical_line = "".to_string();
406            }
407
408            rhythmic_line += measure.rhythmic.clone().unwrap_or_default().text.as_str();
409            lyrical_line += measure.lyrical.clone().unwrap_or_default().text.as_str();
410            harmonic_line += measure.harmonic.text.as_str();
411
412            if (measure.is_last_in_line()) {
413                if rhythmic_line.len() > 0 {
414                    multiline_block.push(rhythmic_line.clone());
415                }
416                if harmonic_line.len() > 0 {
417                    multiline_block.push(harmonic_line.clone());
418                }
419                if lyrical_line.len() > 0 {
420                    multiline_block.push(lyrical_line.clone());
421                }
422                //ret += format!("[{}-{}]\n", measure.index-measure.line_measure_count + 1,measure.index).as_str();
423                ret += multiline_block.join("\n").as_str();
424                ret += "\n";
425
426                //println!("Added block:\n{}", multiline_block.join("\n").as_str());
427            }
428        }
429        ret += &*self
430            .section_indicators()
431            .iter()
432            .map(|m| m.text.clone())
433            .collect::<Vec<String>>()
434            .join("\n");
435        ret
436    }
437}