From 3243f0863559e40e4c9d7a108b16cdbef554af3c Mon Sep 17 00:00:00 2001 From: Harald Eilertsen Date: Mon, 23 Nov 2020 21:53:51 +0100 Subject: Initial commit. Experimenting with reading Cubase project files using the nom parser library for Rust. --- src/main.rs | 363 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 363 insertions(+) create mode 100644 src/main.rs (limited to 'src') 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 . + */ + +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 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> { + 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, + 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\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

(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(()) +} -- cgit v1.2.3