/* 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 . */ use nom::{ branch::alt, 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 may also be zero terminated, with the zero term char /// included in the length. In that case, we trim off the ending zero bytes. fn cmstring(data: &[u8]) -> IResult<&[u8], &str> { let (r, s) = length_data(be_u32)(data)?; let end = s.iter().rposition(|b| b != &0u8).unwrap() + 1; Ok((r, std::str::from_utf8(&s[0..end]).unwrap())) } #[derive(Debug, PartialEq)] enum NodeType { Container, Leaf, } impl From 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>), Raw(Vec), Arrangement(PArrangement), InstrumentTrackEvent(MInstrumentTrackEvent<'a>), MidiAfterTouch(u32), MidiController(u32), MidiNote(u32), MidiPart(MMidiPart<'a>), MidiPartEvent(Vec), MidiPitchBend(u32), MidiPolyPressure(u32), MidiProgramChange(u32), MidiSysex(Vec), SignatureTrackEvent(MSignatureTrackEvent<'a>), TempoTrackEvent(&'a [u8]), Version(PAppVersion<'a>), } #[derive(Debug)] struct Node<'a> { nodetype: NodeType, nodeclass: &'a str, num: u16, value: Box>, } /// A vector of 32bit words preceeded by a 16 bit count. fn counted_vec(data: &[u8]) -> IResult<&[u8], Vec> { 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> { 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, (appname, appversion, appdate, num2, apparch, num3, appencoding, applocale)) = tuple((cmstring, cmstring, cmstring, be_u32, cmstring, be_u32, cmstring, cmstring))(&data)?; Ok((r, PAppVersion { 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, num32_8: Vec, 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, } 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" => { let (r, appver) = p_appversion(&data)?; Ok((r, NodeValue::Version(appver))) }, "PArrangement" => { let (r, arr) = p_arrangement(&data)?; Ok((r, NodeValue::Arrangement(arr))) }, "MRoot" | "MTrackList" => { let (r, v) = counted_vec(&data)?; Ok((r, NodeValue::Raw(v))) }, "MInstrumentTrackEvent" => { let (r, e) = m_instrument_track_event(&data)?; Ok((r, NodeValue::InstrumentTrackEvent(e))) }, "MTempoTrackEvent" => { let (r, e) = m_tempo_track_event(&data)?; Ok((r, NodeValue::TempoTrackEvent(e))) }, "MSignatureTrackEvent" => { let (r, e) = m_signature_track_event(&data)?; Ok((r, NodeValue::SignatureTrackEvent(e))) }, "MMidiPartEvent" => { let (r, v) = count(be_u8, 30)(data)?; Ok((r, NodeValue::MidiPartEvent(v))) }, "MMidiPart" => { let (r, v) = m_midi_part(&data)?; Ok((r, NodeValue::MidiPart(v))) }, "MMidiNote" => { let (r, v) = be_u32(data)?; Ok((r, NodeValue::MidiNote(v))) }, "MMidiPolyPressure" => { let (r, v) = be_u32(data)?; Ok((r, NodeValue::MidiPolyPressure(v))) }, "MMidiAfterTouch" => { let (r, v) = be_u32(data)?; Ok((r, NodeValue::MidiAfterTouch(v))) }, "MMidiProgramChange" => { let (r, v) = be_u32(data)?; Ok((r, NodeValue::MidiProgramChange(v))) }, "MMidiController" => { let (r, v) = be_u32(data)?; Ok((r, NodeValue::MidiController(v))) }, "MMidiPitchBend" => { let (r, v) = be_u32(data)?; Ok((r, NodeValue::MidiPitchBend(v))) }, "MMidiSysex" => { 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, ())) } /** * 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 * object/leaf nodes. */ fn container_node<'a>(data: &'a [u8]) -> IResult<&'a [u8], ()> { let (r, (_, name, num)) = tuple((tag(b"\xff\xff\xff\xfe"), cmstring, be_u16))(data)?; println!(" Container: {}, {}", name, num); Ok((r, ())) } /** * An object node contains serialized structured data. * * It has a size, as well as a payload containing the actual serialized data. */ fn object_node<'a>(data: &'a [u8]) -> IResult<&'a [u8], ()> { let (r, (_, name, num, payload)) = tuple((tag(b"\xff\xff\xff\xff"), cmstring, be_u16, length_data(be_u32)))(data)?; println!(" Object: {}, {}, {}", name, num, payload.len()); let (_, nv) = node_value(name, payload)?; println!(" {:?}", nv); Ok((r, ())) } /** * ARCH chunks contains one or more structured data elements that can be * either container nodes or object/leaf nodes.. */ fn arch_chunk<'a>(data: &'a [u8]) -> IResult<&'a [u8], ()> { let (rest, _nodes) = many0(alt((container_node, object_node)))(data)?; 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, ())) } /** * Parse the Cubase project file header. * * A Cubase project file is almost a standard RIFF file, with the exception of an * extra fourcc (NUND) following the global RIFF header. The extra (NUND) fourcc * is _not_ followed by the length of the chunk, but rather immediately followed by * the next chunk. */ 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

(filename: P) -> Result> where P: AsRef { 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> { let filename = std::env::args() .skip(1) .next() .expect("You must give me a file to analyze"); parse_cubase_project(filename)?; Ok(()) }