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[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
139impl 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") .lines()
163 .map(|line| line.trim_end()) .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(); 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; }
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); 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 } else if i + 1usize < line_measure_indexes.len() {
285 slice_start = line_measure_indexes[i];
287 slice_len = max_len - slice_start; } else {
290 slice_start = line.len();
291 slice_len = 1usize;
292 }
294 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 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 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 }
364 }
365
366 measures
367 }
368
369 pub fn section_indicators(&self) -> Vec<Match> {
370 let re = Regex::new(r"\[([^\]]+)\]").unwrap();
371 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 for (m, measure) in self.measures().iter().enumerate() {
399 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 += multiline_block.join("\n").as_str();
424 ret += "\n";
425
426 }
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}