use super::format::{enveloped, flac, id3, mp4, Tag};
use super::generic::Version;
use super::util::{take_version, write_version};
use crate::error::Error;
use crate::util::Res;
use std::io;
#[derive(Debug, Clone)]
pub struct TerminalMarker {
pub position: f32,
pub bpm: f32,
}
#[derive(Debug, Clone)]
pub struct NonTerminalMarker {
pub position: f32,
pub beats_till_next_marker: u32,
}
#[derive(Debug, Clone)]
pub struct Beatgrid {
pub version: Version,
pub non_terminal_markers: Vec<NonTerminalMarker>,
pub terminal_marker: TerminalMarker,
pub footer: u8,
}
impl Tag for Beatgrid {
const NAME: &'static str = "Serato BeatGrid";
fn parse(input: &[u8]) -> Result<Self, Error> {
let (_, autotags) = nom::combinator::all_consuming(take_beatgrid)(input)?;
Ok(autotags)
}
fn write(&self, writer: &mut impl io::Write) -> Result<usize, Error> {
write_beatgrid(writer, self)
}
}
impl id3::ID3Tag for Beatgrid {}
impl enveloped::EnvelopedTag for Beatgrid {}
impl flac::FLACTag for Beatgrid {
const FLAC_COMMENT: &'static str = "SERATO_BEATGRID";
}
impl mp4::MP4Tag for Beatgrid {
const MP4_ATOM_FREEFORM_NAME: &'static str = "beatgrid";
}
fn take_non_terminal_marker_count(input: &[u8]) -> Res<&[u8], u32> {
let (input, count) =
nom::combinator::verify(nom::number::complete::be_u32, |x: &u32| x > &0u32)(input)?;
Ok((input, count - 1))
}
#[test]
fn test_take_non_terminal_marker_count() {
assert_eq!(
take_non_terminal_marker_count(&[0x00, 0x00, 0x00, 0x01]),
Ok((&[][..], 0x00))
);
assert_eq!(
take_non_terminal_marker_count(&[0x89, 0xAB, 0xCD, 0xEF, 0x12]),
Ok((&[0x12][..], 0x89ABCDEE))
);
assert!(take_non_terminal_marker_count(&[0x00, 0x00, 0x00, 0x00]).is_err());
assert!(take_non_terminal_marker_count(&[0xC0, 0xFF, 0xEE]).is_err());
}
fn take_non_terminal_marker(input: &[u8]) -> Res<&[u8], NonTerminalMarker> {
let (input, position) = nom::number::complete::be_f32(input)?;
let (input, beats_till_next_marker) = nom::number::complete::be_u32(input)?;
Ok((
input,
NonTerminalMarker {
position,
beats_till_next_marker,
},
))
}
fn take_terminal_marker(input: &[u8]) -> Res<&[u8], TerminalMarker> {
let (input, position) = nom::number::complete::be_f32(input)?;
let (input, bpm) = nom::number::complete::be_f32(input)?;
Ok((input, TerminalMarker { position, bpm }))
}
fn take_beatgrid(input: &[u8]) -> Res<&[u8], Beatgrid> {
let (input, version) = take_version(input)?;
let (input, non_terminal_markers) =
nom::multi::length_count(take_non_terminal_marker_count, take_non_terminal_marker)(input)?;
let (input, terminal_marker) = take_terminal_marker(input)?;
let (input, footer) = nom::number::complete::u8(input)?;
let beatgrid = Beatgrid {
version,
non_terminal_markers,
terminal_marker,
footer,
};
Ok((input, beatgrid))
}
pub fn write_non_terminal_marker(
writer: &mut impl io::Write,
marker: &NonTerminalMarker,
) -> Result<usize, Error> {
let mut bytes_written = writer.write(&marker.position.to_be_bytes())?;
bytes_written += writer.write(&marker.beats_till_next_marker.to_be_bytes())?;
Ok(bytes_written)
}
pub fn write_terminal_marker(
writer: &mut impl io::Write,
marker: &TerminalMarker,
) -> Result<usize, Error> {
let mut bytes_written = writer.write(&marker.position.to_be_bytes())?;
bytes_written += writer.write(&marker.bpm.to_be_bytes())?;
Ok(bytes_written)
}
pub fn write_beatgrid(writer: &mut impl io::Write, beatgrid: &Beatgrid) -> Result<usize, Error> {
let mut bytes_written = write_version(writer, beatgrid.version)?;
let num_markers = beatgrid.non_terminal_markers.len() as u32 + 1;
bytes_written += writer.write(&num_markers.to_be_bytes())?;
for marker in &beatgrid.non_terminal_markers {
bytes_written += write_non_terminal_marker(writer, marker)?;
}
bytes_written += write_terminal_marker(writer, &beatgrid.terminal_marker)?;
bytes_written += writer.write(&[beatgrid.footer])?;
Ok(bytes_written)
}