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(|¬e| 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}