Start using vocnom as a dependency for parsing and writing.
continuous-integration/drone/push Build is failing
Details
continuous-integration/drone/push Build is failing
Details
This commit is contained in:
parent
637dc3c721
commit
47a4d69c40
|
@ -160,6 +160,28 @@ version = "0.3.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "36eb31c1778188ae1e64398743890d0877fef36d11521ac60406b42016e8c2cf"
|
checksum = "36eb31c1778188ae1e64398743890d0877fef36d11521ac60406b42016e8c2cf"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memchr"
|
||||||
|
version = "2.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "minimal-lexical"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nom"
|
||||||
|
version = "7.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
"minimal-lexical",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.17.1"
|
version = "1.17.1"
|
||||||
|
@ -192,11 +214,20 @@ version = "0.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
|
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "vocnom"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "git+https://git.deadbsd.org/falso/vocnom.git#4b0e58bb2c5d0465b78f80653edbe5b194d59d22"
|
||||||
|
dependencies = [
|
||||||
|
"nom",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "voctool"
|
name = "voctool"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
|
"vocnom",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
@ -7,3 +7,4 @@ edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = { version = "4.0.22", features = ["cargo"] }
|
clap = { version = "4.0.22", features = ["cargo"] }
|
||||||
|
vocnom = { git = "https://git.deadbsd.org/falso/vocnom.git" }
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
pub mod voc;
|
|
25
src/main.rs
25
src/main.rs
|
@ -1,4 +1,5 @@
|
||||||
use voctool::*;
|
use std::fs;
|
||||||
|
use vocnom::reader::parse_voc;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let cmd = clap::Command::new(env!("CARGO_CRATE_NAME"))
|
let cmd = clap::Command::new(env!("CARGO_CRATE_NAME"))
|
||||||
|
@ -31,24 +32,18 @@ fn main() {
|
||||||
match matches.subcommand() {
|
match matches.subcommand() {
|
||||||
Some(("info", arguments)) => {
|
Some(("info", arguments)) => {
|
||||||
let filename = arguments.get_one::<String>("file").unwrap();
|
let filename = arguments.get_one::<String>("file").unwrap();
|
||||||
let voc = voc::VocFile::from_file(filename);
|
let contents = fs::read(filename).unwrap();
|
||||||
|
let bytes = contents.as_slice();
|
||||||
|
let (_, voc) = parse_voc(bytes).unwrap();
|
||||||
println!(
|
println!(
|
||||||
"Version {}.{} - Checksum: {}",
|
"Version {}.{} - Checksum: {}",
|
||||||
voc.version.0,
|
voc.version.major,
|
||||||
voc.version.1,
|
voc.version.minor,
|
||||||
if voc.checksum { "Valid!" } else { "Invalid!" }
|
if voc.checksum.valid { "Valid!" } else { "Invalid!" }
|
||||||
);
|
);
|
||||||
for (n, block) in voc.blocks.into_iter().enumerate() {
|
for (n, block) in voc.blocks.into_iter().enumerate() {
|
||||||
if let Some(block_info) = block.get_info() {
|
print!("Block #{} - ", n);
|
||||||
println!(
|
block.info();
|
||||||
"Block #{} - Start Address: {:02X?} - End Address: {:02X?} - Size {}",
|
|
||||||
n,
|
|
||||||
block_info.start_address,
|
|
||||||
block_info.end_address,
|
|
||||||
block_info.get_size()
|
|
||||||
);
|
|
||||||
println!("{:?}", block);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(("extract", _arguments)) => {
|
Some(("extract", _arguments)) => {
|
||||||
|
|
|
@ -1,537 +0,0 @@
|
||||||
use std::fmt;
|
|
||||||
use std::fs::File;
|
|
||||||
use std::io::{Read, Seek};
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
|
||||||
pub enum Codec {
|
|
||||||
Pcm8BitUnsigned,
|
|
||||||
Adpcm4to8,
|
|
||||||
Adpcm3to8,
|
|
||||||
Adpcm2to8,
|
|
||||||
Pcm16BitSigned,
|
|
||||||
Alaw,
|
|
||||||
Ulaw,
|
|
||||||
Adpcm4to16,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
||||||
pub enum BlockType {
|
|
||||||
Terminator,
|
|
||||||
SoundData,
|
|
||||||
SoundDataContinuation,
|
|
||||||
Silence,
|
|
||||||
Marker,
|
|
||||||
Text,
|
|
||||||
RepeatStart,
|
|
||||||
RepeatEnd,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type BlockT = Box<dyn Block>;
|
|
||||||
|
|
||||||
pub trait Block: fmt::Debug {
|
|
||||||
fn to_bytes(&self) -> Vec<u8>;
|
|
||||||
fn get_info(&self) -> &Option<BlockInfo>;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct BlockInfo {
|
|
||||||
pub block_type: BlockType,
|
|
||||||
pub start_address: u64,
|
|
||||||
pub end_address: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BlockInfo {
|
|
||||||
pub fn get_size(&self) -> u64 {
|
|
||||||
self.end_address - self.start_address
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Terminator
|
|
||||||
|
|
||||||
pub struct Terminator {
|
|
||||||
info: Option<BlockInfo>,
|
|
||||||
block_type: BlockType,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Terminator {
|
|
||||||
#[cfg_attr(feature = "cargo-clippy", allow(clippy::new_without_default))]
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
info: None,
|
|
||||||
block_type: BlockType::Terminator,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn from_stream(_file: &mut File, start_address: u64, end_address: u64) -> Self {
|
|
||||||
Self {
|
|
||||||
info: Some(BlockInfo {
|
|
||||||
block_type: BlockType::Terminator,
|
|
||||||
start_address,
|
|
||||||
end_address,
|
|
||||||
}),
|
|
||||||
block_type: BlockType::Terminator,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Block for Terminator {
|
|
||||||
fn to_bytes(&self) -> Vec<u8> {
|
|
||||||
vec![self.block_type as u8]
|
|
||||||
}
|
|
||||||
fn get_info(&self) -> &Option<BlockInfo> {
|
|
||||||
&self.info
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Debug for Terminator {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
f.debug_struct("Terminator").finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SoundData
|
|
||||||
|
|
||||||
pub struct SoundData {
|
|
||||||
info: Option<BlockInfo>,
|
|
||||||
block_type: BlockType,
|
|
||||||
sample_rate: u32,
|
|
||||||
codec: Codec,
|
|
||||||
data: Vec<u8>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SoundData {
|
|
||||||
pub fn new(sample_rate: u32, codec: Codec, data: Vec<u8>) -> Self {
|
|
||||||
Self {
|
|
||||||
info: None,
|
|
||||||
block_type: BlockType::SoundData,
|
|
||||||
sample_rate,
|
|
||||||
codec,
|
|
||||||
data,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_stream(file: &mut File, start_address: u64, end_address: u64) -> Self {
|
|
||||||
let mut frequency_divisor_buffer: [u8; 1] = [0];
|
|
||||||
file.read_exact(&mut frequency_divisor_buffer).unwrap();
|
|
||||||
let sample_rate: u32 = 1000000_u32 / (256_u32 - frequency_divisor_buffer[0] as u32);
|
|
||||||
|
|
||||||
let mut codec_buffer: [u8; 1] = [0];
|
|
||||||
file.read_exact(&mut codec_buffer).unwrap();
|
|
||||||
let codec = match codec_buffer[0] {
|
|
||||||
0 => Codec::Pcm8BitUnsigned,
|
|
||||||
1 => Codec::Adpcm4to8,
|
|
||||||
2 => Codec::Adpcm3to8,
|
|
||||||
3 => Codec::Adpcm2to8,
|
|
||||||
4 => Codec::Pcm16BitSigned,
|
|
||||||
5 => Codec::Alaw,
|
|
||||||
6 => Codec::Ulaw,
|
|
||||||
7 => Codec::Adpcm4to16,
|
|
||||||
_ => panic!("Bad Sound format. Got {}", codec_buffer[0]),
|
|
||||||
};
|
|
||||||
|
|
||||||
let address = file.stream_position().unwrap();
|
|
||||||
let mut data: Vec<u8> = vec![0; (end_address - address) as usize];
|
|
||||||
file.read_exact(&mut data).unwrap();
|
|
||||||
|
|
||||||
Self {
|
|
||||||
info: Some(BlockInfo {
|
|
||||||
block_type: BlockType::SoundData,
|
|
||||||
start_address,
|
|
||||||
end_address,
|
|
||||||
}),
|
|
||||||
block_type: BlockType::SoundData,
|
|
||||||
sample_rate,
|
|
||||||
codec,
|
|
||||||
data,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Block for SoundData {
|
|
||||||
fn to_bytes(&self) -> Vec<u8> {
|
|
||||||
let size_bytes: [u8; 4] = (self.data.len() as u32 + 2).to_le_bytes();
|
|
||||||
let frequency_divisor: u8 = (256_u32 - 1000000_u32 / self.sample_rate) as u8;
|
|
||||||
|
|
||||||
let mut result: Vec<u8> = vec![
|
|
||||||
self.block_type as u8,
|
|
||||||
size_bytes[0],
|
|
||||||
size_bytes[1],
|
|
||||||
size_bytes[2],
|
|
||||||
frequency_divisor,
|
|
||||||
self.codec as u8,
|
|
||||||
];
|
|
||||||
|
|
||||||
result.extend_from_slice(&self.data);
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
|
||||||
fn get_info(&self) -> &Option<BlockInfo> {
|
|
||||||
&self.info
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Debug for SoundData {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
f.debug_struct("SoundData")
|
|
||||||
.field("size", &self.data.len())
|
|
||||||
.field("sample rate", &self.sample_rate)
|
|
||||||
.field("codec", &self.codec)
|
|
||||||
.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SoundDataContinuation
|
|
||||||
|
|
||||||
pub struct SoundDataContinuation {
|
|
||||||
info: Option<BlockInfo>,
|
|
||||||
block_type: BlockType,
|
|
||||||
data: Vec<u8>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SoundDataContinuation {
|
|
||||||
pub fn new(data: Vec<u8>) -> Self {
|
|
||||||
Self {
|
|
||||||
info: None,
|
|
||||||
block_type: BlockType::SoundDataContinuation,
|
|
||||||
data,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn from_stream(file: &mut File, start_address: u64, end_address: u64) -> Self {
|
|
||||||
let address = file.stream_position().unwrap();
|
|
||||||
let mut data: Vec<u8> = vec![0; (end_address - address) as usize];
|
|
||||||
file.read_exact(&mut data).unwrap();
|
|
||||||
|
|
||||||
Self {
|
|
||||||
info: Some(BlockInfo {
|
|
||||||
block_type: BlockType::SoundDataContinuation,
|
|
||||||
start_address,
|
|
||||||
end_address,
|
|
||||||
}),
|
|
||||||
block_type: BlockType::SoundDataContinuation,
|
|
||||||
data,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Block for SoundDataContinuation {
|
|
||||||
fn to_bytes(&self) -> Vec<u8> {
|
|
||||||
let size_bytes: [u8; 4] = (self.data.len() as u32).to_le_bytes();
|
|
||||||
let mut result: Vec<u8> = vec![
|
|
||||||
self.block_type as u8,
|
|
||||||
size_bytes[0],
|
|
||||||
size_bytes[1],
|
|
||||||
size_bytes[2],
|
|
||||||
];
|
|
||||||
|
|
||||||
result.extend_from_slice(&self.data);
|
|
||||||
result
|
|
||||||
}
|
|
||||||
fn get_info(&self) -> &Option<BlockInfo> {
|
|
||||||
&self.info
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Debug for SoundDataContinuation {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
f.debug_struct("SoundDataContinuation")
|
|
||||||
.field("size", &self.data.len())
|
|
||||||
.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Silence
|
|
||||||
|
|
||||||
pub struct Silence {
|
|
||||||
info: Option<BlockInfo>,
|
|
||||||
block_type: BlockType,
|
|
||||||
length: u16,
|
|
||||||
sample_rate: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Silence {
|
|
||||||
pub fn new(length: u16, sample_rate: u32) -> Self {
|
|
||||||
Self {
|
|
||||||
info: None,
|
|
||||||
block_type: BlockType::Silence,
|
|
||||||
length,
|
|
||||||
sample_rate,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn from_stream(file: &mut File, start_address: u64, end_address: u64) -> Self {
|
|
||||||
let mut length_buffer: [u8; 2] = [0; 2];
|
|
||||||
file.read_exact(&mut length_buffer).unwrap();
|
|
||||||
let length = u16::from_le_bytes(length_buffer) + 1;
|
|
||||||
|
|
||||||
let mut frequency_divisor_buffer: [u8; 1] = [0];
|
|
||||||
file.read_exact(&mut frequency_divisor_buffer).unwrap();
|
|
||||||
let sample_rate: u32 = 1000000_u32 / (256_u32 - frequency_divisor_buffer[0] as u32);
|
|
||||||
|
|
||||||
Self {
|
|
||||||
info: Some(BlockInfo {
|
|
||||||
block_type: BlockType::Silence,
|
|
||||||
start_address,
|
|
||||||
end_address,
|
|
||||||
}),
|
|
||||||
block_type: BlockType::Silence,
|
|
||||||
length,
|
|
||||||
sample_rate,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Block for Silence {
|
|
||||||
fn to_bytes(&self) -> Vec<u8> {
|
|
||||||
let size_bytes: [u8; 4] = 3_u32.to_le_bytes();
|
|
||||||
let length_bytes: [u8; 2] = (self.length - 1).to_le_bytes();
|
|
||||||
let frequency_divisor: u8 = (256_u32 - 1000000_u32 / self.sample_rate) as u8;
|
|
||||||
|
|
||||||
let result: Vec<u8> = vec![
|
|
||||||
self.block_type as u8,
|
|
||||||
size_bytes[0],
|
|
||||||
size_bytes[1],
|
|
||||||
size_bytes[2],
|
|
||||||
length_bytes[0],
|
|
||||||
length_bytes[1],
|
|
||||||
frequency_divisor,
|
|
||||||
];
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
|
||||||
fn get_info(&self) -> &Option<BlockInfo> {
|
|
||||||
&self.info
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Debug for Silence {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
f.debug_struct("Silence")
|
|
||||||
.field("length", &self.length)
|
|
||||||
.field("sample rate", &self.sample_rate)
|
|
||||||
.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Marker
|
|
||||||
|
|
||||||
pub struct Marker {
|
|
||||||
info: Option<BlockInfo>,
|
|
||||||
block_type: BlockType,
|
|
||||||
value: u16,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Marker {
|
|
||||||
pub fn new(value: u16) -> Self {
|
|
||||||
Self {
|
|
||||||
info: None,
|
|
||||||
block_type: BlockType::Marker,
|
|
||||||
value,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn from_stream(file: &mut File, start_address: u64, end_address: u64) -> Self {
|
|
||||||
let mut value_buffer: [u8; 2] = [0; 2];
|
|
||||||
file.read_exact(&mut value_buffer).unwrap();
|
|
||||||
let value = u16::from_le_bytes(value_buffer);
|
|
||||||
|
|
||||||
Self {
|
|
||||||
info: Some(BlockInfo {
|
|
||||||
block_type: BlockType::Marker,
|
|
||||||
start_address,
|
|
||||||
end_address,
|
|
||||||
}),
|
|
||||||
block_type: BlockType::Marker,
|
|
||||||
value,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Block for Marker {
|
|
||||||
fn to_bytes(&self) -> Vec<u8> {
|
|
||||||
let size_bytes: [u8; 4] = 2_u32.to_le_bytes();
|
|
||||||
let value_bytes: [u8; 2] = self.value.to_le_bytes();
|
|
||||||
|
|
||||||
let result: Vec<u8> = vec![
|
|
||||||
self.block_type as u8,
|
|
||||||
size_bytes[0],
|
|
||||||
size_bytes[1],
|
|
||||||
size_bytes[2],
|
|
||||||
value_bytes[0],
|
|
||||||
value_bytes[1],
|
|
||||||
];
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
|
||||||
fn get_info(&self) -> &Option<BlockInfo> {
|
|
||||||
&self.info
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Debug for Marker {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
f.debug_struct("Marker")
|
|
||||||
.field("value", &self.value)
|
|
||||||
.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Text
|
|
||||||
|
|
||||||
pub struct Text {
|
|
||||||
pub info: Option<BlockInfo>,
|
|
||||||
pub block_type: BlockType,
|
|
||||||
pub data: Vec<u8>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Text {
|
|
||||||
pub fn new(data: Vec<u8>) -> Self {
|
|
||||||
Self {
|
|
||||||
info: None,
|
|
||||||
block_type: BlockType::Text,
|
|
||||||
data,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn from_stream(file: &mut File, start_address: u64, end_address: u64) -> Self {
|
|
||||||
let address = file.stream_position().unwrap();
|
|
||||||
let mut data: Vec<u8> = vec![0; (end_address - address) as usize];
|
|
||||||
file.read_exact(&mut data).unwrap();
|
|
||||||
|
|
||||||
Self {
|
|
||||||
info: Some(BlockInfo {
|
|
||||||
block_type: BlockType::Text,
|
|
||||||
start_address,
|
|
||||||
end_address,
|
|
||||||
}),
|
|
||||||
block_type: BlockType::Text,
|
|
||||||
data,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Block for Text {
|
|
||||||
fn to_bytes(&self) -> Vec<u8> {
|
|
||||||
let size_bytes: [u8; 4] = (self.data.len() as u32).to_le_bytes();
|
|
||||||
|
|
||||||
let mut result: Vec<u8> = vec![
|
|
||||||
self.block_type as u8,
|
|
||||||
size_bytes[0],
|
|
||||||
size_bytes[1],
|
|
||||||
size_bytes[2],
|
|
||||||
];
|
|
||||||
|
|
||||||
result.extend_from_slice(&self.data);
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
|
||||||
fn get_info(&self) -> &Option<BlockInfo> {
|
|
||||||
&self.info
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Debug for Text {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
f.debug_struct("Text")
|
|
||||||
.field("size", &self.data.len())
|
|
||||||
.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// RepeatStart
|
|
||||||
|
|
||||||
pub struct RepeatStart {
|
|
||||||
info: Option<BlockInfo>,
|
|
||||||
block_type: BlockType,
|
|
||||||
count: u16,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RepeatStart {
|
|
||||||
pub fn new(count: u16) -> Self {
|
|
||||||
Self {
|
|
||||||
info: None,
|
|
||||||
block_type: BlockType::RepeatStart,
|
|
||||||
count,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn from_stream(file: &mut File, start_address: u64, end_address: u64) -> Self {
|
|
||||||
let mut count_buffer: [u8; 2] = [0; 2];
|
|
||||||
file.read_exact(&mut count_buffer).unwrap();
|
|
||||||
let count = u16::from_le_bytes(count_buffer) + 1;
|
|
||||||
|
|
||||||
Self {
|
|
||||||
info: Some(BlockInfo {
|
|
||||||
block_type: BlockType::RepeatStart,
|
|
||||||
start_address,
|
|
||||||
end_address,
|
|
||||||
}),
|
|
||||||
block_type: BlockType::RepeatStart,
|
|
||||||
count,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Block for RepeatStart {
|
|
||||||
fn to_bytes(&self) -> Vec<u8> {
|
|
||||||
let size_bytes: [u8; 4] = 2_u32.to_le_bytes();
|
|
||||||
let count_bytes: [u8; 2] = (self.count - 1).to_le_bytes();
|
|
||||||
|
|
||||||
let result: Vec<u8> = vec![
|
|
||||||
self.block_type as u8,
|
|
||||||
size_bytes[0],
|
|
||||||
size_bytes[1],
|
|
||||||
size_bytes[2],
|
|
||||||
count_bytes[0],
|
|
||||||
count_bytes[1],
|
|
||||||
];
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
|
||||||
fn get_info(&self) -> &Option<BlockInfo> {
|
|
||||||
&self.info
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Debug for RepeatStart {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
f.debug_struct("RepeatStart")
|
|
||||||
.field("count", &self.count)
|
|
||||||
.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// RepeatEnd
|
|
||||||
|
|
||||||
pub struct RepeatEnd {
|
|
||||||
info: Option<BlockInfo>,
|
|
||||||
block_type: BlockType,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RepeatEnd {
|
|
||||||
#[cfg_attr(feature = "cargo-clippy", allow(clippy::new_without_default))]
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
info: None,
|
|
||||||
block_type: BlockType::RepeatEnd,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn from_stream(_file: &mut File, start_address: u64, end_address: u64) -> Self {
|
|
||||||
Self {
|
|
||||||
info: Some(BlockInfo {
|
|
||||||
block_type: BlockType::RepeatEnd,
|
|
||||||
start_address,
|
|
||||||
end_address,
|
|
||||||
}),
|
|
||||||
block_type: BlockType::RepeatEnd,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Block for RepeatEnd {
|
|
||||||
fn to_bytes(&self) -> Vec<u8> {
|
|
||||||
vec![self.block_type as u8]
|
|
||||||
}
|
|
||||||
fn get_info(&self) -> &Option<BlockInfo> {
|
|
||||||
&self.info
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Debug for RepeatEnd {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
f.debug_struct("RepeatEnd").finish()
|
|
||||||
}
|
|
||||||
}
|
|
211
src/voc/mod.rs
211
src/voc/mod.rs
|
@ -1,211 +0,0 @@
|
||||||
use std::fs::File;
|
|
||||||
use std::io::{Read, Seek, Write};
|
|
||||||
|
|
||||||
use crate::voc::blocks::*;
|
|
||||||
|
|
||||||
pub mod blocks;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct VocFile {
|
|
||||||
pub version: (u8, u8),
|
|
||||||
pub checksum: bool,
|
|
||||||
pub blocks: Vec<BlockT>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VocFile {
|
|
||||||
const SIGNATURE1: &'static str = "Creative Voice File";
|
|
||||||
const SIGNATURE2: [u8; 3] = [0x1A, 0x1A, 0x0];
|
|
||||||
|
|
||||||
fn checksum(version: i16) -> [u8; 2] {
|
|
||||||
(!version + 0x1234).to_le_bytes()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check_signatures(file: &mut File) {
|
|
||||||
// Signature - Creative Voice File
|
|
||||||
let mut signature1_buffer: [u8; 19] = [0; 19];
|
|
||||||
file.read_exact(&mut signature1_buffer).unwrap();
|
|
||||||
let signature1_str = std::str::from_utf8(&signature1_buffer).unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
signature1_str,
|
|
||||||
Self::SIGNATURE1,
|
|
||||||
"Bad file. Expected \"{}\" got \"{}\"",
|
|
||||||
Self::SIGNATURE1,
|
|
||||||
signature1_str
|
|
||||||
);
|
|
||||||
|
|
||||||
// Signature - 1A 1A 00
|
|
||||||
let mut signature2_buffer: [u8; 3] = [0; 3];
|
|
||||||
file.read_exact(&mut signature2_buffer).unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
signature2_buffer,
|
|
||||||
Self::SIGNATURE2,
|
|
||||||
"Bad file. Expected {:02X?} got {:02X?}",
|
|
||||||
Self::SIGNATURE2,
|
|
||||||
signature2_buffer
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_version(file: &mut File) -> (u8, u8, i16) {
|
|
||||||
// Version
|
|
||||||
let mut version_buffer: [u8; 2] = [0; 2];
|
|
||||||
file.read_exact(&mut version_buffer).unwrap();
|
|
||||||
let version_int = i16::from_le_bytes(version_buffer);
|
|
||||||
(version_buffer[1], version_buffer[0], version_int)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_checksum(file: &mut File, version: i16) -> bool {
|
|
||||||
// Version checksum
|
|
||||||
let mut checksum_buffer: [u8; 2] = [0; 2];
|
|
||||||
file.read_exact(&mut checksum_buffer).unwrap();
|
|
||||||
checksum_buffer == Self::checksum(version)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_block_type(file: &mut File) -> BlockType {
|
|
||||||
// Block Type
|
|
||||||
let mut block_type_buffer: [u8; 1] = [0];
|
|
||||||
file.read_exact(&mut block_type_buffer).unwrap();
|
|
||||||
match block_type_buffer[0] {
|
|
||||||
0 => BlockType::Terminator,
|
|
||||||
1 => BlockType::SoundData,
|
|
||||||
2 => BlockType::SoundDataContinuation,
|
|
||||||
3 => BlockType::Silence,
|
|
||||||
4 => BlockType::Marker,
|
|
||||||
5 => BlockType::Text,
|
|
||||||
6 => BlockType::RepeatStart,
|
|
||||||
7 => BlockType::RepeatEnd,
|
|
||||||
_ => panic!("Bad block type. Got {}", block_type_buffer[0]),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_block_size(file: &mut File, block_type: BlockType) -> u32 {
|
|
||||||
match block_type {
|
|
||||||
BlockType::Terminator | BlockType::RepeatEnd => 0,
|
|
||||||
_ => {
|
|
||||||
let mut block_size_buffer: [u8; 3] = [0; 3];
|
|
||||||
file.read_exact(&mut block_size_buffer).unwrap();
|
|
||||||
u32::from_le_bytes([
|
|
||||||
block_size_buffer[0],
|
|
||||||
block_size_buffer[1],
|
|
||||||
block_size_buffer[2],
|
|
||||||
0,
|
|
||||||
])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_block(
|
|
||||||
block_type: BlockType,
|
|
||||||
file: &mut File,
|
|
||||||
block_start_address: u64,
|
|
||||||
block_end_address: u64,
|
|
||||||
) -> Box<dyn Block> {
|
|
||||||
match block_type {
|
|
||||||
BlockType::Terminator => Box::new(Terminator::from_stream(
|
|
||||||
file,
|
|
||||||
block_start_address,
|
|
||||||
block_end_address,
|
|
||||||
)),
|
|
||||||
BlockType::SoundData => Box::new(SoundData::from_stream(
|
|
||||||
file,
|
|
||||||
block_start_address,
|
|
||||||
block_end_address,
|
|
||||||
)),
|
|
||||||
BlockType::SoundDataContinuation => Box::new(SoundDataContinuation::from_stream(
|
|
||||||
file,
|
|
||||||
block_start_address,
|
|
||||||
block_end_address,
|
|
||||||
)),
|
|
||||||
BlockType::Silence => Box::new(Silence::from_stream(
|
|
||||||
file,
|
|
||||||
block_start_address,
|
|
||||||
block_end_address,
|
|
||||||
)),
|
|
||||||
BlockType::Marker => Box::new(Marker::from_stream(
|
|
||||||
file,
|
|
||||||
block_start_address,
|
|
||||||
block_end_address,
|
|
||||||
)),
|
|
||||||
BlockType::Text => Box::new(Text::from_stream(
|
|
||||||
file,
|
|
||||||
block_start_address,
|
|
||||||
block_end_address,
|
|
||||||
)),
|
|
||||||
BlockType::RepeatStart => Box::new(RepeatStart::from_stream(
|
|
||||||
file,
|
|
||||||
block_start_address,
|
|
||||||
block_end_address,
|
|
||||||
)),
|
|
||||||
BlockType::RepeatEnd => Box::new(RepeatEnd::from_stream(
|
|
||||||
file,
|
|
||||||
block_start_address,
|
|
||||||
block_end_address,
|
|
||||||
)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_file(file_name: &str) -> Self {
|
|
||||||
let mut file = File::open(file_name).unwrap();
|
|
||||||
|
|
||||||
Self::check_signatures(&mut file);
|
|
||||||
let version = Self::parse_version(&mut file);
|
|
||||||
|
|
||||||
let mut voc_file = VocFile {
|
|
||||||
version: (version.0, version.1),
|
|
||||||
checksum: Self::parse_checksum(&mut file, version.2),
|
|
||||||
blocks: Vec::new(),
|
|
||||||
};
|
|
||||||
|
|
||||||
loop {
|
|
||||||
let block_start_address = file.stream_position().unwrap();
|
|
||||||
let block_type = Self::parse_block_type(&mut file);
|
|
||||||
let block_size = Self::parse_block_size(&mut file, block_type);
|
|
||||||
|
|
||||||
let current_address = file.stream_position().unwrap();
|
|
||||||
let block_end_address = current_address + u64::from(block_size);
|
|
||||||
|
|
||||||
let block = Self::create_block(
|
|
||||||
block_type,
|
|
||||||
&mut file,
|
|
||||||
block_start_address,
|
|
||||||
block_end_address,
|
|
||||||
);
|
|
||||||
|
|
||||||
voc_file.blocks.push(block);
|
|
||||||
|
|
||||||
if block_type == BlockType::Terminator {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
voc_file
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_bytes(&self) -> Vec<u8> {
|
|
||||||
let mut result: Vec<u8> = Vec::new();
|
|
||||||
|
|
||||||
// Signature1
|
|
||||||
result.extend_from_slice(Self::SIGNATURE1.as_bytes());
|
|
||||||
|
|
||||||
// Signature2
|
|
||||||
result.extend_from_slice(&Self::SIGNATURE2);
|
|
||||||
|
|
||||||
// Version
|
|
||||||
result.extend_from_slice(&[self.version.1, self.version.0]);
|
|
||||||
|
|
||||||
// Version Checksum
|
|
||||||
let version_int = i16::from_le_bytes([self.version.1, self.version.0]);
|
|
||||||
result.extend_from_slice(&Self::checksum(version_int));
|
|
||||||
|
|
||||||
// Iterate Blocks
|
|
||||||
for block in &self.blocks {
|
|
||||||
result.extend_from_slice(&block.to_bytes());
|
|
||||||
}
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_file(&self, filename: &str) {
|
|
||||||
let mut fp = File::create(filename).unwrap();
|
|
||||||
drop(fp.write(&self.to_bytes()));
|
|
||||||
drop(fp.flush());
|
|
||||||
}
|
|
||||||
}
|
|
BIN
tests/EDEN.MUS
BIN
tests/EDEN.MUS
Binary file not shown.
|
@ -1,24 +0,0 @@
|
||||||
mod tests {
|
|
||||||
use voctool::voc::blocks::BlockType;
|
|
||||||
use voctool::voc::VocFile;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn text_eden_mus() {
|
|
||||||
let tests_path = std::env::current_dir().unwrap().join("tests");
|
|
||||||
let file_path = tests_path.join("EDEN.MUS");
|
|
||||||
let voc = VocFile::from_file(file_path.to_str().unwrap());
|
|
||||||
|
|
||||||
// Version
|
|
||||||
assert_eq!((1, 10), voc.version);
|
|
||||||
|
|
||||||
// Checksum
|
|
||||||
assert!(voc.checksum);
|
|
||||||
|
|
||||||
// Text Block
|
|
||||||
let block = voc.blocks.get(0).unwrap().get_info().as_ref().unwrap();
|
|
||||||
assert_eq!(BlockType::Text, block.block_type);
|
|
||||||
assert_eq!(0x1A, block.start_address);
|
|
||||||
assert_eq!(0xB5, block.end_address);
|
|
||||||
assert_eq!(155, block.get_size());
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue