Create a new utils file with common useful functions.
continuous-integration/drone/push Build is passing Details

Split the codecs in two new enums OriginalCodec and NewCodec.
This commit is contained in:
Pedro de Oliveira 2023-04-30 20:21:01 +01:00
parent 6951dc7f11
commit 3bdcc7faed
10 changed files with 195 additions and 85 deletions

View File

@ -1,3 +1,4 @@
pub mod reader;
pub mod types;
pub mod utils;
pub mod writer;

View File

@ -1,4 +1,5 @@
use crate::types::{BlockType, Checksum, Codec, Version, Voc, SIGNATURE1, SIGNATURE2};
use crate::types::{BlockType, Checksum, Version, Voc, SIGNATURE1, SIGNATURE2};
use crate::utils::{calculate_checksum, match_codec_id_u16, match_codec_id_u8};
use nom::branch::alt;
use nom::bytes::streaming::{tag, take};
use nom::combinator::map_res;
@ -26,15 +27,11 @@ fn parse_sound_data(input: &[u8]) -> IResult<&[u8], BlockType> {
),
)(input)?;
let sample_rate: u32 = 1000000_u32 / (256_u32 - frequency_divisor as u32);
let codec = match codec_id {
0 => Codec::Pcm8BitUnsigned,
1 => Codec::Adpcm4to8,
2 => Codec::Adpcm3to8,
3 => Codec::Adpcm2to8,
4 => Codec::Pcm16BitSigned,
6 => Codec::Alaw,
7 => Codec::Ulaw,
_ => return Err(Err::Error(Error::new(input, ErrorKind::Alt))),
let codec = match match_codec_id_u8(codec_id) {
Ok(original_codec) => original_codec,
Err(error) => {
panic!("Failed to match codec ID: {}", error);
}
};
let (input, data) = context("Failed to parse data", take(block_size - header_size))(input)?;
Ok((
@ -114,15 +111,11 @@ fn parse_extra_information(input: &[u8]) -> IResult<&[u8], BlockType> {
),
)(input)?;
let (input, channels) = context("Failed to parse channels", le_u8)(input)?;
let codec = match codec_id {
0 => Codec::Pcm8BitUnsigned,
1 => Codec::Adpcm4to8,
2 => Codec::Adpcm3to8,
3 => Codec::Adpcm2to8,
4 => Codec::Pcm16BitSigned,
6 => Codec::Alaw,
7 => Codec::Ulaw,
_ => return Err(Err::Error(Error::new(input, ErrorKind::Alt))),
let codec = match match_codec_id_u8(codec_id) {
Ok(original_codec) => original_codec,
Err(error) => {
panic!("Failed to match codec ID: {}", error);
}
};
let sample_rate: u32 =
256000000_u32 / ((channels as u32 + 1) * (65536 - frequency_divisor as u32));
@ -160,16 +153,11 @@ fn parse_sound_data_new(input: &[u8]) -> IResult<&[u8], BlockType> {
)(input)?;
let (input, reserved) = context("Failed to parse reserved", le_u32)(input)?;
let (input, data) = context("Failed to parse data", take(block_size - header_size))(input)?;
let codec = match codec_id {
0 => Codec::Pcm8BitUnsigned,
1 => Codec::Adpcm4to8,
2 => Codec::Adpcm3to8,
3 => Codec::Adpcm2to8,
4 => Codec::Pcm16BitSigned,
6 => Codec::Alaw,
7 => Codec::Ulaw,
0x0200 => Codec::Adpcm4to16,
_ => return Err(Err::Error(Error::new(input, ErrorKind::Alt))),
let codec = match match_codec_id_u16(codec_id) {
Ok(original_codec) => original_codec,
Err(error) => {
panic!("Failed to match codec ID: {}", error);
}
};
Ok((
input,
@ -235,8 +223,11 @@ pub fn parse_voc(input: &[u8]) -> IResult<&[u8], Voc> {
let (input, checksum) = context("Failed to parse checksum", le_u16)(input)?;
// Calculate checksum
let checksum2 = (!i16::from_le_bytes([version_minor, version_major]) + 0x1234) as u16;
let valid = checksum == checksum2;
let correct_checksum = calculate_checksum(Version {
major: version_major,
minor: version_minor,
});
let valid = checksum == correct_checksum;
let mut blocks = Vec::new();
let mut remaining_input = input;

View File

@ -42,13 +42,28 @@ pub struct Checksum {
pub valid: bool,
}
/// Represents the codec formats.
///
/// # Notes
///
/// The `Adpcm4to16` codec is only allowed in a `SoundDataNew`.
/// Represents the codec formats used in `SoundData` and `ExtraInformation`.
#[derive(Eq, Debug, PartialEq)]
pub enum Codec {
pub enum OriginalCodec {
/// PCM 8-bit unsigned format.
Pcm8BitUnsigned,
/// ADPCM 4-bit to 8-bit format.
Adpcm4to8,
/// ADPCM 3-bit to 8-bit format.
Adpcm3to8,
/// ADPCM 2-bit to 8-bit format.
Adpcm2to8,
/// PCM 16-bit signed format.
Pcm16BitSigned,
/// A-law format.
Alaw,
/// u-law format.
Ulaw,
}
/// Represents the codec formats used in `SoundDataNew`.
#[derive(Eq, Debug, PartialEq)]
pub enum NewCodec {
/// PCM 8-bit unsigned format.
Pcm8BitUnsigned,
/// ADPCM 4-bit to 8-bit format.
@ -66,6 +81,7 @@ pub enum Codec {
/// ADPCM 4-bit to 16-bit format.
Adpcm4to16,
}
/// Represents the different types of blocks in a VOC file.
#[derive(Eq, Debug, PartialEq)]
pub enum BlockType {
@ -76,7 +92,7 @@ pub enum BlockType {
/// The sample rate of the sound data.
sample_rate: u32,
/// The codec used to encode the sound data.
codec: Codec,
codec: OriginalCodec,
/// The sound data.
data: Vec<u8>,
},
@ -114,7 +130,7 @@ pub enum BlockType {
/// The sample rate of the sound data.
sample_rate: u32,
/// The codec used to encode the sound data.
codec: Codec,
codec: OriginalCodec,
/// The number of channels in the sound data.
channels: u8,
},
@ -127,7 +143,7 @@ pub enum BlockType {
/// The number of channels in the sound data.
channels: u8,
/// The codec used to encode the sound data.
codec: Codec,
codec: NewCodec,
/// Reserved for future use.
reserved: u32,
/// The sound data.

129
src/utils.rs Normal file
View File

@ -0,0 +1,129 @@
use crate::types::{NewCodec, OriginalCodec, Version};
use std::io::{Error, ErrorKind};
/// Calculates the checksum for a given `Version`.
///
/// The checksum is calculated by taking the bitwise NOT of a little-endian i16
/// value constructed from the `minor` and `major` fields of the `Version`
/// struct, adding 0x1234 to it, and then casting it to a u16.
///
/// # Arguments
///
/// * `version` - The `Version` struct for which to calculate the checksum.
///
/// # Returns
///
/// The calculated checksum as a u16 value.
pub fn calculate_checksum(version: Version) -> u16 {
(!i16::from_le_bytes([version.minor, version.major]) + 0x1234) as u16
}
/// Matches a u8 `codec_id` to its corresponding `OriginalCodec`.
///
/// This function takes a `codec_id` value as a u8 and returns a `Result`
/// containing the corresponding `OriginalCodec`. If the `codec_id` is invalid
/// and does not correspond to any known `OriginalCodec`, an `Error` is returned.
///
/// # Arguments
///
/// * `codec_id` - The u8 `codec_id` value to match to an `OriginalCodec`.
///
/// # Returns
///
/// A `Result` containing the corresponding `OriginalCodec` if the `codec_id`
/// is valid, or an `Error` if it is invalid.
pub fn match_codec_id_u8(codec_id: u8) -> Result<OriginalCodec, Error> {
match codec_id {
0 => Ok(OriginalCodec::Pcm8BitUnsigned),
1 => Ok(OriginalCodec::Adpcm4to8),
2 => Ok(OriginalCodec::Adpcm3to8),
3 => Ok(OriginalCodec::Adpcm2to8),
4 => Ok(OriginalCodec::Pcm16BitSigned),
6 => Ok(OriginalCodec::Alaw),
7 => Ok(OriginalCodec::Ulaw),
_ => Err(Error::new(
ErrorKind::Other,
format!("Invalid codec id `{}` for OriginalCodec", codec_id),
)),
}
}
/// Matches a u16 `codec_id` to its corresponding `NewCodec`.
///
/// This function takes a `codec_id` value as a u16 and returns a `Result`
/// containing the corresponding `NewCodec`. If the `codec_id` is invalid
/// and does not correspond to any known `NewCodec`, an `Error` is returned.
///
/// # Arguments
///
/// * `codec_id` - The u16 `codec_id` value to match to an `NewCodec`.
///
/// # Returns
///
/// A `Result` containing the corresponding `NewCodec` if the `codec_id`
/// is valid, or an `Error` if it is invalid.
pub fn match_codec_id_u16(codec_id: u16) -> Result<NewCodec, Error> {
match codec_id {
0 => Ok(NewCodec::Pcm8BitUnsigned),
1 => Ok(NewCodec::Adpcm4to8),
2 => Ok(NewCodec::Adpcm3to8),
3 => Ok(NewCodec::Adpcm2to8),
4 => Ok(NewCodec::Pcm16BitSigned),
6 => Ok(NewCodec::Alaw),
7 => Ok(NewCodec::Ulaw),
0x0200 => Ok(NewCodec::Adpcm4to16),
_ => Err(Error::new(
ErrorKind::Other,
format!("Invalid codec id `{}` for NewCodec", codec_id),
)),
}
}
/// Returns the corresponding codec ID for a given `OriginalCodec`.
///
/// This function takes a reference to an `OriginalCodec` and returns the corresponding codec ID
/// as an `u8`.
///
/// # Arguments
///
/// * `codec` - A reference to the `OriginalCodec` for which the corresponding codec ID is needed.
///
/// # Returns
///
/// The corresponding codec ID for the given `OriginalCodec` as an `u8`.
pub fn match_original_codec(codec: &OriginalCodec) -> u8 {
match codec {
OriginalCodec::Pcm8BitUnsigned => 0,
OriginalCodec::Adpcm4to8 => 1,
OriginalCodec::Adpcm3to8 => 2,
OriginalCodec::Adpcm2to8 => 3,
OriginalCodec::Pcm16BitSigned => 4,
OriginalCodec::Alaw => 6,
OriginalCodec::Ulaw => 7,
}
}
/// Returns the corresponding codec ID for a given `NewCodec`.
///
/// This function takes a reference to an `NewCodec` and returns the corresponding codec ID
/// as an `u16`.
///
/// # Arguments
///
/// * `codec` - A reference to the `NewCodec` for which the corresponding codec ID is needed.
///
/// # Returns
///
/// The corresponding codec ID for the given `NewCodec` as an `u16`.
pub fn match_new_codec(codec: &NewCodec) -> u16 {
match codec {
NewCodec::Pcm8BitUnsigned => 0,
NewCodec::Adpcm4to8 => 1,
NewCodec::Adpcm3to8 => 2,
NewCodec::Adpcm2to8 => 3,
NewCodec::Pcm16BitSigned => 4,
NewCodec::Alaw => 6,
NewCodec::Ulaw => 7,
NewCodec::Adpcm4to16 => 0x0200,
}
}

View File

@ -1,4 +1,5 @@
use crate::types::{BlockType, Codec, Voc, SIGNATURE1, SIGNATURE2};
use crate::types::{BlockType, Voc, SIGNATURE1, SIGNATURE2};
use crate::utils::{match_new_codec, match_original_codec};
use std::io::{Error, ErrorKind};
fn get_size(size: usize) -> [u8; 3] {
@ -26,16 +27,7 @@ fn write_sound_data(block: &BlockType) -> Result<Vec<u8>, Error> {
bytes.extend_from_slice(&get_size(data.len() + header_size));
let frequency_divisor: u8 = (256_u32 - 1000000_u32 / sample_rate) as u8;
bytes.extend_from_slice(&[frequency_divisor]);
let codec_id: u8 = match codec {
Codec::Pcm8BitUnsigned => 0,
Codec::Adpcm4to8 => 1,
Codec::Adpcm3to8 => 2,
Codec::Adpcm2to8 => 3,
Codec::Pcm16BitSigned => 4,
Codec::Alaw => 6,
Codec::Ulaw => 7,
_ => return Err(Error::new(ErrorKind::Other, "Invalid codec")),
};
let codec_id = match_original_codec(codec);
bytes.extend_from_slice(&[codec_id]);
bytes.extend_from_slice(data);
Ok(bytes)
@ -136,16 +128,7 @@ fn write_extra_information(block: &BlockType) -> Result<Vec<u8>, Error> {
let frequency_divisor =
(65536 - (256000000_u32 / ((*channels as u32 + 1) * sample_rate))) as u16;
bytes.extend_from_slice(&frequency_divisor.to_le_bytes());
let codec_id: u8 = match codec {
Codec::Pcm8BitUnsigned => 0,
Codec::Adpcm4to8 => 1,
Codec::Adpcm3to8 => 2,
Codec::Adpcm2to8 => 3,
Codec::Pcm16BitSigned => 4,
Codec::Alaw => 6,
Codec::Ulaw => 7,
_ => return Err(Error::new(ErrorKind::Other, "Invalid codec")),
};
let codec_id = match_original_codec(codec);
bytes.extend_from_slice(&[codec_id]);
bytes.extend_from_slice(&[*channels]);
Ok(bytes)
@ -173,16 +156,7 @@ fn write_sound_data_new(block: &BlockType) -> Result<Vec<u8>, Error> {
bytes.extend_from_slice(&sample_rate.to_le_bytes());
bytes.extend_from_slice(&[*bits]);
bytes.extend_from_slice(&[*channels]);
let codec_id: u16 = match codec {
Codec::Pcm8BitUnsigned => 0,
Codec::Adpcm4to8 => 1,
Codec::Adpcm3to8 => 2,
Codec::Adpcm2to8 => 3,
Codec::Pcm16BitSigned => 4,
Codec::Alaw => 6,
Codec::Ulaw => 7,
Codec::Adpcm4to16 => 0x0200,
};
let codec_id = match_new_codec(codec);
bytes.extend_from_slice(&codec_id.to_le_bytes());
bytes.extend_from_slice(&reserved.to_le_bytes());
bytes.extend_from_slice(data);
@ -225,13 +199,12 @@ pub fn write_voc(voc: &Voc) -> Vec<u8> {
// Version
bytes.extend_from_slice(&[voc.version.minor, voc.version.major]);
// Checksum
let checksum = (!i16::from_le_bytes([voc.version.minor, voc.version.major]) + 0x1234) as u16;
bytes.extend_from_slice(&checksum.to_le_bytes());
bytes.extend_from_slice(&voc.checksum.value.to_le_bytes());
for block in &voc.blocks {
match write_block(block) {
Ok(block_bytes) => {
bytes.extend_from_slice(&block_bytes);
},
}
Err(error) => {
eprintln!("Error writing block: {}", error);
}

View File

@ -1,5 +1,5 @@
use vocnom::reader::parse_voc;
use vocnom::types::{BlockType, Codec};
use vocnom::types::{BlockType, NewCodec};
const VOC_CONTENTS: &[u8] = include_bytes!("../assets/ADOOR2.VOC");
@ -60,7 +60,7 @@ fn block_0_sound_data_new_test() {
assert_eq!(sample_rate, &11025);
assert_eq!(bits, &8);
assert_eq!(channels, &1);
assert_eq!(codec, &Codec::Pcm8BitUnsigned);
assert_eq!(codec, &NewCodec::Pcm8BitUnsigned);
assert_eq!(reserved, &0);
assert_eq!(data.len(), 21394);
}

View File

@ -1,5 +1,5 @@
use vocnom::reader::parse_voc;
use vocnom::types::{BlockType, Codec};
use vocnom::types::{BlockType, OriginalCodec};
const VOC_CONTENTS: &[u8] = include_bytes!("../assets/C24FF78A.VOC");
@ -55,7 +55,7 @@ fn block_0_sound_data_test() {
} = &voc.blocks[1]
{
assert_eq!(sample_rate, &11111);
assert_eq!(codec, &Codec::Pcm8BitUnsigned);
assert_eq!(codec, &OriginalCodec::Pcm8BitUnsigned);
assert_eq!(data.len(), 485);
}
}

View File

@ -1,5 +1,5 @@
use vocnom::reader::parse_voc;
use vocnom::types::{BlockType, Codec};
use vocnom::types::{BlockType, OriginalCodec};
const VOC_CONTENTS: &[u8] = include_bytes!("../assets/CONGA.VOC");
@ -71,7 +71,7 @@ fn block_1_extra_information_test() {
{
assert_eq!(sample_rate, &11158);
assert_eq!(channels, &1);
assert_eq!(codec, &Codec::Pcm8BitUnsigned);
assert_eq!(codec, &OriginalCodec::Pcm8BitUnsigned);
}
}
Err(e) => {
@ -92,7 +92,7 @@ fn block_2_sound_data_test() {
} = &voc.blocks[2]
{
assert_eq!(sample_rate, &22222);
assert_eq!(codec, &Codec::Pcm8BitUnsigned);
assert_eq!(codec, &OriginalCodec::Pcm8BitUnsigned);
assert_eq!(data.len(), 164912);
}
}

View File

@ -1,5 +1,5 @@
use vocnom::reader::parse_voc;
use vocnom::types::{BlockType, Codec};
use vocnom::types::{BlockType, OriginalCodec};
const VOC_CONTENTS: &[u8] = include_bytes!("../assets/EDEN.MUS");
@ -70,7 +70,7 @@ fn block_1_sound_data_test() {
} = &voc.blocks[1]
{
assert_eq!(sample_rate, &11111);
assert_eq!(codec, &Codec::Pcm8BitUnsigned);
assert_eq!(codec, &OriginalCodec::Pcm8BitUnsigned);
assert_eq!(data.len(), 809282);
}
}

View File

@ -1,5 +1,5 @@
use vocnom::reader::parse_voc;
use vocnom::types::{BlockType, Codec};
use vocnom::types::{BlockType, OriginalCodec};
const VOC_CONTENTS: &[u8] = include_bytes!("../assets/GUARDIAN.VOC");
@ -56,7 +56,7 @@ fn blocks_0_to_3_sound_data_test() {
} = &voc.blocks[idx]
{
assert_eq!(sample_rate, &16129);
assert_eq!(codec, &Codec::Pcm8BitUnsigned);
assert_eq!(codec, &OriginalCodec::Pcm8BitUnsigned);
assert_eq!(data.len(), 202498);
}
}