guitarpro/
lib.rs

1pub mod analysis;
2pub mod comparison;
3pub mod error;
4pub mod formats;
5pub mod parsers;
6pub mod utils;
7pub mod writers;
8
9use crate::formats::FileFormat;
10
11/// Note duration
12#[derive(Debug, Clone, PartialEq)]
13pub enum NoteDuration {
14    Whole,
15    Half,
16    Quarter,
17    Eighth,
18    Sixteenth,
19    ThirtySecond,
20    SixtyFourth,
21}
22
23impl NoteDuration {
24    /// Get the duration value as a fraction of a whole note
25    pub fn as_fraction(&self) -> f64 {
26        match self {
27            NoteDuration::Whole => 1.0,
28            NoteDuration::Half => 0.5,
29            NoteDuration::Quarter => 0.25,
30            NoteDuration::Eighth => 0.125,
31            NoteDuration::Sixteenth => 0.0625,
32            NoteDuration::ThirtySecond => 0.03125,
33            NoteDuration::SixtyFourth => 0.015625,
34        }
35    }
36}
37
38/// Bend point
39#[derive(Debug, Clone, PartialEq)]
40pub struct BendPoint {
41    pub position: u8, // 0-100
42    pub value: f32,   // Semitones
43}
44
45/// Bend effect
46#[derive(Debug, Clone, PartialEq)]
47pub struct BendEffect {
48    pub points: Vec<BendPoint>,
49}
50
51/// Slide effect
52#[derive(Debug, Clone, PartialEq)]
53pub enum SlideEffect {
54    ShiftSlideTo(u8),
55    LegatoSlideTo(u8),
56    SlideOutDownward,
57    SlideOutUpward,
58    SlideInFromBelow,
59    SlideInFromAbove,
60}
61
62/// Note effects
63#[derive(Debug, Clone, PartialEq, Default)]
64pub struct NoteEffects {
65    pub bend: Option<BendEffect>,
66    pub slide: Option<SlideEffect>,
67    pub vibrato: bool,
68    pub hammer_on: bool,
69    pub pull_off: bool,
70    pub palm_mute: bool,
71    pub let_ring: bool,
72}
73
74// impl Default for NoteEffects {
75//     fn default() -> Self {
76//         Self {
77//             bend: None,
78//             slide: None,
79//             vibrato: false,
80//             hammer_on: false,
81//             pull_off: false,
82//             palm_mute: false,
83//             let_ring: false,
84//         }
85//     }
86// }
87
88/// Note representation
89#[derive(Debug, Clone, PartialEq)]
90pub struct Note {
91    pub fret: u8,
92    pub string: u8,
93    pub velocity: u8,
94    pub duration: NoteDuration,
95    pub effects: NoteEffects,
96}
97
98impl Note {
99    /// Create a new note
100    pub fn new(fret: u8, string: u8) -> Self {
101        Self {
102            fret,
103            string,
104            velocity: 100,
105            duration: NoteDuration::Quarter,
106            effects: NoteEffects::default(),
107        }
108    }
109
110    /// Create a new note with effects
111    pub fn new_with_effects(fret: u8, string: u8, effects: NoteEffects) -> Self {
112        Self {
113            fret,
114            string,
115            velocity: 100,
116            duration: NoteDuration::Quarter,
117            effects,
118        }
119    }
120
121    /// Get the actual MIDI note value given a tuning
122    pub fn to_midi_note(&self, tuning: &[u8]) -> Option<u8> {
123        if (self.string as usize) < tuning.len() {
124            Some(tuning[self.string as usize] + self.fret)
125        } else {
126            None
127        }
128    }
129}
130
131/// Chord types
132#[derive(Debug, Clone, PartialEq)]
133pub enum ChordType {
134    Major,
135    Minor,
136    Dominant7,
137    Major7,
138    Minor7,
139    Diminished,
140    Augmented,
141    Sus2,
142    Sus4,
143    Power,
144    Custom(String),
145}
146
147/// Chord information
148#[derive(Debug, Clone, PartialEq)]
149pub struct ChordInfo {
150    pub name: String,
151    pub root_note: u8, // 0-11 (C-B)
152    pub chord_type: ChordType,
153    pub bass_note: Option<u8>,
154    pub frets: Vec<i8>, // -1 for muted, 0+ for fret
155}
156
157impl ChordInfo {
158    /// Create a new chord
159    pub fn new(name: String, root_note: u8, chord_type: ChordType) -> Self {
160        Self {
161            name,
162            root_note,
163            chord_type,
164            bass_note: None,
165            frets: Vec::new(),
166        }
167    }
168}
169
170/// Beat representation
171#[derive(Debug, Clone, PartialEq)]
172pub struct Beat {
173    pub notes: Vec<Note>,
174    pub duration: NoteDuration,
175    pub is_rest: bool,
176    pub chord: Option<ChordInfo>,
177}
178
179impl Beat {
180    /// Create a new rest beat
181    pub fn new_rest(duration: NoteDuration) -> Self {
182        Self {
183            notes: Vec::new(),
184            duration,
185            is_rest: true,
186            chord: None,
187        }
188    }
189
190    /// Create a new beat with notes
191    pub fn new_with_notes(notes: Vec<Note>, duration: NoteDuration) -> Self {
192        Self {
193            notes,
194            duration,
195            is_rest: false,
196            chord: None,
197        }
198    }
199
200    /// Add a note to the beat
201    pub fn add_note(&mut self, note: Note) {
202        self.notes.push(note);
203        self.is_rest = false;
204    }
205}
206
207/// Time signature
208#[derive(Debug, Clone, PartialEq)]
209pub struct TimeSignature {
210    pub numerator: u8,
211    pub denominator: u8,
212}
213
214impl Default for TimeSignature {
215    fn default() -> Self {
216        Self {
217            numerator: 4,
218            denominator: 4,
219        }
220    }
221}
222
223impl TimeSignature {
224    /// Create a new time signature
225    pub fn new(numerator: u8, denominator: u8) -> Self {
226        Self {
227            numerator,
228            denominator,
229        }
230    }
231
232    /// Get the number of beats per measure
233    pub fn beats_per_measure(&self) -> f64 {
234        self.numerator as f64 * (4.0 / self.denominator as f64)
235    }
236}
237
238/// Key signature
239#[derive(Debug, Default, Clone, PartialEq)]
240pub struct KeySignature {
241    pub key: i8, // -7 to 7 (flats to sharps)
242    pub is_minor: bool,
243}
244
245// impl Default for KeySignature {
246//     fn default() -> Self {
247//         Self {
248//             key: 0, // C major
249//             is_minor: false,
250//         }
251//     }
252// }
253
254impl KeySignature {
255    /// Create a new key signature
256    pub fn new(key: i8, is_minor: bool) -> Self {
257        Self { key, is_minor }
258    }
259
260    /// Get the key name
261    pub fn key_name(&self) -> String {
262        let key_names = ["C", "G", "D", "A", "E", "B", "F#", "C#"];
263        let flat_names = ["C", "F", "Bb", "Eb", "Ab", "Db", "Gb", "Cb"];
264
265        let name = if self.key >= 0 {
266            key_names[self.key as usize]
267        } else {
268            flat_names[(-self.key) as usize]
269        };
270
271        if self.is_minor {
272            format!("{}m", name)
273        } else {
274            name.to_string()
275        }
276    }
277}
278
279/// Measure
280#[derive(Debug, Default, Clone)]
281pub struct Measure {
282    pub beats: Vec<Beat>,
283    pub time_signature: Option<TimeSignature>,
284    pub key_signature: Option<KeySignature>,
285    pub tempo: Option<u16>,
286}
287
288impl Measure {
289    /// Create a new empty measure
290    pub fn new() -> Self {
291        Self {
292            beats: Vec::new(),
293            time_signature: None,
294            key_signature: None,
295            tempo: None,
296        }
297    }
298
299    /// Add a beat to the measure
300    pub fn add_beat(&mut self, beat: Beat) {
301        self.beats.push(beat);
302    }
303
304    /// Get the total duration of the measure
305    pub fn total_duration(&self) -> f64 {
306        self.beats
307            .iter()
308            .map(|beat| beat.duration.as_fraction())
309            .sum()
310    }
311}
312
313/// Track
314#[derive(Debug, Clone)]
315pub struct Track {
316    pub name: String,
317    pub channel: u8,
318    pub measures: Vec<Measure>,
319    pub tuning: Vec<u8>, // MIDI note numbers
320    pub is_percussion: bool,
321    pub is_twelve_string: bool,
322    pub capo: u8,
323    pub color: Option<(u8, u8, u8)>,
324}
325
326impl Track {
327    /// Create a new track with standard guitar tuning
328    pub fn new_guitar_track(name: String) -> Self {
329        Self {
330            name,
331            channel: 0,
332            measures: Vec::new(),
333            tuning: vec![40, 45, 50, 55, 59, 64], // Standard guitar tuning (E-A-D-G-B-E)
334            is_percussion: false,
335            is_twelve_string: false,
336            capo: 0,
337            color: None,
338        }
339    }
340
341    /// Create a new bass track with standard bass tuning
342    pub fn new_bass_track(name: String) -> Self {
343        Self {
344            name,
345            channel: 1,
346            measures: Vec::new(),
347            tuning: vec![28, 33, 38, 43], // Standard bass tuning (E-A-D-G)
348            is_percussion: false,
349            is_twelve_string: false,
350            capo: 0,
351            color: None,
352        }
353    }
354
355    /// Create a new percussion track
356    pub fn new_percussion_track(name: String) -> Self {
357        Self {
358            name,
359            channel: 9, // Standard MIDI percussion channel
360            measures: Vec::new(),
361            tuning: Vec::new(),
362            is_percussion: true,
363            is_twelve_string: false,
364            capo: 0,
365            color: None,
366        }
367    }
368
369    /// Add a measure to the track
370    pub fn add_measure(&mut self, measure: Measure) {
371        self.measures.push(measure);
372    }
373
374    /// Get the number of strings for this track
375    pub fn string_count(&self) -> usize {
376        self.tuning.len()
377    }
378
379    /// Get the effective tuning (accounting for capo)
380    pub fn effective_tuning(&self) -> Vec<u8> {
381        self.tuning.iter().map(|&note| note + self.capo).collect()
382    }
383}
384
385/// Score metadata
386#[derive(Debug, Default, Clone)]
387pub struct ScoreMetadata {
388    pub title: String,
389    pub subtitle: String,
390    pub artist: String,
391    pub album: String,
392    pub author: String,
393    pub copyright: String,
394    pub tab_author: String,
395    pub instructions: String,
396    pub notices: Vec<String>,
397}
398
399// impl Default for ScoreMetadata {
400//     fn default() -> Self {
401//         Self {
402//             title: String::new(),
403//             subtitle: String::new(),
404//             artist: String::new(),
405//             album: String::new(),
406//             author: String::new(),
407//             copyright: String::new(),
408//             tab_author: String::new(),
409//             instructions: String::new(),
410//             notices: Vec::new(),
411//         }
412//     }
413// }
414
415/// Main score structure
416#[derive(Debug, Clone)]
417pub struct Score {
418    pub metadata: ScoreMetadata,
419    pub tracks: Vec<Track>,
420    pub master_volume: u8,
421    pub tempo: u16,
422    pub format: FileFormat,
423}
424
425impl Default for Score {
426    fn default() -> Self {
427        Self {
428            metadata: Default::default(),
429            tracks: Default::default(),
430            master_volume: Default::default(),
431            tempo: Default::default(),
432            format: FileFormat::GuitarPro5,
433        }
434    }
435}
436
437impl Score {
438    /// Create a new empty score
439    pub fn new() -> Self {
440        Self {
441            metadata: ScoreMetadata::default(),
442            tracks: Vec::new(),
443            master_volume: 200,
444            tempo: 120,
445            format: FileFormat::GuitarPro5,
446        }
447    }
448
449    /// Add a track to the score
450    pub fn add_track(&mut self, track: Track) {
451        self.tracks.push(track);
452    }
453
454    /// Get track by index
455    pub fn get_track(&self, index: usize) -> Option<&Track> {
456        self.tracks.get(index)
457    }
458
459    /// Get mutable track by index
460    pub fn get_track_mut(&mut self, index: usize) -> Option<&mut Track> {
461        self.tracks.get_mut(index)
462    }
463
464    /// Get total duration in beats (based on longest track)
465    pub fn get_total_beats(&self) -> u32 {
466        self.tracks
467            .iter()
468            .map(|track| {
469                track
470                    .measures
471                    .iter()
472                    .map(|measure| measure.total_duration() as u32)
473                    .sum::<u32>()
474            })
475            .max()
476            .unwrap_or(0)
477    }
478
479    /// Get the number of measures (based on longest track)
480    pub fn measure_count(&self) -> usize {
481        self.tracks
482            .iter()
483            .map(|track| track.measures.len())
484            .max()
485            .unwrap_or(0)
486    }
487}
488
489// /// Time signature representation
490// #[derive(Debug, Clone, PartialEq)]
491// pub struct TimeSignature {
492//     pub numerator: u8,
493//     pub denominator: u8,
494// }
495
496// /// Key signature representation
497// #[derive(Debug, Clone, PartialEq)]
498// pub struct KeySignature {
499//     pub key: i8,     // -7 to 7 (flats to sharps)
500//     pub minor: bool, // true for minor keys
501// }
502
503// /// Note representation
504// #[derive(Debug, Clone, PartialEq)]
505// pub struct Note {
506//     pub fret: u8,
507//     pub string: u8,
508//     pub velocity: u8,
509//     pub duration: NoteDuration,
510//     pub effects: NoteEffects,
511// }
512
513// /// Note duration enumeration
514// #[derive(Debug, Clone, PartialEq)]
515// pub enum NoteDuration {
516//     Whole,
517//     Half,
518//     Quarter,
519//     Eighth,
520//     Sixteenth,
521//     ThirtySecond,
522//     SixtyFourth,
523// }
524
525// /// Note effects (bends, slides, etc.)
526// /// Note effects
527// #[derive(Debug, Clone, PartialEq)]
528// pub struct NoteEffects {
529//     pub bend: Option<BendEffect>,
530//     pub slide: Option<SlideEffect>,
531//     pub vibrato: bool,
532//     pub hammer_on: bool,
533//     pub pull_off: bool,
534//     pub palm_mute: bool,
535//     pub let_ring: bool,
536// }
537
538// /// Bend effect
539// #[derive(Debug, Clone, PartialEq)]
540// pub struct BendEffect {
541//     pub points: Vec<BendPoint>,
542// }
543
544// /// Bend point
545// #[derive(Debug, Clone, PartialEq)]
546// pub struct BendPoint {
547//     pub position: u8,  // 0-100
548//     pub value: f32,    // Semitones
549// }
550
551// #[derive(Debug, Clone, PartialEq)]
552// pub enum SlideEffect {
553//     ShiftSlideTo(u8),
554//     LegatoSlideTo(u8),
555//     SlideOutDownward,
556//     SlideOutUpward,
557//     SlideInFromBelow,
558//     SlideInFromAbove,
559// }
560
561// /// Beat in a measure
562// #[derive(Debug, Clone, PartialEq)]
563// pub struct Beat {
564//     pub notes: Vec<Note>,
565//     pub duration: NoteDuration,
566//     pub is_rest: bool,
567//     pub chord: Option<ChordInfo>,
568// }
569
570// /// Chord information
571// #[derive(Debug, Clone, PartialEq)]
572// pub struct ChordInfo {
573//     pub name: String,
574//     pub root_note: u8,  // 0-11 (C-B)
575//     pub chord_type: ChordType,
576//     pub bass_note: Option<u8>,
577//     pub frets: Vec<i8>,  // -1 for muted, 0+ for fret
578// }
579
580// #[repr(u8)]
581// #[derive(Debug, Clone, PartialEq)]
582// pub enum ChordType {
583//     Major,
584//     Minor,
585//     Dominant7,
586//     Major7,
587//     Minor7,
588//     Diminished,
589//     Augmented,
590//     Sus2,
591//     Sus4,
592//     Power,
593//     Custom(String),
594// }
595
596// /// Measure
597// #[derive(Debug, Clone)]
598// pub struct Measure {
599//     pub beats: Vec<Beat>,
600//     pub time_signature: Option<TimeSignature>,
601//     pub key_signature: Option<KeySignature>,
602//     pub tempo: Option<u16>,
603// }
604
605// /// Track/instrument information
606// #[derive(Debug, Clone)]
607// pub struct Track {
608//     pub name: String,
609//     pub channel: u8,
610//     pub measures: Vec<Measure>,
611//     pub tuning: Vec<u8>,  // MIDI note numbers
612//     pub is_percussion: bool,
613//     pub is_twelve_string: bool,
614//     pub capo: u8,
615//     pub color: Option<(u8, u8, u8)>,
616// }
617
618// /// Complete score representation
619// /// Score metadata
620// #[derive(Debug, Clone)]
621// pub struct ScoreMetadata {
622//     pub title: String,
623//     pub subtitle: String,
624//     pub artist: String,
625//     pub album: String,
626//     pub author: String,
627//     pub copyright: String,
628//     pub tab_author: String,
629//     pub instructions: String,
630//     pub notices: Vec<String>,
631// }
632
633// /// Main score structure
634// #[derive(Debug, Clone)]
635// pub struct Score {
636//     pub metadata: ScoreMetadata,
637//     pub tracks: Vec<Track>,
638//     pub master_volume: u8,
639//     pub tempo: u16,
640//     pub format: FileFormat,
641// }
642
643// /// Comparison result for scores
644// #[derive(Debug, Clone, PartialEq)]
645// pub enum ScoreComparisonResult {
646//     /// Scores are identical in musical content and metadata
647//     Identical,
648//     /// Scores have same musical content but different metadata
649//     SameContent {
650//         metadata_differences: Vec<MetadataDifference>,
651//     },
652//     /// Scores have same structure but different notes/timing
653//     SameStructure {
654//         musical_differences: Vec<MusicalDifference>,
655//     },
656//     /// Scores are completely different
657//     Different {
658//         major_differences: Vec<MajorDifference>,
659//     },
660// }
661
662// /// Metadata differences between scores
663// #[derive(Debug, Clone, PartialEq)]
664// pub enum MetadataDifference {
665//     Title(String, String),
666//     Artist(String, String),
667//     Album(String, String),
668//     Tempo(u16, u16),
669//     MasterVolume(u8, u8),
670//     TrackName {
671//         track_index: usize,
672//         old: String,
673//         new: String,
674//     },
675// }
676
677// /// Musical differences between scores
678// #[derive(Debug, Clone, PartialEq)]
679// pub enum MusicalDifference {
680//     NoteChange {
681//         track_index: usize,
682//         measure_index: usize,
683//         beat_index: usize,
684//         note_index: usize,
685//         old_note: Note,
686//         new_note: Note,
687//     },
688//     BeatChange {
689//         track_index: usize,
690//         measure_index: usize,
691//         beat_index: usize,
692//         old_beat: Beat,
693//         new_beat: Beat,
694//     },
695//     TimeSignatureChange {
696//         track_index: usize,
697//         measure_index: usize,
698//         old: Option<TimeSignature>,
699//         new: Option<TimeSignature>,
700//     },
701//     KeySignatureChange {
702//         track_index: usize,
703//         measure_index: usize,
704//         old: Option<KeySignature>,
705//         new: Option<KeySignature>,
706//     },
707// }
708
709// /// Major structural differences
710// #[derive(Debug, Clone, PartialEq)]
711// pub enum MajorDifference {
712//     TrackCountDifference(usize, usize),
713//     MeasureCountDifference {
714//         track_index: usize,
715//         old_count: usize,
716//         new_count: usize,
717//     },
718//     StringTuningDifference {
719//         track_index: usize,
720//         old_tuning: Vec<u8>,
721//         new_tuning: Vec<u8>,
722//     },
723//     TrackTypeDifference {
724//         track_index: usize,
725//         old_is_percussion: bool,
726//         new_is_percussion: bool,
727//     },
728// }
729
730// #[cfg(test)]
731// mod tests {
732//     use super::*;
733
734//     #[test]
735//     fn test_empty_score_creation() {
736//         let score = Score {
737//             title: "Test Song".to_string(),
738//             artist: "Test Artist".to_string(),
739//             album: "Test Album".to_string(),
740//             tempo: 120,
741//             tracks: vec![],
742//             master_volume: 127,
743//         };
744
745//         assert_eq!(score.title, "Test Song");
746//         assert_eq!(score.tempo, 120);
747//     }
748
749//     #[test]
750//     fn test_score_comparison_identical() {
751//         let score1 = create_test_score();
752//         let score2 = score1.clone();
753
754//         let result = score1.compare(&score2);
755//         assert_eq!(result, ScoreComparisonResult::Identical);
756//         assert!(score1.has_same_musical_content(&score2));
757//         assert!(score1.is_musically_equivalent(&score2));
758//     }
759
760//     #[test]
761//     fn test_score_comparison_metadata_only() {
762//         let mut score1 = create_test_score();
763//         let mut score2 = score1.clone();
764//         score2.title = "Different Title".to_string();
765
766//         let result = score1.compare(&score2);
767//         match result {
768//             ScoreComparisonResult::SameContent {
769//                 metadata_differences,
770//             } => {
771//                 assert_eq!(metadata_differences.len(), 1);
772//             }
773//             _ => panic!("Expected SameContent result"),
774//         }
775
776//         assert!(score1.has_same_musical_content(&score2));
777//         assert!(score1.is_musically_equivalent(&score2));
778//     }
779
780//     #[test]
781//     fn test_score_comparison_different_tracks() {
782//         let mut score1 = create_test_score();
783//         let mut score2 = score1.clone();
784//         score2.tracks.pop(); // Remove a track
785
786//         let result = score1.compare(&score2);
787//         match result {
788//             ScoreComparisonResult::Different { major_differences } => {
789//                 assert!(!major_differences.is_empty());
790//             }
791//             _ => panic!("Expected Different result"),
792//         }
793
794//         assert!(!score1.has_same_musical_content(&score2));
795//         assert!(!score1.is_musically_equivalent(&score2));
796//     }
797
798//     #[test]
799//     fn test_difference_summary() {
800//         let score1 = create_test_score();
801//         let mut score2 = score1.clone();
802//         score2.artist = "Different Artist".to_string();
803
804//         let summary = score1.difference_summary(&score2);
805//         assert!(summary.contains("metadata differences"));
806//     }
807
808//     fn create_test_score() -> Score {
809//         Score {
810//             title: "Test Song".to_string(),
811//             artist: "Test Artist".to_string(),
812//             album: "Test Album".to_string(),
813//             tempo: 120,
814//             tracks: vec![Track {
815//                 name: "Guitar".to_string(),
816//                 channel: 1,
817//                 strings: vec![64, 59, 55, 50, 45, 40],
818//                 measures: vec![Measure {
819//                     beats: vec![Beat {
820//                         notes: vec![Note {
821//                             fret: 3,
822//                             string: 1,
823//                             duration: NoteDuration::Quarter,
824//                             effects: NoteEffects::default(),
825//                         }],
826//                         duration: NoteDuration::Quarter,
827//                         rest: false,
828//                     }],
829//                     time_signature: Some(TimeSignature {
830//                         numerator: 4,
831//                         denominator: 4,
832//                     }),
833//                     key_signature: None,
834//                 }],
835//                 is_percussion: false,
836//             }],
837//             master_volume: 127,
838//         }
839//     }
840// }
841
842// // Example usage:
843// /*
844// use std::fs;
845
846// fn main() -> Result<(), Box<dyn std::error::Error>> {
847//     // Read files as bytes (async or sync, user's choice)
848//     let gp3_data = fs::read("song.gp3")?;
849//     let gp4_data = fs::read("song.gp4")?;
850
851//     // Parse both files
852//     let gp3_score = MusicParser::parse(gp3_data)?;
853//     let gp4_score = MusicParser::parse(gp4_data)?;
854
855//     // Compare the scores
856//     let comparison = gp3_score.compare(&gp4_score);
857
858//     match comparison {
859//         ScoreComparisonResult::Identical => {
860//             println!("Files contain identical musical data!");
861//         },
862//         ScoreComparisonResult::SameContent { metadata_differences } => {
863//             println!("Same music, different metadata:");
864//             for diff in metadata_differences {
865//                 match diff {
866//                     MetadataDifference::Title(old, new) => {
867//                         println!("  Title: '{}' -> '{}'", old, new);
868//                     },
869//                     MetadataDifference::Artist(old, new) => {
870//                         println!("  Artist: '{}' -> '{}'", old, new);
871//                     },
872//                     // Handle other metadata differences...
873//                     _ => println!("  Other metadata difference: {:?}", diff),
874//                 }
875//             }
876//         },
877//         ScoreComparisonResult::SameStructure { musical_differences } => {
878//             println!("Same structure, {} musical differences", musical_differences.len());
879//         },
880//         ScoreComparisonResult::Different { major_differences } => {
881//             println!("Completely different scores: {} major differences", major_differences.len());
882//         }
883//     }
884
885//     // Quick checks
886//     if gp3_score.has_same_musical_content(&gp4_score) {
887//         println!("Both files represent the same song!");
888//     }
889
890//     if gp3_score.is_musically_equivalent(&gp4_score) {
891//         println!("Musically equivalent (ignoring metadata)");
892//     }
893
894//     // Get summary
895//     println!("Summary: {}", gp3_score.difference_summary(&gp4_score));
896
897//     Ok(())
898// }
899// */
900#[cfg(test)]
901mod tests {
902    use crate::*;
903
904    #[test]
905    fn test_note_duration_conversion() {
906        assert_eq!(NoteDuration::Whole.as_fraction(), 1.0);
907        assert_eq!(NoteDuration::Half.as_fraction(), 0.5);
908        assert_eq!(NoteDuration::Quarter.as_fraction(), 0.25);
909        assert_eq!(NoteDuration::Eighth.as_fraction(), 0.125);
910    }
911
912    #[test]
913    fn test_time_signature_beats() {
914        let ts_4_4 = TimeSignature::new(4, 4);
915        assert_eq!(ts_4_4.beats_per_measure(), 4.0);
916
917        let ts_3_4 = TimeSignature::new(3, 4);
918        assert_eq!(ts_3_4.beats_per_measure(), 3.0);
919
920        let ts_6_8 = TimeSignature::new(6, 8);
921        assert_eq!(ts_6_8.beats_per_measure(), 3.0); // 6/8 = 6 * (4/8) = 3.0
922    }
923
924    #[test]
925    fn test_key_signature_names() {
926        let c_major = KeySignature::new(0, false);
927        assert_eq!(c_major.key_name(), "C");
928        
929        let a_minor = KeySignature::new(0, true);
930        assert_eq!(a_minor.key_name(), "Cm");
931        
932        let g_major = KeySignature::new(1, false);
933        assert_eq!(g_major.key_name(), "G");
934    }
935}