use crate::error::{GpResult, ToPrimitiveGp};
use crate::{
io::primitive::*,
model::{enums::*, song::*},
};
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Chord {
pub length: u8,
pub sharp: Option<bool>,
pub root: Option<PitchClass>,
pub kind: Option<ChordType>,
pub extension: Option<ChordExtension>,
pub bass: Option<PitchClass>,
pub tonality: Option<ChordAlteration>,
pub add: Option<bool>,
pub name: String,
pub fifth: Option<ChordAlteration>,
pub ninth: Option<ChordAlteration>,
pub eleventh: Option<ChordAlteration>,
pub first_fret: Option<u8>,
pub strings: Vec<i8>,
pub barres: Vec<Barre>,
pub omissions: Vec<bool>,
pub fingerings: Vec<Fingering>,
pub show: Option<bool>,
pub new_format: Option<bool>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Barre {
pub fret: i8,
pub start: i8,
pub end: i8,
}
pub const SHARP_NOTES: [&str; 12] = [
"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B",
];
pub const FLAT_NOTES: [&str; 12] = [
"C", "Db", "D", "Eb", "E", "F", "Gb", "G", "Ab", "A", "Bb", "B",
];
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PitchClass {
pub note: String,
pub just: i8,
pub accidental: i8,
pub value: i8,
pub sharp: bool,
}
impl PitchClass {
pub(crate) fn from(just: i8, accidental: Option<i8>, sharp: Option<bool>) -> PitchClass {
let mut p = PitchClass {
just,
accidental: 0,
value: -1,
sharp: true,
note: String::with_capacity(2),
};
let pitch: i8;
let accidental2: i8;
if let Some(a) = accidental {
pitch = p.just;
accidental2 = a;
} else {
let value = p.just % 12;
p.note = if value >= 0 {
String::from(SHARP_NOTES[value as usize])
} else {
String::from(SHARP_NOTES[(12 + value) as usize])
}; if p.note.ends_with('b') {
accidental2 = -1;
p.sharp = false;
} else if p.note.ends_with('#') {
accidental2 = 1;
} else {
accidental2 = 0;
}
pitch = value - accidental2;
}
p.just = pitch % 12;
p.accidental = accidental2;
p.value = p.just + accidental2;
if sharp.is_none() {
p.sharp = p.accidental >= 0;
}
p
}
#[allow(dead_code)]
pub(crate) fn from_note(note: String) -> PitchClass {
let mut p = PitchClass {
note,
just: 0,
accidental: 0,
value: -1,
sharp: true,
};
if p.note.ends_with('b') {
p.accidental = -1;
p.sharp = false;
} else if p.note.ends_with('#') {
p.accidental = 1;
}
for i in 0i8..12i8 {
if SHARP_NOTES[i as usize] == p.note || FLAT_NOTES[i as usize] == p.note {
p.value = i;
break;
}
}
let pitch = p.value - p.accidental;
p.just = pitch % 12;
p.value = p.just + p.accidental;
p
}
}
impl std::fmt::Display for PitchClass {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
if self.sharp {
write!(f, "{}", SHARP_NOTES[self.value as usize])
} else {
write!(f, "{}", FLAT_NOTES[self.value as usize])
}
}
}
pub trait SongChordOps {
fn read_chord(&self, data: &[u8], seek: &mut usize, string_count: u8) -> GpResult<Chord>;
fn read_old_format_chord(
&self,
data: &[u8],
seek: &mut usize,
chord: &mut Chord,
) -> GpResult<()>;
fn read_new_format_chord_v3(
&self,
data: &[u8],
seek: &mut usize,
chord: &mut Chord,
) -> GpResult<()>;
fn read_new_format_chord_v4(
&self,
data: &[u8],
seek: &mut usize,
chord: &mut Chord,
) -> GpResult<()>;
fn write_chord(&self, data: &mut Vec<u8>, beat: &crate::model::beat::Beat);
fn write_new_format_chord(&self, data: &mut Vec<u8>, chord: &Chord);
fn write_old_format_chord(&self, data: &mut Vec<u8>, chord: &Chord);
fn write_chord_v4(&self, data: &mut Vec<u8>, beat: &crate::model::beat::Beat);
}
impl SongChordOps for Song {
fn read_chord(&self, data: &[u8], seek: &mut usize, string_count: u8) -> GpResult<Chord> {
let mut c = Chord {
length: string_count,
strings: Vec::new(),
..Default::default()
};
c.new_format = Some(read_bool(data, seek)?);
if c.new_format == Some(true) {
if self.version.number.0 == 3 {
self.read_new_format_chord_v3(data, seek, &mut c)?;
} else {
self.read_new_format_chord_v4(data, seek, &mut c)?;
}
} else {
if self.version.number.0 == 3 {
read_byte(data, seek)?;
}
self.read_old_format_chord(data, seek, &mut c)?;
}
Ok(c)
}
fn read_old_format_chord(
&self,
data: &[u8],
seek: &mut usize,
chord: &mut Chord,
) -> GpResult<()> {
chord.name = read_int_byte_size_string(data, seek)?;
chord.first_fret = Some(read_int(data, seek)? as u8);
if chord.first_fret.is_some() {
for _ in 0u8..6u8 {
chord.strings.push(read_int(data, seek)? as i8);
}
}
Ok(())
}
fn read_new_format_chord_v3(
&self,
data: &[u8],
seek: &mut usize,
chord: &mut Chord,
) -> GpResult<()> {
chord.sharp = Some(read_bool(data, seek)?);
*seek += 3;
chord.root = Some(PitchClass::from(
read_int(data, seek)?.to_i8_gp("chord root")?,
None,
chord.sharp,
));
chord.kind = Some(get_chord_type(
read_int(data, seek)?.to_u8_gp("chord type")?,
));
chord.extension = Some(get_chord_extension(
read_int(data, seek)?.to_u8_gp("chord extension")?,
));
chord.bass = Some(PitchClass::from(
read_int(data, seek)?.to_i8_gp("chord bass")?,
None,
chord.sharp,
));
chord.tonality = Some(get_chord_alteration(
read_int(data, seek)?.to_u8_gp("chord tonality")?,
)?);
chord.add = Some(read_bool(data, seek)?);
chord.name = read_byte_size_string(data, seek, 22)?;
chord.fifth = Some(get_chord_alteration(
read_int(data, seek)?.to_u8_gp("chord fifth")?,
)?);
chord.ninth = Some(get_chord_alteration(
read_int(data, seek)?.to_u8_gp("chord ninth")?,
)?);
chord.eleventh = Some(get_chord_alteration(
read_int(data, seek)?.to_u8_gp("chord eleventh")?,
)?);
chord.first_fret = Some(read_int(data, seek)?.to_u8_gp("chord first fret")?);
for _ in 0u8..6u8 {
chord
.strings
.push(read_int(data, seek)?.to_i8_gp("chord string fret")?);
}
let barre_count = read_int(data, seek)?.to_usize_gp("chord barre count")?;
let mut barre_frets: Vec<i32> = Vec::with_capacity(2);
let mut barre_starts: Vec<i32> = Vec::with_capacity(2);
let mut barre_ends: Vec<i32> = Vec::with_capacity(2);
for _ in 0u8..2u8 {
barre_frets.push(read_int(data, seek)?);
}
for _ in 0u8..2u8 {
barre_starts.push(read_int(data, seek)?);
}
for _ in 0u8..2u8 {
barre_ends.push(read_int(data, seek)?);
}
for i in 0..barre_count.min(2) {
chord.barres.push(Barre {
fret: barre_frets[i] as i8,
start: barre_starts[i] as i8,
end: barre_ends[i] as i8,
});
}
for _ in 0u8..7u8 {
chord.omissions.push(read_bool(data, seek)?);
}
*seek += 1;
Ok(())
}
fn read_new_format_chord_v4(
&self,
data: &[u8],
seek: &mut usize,
chord: &mut Chord,
) -> GpResult<()> {
chord.sharp = Some(read_bool(data, seek)?);
*seek += 3;
chord.root = Some(PitchClass::from(
read_byte(data, seek)? as i8,
None,
chord.sharp,
));
chord.kind = Some(get_chord_type(read_byte(data, seek)?));
chord.extension = Some(get_chord_extension(read_byte(data, seek)?));
let i = read_int(data, seek)?;
chord.bass = Some(PitchClass::from(i as i8, None, chord.sharp));
chord.tonality = Some(get_chord_alteration(read_int(data, seek)? as u8)?);
chord.add = Some(read_bool(data, seek)?);
chord.name = read_byte_size_string(data, seek, 22)?;
chord.fifth = Some(get_chord_alteration(read_byte(data, seek)?)?);
chord.ninth = Some(get_chord_alteration(read_byte(data, seek)?)?);
chord.eleventh = Some(get_chord_alteration(read_byte(data, seek)?)?);
chord.first_fret = Some(read_int(data, seek)? as u8);
for _ in 0u8..7u8 {
chord.strings.push(read_int(data, seek)? as i8);
}
let barre_count = read_byte(data, seek)?.to_usize_gp("chord barre count")?;
let mut barre_frets: Vec<u8> = Vec::with_capacity(5);
let mut barre_starts: Vec<u8> = Vec::with_capacity(5);
let mut barre_ends: Vec<u8> = Vec::with_capacity(5);
for _ in 0u8..5u8 {
barre_frets.push(read_byte(data, seek)?);
}
for _ in 0u8..5u8 {
barre_starts.push(read_byte(data, seek)?);
}
for _ in 0u8..5u8 {
barre_ends.push(read_byte(data, seek)?);
}
for i in 0..barre_count.min(5) {
chord.barres.push(Barre {
fret: barre_frets[i] as i8,
start: barre_starts[i] as i8,
end: barre_ends[i] as i8,
});
}
for _ in 0u8..7u8 {
chord.omissions.push(read_bool(data, seek)?);
}
*seek += 1;
for _ in 0u8..7u8 {
chord
.fingerings
.push(get_fingering(read_signed_byte(data, seek)?));
}
chord.show = Some(read_bool(data, seek)?);
Ok(())
}
fn write_chord(&self, data: &mut Vec<u8>, beat: &crate::model::beat::Beat) {
if let Some(c) = &beat.effect.chord {
write_bool(data, c.new_format == Some(true));
if c.new_format == Some(true) {
self.write_new_format_chord(data, c);
} else {
self.write_old_format_chord(data, c);
}
}
}
fn write_new_format_chord(&self, data: &mut Vec<u8>, chord: &Chord) {
write_bool(data, chord.sharp == Some(true));
write_placeholder_default(data, 3);
if let Some(r) = &chord.root {
write_i32(data, r.value as i32);
} else {
write_i32(data, 0);
}
if let Some(t) = &chord.kind {
write_i32(data, from_chord_type(t) as i32);
} else {
write_i32(data, 0);
}
if let Some(e) = &chord.extension {
write_i32(data, from_chord_extension(e) as i32);
} else {
write_i32(data, 0);
}
if let Some(b) = &chord.bass {
write_i32(data, b.value as i32);
} else {
write_i32(data, 0);
}
if let Some(t) = &chord.tonality {
write_i32(data, from_chord_alteration(t) as i32);
} else {
write_i32(data, 0);
}
write_bool(data, chord.add == Some(true));
write_byte_size_string(data, &chord.name);
write_placeholder_default(data, 22 - chord.name.len());
if let Some(f) = &chord.fifth {
write_i32(data, from_chord_alteration(f) as i32);
} else {
write_i32(data, 0);
}
if let Some(n) = &chord.ninth {
write_i32(data, from_chord_alteration(n) as i32);
} else {
write_i32(data, 0);
}
if let Some(e) = &chord.eleventh {
write_i32(data, from_chord_alteration(e) as i32);
} else {
write_i32(data, 0);
}
if let Some(ff) = chord.first_fret {
write_i32(data, ff as i32);
} else {
write_i32(data, 0);
}
for i in 0..6 {
if i < chord.strings.len() {
write_i32(data, chord.strings[i] as i32);
} else {
write_i32(data, -1);
}
}
let mut barres: Vec<Barre> = Vec::with_capacity(2);
for i in 0..2usize {
if i < chord.barres.len() {
barres.push(chord.barres[i].clone());
} else {
break;
}
}
write_i32(data, barres.len() as i32);
while barres.len() < 2 {
barres.push(Barre {
fret: 0,
start: 0,
end: 0,
});
}
for b in barres.iter().take(2) {
write_i32(data, b.fret as i32);
}
for b in barres.iter().take(2) {
write_i32(data, b.start as i32);
}
for b in barres.iter().take(2) {
write_i32(data, b.end as i32);
}
for i in 0..7usize {
if i < chord.omissions.len() {
write_bool(data, chord.omissions[i]);
} else {
write_bool(data, true);
}
}
write_placeholder_default(data, 1);
}
fn write_old_format_chord(&self, data: &mut Vec<u8>, chord: &Chord) {
write_int_byte_size_string(data, &chord.name);
if let Some(ff) = chord.first_fret {
write_i32(data, ff as i32);
} else {
write_i32(data, 0);
} for i in 0..6 {
if i < chord.strings.len() {
write_i32(data, chord.strings[i] as i32);
} else {
write_i32(data, -1);
}
}
}
fn write_chord_v4(&self, data: &mut Vec<u8>, beat: &crate::model::beat::Beat) {
if let Some(c) = &beat.effect.chord {
write_signed_byte(data, 1); write_bool(data, c.sharp == Some(true));
write_placeholder_default(data, 3);
if let Some(r) = &c.root {
write_byte(data, r.value as u8);
} else {
write_byte(data, 0);
}
if let Some(t) = &c.kind {
write_byte(data, from_chord_type(t));
} else {
write_byte(data, 0);
}
if let Some(e) = &c.extension {
write_byte(data, from_chord_extension(e));
} else {
write_byte(data, 0);
}
if let Some(b) = &c.bass {
write_i32(data, b.value as i32);
} else {
write_i32(data, 0);
}
if let Some(t) = &c.tonality {
write_i32(data, from_chord_alteration(t) as i32);
} else {
write_i32(data, 0);
}
write_bool(data, c.add == Some(true));
let name_bytes = c.name.len().min(22);
write_byte_size_string(data, &c.name);
write_placeholder_default(data, 22 - name_bytes);
if let Some(f) = &c.fifth {
write_byte(data, from_chord_alteration(f));
} else {
write_byte(data, 0);
}
if let Some(n) = &c.ninth {
write_byte(data, from_chord_alteration(n));
} else {
write_byte(data, 0);
}
if let Some(e) = &c.eleventh {
write_byte(data, from_chord_alteration(e));
} else {
write_byte(data, 0);
}
if let Some(ff) = c.first_fret {
write_i32(data, ff as i32);
} else {
write_i32(data, 0);
}
for i in 0..7 {
if i < c.strings.len() {
write_i32(data, c.strings[i] as i32);
} else {
write_i32(data, -1);
}
}
let barre_count = c.barres.len().min(5);
write_byte(data, barre_count as u8);
let mut barres = c.barres.clone();
while barres.len() < 5 {
barres.push(Barre {
fret: 0,
start: 0,
end: 0,
});
}
for b in barres.iter().take(5) {
write_byte(data, b.fret as u8);
}
for b in barres.iter().take(5) {
write_byte(data, b.start as u8);
}
for b in barres.iter().take(5) {
write_byte(data, b.end as u8);
}
for i in 0..7usize {
if i < c.omissions.len() {
write_bool(data, c.omissions[i]);
} else {
write_bool(data, true);
}
}
write_placeholder_default(data, 1);
for i in 0..7 {
if i < c.fingerings.len() {
write_signed_byte(data, from_fingering(&c.fingerings[i]));
} else {
write_signed_byte(data, -2);
}
}
write_bool(data, c.show == Some(true));
}
}
}
#[cfg(test)]
mod test {
use crate::model::chord::PitchClass;
#[test]
fn test_pitch_1() {
let p = PitchClass::from_note("D#".to_string());
assert!(p.sharp, "D# is sharp? {}", true);
assert_eq!(1, p.accidental);
}
#[test]
fn test_pitch_2() {
let p = PitchClass::from(4, Some(-1), None);
assert_eq!(3, p.value);
assert!(!p.sharp);
assert_eq!("Eb", p.to_string(), "Note should be Eb");
}
#[test]
fn test_pitch_3() {
let p = PitchClass::from(4, Some(-1), Some(true));
assert_eq!(3, p.value);
assert_eq!("D#", p.to_string(), "Note should be D#");
}
#[test]
fn test_pitch_4() {
}
#[test]
fn test_pitch_5() {
let p = PitchClass::from(3, None, Some(true));
assert_eq!("D#", p.to_string(), "Note should be D#");
}
}