aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHarald Eilertsen <haraldei@anduin.net>2020-11-23 21:53:51 +0100
committerHarald Eilertsen <haraldei@anduin.net>2020-11-23 21:55:08 +0100
commit3243f0863559e40e4c9d7a108b16cdbef554af3c (patch)
tree4d2d0f80b54b46ae16323af92679c9e122a2a23a
downloadcbconv-3243f0863559e40e4c9d7a108b16cdbef554af3c.tar.gz
cbconv-3243f0863559e40e4c9d7a108b16cdbef554af3c.tar.bz2
cbconv-3243f0863559e40e4c9d7a108b16cdbef554af3c.zip
Initial commit.
Experimenting with reading Cubase project files using the nom parser library for Rust.
-rw-r--r--.gitignore2
-rw-r--r--Cargo.lock113
-rw-r--r--Cargo.toml8
-rw-r--r--src/main.rs363
4 files changed, 486 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..53eaa21
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+/target
+**/*.rs.bk
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 0000000..e2010bd
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,113 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+[[package]]
+name = "arrayvec"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "bitflags"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "bitvec"
+version = "0.19.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "funty 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "radium 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tap 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "wyz 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "cbconv"
+version = "0.1.0"
+dependencies = [
+ "nom 6.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "cfg-if"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "funty"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "lexical-core"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "arrayvec 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
+ "ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "static_assertions 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "memchr"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "nom"
+version = "6.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "bitvec 0.19.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "lexical-core 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "version_check 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "radium"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "ryu"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "static_assertions"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "tap"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "version_check"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "wyz"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[metadata]
+"checksum arrayvec 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
+"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
+"checksum bitvec 0.19.4 (registry+https://github.com/rust-lang/crates.io-index)" = "a7ba35e9565969edb811639dbebfe34edc0368e472c5018474c8eb2543397f81"
+"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
+"checksum funty 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0ba62103ce691c2fd80fbae2213dfdda9ce60804973ac6b6e97de818ea7f52c8"
+"checksum lexical-core 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)" = "db65c6da02e61f55dae90a0ae427b2a5f6b3e8db09f58d10efab23af92592616"
+"checksum memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e"
+"checksum nom 6.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4489ccc7d668957ddf64af7cd027c081728903afa6479d35da7e99bf5728f75f"
+"checksum radium 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "941ba9d78d8e2f7ce474c015eea4d9c6d25b6a3327f9832ee29a4de27f91bbb8"
+"checksum ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8"
+"checksum static_assertions 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
+"checksum tap 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "36474e732d1affd3a6ed582781b3683df3d0563714c59c39591e8ff707cf078e"
+"checksum version_check 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
+"checksum wyz 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214"
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..513cea1
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,8 @@
+[package]
+name = "cbconv"
+version = "0.1.0"
+authors = ["Harald Eilertsen <haraldei@anduin.net>"]
+edition = "2018"
+
+[dependencies]
+nom = "6.0.0"
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..0c51ea0
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,363 @@
+/* cbconvert -- A program to parse and convert Cubase projects
+ * Copyright (C) 2020 Harald Eilertsen
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+use nom::{
+ bytes::complete::*,
+ multi::{count, length_data, many0},
+ number::complete::*,
+ sequence::tuple,
+ Finish,
+ IResult,
+};
+use std::{
+ convert::From,
+ error::Error,
+ path::Path,
+};
+
+/// Parser for length prefixed strings.
+///
+/// Cubase uses a string format where the length of the string
+/// is given as a 32 bit big endian word before the actual string
+/// data. The string is also zero terminated, with the zero term
+/// char included in the length.
+fn cmstring(data: &[u8]) -> IResult<&[u8], &str> {
+ let (r, s) = length_data(be_u32)(data)?;
+ Ok((r, std::str::from_utf8(s).unwrap()))
+}
+
+#[derive(Debug, PartialEq)]
+enum NodeType {
+ Container,
+ Leaf,
+}
+
+impl From<u32> for NodeType {
+ fn from(v: u32) -> NodeType {
+ match v {
+ 0xffffffff => NodeType::Leaf,
+ 0xfffffffe => NodeType::Container,
+ _ => panic!(format!("Unexpected node type: {:#x} found.", v)),
+ }
+ }
+}
+
+#[derive(Debug)]
+enum NodeValue<'a> {
+ Unknown,
+ //List(Vec<Node<'a>>),
+ Raw(Vec<u32>),
+ Arrangement(PArrangement),
+ InstrumentTrackEvent(MInstrumentTrackEvent<'a>),
+
+ MidiAfterTouch(u32),
+ MidiController(u32),
+ MidiNote(u32),
+ MidiPart(MMidiPart<'a>),
+ MidiPartEvent(Vec<u8>),
+ MidiPitchBend(u32),
+ MidiPolyPressure(u32),
+ MidiProgramChange(u32),
+ MidiSysex(Vec<u8>),
+
+ SignatureTrackEvent(MSignatureTrackEvent<'a>),
+ TempoTrackEvent(&'a [u8]),
+ Version(PAppVersion<'a>),
+}
+
+#[derive(Debug)]
+struct Node<'a> {
+ nodetype: NodeType,
+ nodeclass: &'a str,
+ num: u16,
+ value: Box<NodeValue<'a>>,
+}
+
+/// A vector of 32bit words preceeded by a 16 bit count.
+fn counted_vec(data: &[u8]) -> IResult<&[u8], Vec<u32>> {
+ let (r, len) = be_u16(data)?;
+ count(be_u32, len as usize)(&r)
+}
+
+/// A byte vector prefixed with a 32 bit count.
+fn bytevec<'a>(data: &'a[u8]) -> IResult<&'a [u8], &'a [u8]> {
+ length_data(be_u32)(&data)
+}
+
+/// Version information about the app that created the file.
+#[derive(Debug)]
+struct PAppVersion<'a> {
+ num1: u32,
+ appname: &'a str,
+ appversion: &'a str,
+ appdate: &'a str,
+ num2: u32,
+ apparch: &'a str,
+ num3: u32,
+ appencoding: &'a str,
+ applocale: &'a str,
+}
+
+fn p_appversion<'a>(data: &'a [u8]) -> IResult<&'a [u8], PAppVersion<'a>> {
+ let (r, (num1, appname, appversion, appdate, num2, apparch, num3, appencoding, applocale)) =
+ tuple((be_u32, cmstring, cmstring, cmstring, be_u32, cmstring, be_u32, cmstring, cmstring))(&data)?;
+
+ Ok((r, PAppVersion {
+ num1,
+ appname,
+ appversion,
+ appdate,
+ num2,
+ apparch,
+ num3,
+ appencoding,
+ applocale,
+ }))
+}
+
+#[derive(Debug)]
+struct PArrangement {
+ num1: u16,
+ num2: u16,
+}
+
+fn p_arrangement(data: &[u8]) -> IResult<&[u8], PArrangement> {
+ let (r, (num1, num2)) = tuple((be_u16, be_u16))(data)?;
+ Ok((r, PArrangement { num1, num2 }))
+}
+
+#[derive(Debug)]
+struct MInstrumentTrackEvent<'a> {
+ num16_3: Vec<u16>,
+ num32_8: Vec<u32>,
+ name: &'a str,
+ num32: u32,
+}
+
+fn m_instrument_track_event<'a>(data: &'a [u8]) -> IResult<&'a [u8], MInstrumentTrackEvent<'a>> {
+ let (r, (num16_3, num32_8, name, num32)) =
+ tuple((count(be_u16, 3), count(be_u32, 8), cmstring, be_u32))(&data)?;
+ Ok((r, MInstrumentTrackEvent {
+ num16_3,
+ num32_8,
+ name,
+ num32,
+ }))
+}
+
+fn m_tempo_track_event<'a>(data: &'a [u8]) -> IResult<&'a [u8], &'a [u8]> {
+ bytevec(&data)
+}
+
+#[derive(Debug)]
+struct MSignatureTrackEvent<'a> {
+ bv: &'a [u8],
+ num32: u32,
+}
+
+fn m_signature_track_event<'a>(data: &'a [u8]) -> IResult<&'a [u8], MSignatureTrackEvent<'a>> {
+ let (r, (bv, num32)) = tuple((bytevec, be_u32))(&data)?;
+ Ok((r, MSignatureTrackEvent { bv, num32 }))
+}
+
+#[derive(Debug)]
+struct MMidiPart<'a> {
+ num32: u32,
+ name: &'a str,
+ data: Vec<u8>,
+}
+
+fn m_midi_part<'a>(data: &'a [u8]) -> IResult<&'a [u8], MMidiPart<'a>> {
+ let (r, (num32, name, data)) = tuple((be_u32, cmstring, count(be_u8, 17)))(&data)?;
+ Ok((r, MMidiPart { num32, name, data }))
+}
+
+fn node_value<'a>(class: &str, data: &'a [u8]) -> IResult<&'a [u8], NodeValue<'a>> {
+ match class {
+ "PAppVersion\0" => {
+ let (r, appver) = p_appversion(&data)?;
+ Ok((r, NodeValue::Version(appver)))
+ },
+ "PArrangement\0" => {
+ let (r, arr) = p_arrangement(&data)?;
+ Ok((r, NodeValue::Arrangement(arr)))
+ },
+ "MRoot\0" |
+ "MTrackList\0" => {
+ let (r, v) = counted_vec(&data)?;
+ Ok((r, NodeValue::Raw(v)))
+ },
+ "MInstrumentTrackEvent\0" => {
+ let (r, e) = m_instrument_track_event(&data)?;
+ Ok((r, NodeValue::InstrumentTrackEvent(e)))
+ },
+ "MTempoTrackEvent\0" => {
+ let (r, e) = m_tempo_track_event(&data)?;
+ Ok((r, NodeValue::TempoTrackEvent(e)))
+ },
+ "MSignatureTrackEvent\0" => {
+ let (r, e) = m_signature_track_event(&data)?;
+ Ok((r, NodeValue::SignatureTrackEvent(e)))
+ },
+ "MMidiPartEvent\0" => {
+ let (r, v) = count(be_u8, 30)(data)?;
+ Ok((r, NodeValue::MidiPartEvent(v)))
+ },
+ "MMidiPart\0" => {
+ let (r, v) = m_midi_part(&data)?;
+ Ok((r, NodeValue::MidiPart(v)))
+ },
+ "MMidiNote\0" => {
+ let (r, v) = be_u32(data)?;
+ Ok((r, NodeValue::MidiNote(v)))
+ },
+ "MMidiPolyPressure\0" => {
+ let (r, v) = be_u32(data)?;
+ Ok((r, NodeValue::MidiPolyPressure(v)))
+ },
+ "MMidiAfterTouch\0" => {
+ let (r, v) = be_u32(data)?;
+ Ok((r, NodeValue::MidiAfterTouch(v)))
+ },
+ "MMidiProgramChange\0" => {
+ let (r, v) = be_u32(data)?;
+ Ok((r, NodeValue::MidiProgramChange(v)))
+ },
+ "MMidiController\0" => {
+ let (r, v) = be_u32(data)?;
+ Ok((r, NodeValue::MidiController(v)))
+ },
+ "MMidiPitchBend\0" => {
+ let (r, v) = be_u32(data)?;
+ Ok((r, NodeValue::MidiPitchBend(v)))
+ },
+ "MMidiSysex\0" => {
+ let sep = vec![0xff, 0xff, 0xff, 0xff];
+ let (r, v) = take_until(sep.as_slice())(data)?;
+ Ok((r, NodeValue::MidiSysex(v.to_vec())))
+ },
+ &_ => Ok((&[], NodeValue::Unknown)) //"Unknown node class \"{}\".", class)
+ }
+}
+
+fn nodetype<'a>(data: &'a [u8]) -> IResult<&'a [u8], NodeType> {
+ let (rest, nt) = be_u32(data)?;
+
+ Ok((rest, NodeType::from(nt)))
+}
+
+fn fourcc<'a>(data: &'a [u8]) -> IResult<&'a [u8], &'a str> {
+ let (rest, tag) = take(4usize)(data)?;
+ Ok((rest, std::str::from_utf8(tag).unwrap()))
+}
+
+fn root_chunk<'a>(data: &'a [u8]) -> IResult<&'a [u8], ()> {
+ let (rest, (key, val)) = tuple((cmstring, cmstring))(data)?;
+ println!(" {}: {}", key, val);
+ Ok((rest, ()))
+}
+
+fn arch_chunk<'a>(data: &'a [u8]) -> IResult<&'a [u8], ()> {
+ let (rest, (nt, t, x)) = tuple((nodetype, cmstring, be_u16))(data)?;
+ println!(" {:?}: {}, {}", nt, t, x);
+
+ match nt {
+ NodeType::Container => {
+ arch_chunk(rest)?;
+ },
+ NodeType::Leaf => {
+ let (_, nodeval) = node_value(t, rest)?;
+ println!(" {:?}", nodeval);
+ }
+ };
+
+ Ok((rest, ()))
+}
+
+fn chunk<'a>(data: &'a [u8]) -> IResult<&'a [u8], ()> {
+ let (rest, (tag, payload)) = tuple((fourcc, length_data(be_u32)))(data)?;
+
+ println!(" {}: {}", tag, payload.len());
+
+ match tag {
+ "ROOT" => root_chunk(payload)?,
+ "ARCH" => arch_chunk(payload)?,
+ &_ => (payload, ())
+ };
+
+ Ok((rest, ()))
+}
+
+fn file_hdr<'a>(data: &'a [u8]) -> IResult<&'a [u8], ()> {
+ let (rest, (_, len, _)) = tuple((tag("RIFF"), be_u32, tag("NUND")))(data)?;
+ println!("RIFF: {}, NUND", len);
+ Ok((rest, ()))
+}
+
+pub struct CubaseProject;
+
+pub fn parse_cubase_project<P>(filename: P) -> Result<CubaseProject, Box<dyn Error>>
+where
+ P: AsRef<Path>
+{
+ println!("Reading {}...", filename.as_ref().to_str().ok_or("Invalid file name")?);
+
+ let data = std::fs::read(filename)?;
+ let (rest, _) = tuple((file_hdr, many0(chunk)))(&data)
+ .finish()
+ .map_err(|e| format!("{:?}", e))?;
+
+ println!("Rest: {}", rest.len());
+
+ // let mut rest = chunk.payload;
+
+ // while dbg!(rest.len()) > 0 {
+ // let (r, chunk) = parse_chunk(&rest).unwrap();
+ // println!(" Chunk id : {}", chunk.tag);
+ // println!(" Chunk data size : {}/{} bytes", chunk.len, chunk.payload.len());
+
+ // match chunk.tag {
+ // "NUNDROOT" | "ROOT" => {
+ // let (_r, hm) = parse_nundroot(&chunk.payload).unwrap();
+ // println!(" data : {:?}", hm);
+ // },
+ // "ARCH" => {
+ // let (_r, n) = node(&chunk.payload).unwrap();
+ // println!(" {}: {{", n.nodeclass);
+ // println!(" {:#?}", n.value);
+ // println!(" }},");
+ // },
+ // _ => (),
+ // };
+
+ // rest = r;
+ // std::thread::sleep(std::time::Duration::from_millis(200));
+ // }
+
+ Ok(CubaseProject {})
+}
+
+fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
+ let filename = std::env::args()
+ .skip(1)
+ .next()
+ .expect("You must give me a file to analyze");
+
+ parse_cubase_project(filename)?;
+
+ Ok(())
+}