/* 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::{
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<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> {
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<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" => {
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<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(())
}