aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHarald Eilertsen <haraldei@anduin.net>2021-08-23 09:33:54 +0200
committerHarald Eilertsen <haraldei@anduin.net>2021-08-23 09:33:54 +0200
commitfc90574ded706faa3c1ff7e5f66a2058d5869c3f (patch)
treec51754d4bf73ba42d97417c98187b36fe27b7e93
parent1ac4165e94b64ac2a8b30bf684f3ca8a3cb10965 (diff)
downloadcbconv-fc90574ded706faa3c1ff7e5f66a2058d5869c3f.tar.gz
cbconv-fc90574ded706faa3c1ff7e5f66a2058d5869c3f.tar.bz2
cbconv-fc90574ded706faa3c1ff7e5f66a2058d5869c3f.zip
Strip logic and rely more on combinatorial parsing.
Make more use of the parser library for parisng instead of driving it mostly through program logic. Also try more to populate the CubaseProject struct during parsing. For now we also mostly ignore container nodes. List them to the terminal, but we only care about parsing the actial object nodes. This may change in the future, but for now looks like a useful strategy.
-rw-r--r--src/cubase_project.rs7
-rw-r--r--src/main.rs204
2 files changed, 111 insertions, 100 deletions
diff --git a/src/cubase_project.rs b/src/cubase_project.rs
index 498936d..b58bbc9 100644
--- a/src/cubase_project.rs
+++ b/src/cubase_project.rs
@@ -18,15 +18,16 @@
/**
* A struct to hold the information about a Cubase project.
*/
-#[derive(Debug, Default)]
+#[derive(Clone, Debug, Default)]
pub struct CubaseProject {
pub app_version: PAppVersion,
+ pub arrangement: PArrangement,
}
/*
* Version information about the app that created the file.
*/
-#[derive(Debug, Default)]
+#[derive(Clone, Debug, Default)]
pub struct PAppVersion {
pub appname: String,
pub appversion: String,
@@ -38,6 +39,6 @@ pub struct PAppVersion {
pub applocale: String,
}
-#[derive(Debug, Default)]
+#[derive(Clone, Debug, Default)]
pub struct PArrangement {
}
diff --git a/src/main.rs b/src/main.rs
index a561af4..b4c6bbf 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -22,17 +22,16 @@ use cubase_project::*;
use nom::{
branch::alt,
bytes::complete::*,
- combinator::{map, rest, value},
- error::ParseError,
- multi::{length_data, length_value, many1},
+ combinator::{map},
+ multi::{fold_many0, length_data, length_value},
number::complete::*,
sequence::{preceded, terminated, tuple},
Finish,
IResult,
- Parser,
};
use std::{
error::Error,
+ fmt,
path::Path,
};
@@ -48,18 +47,6 @@ fn cmstring(input: &[u8]) -> IResult<&[u8], &str> {
map(strdata, |s| std::str::from_utf8(s).unwrap())(input)
}
-fn cmtag<'a>(t: &'a str) -> impl FnMut(&'a [u8]) -> IResult<&'a [u8], (NodeType, u16)> {
- map(
- tuple((
- node_type,
- length_value(
- be_u32, tuple((tag(t), tag(b"\0")))
- ),
- be_u16)
- ),
- |(nt, (_, _), v)| (nt, v))
-}
-
/**
* The data chunks in the file is split into what seems like a structure of containers and leaf
* nodes. Where the containers don't contain any data of themselves, but leaf nodes may contain
@@ -68,22 +55,13 @@ fn cmtag<'a>(t: &'a str) -> impl FnMut(&'a [u8]) -> IResult<&'a [u8], (NodeType,
* Each node regardless of type has a name, and a 16 bit number which meaning I'm not sure about.
* Leaf nodes also has a data payload prefixed by a 32 bit length (be).
*/
-#[derive(Clone, Debug)]
+#[derive(Clone, Debug, PartialEq)]
enum NodeType {
Container,
Object,
}
-fn node_type<'a>(input: &'a [u8]) -> IResult<&'a [u8], NodeType> {
- alt((
- value(NodeType::Container, tag(b"\xff\xff\xff\xfe")),
- value(NodeType::Object, tag(b"\xff\xff\xff\xff"))
- ))(input)
-}
-
-
-#[derive(Debug)]
struct Node<'a> {
node_type: NodeType,
name: &'a str,
@@ -91,6 +69,22 @@ struct Node<'a> {
payload: Option<&'a [u8]>,
}
+impl<'a> fmt::Debug for Node<'a> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ let mut r = f.debug_struct("Node");
+
+ r.field("node_type", &self.node_type)
+ .field("name", &self.name)
+ .field("num", &self.num);
+
+ if let Some(p) = self.payload {
+ r.field("payload_size", &p.len());
+ }
+
+ r.finish()
+ }
+}
+
fn p_app_version<'a>(input: &'a [u8]) -> IResult<&'a [u8], PAppVersion> {
let mut v = PAppVersion::default();
@@ -116,9 +110,16 @@ fn p_app_version<'a>(input: &'a [u8]) -> IResult<&'a [u8], PAppVersion> {
}
fn p_arrangement<'a>(input: &'a [u8]) -> IResult<&'a [u8], PArrangement> {
- let v = PArrangement::default();
- Ok((input, v))
+ fold_many0(
+ node,
+ PArrangement::default(),
+ |a: PArrangement, n: Node<'_>| {
+ println!(" {:?}", n);
+ a
+ }
+ )(input)
+
}
/**
@@ -133,16 +134,25 @@ fn root_chunk<'a>(input: &'a [u8]) -> IResult<&'a [u8], (&str, &str)> {
length_value(be_u32, tuple((cmstring, cmstring))))(input)
}
-fn arch_chunk<'a, O, E, F>(subparser: F) -> impl FnMut(&'a [u8]) -> IResult<&'a [u8], O, E>
-where
- E: ParseError<&'a [u8]>,
- F: Parser<&'a [u8], O, E>,
-{
+// fn arch_chunk<'a, O, E, F>(subparser: F) -> impl FnMut(&'a [u8]) -> IResult<&'a [u8], O, E>
+// where
+// E: ParseError<&'a [u8]>,
+// F: Parser<&'a [u8], O, E>,
+// {
+// preceded(
+// tag(b"ARCH"),
+// length_value(be_u32, subparser))
+// }
+
+
+fn arch_chunk<'a>(input: &'a [u8]) -> IResult<&'a [u8], &'a [u8]> {
preceded(
tag(b"ARCH"),
- length_value(be_u32, subparser))
+ length_data(be_u32)
+ )(input)
}
+
/**
* A container node does not contain any data of it's own, but contains
* one or more sub elements. These can be either other containers, or
@@ -163,40 +173,50 @@ fn object_node<'a>(data: &'a [u8]) -> IResult<&'a [u8], Node> {
Ok((r, Node { node_type: NodeType::Object, name, num, payload: Some(payload) } ))
}
+fn node<'a>(data: &'a [u8]) -> IResult<&'a [u8], Node> {
+ alt((container_node, object_node))(data)
+}
-fn cpr_file<'a>(input: &'a [u8]) -> IResult<&'a [u8], CubaseProject> {
- let mut proj = CubaseProject::default();
-
- let (mut payload, len) = cpr_file_header(input)?;
- assert_eq!(payload.len(), len as usize);
-
- println!("[*] Reading Cubase CPR file, {} bytes of data...", payload.len());
-
- while payload.len() > 0 {
- // Expect a root chunk first:
- let (r, (k, t)) = root_chunk(payload)?;
- println!("[*] Found root: ({}, {})", k, t);
-
- match k {
- "Version" => {
- let (r2, v) = arch_chunk(version_chunk)(r)?;
- println!("[*] {:?}", v);
- proj.app_version = v;
- payload = r2;
- },
- "Arrangement1" => {
- let (r2, a) = arch_chunk(arrangement_chunk)(r)?;
- println!("[*] {:?}", a);
- payload = r2;
- },
- _ => {
- let (r2, _) = arch_chunk(rest)(r)?;
- payload = r2;
+
+fn version_chunk<'a>(input: &'a [u8]) -> IResult<&'a [u8], PAppVersion> {
+ fold_many0(
+ node,
+ PAppVersion::default(),
+ |mut version, n: Node<'_>| {
+ println!(" {:?}", n);
+ if n.node_type == NodeType::Object {
+ let (_, v) = p_app_version(n.payload.unwrap()).unwrap();
+ version = v;
}
+ version
}
- }
+ )(input)
+ // preceded(
+ // container_node,
+ // p_app_version_node
+ // )(input)
+}
- Ok((payload, proj))
+fn arrangement_chunk<'a>(input: &'a [u8]) -> IResult<&'a [u8], PArrangement> {
+ fold_many0(
+ node,
+ PArrangement::default(),
+ |a: PArrangement, n: Node<'_>| {
+ println!(" {:?}", n);
+ if n.node_type == NodeType::Object {
+ let _ = p_arrangement(n.payload.unwrap());
+ }
+ a
+ }
+ )(input)
+}
+
+
+/**
+ * Main parser for a Cubase Project (.cpr) file.
+ */
+fn cpr_file<'a>(input: &'a [u8]) -> IResult<&'a [u8], CubaseProject> {
+ preceded(cpr_file_header, cpr_file_body)(input)
}
/**
@@ -216,41 +236,31 @@ fn cpr_file<'a>(input: &'a [u8]) -> IResult<&'a [u8], CubaseProject> {
*/
fn cpr_file_header<'a>(input: &'a [u8]) -> IResult<&'a [u8], u32> {
terminated(
- preceded(
- tag(b"RIFF"), be_u32),
- tag(b"NUND"))(input)
-}
-
-
-fn version_chunk<'a>(input: &'a [u8]) -> IResult<&'a [u8], PAppVersion> {
- preceded(
- tuple((cmtag("CmObject"), cmtag("PAppVersion"))),
- length_value(be_u32, p_app_version))(input)
+ preceded(tag(b"RIFF"), be_u32),
+ tag(b"NUND")
+ )(input)
}
-
-fn arrangement_chunk<'a>(input: &'a [u8]) -> IResult<&'a [u8], PArrangement> {
- // It varies a bit how many levels deep the actual PArrangement object is.
- //
- // File created with Cubase 4.5.x (file version 310?) seems to have this
- // structure:
- //
- // GDocument -> GModel -> CmObject
- //
- // While Cubase version 5.x (file version 400?) seems to have the CmObject
- // directly under the GDocument container.
- let (odata, c) = many1(container_node)(input)?;
- println!("[*] {:?}", c);
-
- // This is the actual PArrangeent object
- let (r2, o) = object_node(odata)?;
- println!("[*] {:?}: {} ({} bytes)", o.node_type, o.name, o.payload.unwrap().len());
- assert_eq!(r2.len(), 0);
-
- let (r2, _v) = p_arrangement(o.payload.unwrap())?;
- // assert_eq!(r2.len(), 0);
-
- Ok((r2, PArrangement::default()))
+fn cpr_file_body<'a>(input: &'a [u8]) -> IResult<&'a [u8], CubaseProject> {
+ fold_many0(
+ tuple((root_chunk, arch_chunk)),
+ CubaseProject::default(),
+ |mut prj, ((key, value), arch)| {
+ println!("Root: {} = {}", key, value);
+ match key {
+ "Version" => {
+ let (_, v) = version_chunk(arch).unwrap();
+ prj.app_version = v;
+ },
+ "Arrangement1" => {
+ let (_, a) = arrangement_chunk(arch).unwrap();
+ prj.arrangement = a;
+ }
+ _ => {},
+ }
+ prj
+ }
+ )(input)
}
pub fn parse_cubase_project<P>(filename: P) -> Result<CubaseProject, Box<dyn Error>>