oxihuman_export/
abc_notation_export.rs1#![allow(dead_code)]
4
5#[derive(Debug, Clone, Default)]
9pub struct AbcTuneHeader {
10 pub index: u32,
11 pub title: String,
12 pub composer: String,
13 pub meter: String,
14 pub default_note_length: String,
15 pub tempo: String,
16 pub key: String,
17}
18
19impl AbcTuneHeader {
20 pub fn new(
21 index: u32,
22 title: impl Into<String>,
23 composer: impl Into<String>,
24 meter: impl Into<String>,
25 key: impl Into<String>,
26 ) -> Self {
27 Self {
28 index,
29 title: title.into(),
30 composer: composer.into(),
31 meter: meter.into(),
32 default_note_length: "1/4".to_string(),
33 tempo: "120".to_string(),
34 key: key.into(),
35 }
36 }
37
38 pub fn to_abc_header(&self) -> String {
39 let mut s = String::new();
40 s.push_str(&format!("X:{}\n", self.index));
41 s.push_str(&format!("T:{}\n", self.title));
42 if !self.composer.is_empty() {
43 s.push_str(&format!("C:{}\n", self.composer));
44 }
45 s.push_str(&format!("M:{}\n", self.meter));
46 s.push_str(&format!("L:{}\n", self.default_note_length));
47 s.push_str(&format!("Q:{}\n", self.tempo));
48 s.push_str(&format!("K:{}\n", self.key));
49 s
50 }
51}
52
53#[derive(Debug, Clone)]
55pub struct AbcNote {
56 pub pitch: String,
57 pub duration_modifier: String,
58}
59
60impl AbcNote {
61 pub fn new(pitch: impl Into<String>) -> Self {
62 Self {
63 pitch: pitch.into(),
64 duration_modifier: String::new(),
65 }
66 }
67
68 pub fn with_duration(mut self, modifier: impl Into<String>) -> Self {
69 self.duration_modifier = modifier.into();
70 self
71 }
72
73 pub fn to_abc_token(&self) -> String {
74 format!("{}{}", self.pitch, self.duration_modifier)
75 }
76}
77
78#[derive(Debug, Clone, Default)]
80pub struct AbcTuneBody {
81 pub bars: Vec<Vec<AbcNote>>,
82}
83
84impl AbcTuneBody {
85 pub fn new() -> Self {
86 Self { bars: Vec::new() }
87 }
88
89 pub fn add_bar(&mut self, notes: Vec<AbcNote>) {
90 self.bars.push(notes);
91 }
92
93 pub fn to_abc_body(&self) -> String {
94 self.bars
95 .iter()
96 .map(|bar| {
97 let tokens: String = bar
98 .iter()
99 .map(|n| n.to_abc_token())
100 .collect::<Vec<_>>()
101 .join(" ");
102 format!("{} |", tokens)
103 })
104 .collect::<Vec<_>>()
105 .join("\n")
106 }
107}
108
109#[derive(Debug, Clone, Default)]
111pub struct AbcTune {
112 pub header: AbcTuneHeader,
113 pub body: AbcTuneBody,
114}
115
116impl AbcTune {
117 pub fn new(header: AbcTuneHeader) -> Self {
118 Self {
119 header,
120 body: AbcTuneBody::new(),
121 }
122 }
123}
124
125pub fn generate_abc_notation(tune: &AbcTune) -> String {
127 format!(
128 "{}\n{}\n",
129 tune.header.to_abc_header(),
130 tune.body.to_abc_body()
131 )
132}
133
134pub fn is_valid_abc(src: &str) -> bool {
136 src.contains("X:") && src.contains("T:") && src.contains("K:")
137}
138
139pub fn count_abc_notes(body: &AbcTuneBody) -> usize {
141 body.bars.iter().map(|b| b.len()).sum()
142}
143
144pub fn c_major_scale_abc() -> AbcTune {
146 let header = AbcTuneHeader::new(1, "C Major Scale", "", "4/4", "C");
147 let mut tune = AbcTune::new(header);
148 let notes = ["C", "D", "E", "F", "G", "A", "B", "c"];
149 let bar1: Vec<AbcNote> = notes[0..4].iter().map(|&n| AbcNote::new(n)).collect();
150 let bar2: Vec<AbcNote> = notes[4..8].iter().map(|&n| AbcNote::new(n)).collect();
151 tune.body.add_bar(bar1);
152 tune.body.add_bar(bar2);
153 tune
154}
155
156#[cfg(test)]
157mod tests {
158 use super::*;
159
160 #[test]
161 fn test_header_x_field() {
162 let h = AbcTuneHeader::new(1, "Test", "", "4/4", "G");
163 let abc = h.to_abc_header();
164 assert!(abc.contains("X:1") );
165 }
166
167 #[test]
168 fn test_header_key_field() {
169 let h = AbcTuneHeader::new(1, "Test", "", "4/4", "D");
170 let abc = h.to_abc_header();
171 assert!(abc.contains("K:D") );
172 }
173
174 #[test]
175 fn test_abc_note_token() {
176 let note = AbcNote::new("C").with_duration("2");
177 assert_eq!(note.to_abc_token(), "C2" );
178 }
179
180 #[test]
181 fn test_abc_note_no_modifier() {
182 let note = AbcNote::new("G");
183 assert_eq!(note.to_abc_token(), "G" );
184 }
185
186 #[test]
187 fn test_generate_abc_valid() {
188 let tune = c_major_scale_abc();
189 let abc = generate_abc_notation(&tune);
190 assert!(is_valid_abc(&abc) );
191 }
192
193 #[test]
194 fn test_count_abc_notes() {
195 let tune = c_major_scale_abc();
196 assert_eq!(count_abc_notes(&tune.body), 8 );
197 }
198
199 #[test]
200 fn test_is_valid_abc_false() {
201 assert!(!is_valid_abc("not abc notation") );
202 }
203
204 #[test]
205 fn test_body_contains_bar_separator() {
206 let tune = c_major_scale_abc();
207 let body = tune.body.to_abc_body();
208 assert!(body.contains('|') );
209 }
210
211 #[test]
212 fn test_composer_in_header() {
213 let h = AbcTuneHeader::new(1, "Test", "J.S. Bach", "3/4", "Am");
214 let abc = h.to_abc_header();
215 assert!(abc.contains("C:J.S. Bach") );
216 }
217}