pub mod error;
pub use error::*;
use nom::{
branch::alt,
bytes::complete::tag,
combinator::{complete, eof, map_res},
error::context,
sequence::{preceded, tuple},
Parser,
};
use crate::{
osu_file::{
FilePath, Integer, InvalidRepr, Version, VersionedDefault, VersionedFrom,
VersionedFromRepr, VersionedFromStr, VersionedToString, VersionedTryFrom,
},
parsers::{comma, comma_field, comma_field_type},
};
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct AudioSample {
pub time: Integer,
pub layer: Layer,
pub filepath: FilePath,
pub volume: Volume,
}
impl VersionedFromStr for AudioSample {
type Err = ParseAudioSampleError;
fn from_str(s: &str, version: Version) -> Result<Option<Self>, Self::Err> {
if s.is_empty() {
return Err(ParseAudioSampleError::WrongEvent);
}
let header = context(ParseAudioSampleError::WrongEvent.into(), tag("Sample"));
let time = context(
ParseAudioSampleError::InvalidTime.into(),
comma_field_type(),
);
let layer = context(
ParseAudioSampleError::InvalidLayer.into(),
map_res(
context(
ParseAudioSampleError::InvalidLayer.into(),
comma_field_type(),
),
|layer| Layer::from_repr(layer, version).map(|layer| layer.unwrap()),
),
);
let filepath = comma_field().map(|p| p.into());
let volume = alt((
eof.map(|_| Volume::default(version).unwrap()),
preceded(
context(ParseAudioSampleError::MissingVolume.into(), comma()),
context(
ParseAudioSampleError::InvalidVolume.into(),
map_res::<_, _, _, _, ParseVolumeError, _, _>(comma_field(), |s| {
Ok(Volume::from_str(s, version)?.unwrap())
}),
),
),
));
let (_, (time, layer, filepath, volume)) = tuple((
preceded(
tuple((
complete(header),
context(ParseAudioSampleError::MissingTime.into(), comma()),
)),
time,
),
preceded(
context(ParseAudioSampleError::MissingLayer.into(), comma()),
layer,
),
preceded(
context(ParseAudioSampleError::MissingFilepath.into(), comma()),
filepath,
),
volume,
))(s)?;
Ok(Some(AudioSample {
time,
layer,
filepath,
volume,
}))
}
}
impl VersionedToString for AudioSample {
fn to_string(&self, version: Version) -> Option<String> {
Some(format!(
"Sample,{},{},{},{}",
self.time,
self.layer as usize,
self.filepath.to_string(version).unwrap(),
self.volume.to_string(version).unwrap()
))
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Volume(u8);
impl VersionedDefault for Volume {
fn default(_: Version) -> Option<Self> {
Some(Volume(100))
}
}
impl Volume {
pub fn new(volume: u8, version: Version) -> Result<Volume, VolumeSetError> {
<Volume as VersionedTryFrom<u8>>::try_from(volume, version).map(|volume| volume.unwrap())
}
pub fn get(&self) -> u8 {
self.0
}
pub fn set(&mut self, value: u8, version: Version) -> Result<(), VolumeSetError> {
*self = <Volume as VersionedTryFrom<u8>>::try_from(value, version)
.map(|volume| volume.unwrap())?;
Ok(())
}
}
impl VersionedTryFrom<u8> for Volume {
type Error = VolumeSetError;
fn try_from(value: u8, _: Version) -> Result<Option<Self>, Self::Error> {
if value > 100 {
Err(VolumeSetError::VolumeTooHigh)
} else {
Ok(Some(Volume(value)))
}
}
}
impl VersionedFrom<Volume> for u8 {
fn from(volume: Volume, _: Version) -> Option<Self> {
Some(volume.get())
}
}
impl VersionedFromStr for Volume {
type Err = ParseVolumeError;
fn from_str(s: &str, version: Version) -> Result<Option<Self>, Self::Err> {
<Volume as VersionedTryFrom<u8>>::try_from(s.parse::<u8>()?, version)
.map_err(|err| err.into())
}
}
impl VersionedToString for Volume {
fn to_string(&self, version: Version) -> Option<String> {
<u8 as VersionedFrom<Volume>>::from(*self, version).map(|volume| volume.to_string())
}
}
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
#[non_exhaustive]
pub enum Layer {
Background,
Fail,
Pass,
Foreground,
}
impl VersionedFromRepr for Layer {
fn from_repr(repr: usize, _: Version) -> Result<Option<Self>, InvalidRepr> {
match repr {
0 => Ok(Some(Layer::Background)),
1 => Ok(Some(Layer::Fail)),
2 => Ok(Some(Layer::Pass)),
3 => Ok(Some(Layer::Foreground)),
_ => Err(InvalidRepr),
}
}
}