aboutsummaryrefslogblamecommitdiffstats
path: root/src/main.rs
blob: 52a8d3f6bc2437019912759dac6496b57bcfbf56 (plain) (tree)
































                                                                        


                                                                                                   

                                                  

                                                             





















































































































































                                                                                                            
                          











































































































































































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