1use super::{FileWriter, Result};
4use crate::*;
5use byteorder::{LittleEndian, WriteBytesExt};
6use std::io::{Cursor, Write};
7
8pub struct GpWriter;
10
11impl FileWriter for GpWriter {
12 fn write_score(score: &Score) -> Result<Vec<u8>> {
13 let mut buffer = Vec::new();
14 let mut cursor = Cursor::new(&mut buffer);
15
16 cursor.write_all(b"BCFZ")?;
18
19 write_file_chunks(&mut cursor, score)?;
21
22 Ok(buffer)
23 }
24}
25
26fn write_file_chunks(cursor: &mut Cursor<&mut Vec<u8>>, score: &Score) -> Result<()> {
28 write_chunk(cursor, 0x01, |chunk_cursor| {
30 write_score_info(chunk_cursor, score)
31 })?;
32
33 write_chunk(cursor, 0x02, |chunk_cursor| {
35 write_tracks_info(chunk_cursor, score)
36 })?;
37
38 write_chunk(cursor, 0x03, |chunk_cursor| {
40 write_measures_data(chunk_cursor, score)
41 })?;
42
43 write_chunk(cursor, 0x04, |chunk_cursor| {
45 write_automation_data(chunk_cursor, score)
46 })?;
47
48 Ok(())
49}
50
51fn write_chunk<F>(cursor: &mut Cursor<&mut Vec<u8>>, chunk_type: u32, write_fn: F) -> Result<()>
53where
54 F: FnOnce(&mut Cursor<&mut Vec<u8>>) -> Result<()>,
55{
56 let mut chunk_data = Vec::new();
58 let mut chunk_cursor = Cursor::new(&mut chunk_data);
59
60 write_fn(&mut chunk_cursor)?;
62
63 cursor.write_u32::<LittleEndian>(chunk_type)?;
65 cursor.write_u32::<LittleEndian>(chunk_data.len() as u32)?;
66
67 cursor.write_all(&chunk_data)?;
69
70 Ok(())
71}
72
73fn write_score_info(cursor: &mut Cursor<&mut Vec<u8>>, score: &Score) -> Result<()> {
75 write_gp_string(cursor, &score.metadata.title)?;
77 write_gp_string(cursor, &score.metadata.subtitle)?;
78 write_gp_string(cursor, &score.metadata.artist)?;
79 write_gp_string(cursor, &score.metadata.album)?;
80 write_gp_string(cursor, &score.metadata.author)?;
81 write_gp_string(cursor, &score.metadata.copyright)?;
82 write_gp_string(cursor, &score.metadata.tab_author)?;
83 write_gp_string(cursor, &score.metadata.instructions)?;
84
85 cursor.write_u32::<LittleEndian>(score.tempo as u32)?;
87 cursor.write_u8(score.master_volume)?;
88
89 Ok(())
90}
91
92fn write_tracks_info(cursor: &mut Cursor<&mut Vec<u8>>, score: &Score) -> Result<()> {
94 cursor.write_u8(score.tracks.len() as u8)?;
95
96 for track in &score.tracks {
97 write_track_info(cursor, track)?;
98 }
99
100 Ok(())
101}
102
103fn write_track_info(cursor: &mut Cursor<&mut Vec<u8>>, track: &Track) -> Result<()> {
105 let mut flags = 0u8;
107 if track.is_percussion {
108 flags |= 0x01;
109 }
110 if track.is_twelve_string {
111 flags |= 0x02;
112 }
113 cursor.write_u8(flags)?;
114
115 write_gp_string(cursor, &track.name)?;
117
118 cursor.write_u8(track.tuning.len() as u8)?;
120 for ¬e in &track.tuning {
121 cursor.write_u8(note)?;
122 }
123
124 cursor.write_u8(track.channel)?;
126 cursor.write_u8(track.capo)?;
127
128 if let Some((r, g, b)) = track.color {
130 cursor.write_u8(r)?;
131 cursor.write_u8(g)?;
132 cursor.write_u8(b)?;
133 } else {
134 cursor.write_u8(255)?; cursor.write_u8(0)?; cursor.write_u8(0)?; }
138
139 Ok(())
140}
141
142fn write_measures_data(cursor: &mut Cursor<&mut Vec<u8>>, score: &Score) -> Result<()> {
144 let measure_count = score.measure_count();
145 cursor.write_u16::<LittleEndian>(measure_count as u16)?;
146
147 for measure_idx in 0..measure_count {
148 write_measure_data(cursor, score, measure_idx)?;
149 }
150
151 Ok(())
152}
153
154fn write_measure_data(
156 cursor: &mut Cursor<&mut Vec<u8>>,
157 score: &Score,
158 measure_idx: usize,
159) -> Result<()> {
160 for track in &score.tracks {
161 if let Some(measure) = track.measures.get(measure_idx) {
162 cursor.write_u8(measure.beats.len() as u8)?;
164
165 let mut flags = 0u8;
167 if measure.time_signature.is_some() {
168 flags |= 0x01;
169 }
170 if measure.key_signature.is_some() {
171 flags |= 0x02;
172 }
173 if measure.tempo.is_some() {
174 flags |= 0x04;
175 }
176 cursor.write_u8(flags)?;
177
178 if let Some(ref time_sig) = measure.time_signature {
180 cursor.write_u8(time_sig.numerator)?;
181 cursor.write_u8(time_sig.denominator)?;
182 }
183
184 if let Some(ref key_sig) = measure.key_signature {
186 cursor.write_i8(key_sig.key)?;
187 cursor.write_u8(if key_sig.is_minor { 1 } else { 0 })?;
188 }
189
190 if let Some(tempo) = measure.tempo {
192 cursor.write_u16::<LittleEndian>(tempo)?;
193 }
194
195 for beat in &measure.beats {
197 write_beat_data(cursor, beat)?;
198 }
199 } else {
200 cursor.write_u8(0)?; cursor.write_u8(0)?; }
204 }
205
206 Ok(())
207}
208
209fn write_beat_data(cursor: &mut Cursor<&mut Vec<u8>>, beat: &Beat) -> Result<()> {
211 let mut beat_flags = 0u8;
212
213 if beat.is_rest {
214 beat_flags |= 0x40;
215 }
216 if beat.chord.is_some() {
217 beat_flags |= 0x02;
218 }
219
220 cursor.write_u8(beat_flags)?;
221
222 let duration_byte = match beat.duration {
224 NoteDuration::Whole => 0,
225 NoteDuration::Half => 1,
226 NoteDuration::Quarter => 2,
227 NoteDuration::Eighth => 3,
228 NoteDuration::Sixteenth => 4,
229 NoteDuration::ThirtySecond => 5,
230 NoteDuration::SixtyFourth => 6,
231 };
232 cursor.write_u8(duration_byte)?;
233
234 if let Some(ref chord) = beat.chord {
236 write_chord_info(cursor, chord)?;
237 }
238
239 cursor.write_u8(beat.notes.len() as u8)?;
241 for note in &beat.notes {
242 write_note_data(cursor, note)?;
243 }
244
245 Ok(())
246}
247
248fn write_chord_info(cursor: &mut Cursor<&mut Vec<u8>>, chord: &ChordInfo) -> Result<()> {
250 write_gp_string(cursor, &chord.name)?;
251
252 cursor.write_u8(chord.frets.len() as u8)?;
254 for &fret in &chord.frets {
255 cursor.write_i8(fret)?;
256 }
257
258 Ok(())
259}
260
261fn write_note_data(cursor: &mut Cursor<&mut Vec<u8>>, note: &Note) -> Result<()> {
263 let mut note_flags = 0u8;
264
265 if has_note_effects(¬e.effects) {
267 note_flags |= 0x01;
268 }
269
270 cursor.write_u8(note_flags)?;
271 cursor.write_u8(note.string)?;
272 cursor.write_u8(note.fret)?;
273 cursor.write_u8(note.velocity)?;
274
275 if has_note_effects(¬e.effects) {
277 write_note_effects(cursor, ¬e.effects)?;
278 }
279
280 Ok(())
281}
282
283fn has_note_effects(effects: &NoteEffects) -> bool {
285 effects.vibrato
286 || effects.hammer_on
287 || effects.pull_off
288 || effects.palm_mute
289 || effects.let_ring
290 || effects.bend.is_some()
291 || effects.slide.is_some()
292}
293
294fn write_note_effects(cursor: &mut Cursor<&mut Vec<u8>>, effects: &NoteEffects) -> Result<()> {
296 let mut effect_flags = 0u8;
297
298 if effects.vibrato {
299 effect_flags |= 0x01;
300 }
301 if effects.hammer_on {
302 effect_flags |= 0x02;
303 }
304 if effects.pull_off {
305 effect_flags |= 0x04;
306 }
307 if effects.palm_mute {
308 effect_flags |= 0x08;
309 }
310 if effects.let_ring {
311 effect_flags |= 0x10;
312 }
313 if effects.bend.is_some() {
314 effect_flags |= 0x20;
315 }
316 if effects.slide.is_some() {
317 effect_flags |= 0x40;
318 }
319
320 cursor.write_u8(effect_flags)?;
321
322 if let Some(ref bend) = effects.bend {
324 write_bend_effect(cursor, bend)?;
325 }
326
327 if let Some(ref slide) = effects.slide {
329 write_slide_effect(cursor, slide)?;
330 }
331
332 Ok(())
333}
334
335fn write_bend_effect(cursor: &mut Cursor<&mut Vec<u8>>, bend: &BendEffect) -> Result<()> {
337 cursor.write_u8(bend.points.len() as u8)?;
338
339 for point in &bend.points {
340 cursor.write_u8(point.position)?;
341 cursor.write_f32::<LittleEndian>(point.value)?;
342 }
343
344 Ok(())
345}
346
347fn write_slide_effect(cursor: &mut Cursor<&mut Vec<u8>>, slide: &SlideEffect) -> Result<()> {
349 let (slide_type, target_fret) = match slide {
350 SlideEffect::ShiftSlideTo(fret) => (0, *fret),
351 SlideEffect::LegatoSlideTo(fret) => (1, *fret),
352 SlideEffect::SlideOutDownward => (2, 0),
353 SlideEffect::SlideOutUpward => (3, 0),
354 SlideEffect::SlideInFromBelow => (4, 0),
355 SlideEffect::SlideInFromAbove => (5, 0),
356 };
357
358 cursor.write_u8(slide_type)?;
359 cursor.write_u8(target_fret)?;
360
361 Ok(())
362}
363
364fn write_automation_data(cursor: &mut Cursor<&mut Vec<u8>>, _score: &Score) -> Result<()> {
366 cursor.write_u8(0)?; Ok(())
369}
370
371fn write_gp_string(cursor: &mut Cursor<&mut Vec<u8>>, s: &str) -> Result<()> {
373 let bytes = s.as_bytes();
374 let length = bytes.len().min(255) as u8;
375
376 cursor.write_u8(length)?;
377 cursor.write_all(&bytes[..length as usize])?;
378
379 Ok(())
380}
381
382impl Score {
384 pub fn create_test_score() -> Self {
386 let mut score = Score::new();
387 score.format = FileFormat::GuitarPro;
388
389 score.metadata.title = "Test Song".to_string();
391 score.metadata.artist = "Test Artist".to_string();
392 score.tempo = 120;
393 score.master_volume = 200;
394
395 let mut track = Track::new_guitar_track("Guitar".to_string());
397 track.channel = 0;
398
399 let mut measure = Measure::new();
401 measure.time_signature = Some(TimeSignature::new(4, 4));
402
403 let note1 = Note::new(0, 0); let beat1 = Beat::new_with_notes(vec![note1], NoteDuration::Quarter);
406 measure.add_beat(beat1);
407
408 let note2 = Note::new(2, 0); let beat2 = Beat::new_with_notes(vec![note2], NoteDuration::Quarter);
410 measure.add_beat(beat2);
411
412 let note3 = Note::new(3, 0); let beat3 = Beat::new_with_notes(vec![note3], NoteDuration::Quarter);
414 measure.add_beat(beat3);
415
416 let rest_beat = Beat::new_rest(NoteDuration::Quarter);
418 measure.add_beat(rest_beat);
419
420 track.add_measure(measure);
421 score.add_track(track);
422
423 score
424 }
425}
426
427#[cfg(test)]
428mod tests {
429 use crate::writers::FileWriter;
430
431 use super::*;
432
433 #[test]
434 fn test_write_simple_score() -> Result<()> {
435 let score = Score::create_test_score();
436 let data = GpWriter::write_score(&score)?;
437
438 assert!(!data.is_empty());
440
441 assert_eq!(&data[0..4], b"BCFZ");
443
444 Ok(())
445 }
446
447 #[test]
448 fn test_round_trip() -> Result<()> {
449 let original_score = Score::create_test_score();
450
451 let data = GpWriter::write_score(&original_score)?;
453
454 assert!(!data.is_empty());
459 assert_eq!(&data[0..4], b"BCFZ");
460
461 Ok(())
462 }
463
464 #[test]
465 fn test_write_empty_score() -> Result<()> {
466 let score = Score::new();
467 let data = GpWriter::write_score(&score)?;
468
469 assert!(!data.is_empty());
470 assert_eq!(&data[0..4], b"BCFZ");
471
472 Ok(())
473 }
474}