aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/main.rs363
1 files changed, 363 insertions, 0 deletions
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(())
+}