From 12560df8235ca65ff7a5ca7fe7987db8e416f9a9 Mon Sep 17 00:00:00 2001 From: Damien Zammit Date: Fri, 21 Jun 2019 20:38:40 +1000 Subject: More API cleanups - Make naming of files more consistent - Use const& for strings where applicable - Move macros out of the global namespace header --- Makefile | 18 +- ptfformat.cc | 1347 -------------------------------------------------- ptformat.cc | 1337 +++++++++++++++++++++++++++++++++++++++++++++++++ ptformat/ptfformat.h | 283 ----------- ptformat/ptformat.h | 277 +++++++++++ ptftool.cc | 2 +- ptgenmissing.cc | 2 +- ptunxor.cc | 2 +- 8 files changed, 1626 insertions(+), 1642 deletions(-) delete mode 100644 ptfformat.cc create mode 100644 ptformat.cc delete mode 100644 ptformat/ptfformat.h create mode 100644 ptformat/ptformat.h diff --git a/Makefile b/Makefile index d2b0fa4..d3391d8 100644 --- a/Makefile +++ b/Makefile @@ -4,19 +4,19 @@ STRICT=-Wall -Wcast-align -Wextra -Wwrite-strings -Wunsafe-loop-optimizations -W CLANGSTRICT=-Woverloaded-virtual -Wno-mismatched-tags -ansi -Wnon-virtual-dtor -Woverloaded-virtual -fstrict-overflow -Wall -Wcast-align -Wextra -Wwrite-strings -Wno-unused-function -std=c++98 all: - $(CXX) -o ptftool -g ${INCL} ${STRICT} ptftool.cc ptfformat.cc - $(CXX) -o ptunxor -g ${INCL} ${STRICT} ptunxor.cc ptfformat.cc - $(CXX) -o ptgenmissing -g ${INCL} ${STRICT} ptgenmissing.cc ptfformat.cc + $(CXX) -o ptftool -g ${INCL} ${STRICT} ptftool.cc ptformat.cc + $(CXX) -o ptunxor -g ${INCL} ${STRICT} ptunxor.cc ptformat.cc + $(CXX) -o ptgenmissing -g ${INCL} ${STRICT} ptgenmissing.cc ptformat.cc all32: - $(CXX) -m32 -o ptftool -g ${INCL32} ${STRICT} ptftool.cc ptfformat.cc - $(CXX) -m32 -o ptunxor -g ${INCL32} ${STRICT} ptunxor.cc ptfformat.cc - $(CXX) -m32 -o ptgenmissing -g ${INCL32} ${STRICT} ptgenmissing.cc ptfformat.cc + $(CXX) -m32 -o ptftool -g ${INCL32} ${STRICT} ptftool.cc ptformat.cc + $(CXX) -m32 -o ptunxor -g ${INCL32} ${STRICT} ptunxor.cc ptformat.cc + $(CXX) -m32 -o ptgenmissing -g ${INCL32} ${STRICT} ptgenmissing.cc ptformat.cc clangall: - clang++ -o ptftool -g ${INCL} ${CLANGSTRICT} ptftool.cc ptfformat.cc - clang++ -o ptunxor -g ${INCL} ${CLANGSTRICT} ptunxor.cc ptfformat.cc - clang++ -o ptgenmissing -g ${INCL} ${CLANGSTRICT} ptgenmissing.cc ptfformat.cc + clang++ -o ptftool -g ${INCL} ${CLANGSTRICT} ptftool.cc ptformat.cc + clang++ -o ptunxor -g ${INCL} ${CLANGSTRICT} ptunxor.cc ptformat.cc + clang++ -o ptgenmissing -g ${INCL} ${CLANGSTRICT} ptgenmissing.cc ptformat.cc clean: rm ptftool ptunxor ptgenmissing diff --git a/ptfformat.cc b/ptfformat.cc deleted file mode 100644 index 6ab2d5a..0000000 --- a/ptfformat.cc +++ /dev/null @@ -1,1347 +0,0 @@ -/* - * libptformat - a library to read ProTools sessions - * - * Copyright (C) 2015 Damien Zammit - * Copyright (C) 2015 Robin Gareus - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - */ - -#include -#include -#include -#include -#include - -#include - -#include "ptformat/ptfformat.h" - -#if 0 -#define DEBUG -#endif - -#ifdef DEBUG -#define verbose_printf(...) printf("XXX PTFORMAT XXX: " __VA_ARGS__) -#else -#define verbose_printf(...) -#endif - -using namespace std; - -static bool wavidx_compare(PTFFormat::wav_t& w1, PTFFormat::wav_t& w2) { - return w1.index < w2.index; -} - -static bool wavname_compare(PTFFormat::wav_t& w1, PTFFormat::wav_t& w2) { - return (strcasecmp(w1.filename.c_str(), w2.filename.c_str()) < 0); -} - -static bool regidx_compare(PTFFormat::region_t& r1, PTFFormat::region_t& r2) { - return r1.index < r2.index; -} - -static bool regname_compare(PTFFormat::region_t& r1, PTFFormat::region_t& r2) { - return (strcasecmp(r1.name.c_str(), r2.name.c_str()) < 0); -} - -static void -hexdump(uint8_t *data, int length, int level) -{ - int i,j,k,end,step=16; - - for (i = 0; i < length; i += step) { - end = i + step; - if (end > length) end = length; - for (k = 0; k < level; k++) - printf(" "); - for (j = i; j < end; j++) { - printf("%02X ", data[j]); - } - for (j = i; j < end; j++) { - if (data[j] < 128 && data[j] > 32) - printf("%c", data[j]); - else - printf("."); - } - printf("\n"); - } -} - -PTFFormat::PTFFormat() - : _ptfunxored(0) - , _len(0) - , _sessionrate(0) - , _version(0) - , _product(NULL) - , _targetrate (0) - , _ratefactor (1.0) - , is_bigendian(false) -{ -} - -PTFFormat::~PTFFormat() { - cleanup(); -} - -std::string -PTFFormat::get_content_description(uint16_t ctype) { - switch(ctype) { - case 0x0030: - return std::string("INFO product and version"); - case 0x1001: - return std::string("WAV samplerate, size"); - case 0x1003: - return std::string("WAV metadata"); - case 0x1004: - return std::string("WAV list full"); - case 0x1007: - return std::string("region name, number"); - case 0x1008: - return std::string("AUDIO region name, number (v5)"); - case 0x100b: - return std::string("AUDIO region list (v5)"); - case 0x100f: - return std::string("AUDIO region->track entry"); - case 0x1011: - return std::string("AUDIO region->track map entries"); - case 0x1012: - return std::string("AUDIO region->track full map"); - case 0x1014: - return std::string("AUDIO track name, number"); - case 0x1015: - return std::string("AUDIO tracks"); - case 0x1017: - return std::string("PLUGIN entry"); - case 0x1018: - return std::string("PLUGIN full list"); - case 0x1021: - return std::string("I/O channel entry"); - case 0x1022: - return std::string("I/O channel list"); - case 0x1028: - return std::string("INFO sample rate"); - case 0x103a: - return std::string("WAV names"); - case 0x104f: - return std::string("AUDIO region->track subentry (v8)"); - case 0x1050: - return std::string("AUDIO region->track entry (v8)"); - case 0x1052: - return std::string("AUDIO region->track map entries (v8)"); - case 0x1054: - return std::string("AUDIO region->track full map (v8)"); - case 0x1056: - return std::string("MIDI region->track entry"); - case 0x1057: - return std::string("MIDI region->track map entries"); - case 0x1058: - return std::string("MIDI region->track full map"); - case 0x2000: - return std::string("MIDI events block"); - case 0x2001: - return std::string("MIDI region name, number (v5)"); - case 0x2002: - return std::string("MIDI regions map (v5)"); - case 0x2067: - return std::string("INFO path of session"); - case 0x2511: - return std::string("Snaps block"); - case 0x2519: - return std::string("MIDI track full list"); - case 0x251a: - return std::string("MIDI track name, number"); - case 0x2523: - return std::string("COMPOUND region element"); - case 0x2602: - return std::string("I/O route"); - case 0x2603: - return std::string("I/O routing table"); - case 0x2628: - return std::string("COMPOUND region group"); - case 0x2629: - return std::string("AUDIO region name, number (v10)"); - case 0x262a: - return std::string("AUDIO region list (v10)"); - case 0x262c: - return std::string("COMPOUND region full map"); - case 0x2633: - return std::string("MIDI regions name, number (v10)"); - case 0x2634: - return std::string("MIDI regions map (v10)"); - case 0x271a: - return std::string("MARKER list"); - default: - return std::string("UNKNOWN content type"); - } -} - -uint16_t -PTFFormat::u_endian_read2(unsigned char *buf, bool bigendian) -{ - if (bigendian) { - return ((uint16_t)(buf[0]) << 8) | (uint16_t)(buf[1]); - } else { - return ((uint16_t)(buf[1]) << 8) | (uint16_t)(buf[0]); - } -} - -uint32_t -PTFFormat::u_endian_read3(unsigned char *buf, bool bigendian) -{ - if (bigendian) { - return ((uint32_t)(buf[0]) << 16) | - ((uint32_t)(buf[1]) << 8) | - (uint32_t)(buf[2]); - } else { - return ((uint32_t)(buf[2]) << 16) | - ((uint32_t)(buf[1]) << 8) | - (uint32_t)(buf[0]); - } -} - -uint32_t -PTFFormat::u_endian_read4(unsigned char *buf, bool bigendian) -{ - if (bigendian) { - return ((uint32_t)(buf[0]) << 24) | - ((uint32_t)(buf[1]) << 16) | - ((uint32_t)(buf[2]) << 8) | - (uint32_t)(buf[3]); - } else { - return ((uint32_t)(buf[3]) << 24) | - ((uint32_t)(buf[2]) << 16) | - ((uint32_t)(buf[1]) << 8) | - (uint32_t)(buf[0]); - } -} - -uint64_t -PTFFormat::u_endian_read5(unsigned char *buf, bool bigendian) -{ - if (bigendian) { - return ((uint64_t)(buf[0]) << 32) | - ((uint64_t)(buf[1]) << 24) | - ((uint64_t)(buf[2]) << 16) | - ((uint64_t)(buf[3]) << 8) | - (uint64_t)(buf[4]); - } else { - return ((uint64_t)(buf[4]) << 32) | - ((uint64_t)(buf[3]) << 24) | - ((uint64_t)(buf[2]) << 16) | - ((uint64_t)(buf[1]) << 8) | - (uint64_t)(buf[0]); - } -} - -uint64_t -PTFFormat::u_endian_read8(unsigned char *buf, bool bigendian) -{ - if (bigendian) { - return ((uint64_t)(buf[0]) << 56) | - ((uint64_t)(buf[1]) << 48) | - ((uint64_t)(buf[2]) << 40) | - ((uint64_t)(buf[3]) << 32) | - ((uint64_t)(buf[4]) << 24) | - ((uint64_t)(buf[5]) << 16) | - ((uint64_t)(buf[6]) << 8) | - (uint64_t)(buf[7]); - } else { - return ((uint64_t)(buf[7]) << 56) | - ((uint64_t)(buf[6]) << 48) | - ((uint64_t)(buf[5]) << 40) | - ((uint64_t)(buf[4]) << 32) | - ((uint64_t)(buf[3]) << 24) | - ((uint64_t)(buf[2]) << 16) | - ((uint64_t)(buf[1]) << 8) | - (uint64_t)(buf[0]); - } -} - -void -PTFFormat::cleanup(void) { - _len = 0; - _sessionrate = 0; - _version = 0; - free(_ptfunxored); - _ptfunxored = NULL; - free (_product); - _product = NULL; - _audiofiles.clear(); - _regions.clear(); - _midiregions.clear(); - _tracks.clear(); - _miditracks.clear(); - free_all_blocks(); -} - -int64_t -PTFFormat::foundat(unsigned char *haystack, uint64_t n, const char *needle) { - int64_t found = 0; - uint64_t i, j, needle_n; - needle_n = strlen(needle); - - for (i = 0; i < n; i++) { - found = i; - for (j = 0; j < needle_n; j++) { - if (haystack[i+j] != needle[j]) { - found = -1; - break; - } - } - if (found > 0) - return found; - } - return -1; -} - -bool -PTFFormat::jumpto(uint32_t *currpos, unsigned char *buf, const uint32_t maxoffset, const unsigned char *needle, const uint32_t needlelen) { - uint64_t i; - uint64_t k = *currpos; - while (k + needlelen < maxoffset) { - bool foundall = true; - for (i = 0; i < needlelen; i++) { - if (buf[k+i] != needle[i]) { - foundall = false; - break; - } - } - if (foundall) { - *currpos = k; - return true; - } - k++; - } - return false; -} - -bool -PTFFormat::jumpback(uint32_t *currpos, unsigned char *buf, const uint32_t maxoffset, const unsigned char *needle, const uint32_t needlelen) { - uint64_t i; - uint64_t k = *currpos; - while (k > 0 && k + needlelen < maxoffset) { - bool foundall = true; - for (i = 0; i < needlelen; i++) { - if (buf[k+i] != needle[i]) { - foundall = false; - break; - } - } - if (foundall) { - *currpos = k; - return true; - } - k--; - } - return false; -} - -bool -PTFFormat::foundin(std::string haystack, std::string needle) { - size_t found = haystack.find(needle); - if (found != std::string::npos) { - return true; - } else { - return false; - } -} - -/* Return values: 0 success - -1 could not decrypt pt session -*/ -int -PTFFormat::unxor(std::string const& path) { - FILE *fp; - unsigned char xxor[256]; - unsigned char ct; - uint64_t i; - uint8_t xor_type; - uint8_t xor_value; - uint8_t xor_delta; - uint16_t xor_len; - - if (! (fp = g_fopen(path.c_str(), "rb"))) { - return -1; - } - - fseek(fp, 0, SEEK_END); - _len = ftell(fp); - if (_len < 0x14) { - fclose(fp); - return -1; - } - - if (! (_ptfunxored = (unsigned char*) malloc(_len * sizeof(unsigned char)))) { - /* Silently fail -- out of memory*/ - fclose(fp); - _ptfunxored = 0; - return -1; - } - - /* The first 20 bytes are always unencrypted */ - fseek(fp, 0x00, SEEK_SET); - i = fread(_ptfunxored, 1, 0x14, fp); - if (i < 0x14) { - fclose(fp); - return -1; - } - - xor_type = _ptfunxored[0x12]; - xor_value = _ptfunxored[0x13]; - xor_len = 256; - - // xor_type 0x01 = ProTools 5, 6, 7, 8 and 9 - // xor_type 0x05 = ProTools 10, 11, 12 - switch(xor_type) { - case 0x01: - xor_delta = gen_xor_delta(xor_value, 53, false); - break; - case 0x05: - xor_delta = gen_xor_delta(xor_value, 11, true); - break; - default: - fclose(fp); - return -1; - } - - /* Generate the xor_key */ - for (i=0; i < xor_len; i++) - xxor[i] = (i * xor_delta) & 0xff; - - /* hexdump(xxor, xor_len); */ - - /* Read file and decrypt rest of file */ - i = 0x14; - fseek(fp, i, SEEK_SET); - while (fread(&ct, 1, 1, fp) != 0) { - uint8_t xor_index = (xor_type == 0x01) ? i & 0xff : (i >> 12) & 0xff; - _ptfunxored[i++] = ct ^ xxor[xor_index]; - } - fclose(fp); - return 0; -} - -/* Return values: 0 success - -1 could not parse pt session -*/ -int -PTFFormat::load(std::string const& ptf, int64_t targetsr) { - cleanup(); - path = ptf; - - if (unxor(path)) - return -1; - - if (parse_version()) - return -2; - - if (_version < 5 || _version > 12) - return -3; - - _targetrate = targetsr; - - int err = 0; - if ((err = parse())) { - printf ("PARSE FAILED %d\n", err); - return -4; - } - - return 0; -} - -bool -PTFFormat::parse_version() { - uint32_t seg_len,str_len; - uint8_t *data = _ptfunxored + 0x14; - uintptr_t data_end = ((uintptr_t)_ptfunxored) + 0x100; - uint8_t seg_type; - bool success = false; - - if (_ptfunxored[0] != '\x03' && foundat(_ptfunxored, 0x100, BITCODE) != 1) { - return false; - } - - while( ((uintptr_t)data < data_end) && (success == false) ) { - - if (data[0] != 0x5a) { - success = false; - break; - } - - seg_type = data[1]; - /* Skip segment header */ - data += 3; - if (data[0] == 0 && data[1] == 0) { - /* BE */ - is_bigendian = true; - } else { - /* LE */ - is_bigendian = false; - } - seg_len = u_endian_read4(&data[0], is_bigendian); - - /* Skip seg_len */ - data += 4; - if (!(seg_type == 0x04 || seg_type == 0x03) || data[0] != 0x03) { - /* Go to next segment */ - data += seg_len; - continue; - } - /* Skip 0x03 0x00 0x00 */ - data += 3; - seg_len -= 3; - str_len = (*(uint8_t *)data); - if (! (_product = (uint8_t *)malloc((str_len+1) * sizeof(uint8_t)))) { - success = false; - break; - } - - /* Skip str_len */ - data += 4; - seg_len -= 4; - - memcpy(_product, data, str_len); - _product[str_len] = 0; - data += str_len; - seg_len -= str_len; - - /* Skip 0x03 0x00 0x00 0x00 */ - data += 4; - seg_len -= 4; - - _version = data[0]; - if (_version == 0) { - _version = data[3]; - } - data += seg_len; - success = true; - } - - /* If the above does not work, try other heuristics */ - if ((uintptr_t)data >= data_end - seg_len) { - _version = _ptfunxored[0x40]; - if (_version == 0) { - _version = _ptfunxored[0x3d]; - } - if (_version == 0) { - _version = _ptfunxored[0x3a] + 2; - } - if (_version != 0) { - success = true; - } - } - return (!success); -} - -uint8_t -PTFFormat::gen_xor_delta(uint8_t xor_value, uint8_t mul, bool negative) { - uint16_t i; - for (i = 0; i < 256; i++) { - if (((i * mul) & 0xff) == xor_value) { - return (negative) ? i * (-1) : i; - } - } - // Should not occur - return 0; -} - -void -PTFFormat::setrates(void) { - _ratefactor = 1.f; - if (_sessionrate != 0) { - _ratefactor = (float)_targetrate / _sessionrate; - } -} - -bool -PTFFormat::parse_block_at(uint32_t pos, struct block_t *block, int level) { - struct block_t b; - int childjump = 0; - uint32_t i; - - if (pos + 7 > _len) - return false; - if (level > 10) - return false; - if (_ptfunxored[pos] != ZMARK) - return false; - - b.zmark = ZMARK; - b.block_type = u_endian_read2(&_ptfunxored[pos+1], is_bigendian); - b.block_size = u_endian_read4(&_ptfunxored[pos+3], is_bigendian); - b.content_type = u_endian_read2(&_ptfunxored[pos+7], is_bigendian); - b.offset = pos + 7; - - if (b.block_size + b.offset > _len) - return false; - if (b.block_type & 0xff00) - return false; - - block->zmark = b.zmark; - block->block_type = b.block_type; - block->block_size = b.block_size; - block->content_type = b.content_type; - block->offset = b.offset; - - for (i = 1; (i < block->block_size) && (pos + i + childjump < _len); i += childjump ? childjump : 1) { - int p = pos + i; - struct block_t bchild; - childjump = 0; - if (parse_block_at(p, &bchild, level+1)) { - block->child.push_back(bchild); - childjump = bchild.block_size + 7; - } - } - return true; -} - -void -PTFFormat::dump_block(struct block_t& b, int level) -{ - int i; - - for (i = 0; i < level; i++) { - printf(" "); - } - printf("%s(0x%04x)\n", get_content_description(b.content_type).c_str(), b.content_type); - hexdump(&_ptfunxored[b.offset], b.block_size, level); - - for (vector::iterator c = b.child.begin(); - c != b.child.end(); ++c) { - dump_block(*c, level + 1); - } -} - -void -PTFFormat::free_block(struct block_t& b) -{ - for (vector::iterator c = b.child.begin(); - c != b.child.end(); ++c) { - free_block(*c); - } - - b.child.clear(); -} - -void -PTFFormat::free_all_blocks(void) -{ - for (vector::iterator b = blocks.begin(); - b != blocks.end(); ++b) { - free_block(*b); - } - - blocks.clear(); -} - -void -PTFFormat::dump(void) { - for (vector::iterator b = blocks.begin(); - b != blocks.end(); ++b) { - dump_block(*b, 0); - } -} - -void -PTFFormat::parseblocks(void) { - uint32_t i = 20; - - while (i < _len) { - struct block_t b; - if (parse_block_at(i, &b, 0)) { - blocks.push_back(b); - } - i += b.block_size ? b.block_size + 7 : 1; - } -} - -int -PTFFormat::parse(void) { - parseblocks(); -#ifdef DEBUG - dump(); -#endif - if (!parseheader()) - return -1; - setrates(); - if (_sessionrate < 44100 || _sessionrate > 192000) - return -2; - if (!parseaudio()) - return -3; - if (!parserest()) - return -4; - if (!parsemidi()) - return -5; - return 0; -} - -bool -PTFFormat::parseheader(void) { - bool found = false; - - for (vector::iterator b = blocks.begin(); - b != blocks.end(); ++b) { - if (b->content_type == 0x1028) { - _sessionrate = u_endian_read4(&_ptfunxored[b->offset+4], is_bigendian); - found = true; - } - } - return found; -} - -char * -PTFFormat::parsestring(uint32_t pos) { - uint32_t length = u_endian_read4(&_ptfunxored[pos], is_bigendian); - pos += 4; - return strndup((const char *)&_ptfunxored[pos], length); -} - -bool -PTFFormat::parseaudio(void) { - bool found = false; - uint32_t nwavs, i, n; - uint32_t pos = 0; - char *str; - std::string wavtype; - std::string wavname; - - // Parse wav names - for (vector::iterator b = blocks.begin(); - b != blocks.end(); ++b) { - if (b->content_type == 0x1004) { - - nwavs = u_endian_read4(&_ptfunxored[b->offset+2], is_bigendian); - - for (vector::iterator c = b->child.begin(); - c != b->child.end(); ++c) { - if (c->content_type == 0x103a) { - found = true; - //nstrings = u_endian_read4(&_ptfunxored[c->offset+1], is_bigendian); - pos = c->offset + 11; - // Found wav list - for (i = n = 0; (pos < c->offset + c->block_size) && (n < nwavs); i++) { - str = parsestring(pos); - wavname = std::string(str); - free(str); - pos += wavname.size() + 4; - str = strndup((const char *)&_ptfunxored[pos], 4); - wavtype = std::string(str); - free(str); - pos += 9; - if (foundin(wavname, std::string(".grp"))) - continue; - - if (foundin(wavname, std::string("Audio Files"))) { - continue; - } - if (foundin(wavname, std::string("Fade Files"))) { - continue; - } - if (_version < 10) { - if (!(foundin(wavtype, std::string("WAVE")) || - foundin(wavtype, std::string("EVAW")) || - foundin(wavtype, std::string("AIFF")) || - foundin(wavtype, std::string("FFIA"))) ) { - continue; - } - } else { - if (wavtype.size() != 0) { - if (!(foundin(wavtype, std::string("WAVE")) || - foundin(wavtype, std::string("EVAW")) || - foundin(wavtype, std::string("AIFF")) || - foundin(wavtype, std::string("FFIA"))) ) { - continue; - } - } else if (!(foundin(wavname, std::string(".wav")) || - foundin(wavname, std::string(".aif"))) ) { - continue; - } - } - wav_t f (n); - f.filename = wavname; - n++; - _audiofiles.push_back(f); - } - } - } - } - } - - // Add wav length information - for (vector::iterator b = blocks.begin(); - b != blocks.end(); ++b) { - if (b->content_type == 0x1004) { - - vector::iterator wav = _audiofiles.begin(); - - for (vector::iterator c = b->child.begin(); - c != b->child.end(); ++c) { - if (c->content_type == 0x1003) { - for (vector::iterator d = c->child.begin(); - d != c->child.end(); ++d) { - if (d->content_type == 0x1001) { - (*wav).length = u_endian_read8(&_ptfunxored[d->offset+8], is_bigendian); - wav++; - } - } - } - } - } - } - - return found; -} - - -void -PTFFormat::parse_three_point(uint32_t j, uint64_t& start, uint64_t& offset, uint64_t& length) { - uint8_t offsetbytes, lengthbytes, startbytes; - - if (is_bigendian) { - offsetbytes = (_ptfunxored[j+4] & 0xf0) >> 4; - lengthbytes = (_ptfunxored[j+3] & 0xf0) >> 4; - startbytes = (_ptfunxored[j+2] & 0xf0) >> 4; - //somethingbytes = (_ptfunxored[j+2] & 0xf); - //skipbytes = _ptfunxored[j+1]; - } else { - offsetbytes = (_ptfunxored[j+1] & 0xf0) >> 4; //3 - lengthbytes = (_ptfunxored[j+2] & 0xf0) >> 4; - startbytes = (_ptfunxored[j+3] & 0xf0) >> 4; //1 - //somethingbytes = (_ptfunxored[j+3] & 0xf); - //skipbytes = _ptfunxored[j+4]; - } - - switch (offsetbytes) { - case 5: - offset = u_endian_read5(&_ptfunxored[j+5], false); - break; - case 4: - offset = (uint64_t)u_endian_read4(&_ptfunxored[j+5], false); - break; - case 3: - offset = (uint64_t)u_endian_read3(&_ptfunxored[j+5], false); - break; - case 2: - offset = (uint64_t)u_endian_read2(&_ptfunxored[j+5], false); - break; - case 1: - offset = (uint64_t)(_ptfunxored[j+5]); - break; - default: - offset = 0; - break; - } - j+=offsetbytes; - switch (lengthbytes) { - case 5: - length = u_endian_read5(&_ptfunxored[j+5], false); - break; - case 4: - length = (uint64_t)u_endian_read4(&_ptfunxored[j+5], false); - break; - case 3: - length = (uint64_t)u_endian_read3(&_ptfunxored[j+5], false); - break; - case 2: - length = (uint64_t)u_endian_read2(&_ptfunxored[j+5], false); - break; - case 1: - length = (uint64_t)(_ptfunxored[j+5]); - break; - default: - length = 0; - break; - } - j+=lengthbytes; - switch (startbytes) { - case 5: - start = u_endian_read5(&_ptfunxored[j+5], false); - break; - case 4: - start = (uint64_t)u_endian_read4(&_ptfunxored[j+5], false); - break; - case 3: - start = (uint64_t)u_endian_read3(&_ptfunxored[j+5], false); - break; - case 2: - start = (uint64_t)u_endian_read2(&_ptfunxored[j+5], false); - break; - case 1: - start = (uint64_t)(_ptfunxored[j+5]); - break; - default: - start = 0; - break; - } -} - -void -PTFFormat::parse_region_info(uint32_t j, block_t& blk, region_t& r) { - uint64_t findex, start, sampleoffset, length; - - parse_three_point(j, start, sampleoffset, length); - - findex = u_endian_read4(&_ptfunxored[blk.offset + blk.block_size], is_bigendian); - wav_t f (findex); - f.posabsolute = start * _ratefactor; - f.length = length * _ratefactor; - - wav_t found; - if (find_wav(findex, found)) { - f.filename = found.filename; - } - - std::vector m; - r.startpos = (int64_t)(start*_ratefactor); - r.sampleoffset = (int64_t)(sampleoffset*_ratefactor); - r.length = (int64_t)(length*_ratefactor); - r.wave = f; - r.midi = m; -} - -bool -PTFFormat::parserest(void) { - uint32_t i, j, count; - uint64_t start; - uint16_t rindex, rawindex, tindex, mindex; - uint32_t nch; - uint16_t ch_map[MAX_CHANNELS_PER_TRACK]; - bool found = false; - char *reg; - std::string regionname, trackname, midiregionname; - rindex = 0; - - // Parse sources->regions - for (vector::iterator b = blocks.begin(); - b != blocks.end(); ++b) { - if (b->content_type == 0x100b || b->content_type == 0x262a) { - //nregions = u_endian_read4(&_ptfunxored[b->offset+2], is_bigendian); - for (vector::iterator c = b->child.begin(); - c != b->child.end(); ++c) { - if (c->content_type == 0x1008 || c->content_type == 0x2629) { - vector::iterator d = c->child.begin(); - region_t r; - - found = true; - j = c->offset + 11; - reg = parsestring(j); - regionname = std::string(reg); - free(reg); - j += regionname.size() + 4; - - r.name = regionname; - r.index = rindex; - parse_region_info(j, *d, r); - - _regions.push_back(r); - rindex++; - } - } - found = true; - } - } - - // Parse tracks - for (vector::iterator b = blocks.begin(); - b != blocks.end(); ++b) { - if (b->content_type == 0x1015) { - //ntracks = u_endian_read4(&_ptfunxored[b->offset+2], is_bigendian); - for (vector::iterator c = b->child.begin(); - c != b->child.end(); ++c) { - if (c->content_type == 0x1014) { - j = c->offset + 2; - reg = parsestring(j); - trackname = std::string(reg); - j += trackname.size() + 5; - nch = u_endian_read4(&_ptfunxored[j], is_bigendian); - j += 4; - for (i = 0; i < nch; i++) { - ch_map[i] = u_endian_read2(&_ptfunxored[j], is_bigendian); - - track_t ti; - if (!find_track(ch_map[i], ti)) { - // Add a dummy region for now - region_t r (65535); - track_t t (ch_map[i]); - t.name = trackname; - t.reg = r; - _tracks.push_back(t); - } - //verbose_printf("%s : %d(%d)\n", reg, nch, ch_map[0]); - j += 2; - } - free(reg); - } - } - } - } - - // Reparse from scratch to exclude audio tracks from all tracks to get midi tracks - for (vector::iterator b = blocks.begin(); - b != blocks.end(); ++b) { - if (b->content_type == 0x2519) { - tindex = 0; - mindex = 0; - //ntracks = u_endian_read4(&_ptfunxored[b->offset+2], is_bigendian); - for (vector::iterator c = b->child.begin(); - c != b->child.end(); ++c) { - if (c->content_type == 0x251a) { - j = c->offset + 4; - reg = parsestring(j); - trackname = std::string(reg); - free(reg); - j += trackname.size() + 4 + 18; - //tindex = u_endian_read4(&_ptfunxored[j], is_bigendian); - - // Add a dummy region for now - region_t r (65535); - track_t t (mindex); - t.name = trackname; - t.reg = r; - - track_t ti; - // If the current track is not an audio track, insert as midi track - if (!(find_track(tindex, ti) && foundin(trackname, ti.name))) { - _miditracks.push_back(t); - mindex++; - } - tindex++; - } - } - } - } - - // Parse regions->tracks - for (vector::iterator b = blocks.begin(); - b != blocks.end(); ++b) { - tindex = 0; - if (b->content_type == 0x1012) { - //nregions = u_endian_read4(&_ptfunxored[b->offset+2], is_bigendian); - count = 0; - for (vector::iterator c = b->child.begin(); - c != b->child.end(); ++c) { - if (c->content_type == 0x1011) { - reg = parsestring(c->offset + 2); - regionname = std::string(reg); - free(reg); - for (vector::iterator d = c->child.begin(); - d != c->child.end(); ++d) { - if (d->content_type == 0x100f) { - for (vector::iterator e = d->child.begin(); - e != d->child.end(); ++e) { - if (e->content_type == 0x100e) { - // Region->track - track_t ti; - j = e->offset + 4; - rawindex = u_endian_read4(&_ptfunxored[j], is_bigendian); - if (!find_track(count, ti)) - continue; - if (!find_region(rawindex, ti.reg)) - continue; - if (ti.reg.index != 65535) { - _tracks.push_back(ti); - } - } - } - } - } - found = true; - count++; - } - } - } else if (b->content_type == 0x1054) { - //nregions = u_endian_read4(&_ptfunxored[b->offset+2], is_bigendian); - count = 0; - for (vector::iterator c = b->child.begin(); - c != b->child.end(); ++c) { - if (c->content_type == 0x1052) { - reg = parsestring(c->offset + 2); - regionname = std::string(reg); - free(reg); - for (vector::iterator d = c->child.begin(); - d != c->child.end(); ++d) { - if (d->content_type == 0x1050) { - for (vector::iterator e = d->child.begin(); - e != d->child.end(); ++e) { - if (e->content_type == 0x104f) { - // Region->track - j = e->offset + 4; - rawindex = u_endian_read4(&_ptfunxored[j], is_bigendian); - j += 4 + 1; - start = u_endian_read4(&_ptfunxored[j], is_bigendian); - tindex = count; - track_t ti; - if (!find_track(tindex, ti)) { - verbose_printf("dropped track %d\n", tindex); - continue; - } - if (!find_region(rawindex, ti.reg)) { - verbose_printf("dropped region %d\n", rawindex); - continue; - } - ti.reg.startpos = start; - if (ti.reg.index != 65535) { - _tracks.push_back(ti); - } - } - } - } - } - found = true; - count++; - } - } - } - } - for (std::vector::iterator tr = _tracks.begin(); - tr != _tracks.end(); ++tr) { - if ((*tr).reg.index == 65535) { - _tracks.erase(tr--); - } - } - return found; -} - -struct mchunk { - mchunk (uint64_t zt, uint64_t ml, std::vector const& c) - : zero (zt) - , maxlen (ml) - , chunk (c) - {} - uint64_t zero; - uint64_t maxlen; - std::vector chunk; -}; - -bool -PTFFormat::parsemidi(void) { - uint32_t i, j, k, n, rindex, tindex, mindex, count, rawindex; - uint64_t n_midi_events, zero_ticks, start, offset, length, start2, stop2; - uint64_t midi_pos, midi_len, max_pos, region_pos; - uint8_t midi_velocity, midi_note; - uint16_t regionnumber = 0; - std::string midiregionname; - - std::vector midichunks; - midi_ev_t m; - - char *str; - std::string regionname, trackname; - rindex = 0; - - // Parse MIDI events - for (vector::iterator b = blocks.begin(); - b != blocks.end(); ++b) { - if (b->content_type == 0x2000) { - - k = b->offset; - - // Parse all midi chunks, not 1:1 mapping to regions yet - while (k + 35 < b->block_size + b->offset) { - max_pos = 0; - std::vector midi; - - if (!jumpto(&k, _ptfunxored, _len, (const unsigned char *)"MdNLB", 5)) { - break; - } - k += 11; - n_midi_events = u_endian_read4(&_ptfunxored[k], is_bigendian); - - k += 4; - zero_ticks = u_endian_read5(&_ptfunxored[k], is_bigendian); - for (i = 0; i < n_midi_events && k < _len; i++, k += 35) { - midi_pos = u_endian_read5(&_ptfunxored[k], is_bigendian); - midi_pos -= zero_ticks; - midi_note = _ptfunxored[k+8]; - midi_len = u_endian_read5(&_ptfunxored[k+9], is_bigendian); - midi_velocity = _ptfunxored[k+17]; - - if (midi_pos + midi_len > max_pos) { - max_pos = midi_pos + midi_len; - } - - m.pos = midi_pos; - m.length = midi_len; - m.note = midi_note; - m.velocity = midi_velocity; - midi.push_back(m); - } - midichunks.push_back(mchunk (zero_ticks, max_pos, midi)); - } - - // Put chunks onto regions - } else if ((b->content_type == 0x2002) || (b->content_type == 0x2634)) { - for (vector::iterator c = b->child.begin(); - c != b->child.end(); ++c) { - if ((c->content_type == 0x2001) || (c->content_type == 0x2633)) { - for (vector::iterator d = c->child.begin(); - d != c->child.end(); ++d) { - if ((d->content_type == 0x1007) || (d->content_type == 0x2628)) { - j = d->offset + 2; - str = parsestring(j); - midiregionname = std::string(str); - j += 4 + midiregionname.size(); - parse_three_point(j, region_pos, zero_ticks, midi_len); - j = d->offset + d->block_size; - rindex = u_endian_read4(&_ptfunxored[j], is_bigendian); - struct mchunk mc = *(midichunks.begin()+rindex); - - region_t r (regionnumber++); - r.name = midiregionname; - r.startpos = (int64_t)0xe8d4a51000ULL; - r.sampleoffset = 0; - r.length = mc.maxlen; - r.midi = mc.chunk; - - _midiregions.push_back(r); - //verbose_printf("MIDI %s : r(%d) (%llu, %llu, %llu)\n", str, rindex, zero_ticks, region_pos, midi_len); - //dump_block(*d, 1); - free(str); - } - } - } - } - } - } - - // COMPOUND MIDI regions - for (vector::iterator b = blocks.begin(); - b != blocks.end(); ++b) { - if (b->content_type == 0x262c) { - mindex = 0; - for (vector::iterator c = b->child.begin(); - c != b->child.end(); ++c) { - if (c->content_type == 0x262b) { - for (vector::iterator d = c->child.begin(); - d != c->child.end(); ++d) { - if (d->content_type == 0x2628) { - count = 0; - j = d->offset + 2; - str = parsestring(j); - regionname = std::string(str); - j += 4 + regionname.size(); - parse_three_point(j, start, offset, length); - j = d->offset + d->block_size + 2; - n = u_endian_read2(&_ptfunxored[j], is_bigendian); - - for (vector::iterator e = d->child.begin(); - e != d->child.end(); ++e) { - if (e->content_type == 0x2523) { - // FIXME Compound MIDI region - j = e->offset + 39; - rawindex = u_endian_read4(&_ptfunxored[j], is_bigendian); - j += 12; - start2 = u_endian_read5(&_ptfunxored[j], is_bigendian); - int64_t signedval = (int64_t)start2; - signedval -= ZERO_TICKS; - if (signedval < 0) { - signedval = -signedval; - } - start2 = signedval; - j += 8; - stop2 = u_endian_read5(&_ptfunxored[j], is_bigendian); - signedval = (int64_t)stop2; - signedval -= ZERO_TICKS; - if (signedval < 0) { - signedval = -signedval; - } - stop2 = signedval; - j += 16; - //nn = u_endian_read4(&_ptfunxored[j], is_bigendian); - //verbose_printf("COMPOUND %s : c(%d) r(%d) ?(%d) ?(%d) (%llu %llu)(%llu %llu %llu)\n", str, mindex, rawindex, n, nn, start2, stop2, start, offset, length); - count++; - } - } - if (!count) { - // Plain MIDI region - struct mchunk mc = *(midichunks.begin()+n); - - region_t r (n); - r.name = midiregionname; - r.startpos = (int64_t)0xe8d4a51000ULL; - r.length = mc.maxlen; - r.midi = mc.chunk; - _midiregions.push_back(r); - verbose_printf("%s : MIDI region mr(%d) ?(%d) (%llu %llu %llu)\n", str, mindex, n, start, offset, length); - mindex++; - } - free(str); - } - } - } - } - } - } - - // Put midi regions onto midi tracks - for (vector::iterator b = blocks.begin(); - b != blocks.end(); ++b) { - if (b->content_type == 0x1058) { - //nregions = u_endian_read4(&_ptfunxored[b->offset+2], is_bigendian); - count = 0; - for (vector::iterator c = b->child.begin(); - c != b->child.end(); ++c) { - if (c->content_type == 0x1057) { - str = parsestring(c->offset + 2); - regionname = std::string(str); - free(str); - for (vector::iterator d = c->child.begin(); - d != c->child.end(); ++d) { - if (d->content_type == 0x1056) { - for (vector::iterator e = d->child.begin(); - e != d->child.end(); ++e) { - if (e->content_type == 0x104f) { - // MIDI region->MIDI track - track_t ti; - j = e->offset + 4; - rawindex = u_endian_read4(&_ptfunxored[j], is_bigendian); - j += 4 + 1; - start = u_endian_read5(&_ptfunxored[j], is_bigendian); - tindex = count; - if (!find_miditrack(tindex, ti)) { - verbose_printf("dropped midi t(%d) r(%d)\n", tindex, rawindex); - continue; - } - if (!find_midiregion(rawindex, ti.reg)) { - verbose_printf("dropped midiregion\n"); - continue; - } - //verbose_printf("MIDI : %s : t(%d) r(%d) %llu(%llu)\n", ti.name.c_str(), tindex, rawindex, start, ti.reg.startpos); - int64_t signedstart = (int64_t)(start - ZERO_TICKS); - if (signedstart < 0) - signedstart = -signedstart; - ti.reg.startpos = (uint64_t)signedstart; - if (ti.reg.index != 65535) { - _miditracks.push_back(ti); - } - } - } - } - } - count++; - } - } - } - } - for (std::vector::iterator tr = _miditracks.begin(); - tr != _miditracks.end(); ++tr) { - if ((*tr).reg.index == 65535) { - _miditracks.erase(tr--); - } - } - return true; -} diff --git a/ptformat.cc b/ptformat.cc new file mode 100644 index 0000000..fd0792a --- /dev/null +++ b/ptformat.cc @@ -0,0 +1,1337 @@ +/* + * libptformat - a library to read ProTools sessions + * + * Copyright (C) 2015 Damien Zammit + * Copyright (C) 2015 Robin Gareus + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include +#include +#include +#include +#include + +#include + +#include "ptformat/ptformat.h" + +#define BITCODE "0010111100101011" +#define ZMARK '\x5a' +#define ZERO_TICKS 0xe8d4a51000ULL +#define MAX_CONTENT_TYPE 0x3000 +#define MAX_CHANNELS_PER_TRACK 8 + +#if 0 +#define DEBUG +#endif + +#ifdef DEBUG +#define verbose_printf(...) printf("XXX PTFORMAT XXX: " __VA_ARGS__) +#else +#define verbose_printf(...) +#endif + +using namespace std; + +static void +hexdump(uint8_t *data, int length, int level) +{ + int i,j,k,end,step=16; + + for (i = 0; i < length; i += step) { + end = i + step; + if (end > length) end = length; + for (k = 0; k < level; k++) + printf(" "); + for (j = i; j < end; j++) { + printf("%02X ", data[j]); + } + for (j = i; j < end; j++) { + if (data[j] < 128 && data[j] > 32) + printf("%c", data[j]); + else + printf("."); + } + printf("\n"); + } +} + +PTFFormat::PTFFormat() + : _ptfunxored(0) + , _len(0) + , _sessionrate(0) + , _version(0) + , _product(NULL) + , _targetrate (0) + , _ratefactor (1.0) + , is_bigendian(false) +{ +} + +PTFFormat::~PTFFormat() { + cleanup(); +} + +const std::string +PTFFormat::get_content_description(uint16_t ctype) { + switch(ctype) { + case 0x0030: + return std::string("INFO product and version"); + case 0x1001: + return std::string("WAV samplerate, size"); + case 0x1003: + return std::string("WAV metadata"); + case 0x1004: + return std::string("WAV list full"); + case 0x1007: + return std::string("region name, number"); + case 0x1008: + return std::string("AUDIO region name, number (v5)"); + case 0x100b: + return std::string("AUDIO region list (v5)"); + case 0x100f: + return std::string("AUDIO region->track entry"); + case 0x1011: + return std::string("AUDIO region->track map entries"); + case 0x1012: + return std::string("AUDIO region->track full map"); + case 0x1014: + return std::string("AUDIO track name, number"); + case 0x1015: + return std::string("AUDIO tracks"); + case 0x1017: + return std::string("PLUGIN entry"); + case 0x1018: + return std::string("PLUGIN full list"); + case 0x1021: + return std::string("I/O channel entry"); + case 0x1022: + return std::string("I/O channel list"); + case 0x1028: + return std::string("INFO sample rate"); + case 0x103a: + return std::string("WAV names"); + case 0x104f: + return std::string("AUDIO region->track subentry (v8)"); + case 0x1050: + return std::string("AUDIO region->track entry (v8)"); + case 0x1052: + return std::string("AUDIO region->track map entries (v8)"); + case 0x1054: + return std::string("AUDIO region->track full map (v8)"); + case 0x1056: + return std::string("MIDI region->track entry"); + case 0x1057: + return std::string("MIDI region->track map entries"); + case 0x1058: + return std::string("MIDI region->track full map"); + case 0x2000: + return std::string("MIDI events block"); + case 0x2001: + return std::string("MIDI region name, number (v5)"); + case 0x2002: + return std::string("MIDI regions map (v5)"); + case 0x2067: + return std::string("INFO path of session"); + case 0x2511: + return std::string("Snaps block"); + case 0x2519: + return std::string("MIDI track full list"); + case 0x251a: + return std::string("MIDI track name, number"); + case 0x2523: + return std::string("COMPOUND region element"); + case 0x2602: + return std::string("I/O route"); + case 0x2603: + return std::string("I/O routing table"); + case 0x2628: + return std::string("COMPOUND region group"); + case 0x2629: + return std::string("AUDIO region name, number (v10)"); + case 0x262a: + return std::string("AUDIO region list (v10)"); + case 0x262c: + return std::string("COMPOUND region full map"); + case 0x2633: + return std::string("MIDI regions name, number (v10)"); + case 0x2634: + return std::string("MIDI regions map (v10)"); + case 0x271a: + return std::string("MARKER list"); + default: + return std::string("UNKNOWN content type"); + } +} + +uint16_t +PTFFormat::u_endian_read2(unsigned char *buf, bool bigendian) +{ + if (bigendian) { + return ((uint16_t)(buf[0]) << 8) | (uint16_t)(buf[1]); + } else { + return ((uint16_t)(buf[1]) << 8) | (uint16_t)(buf[0]); + } +} + +uint32_t +PTFFormat::u_endian_read3(unsigned char *buf, bool bigendian) +{ + if (bigendian) { + return ((uint32_t)(buf[0]) << 16) | + ((uint32_t)(buf[1]) << 8) | + (uint32_t)(buf[2]); + } else { + return ((uint32_t)(buf[2]) << 16) | + ((uint32_t)(buf[1]) << 8) | + (uint32_t)(buf[0]); + } +} + +uint32_t +PTFFormat::u_endian_read4(unsigned char *buf, bool bigendian) +{ + if (bigendian) { + return ((uint32_t)(buf[0]) << 24) | + ((uint32_t)(buf[1]) << 16) | + ((uint32_t)(buf[2]) << 8) | + (uint32_t)(buf[3]); + } else { + return ((uint32_t)(buf[3]) << 24) | + ((uint32_t)(buf[2]) << 16) | + ((uint32_t)(buf[1]) << 8) | + (uint32_t)(buf[0]); + } +} + +uint64_t +PTFFormat::u_endian_read5(unsigned char *buf, bool bigendian) +{ + if (bigendian) { + return ((uint64_t)(buf[0]) << 32) | + ((uint64_t)(buf[1]) << 24) | + ((uint64_t)(buf[2]) << 16) | + ((uint64_t)(buf[3]) << 8) | + (uint64_t)(buf[4]); + } else { + return ((uint64_t)(buf[4]) << 32) | + ((uint64_t)(buf[3]) << 24) | + ((uint64_t)(buf[2]) << 16) | + ((uint64_t)(buf[1]) << 8) | + (uint64_t)(buf[0]); + } +} + +uint64_t +PTFFormat::u_endian_read8(unsigned char *buf, bool bigendian) +{ + if (bigendian) { + return ((uint64_t)(buf[0]) << 56) | + ((uint64_t)(buf[1]) << 48) | + ((uint64_t)(buf[2]) << 40) | + ((uint64_t)(buf[3]) << 32) | + ((uint64_t)(buf[4]) << 24) | + ((uint64_t)(buf[5]) << 16) | + ((uint64_t)(buf[6]) << 8) | + (uint64_t)(buf[7]); + } else { + return ((uint64_t)(buf[7]) << 56) | + ((uint64_t)(buf[6]) << 48) | + ((uint64_t)(buf[5]) << 40) | + ((uint64_t)(buf[4]) << 32) | + ((uint64_t)(buf[3]) << 24) | + ((uint64_t)(buf[2]) << 16) | + ((uint64_t)(buf[1]) << 8) | + (uint64_t)(buf[0]); + } +} + +void +PTFFormat::cleanup(void) { + _len = 0; + _sessionrate = 0; + _version = 0; + free(_ptfunxored); + _ptfunxored = NULL; + free (_product); + _product = NULL; + _audiofiles.clear(); + _regions.clear(); + _midiregions.clear(); + _tracks.clear(); + _miditracks.clear(); + free_all_blocks(); +} + +int64_t +PTFFormat::foundat(unsigned char *haystack, uint64_t n, const char *needle) { + int64_t found = 0; + uint64_t i, j, needle_n; + needle_n = strlen(needle); + + for (i = 0; i < n; i++) { + found = i; + for (j = 0; j < needle_n; j++) { + if (haystack[i+j] != needle[j]) { + found = -1; + break; + } + } + if (found > 0) + return found; + } + return -1; +} + +bool +PTFFormat::jumpto(uint32_t *currpos, unsigned char *buf, const uint32_t maxoffset, const unsigned char *needle, const uint32_t needlelen) { + uint64_t i; + uint64_t k = *currpos; + while (k + needlelen < maxoffset) { + bool foundall = true; + for (i = 0; i < needlelen; i++) { + if (buf[k+i] != needle[i]) { + foundall = false; + break; + } + } + if (foundall) { + *currpos = k; + return true; + } + k++; + } + return false; +} + +bool +PTFFormat::jumpback(uint32_t *currpos, unsigned char *buf, const uint32_t maxoffset, const unsigned char *needle, const uint32_t needlelen) { + uint64_t i; + uint64_t k = *currpos; + while (k > 0 && k + needlelen < maxoffset) { + bool foundall = true; + for (i = 0; i < needlelen; i++) { + if (buf[k+i] != needle[i]) { + foundall = false; + break; + } + } + if (foundall) { + *currpos = k; + return true; + } + k--; + } + return false; +} + +bool +PTFFormat::foundin(std::string const& haystack, std::string const& needle) { + size_t found = haystack.find(needle); + if (found != std::string::npos) { + return true; + } else { + return false; + } +} + +/* Return values: 0 success + -1 could not decrypt pt session +*/ +int +PTFFormat::unxor(std::string const& path) { + FILE *fp; + unsigned char xxor[256]; + unsigned char ct; + uint64_t i; + uint8_t xor_type; + uint8_t xor_value; + uint8_t xor_delta; + uint16_t xor_len; + + if (! (fp = g_fopen(path.c_str(), "rb"))) { + return -1; + } + + fseek(fp, 0, SEEK_END); + _len = ftell(fp); + if (_len < 0x14) { + fclose(fp); + return -1; + } + + if (! (_ptfunxored = (unsigned char*) malloc(_len * sizeof(unsigned char)))) { + /* Silently fail -- out of memory*/ + fclose(fp); + _ptfunxored = 0; + return -1; + } + + /* The first 20 bytes are always unencrypted */ + fseek(fp, 0x00, SEEK_SET); + i = fread(_ptfunxored, 1, 0x14, fp); + if (i < 0x14) { + fclose(fp); + return -1; + } + + xor_type = _ptfunxored[0x12]; + xor_value = _ptfunxored[0x13]; + xor_len = 256; + + // xor_type 0x01 = ProTools 5, 6, 7, 8 and 9 + // xor_type 0x05 = ProTools 10, 11, 12 + switch(xor_type) { + case 0x01: + xor_delta = gen_xor_delta(xor_value, 53, false); + break; + case 0x05: + xor_delta = gen_xor_delta(xor_value, 11, true); + break; + default: + fclose(fp); + return -1; + } + + /* Generate the xor_key */ + for (i=0; i < xor_len; i++) + xxor[i] = (i * xor_delta) & 0xff; + + /* hexdump(xxor, xor_len); */ + + /* Read file and decrypt rest of file */ + i = 0x14; + fseek(fp, i, SEEK_SET); + while (fread(&ct, 1, 1, fp) != 0) { + uint8_t xor_index = (xor_type == 0x01) ? i & 0xff : (i >> 12) & 0xff; + _ptfunxored[i++] = ct ^ xxor[xor_index]; + } + fclose(fp); + return 0; +} + +/* Return values: 0 success + -1 could not parse pt session +*/ +int +PTFFormat::load(std::string const& ptf, int64_t targetsr) { + cleanup(); + path = ptf; + + if (unxor(path)) + return -1; + + if (parse_version()) + return -2; + + if (_version < 5 || _version > 12) + return -3; + + _targetrate = targetsr; + + int err = 0; + if ((err = parse())) { + printf ("PARSE FAILED %d\n", err); + return -4; + } + + return 0; +} + +bool +PTFFormat::parse_version() { + uint32_t seg_len,str_len; + uint8_t *data = _ptfunxored + 0x14; + uintptr_t data_end = ((uintptr_t)_ptfunxored) + 0x100; + uint8_t seg_type; + bool success = false; + + if (_ptfunxored[0] != '\x03' && foundat(_ptfunxored, 0x100, BITCODE) != 1) { + return false; + } + + while( ((uintptr_t)data < data_end) && (success == false) ) { + + if (data[0] != 0x5a) { + success = false; + break; + } + + seg_type = data[1]; + /* Skip segment header */ + data += 3; + if (data[0] == 0 && data[1] == 0) { + /* BE */ + is_bigendian = true; + } else { + /* LE */ + is_bigendian = false; + } + seg_len = u_endian_read4(&data[0], is_bigendian); + + /* Skip seg_len */ + data += 4; + if (!(seg_type == 0x04 || seg_type == 0x03) || data[0] != 0x03) { + /* Go to next segment */ + data += seg_len; + continue; + } + /* Skip 0x03 0x00 0x00 */ + data += 3; + seg_len -= 3; + str_len = (*(uint8_t *)data); + if (! (_product = (uint8_t *)malloc((str_len+1) * sizeof(uint8_t)))) { + success = false; + break; + } + + /* Skip str_len */ + data += 4; + seg_len -= 4; + + memcpy(_product, data, str_len); + _product[str_len] = 0; + data += str_len; + seg_len -= str_len; + + /* Skip 0x03 0x00 0x00 0x00 */ + data += 4; + seg_len -= 4; + + _version = data[0]; + if (_version == 0) { + _version = data[3]; + } + data += seg_len; + success = true; + } + + /* If the above does not work, try other heuristics */ + if ((uintptr_t)data >= data_end - seg_len) { + _version = _ptfunxored[0x40]; + if (_version == 0) { + _version = _ptfunxored[0x3d]; + } + if (_version == 0) { + _version = _ptfunxored[0x3a] + 2; + } + if (_version != 0) { + success = true; + } + } + return (!success); +} + +uint8_t +PTFFormat::gen_xor_delta(uint8_t xor_value, uint8_t mul, bool negative) { + uint16_t i; + for (i = 0; i < 256; i++) { + if (((i * mul) & 0xff) == xor_value) { + return (negative) ? i * (-1) : i; + } + } + // Should not occur + return 0; +} + +void +PTFFormat::setrates(void) { + _ratefactor = 1.f; + if (_sessionrate != 0) { + _ratefactor = (float)_targetrate / _sessionrate; + } +} + +bool +PTFFormat::parse_block_at(uint32_t pos, struct block_t *block, int level) { + struct block_t b; + int childjump = 0; + uint32_t i; + + if (pos + 7 > _len) + return false; + if (level > 10) + return false; + if (_ptfunxored[pos] != ZMARK) + return false; + + b.zmark = ZMARK; + b.block_type = u_endian_read2(&_ptfunxored[pos+1], is_bigendian); + b.block_size = u_endian_read4(&_ptfunxored[pos+3], is_bigendian); + b.content_type = u_endian_read2(&_ptfunxored[pos+7], is_bigendian); + b.offset = pos + 7; + + if (b.block_size + b.offset > _len) + return false; + if (b.block_type & 0xff00) + return false; + + block->zmark = b.zmark; + block->block_type = b.block_type; + block->block_size = b.block_size; + block->content_type = b.content_type; + block->offset = b.offset; + + for (i = 1; (i < block->block_size) && (pos + i + childjump < _len); i += childjump ? childjump : 1) { + int p = pos + i; + struct block_t bchild; + childjump = 0; + if (parse_block_at(p, &bchild, level+1)) { + block->child.push_back(bchild); + childjump = bchild.block_size + 7; + } + } + return true; +} + +void +PTFFormat::dump_block(struct block_t& b, int level) +{ + int i; + + for (i = 0; i < level; i++) { + printf(" "); + } + printf("%s(0x%04x)\n", get_content_description(b.content_type).c_str(), b.content_type); + hexdump(&_ptfunxored[b.offset], b.block_size, level); + + for (vector::iterator c = b.child.begin(); + c != b.child.end(); ++c) { + dump_block(*c, level + 1); + } +} + +void +PTFFormat::free_block(struct block_t& b) +{ + for (vector::iterator c = b.child.begin(); + c != b.child.end(); ++c) { + free_block(*c); + } + + b.child.clear(); +} + +void +PTFFormat::free_all_blocks(void) +{ + for (vector::iterator b = blocks.begin(); + b != blocks.end(); ++b) { + free_block(*b); + } + + blocks.clear(); +} + +void +PTFFormat::dump(void) { + for (vector::iterator b = blocks.begin(); + b != blocks.end(); ++b) { + dump_block(*b, 0); + } +} + +void +PTFFormat::parseblocks(void) { + uint32_t i = 20; + + while (i < _len) { + struct block_t b; + if (parse_block_at(i, &b, 0)) { + blocks.push_back(b); + } + i += b.block_size ? b.block_size + 7 : 1; + } +} + +int +PTFFormat::parse(void) { + parseblocks(); +#ifdef DEBUG + dump(); +#endif + if (!parseheader()) + return -1; + setrates(); + if (_sessionrate < 44100 || _sessionrate > 192000) + return -2; + if (!parseaudio()) + return -3; + if (!parserest()) + return -4; + if (!parsemidi()) + return -5; + return 0; +} + +bool +PTFFormat::parseheader(void) { + bool found = false; + + for (vector::iterator b = blocks.begin(); + b != blocks.end(); ++b) { + if (b->content_type == 0x1028) { + _sessionrate = u_endian_read4(&_ptfunxored[b->offset+4], is_bigendian); + found = true; + } + } + return found; +} + +char * +PTFFormat::parsestring(uint32_t pos) { + uint32_t length = u_endian_read4(&_ptfunxored[pos], is_bigendian); + pos += 4; + return strndup((const char *)&_ptfunxored[pos], length); +} + +bool +PTFFormat::parseaudio(void) { + bool found = false; + uint32_t nwavs, i, n; + uint32_t pos = 0; + char *str; + std::string wavtype; + std::string wavname; + + // Parse wav names + for (vector::iterator b = blocks.begin(); + b != blocks.end(); ++b) { + if (b->content_type == 0x1004) { + + nwavs = u_endian_read4(&_ptfunxored[b->offset+2], is_bigendian); + + for (vector::iterator c = b->child.begin(); + c != b->child.end(); ++c) { + if (c->content_type == 0x103a) { + found = true; + //nstrings = u_endian_read4(&_ptfunxored[c->offset+1], is_bigendian); + pos = c->offset + 11; + // Found wav list + for (i = n = 0; (pos < c->offset + c->block_size) && (n < nwavs); i++) { + str = parsestring(pos); + wavname = std::string(str); + free(str); + pos += wavname.size() + 4; + str = strndup((const char *)&_ptfunxored[pos], 4); + wavtype = std::string(str); + free(str); + pos += 9; + if (foundin(wavname, std::string(".grp"))) + continue; + + if (foundin(wavname, std::string("Audio Files"))) { + continue; + } + if (foundin(wavname, std::string("Fade Files"))) { + continue; + } + if (_version < 10) { + if (!(foundin(wavtype, std::string("WAVE")) || + foundin(wavtype, std::string("EVAW")) || + foundin(wavtype, std::string("AIFF")) || + foundin(wavtype, std::string("FFIA"))) ) { + continue; + } + } else { + if (wavtype.size() != 0) { + if (!(foundin(wavtype, std::string("WAVE")) || + foundin(wavtype, std::string("EVAW")) || + foundin(wavtype, std::string("AIFF")) || + foundin(wavtype, std::string("FFIA"))) ) { + continue; + } + } else if (!(foundin(wavname, std::string(".wav")) || + foundin(wavname, std::string(".aif"))) ) { + continue; + } + } + wav_t f (n); + f.filename = wavname; + n++; + _audiofiles.push_back(f); + } + } + } + } + } + + // Add wav length information + for (vector::iterator b = blocks.begin(); + b != blocks.end(); ++b) { + if (b->content_type == 0x1004) { + + vector::iterator wav = _audiofiles.begin(); + + for (vector::iterator c = b->child.begin(); + c != b->child.end(); ++c) { + if (c->content_type == 0x1003) { + for (vector::iterator d = c->child.begin(); + d != c->child.end(); ++d) { + if (d->content_type == 0x1001) { + (*wav).length = u_endian_read8(&_ptfunxored[d->offset+8], is_bigendian); + wav++; + } + } + } + } + } + } + + return found; +} + + +void +PTFFormat::parse_three_point(uint32_t j, uint64_t& start, uint64_t& offset, uint64_t& length) { + uint8_t offsetbytes, lengthbytes, startbytes; + + if (is_bigendian) { + offsetbytes = (_ptfunxored[j+4] & 0xf0) >> 4; + lengthbytes = (_ptfunxored[j+3] & 0xf0) >> 4; + startbytes = (_ptfunxored[j+2] & 0xf0) >> 4; + //somethingbytes = (_ptfunxored[j+2] & 0xf); + //skipbytes = _ptfunxored[j+1]; + } else { + offsetbytes = (_ptfunxored[j+1] & 0xf0) >> 4; //3 + lengthbytes = (_ptfunxored[j+2] & 0xf0) >> 4; + startbytes = (_ptfunxored[j+3] & 0xf0) >> 4; //1 + //somethingbytes = (_ptfunxored[j+3] & 0xf); + //skipbytes = _ptfunxored[j+4]; + } + + switch (offsetbytes) { + case 5: + offset = u_endian_read5(&_ptfunxored[j+5], false); + break; + case 4: + offset = (uint64_t)u_endian_read4(&_ptfunxored[j+5], false); + break; + case 3: + offset = (uint64_t)u_endian_read3(&_ptfunxored[j+5], false); + break; + case 2: + offset = (uint64_t)u_endian_read2(&_ptfunxored[j+5], false); + break; + case 1: + offset = (uint64_t)(_ptfunxored[j+5]); + break; + default: + offset = 0; + break; + } + j+=offsetbytes; + switch (lengthbytes) { + case 5: + length = u_endian_read5(&_ptfunxored[j+5], false); + break; + case 4: + length = (uint64_t)u_endian_read4(&_ptfunxored[j+5], false); + break; + case 3: + length = (uint64_t)u_endian_read3(&_ptfunxored[j+5], false); + break; + case 2: + length = (uint64_t)u_endian_read2(&_ptfunxored[j+5], false); + break; + case 1: + length = (uint64_t)(_ptfunxored[j+5]); + break; + default: + length = 0; + break; + } + j+=lengthbytes; + switch (startbytes) { + case 5: + start = u_endian_read5(&_ptfunxored[j+5], false); + break; + case 4: + start = (uint64_t)u_endian_read4(&_ptfunxored[j+5], false); + break; + case 3: + start = (uint64_t)u_endian_read3(&_ptfunxored[j+5], false); + break; + case 2: + start = (uint64_t)u_endian_read2(&_ptfunxored[j+5], false); + break; + case 1: + start = (uint64_t)(_ptfunxored[j+5]); + break; + default: + start = 0; + break; + } +} + +void +PTFFormat::parse_region_info(uint32_t j, block_t& blk, region_t& r) { + uint64_t findex, start, sampleoffset, length; + + parse_three_point(j, start, sampleoffset, length); + + findex = u_endian_read4(&_ptfunxored[blk.offset + blk.block_size], is_bigendian); + wav_t f (findex); + f.posabsolute = start * _ratefactor; + f.length = length * _ratefactor; + + wav_t found; + if (find_wav(findex, found)) { + f.filename = found.filename; + } + + std::vector m; + r.startpos = (int64_t)(start*_ratefactor); + r.sampleoffset = (int64_t)(sampleoffset*_ratefactor); + r.length = (int64_t)(length*_ratefactor); + r.wave = f; + r.midi = m; +} + +bool +PTFFormat::parserest(void) { + uint32_t i, j, count; + uint64_t start; + uint16_t rindex, rawindex, tindex, mindex; + uint32_t nch; + uint16_t ch_map[MAX_CHANNELS_PER_TRACK]; + bool found = false; + char *reg; + std::string regionname, trackname, midiregionname; + rindex = 0; + + // Parse sources->regions + for (vector::iterator b = blocks.begin(); + b != blocks.end(); ++b) { + if (b->content_type == 0x100b || b->content_type == 0x262a) { + //nregions = u_endian_read4(&_ptfunxored[b->offset+2], is_bigendian); + for (vector::iterator c = b->child.begin(); + c != b->child.end(); ++c) { + if (c->content_type == 0x1008 || c->content_type == 0x2629) { + vector::iterator d = c->child.begin(); + region_t r; + + found = true; + j = c->offset + 11; + reg = parsestring(j); + regionname = std::string(reg); + free(reg); + j += regionname.size() + 4; + + r.name = regionname; + r.index = rindex; + parse_region_info(j, *d, r); + + _regions.push_back(r); + rindex++; + } + } + found = true; + } + } + + // Parse tracks + for (vector::iterator b = blocks.begin(); + b != blocks.end(); ++b) { + if (b->content_type == 0x1015) { + //ntracks = u_endian_read4(&_ptfunxored[b->offset+2], is_bigendian); + for (vector::iterator c = b->child.begin(); + c != b->child.end(); ++c) { + if (c->content_type == 0x1014) { + j = c->offset + 2; + reg = parsestring(j); + trackname = std::string(reg); + j += trackname.size() + 5; + nch = u_endian_read4(&_ptfunxored[j], is_bigendian); + j += 4; + for (i = 0; i < nch; i++) { + ch_map[i] = u_endian_read2(&_ptfunxored[j], is_bigendian); + + track_t ti; + if (!find_track(ch_map[i], ti)) { + // Add a dummy region for now + region_t r (65535); + track_t t (ch_map[i]); + t.name = trackname; + t.reg = r; + _tracks.push_back(t); + } + //verbose_printf("%s : %d(%d)\n", reg, nch, ch_map[0]); + j += 2; + } + free(reg); + } + } + } + } + + // Reparse from scratch to exclude audio tracks from all tracks to get midi tracks + for (vector::iterator b = blocks.begin(); + b != blocks.end(); ++b) { + if (b->content_type == 0x2519) { + tindex = 0; + mindex = 0; + //ntracks = u_endian_read4(&_ptfunxored[b->offset+2], is_bigendian); + for (vector::iterator c = b->child.begin(); + c != b->child.end(); ++c) { + if (c->content_type == 0x251a) { + j = c->offset + 4; + reg = parsestring(j); + trackname = std::string(reg); + free(reg); + j += trackname.size() + 4 + 18; + //tindex = u_endian_read4(&_ptfunxored[j], is_bigendian); + + // Add a dummy region for now + region_t r (65535); + track_t t (mindex); + t.name = trackname; + t.reg = r; + + track_t ti; + // If the current track is not an audio track, insert as midi track + if (!(find_track(tindex, ti) && foundin(trackname, ti.name))) { + _miditracks.push_back(t); + mindex++; + } + tindex++; + } + } + } + } + + // Parse regions->tracks + for (vector::iterator b = blocks.begin(); + b != blocks.end(); ++b) { + tindex = 0; + if (b->content_type == 0x1012) { + //nregions = u_endian_read4(&_ptfunxored[b->offset+2], is_bigendian); + count = 0; + for (vector::iterator c = b->child.begin(); + c != b->child.end(); ++c) { + if (c->content_type == 0x1011) { + reg = parsestring(c->offset + 2); + regionname = std::string(reg); + free(reg); + for (vector::iterator d = c->child.begin(); + d != c->child.end(); ++d) { + if (d->content_type == 0x100f) { + for (vector::iterator e = d->child.begin(); + e != d->child.end(); ++e) { + if (e->content_type == 0x100e) { + // Region->track + track_t ti; + j = e->offset + 4; + rawindex = u_endian_read4(&_ptfunxored[j], is_bigendian); + if (!find_track(count, ti)) + continue; + if (!find_region(rawindex, ti.reg)) + continue; + if (ti.reg.index != 65535) { + _tracks.push_back(ti); + } + } + } + } + } + found = true; + count++; + } + } + } else if (b->content_type == 0x1054) { + //nregions = u_endian_read4(&_ptfunxored[b->offset+2], is_bigendian); + count = 0; + for (vector::iterator c = b->child.begin(); + c != b->child.end(); ++c) { + if (c->content_type == 0x1052) { + reg = parsestring(c->offset + 2); + regionname = std::string(reg); + free(reg); + for (vector::iterator d = c->child.begin(); + d != c->child.end(); ++d) { + if (d->content_type == 0x1050) { + for (vector::iterator e = d->child.begin(); + e != d->child.end(); ++e) { + if (e->content_type == 0x104f) { + // Region->track + j = e->offset + 4; + rawindex = u_endian_read4(&_ptfunxored[j], is_bigendian); + j += 4 + 1; + start = u_endian_read4(&_ptfunxored[j], is_bigendian); + tindex = count; + track_t ti; + if (!find_track(tindex, ti)) { + verbose_printf("dropped track %d\n", tindex); + continue; + } + if (!find_region(rawindex, ti.reg)) { + verbose_printf("dropped region %d\n", rawindex); + continue; + } + ti.reg.startpos = start; + if (ti.reg.index != 65535) { + _tracks.push_back(ti); + } + } + } + } + } + found = true; + count++; + } + } + } + } + for (std::vector::iterator tr = _tracks.begin(); + tr != _tracks.end(); ++tr) { + if ((*tr).reg.index == 65535) { + _tracks.erase(tr--); + } + } + return found; +} + +struct mchunk { + mchunk (uint64_t zt, uint64_t ml, std::vector const& c) + : zero (zt) + , maxlen (ml) + , chunk (c) + {} + uint64_t zero; + uint64_t maxlen; + std::vector chunk; +}; + +bool +PTFFormat::parsemidi(void) { + uint32_t i, j, k, n, rindex, tindex, mindex, count, rawindex; + uint64_t n_midi_events, zero_ticks, start, offset, length, start2, stop2; + uint64_t midi_pos, midi_len, max_pos, region_pos; + uint8_t midi_velocity, midi_note; + uint16_t regionnumber = 0; + std::string midiregionname; + + std::vector midichunks; + midi_ev_t m; + + char *str; + std::string regionname, trackname; + rindex = 0; + + // Parse MIDI events + for (vector::iterator b = blocks.begin(); + b != blocks.end(); ++b) { + if (b->content_type == 0x2000) { + + k = b->offset; + + // Parse all midi chunks, not 1:1 mapping to regions yet + while (k + 35 < b->block_size + b->offset) { + max_pos = 0; + std::vector midi; + + if (!jumpto(&k, _ptfunxored, _len, (const unsigned char *)"MdNLB", 5)) { + break; + } + k += 11; + n_midi_events = u_endian_read4(&_ptfunxored[k], is_bigendian); + + k += 4; + zero_ticks = u_endian_read5(&_ptfunxored[k], is_bigendian); + for (i = 0; i < n_midi_events && k < _len; i++, k += 35) { + midi_pos = u_endian_read5(&_ptfunxored[k], is_bigendian); + midi_pos -= zero_ticks; + midi_note = _ptfunxored[k+8]; + midi_len = u_endian_read5(&_ptfunxored[k+9], is_bigendian); + midi_velocity = _ptfunxored[k+17]; + + if (midi_pos + midi_len > max_pos) { + max_pos = midi_pos + midi_len; + } + + m.pos = midi_pos; + m.length = midi_len; + m.note = midi_note; + m.velocity = midi_velocity; + midi.push_back(m); + } + midichunks.push_back(mchunk (zero_ticks, max_pos, midi)); + } + + // Put chunks onto regions + } else if ((b->content_type == 0x2002) || (b->content_type == 0x2634)) { + for (vector::iterator c = b->child.begin(); + c != b->child.end(); ++c) { + if ((c->content_type == 0x2001) || (c->content_type == 0x2633)) { + for (vector::iterator d = c->child.begin(); + d != c->child.end(); ++d) { + if ((d->content_type == 0x1007) || (d->content_type == 0x2628)) { + j = d->offset + 2; + str = parsestring(j); + midiregionname = std::string(str); + j += 4 + midiregionname.size(); + parse_three_point(j, region_pos, zero_ticks, midi_len); + j = d->offset + d->block_size; + rindex = u_endian_read4(&_ptfunxored[j], is_bigendian); + struct mchunk mc = *(midichunks.begin()+rindex); + + region_t r (regionnumber++); + r.name = midiregionname; + r.startpos = (int64_t)0xe8d4a51000ULL; + r.sampleoffset = 0; + r.length = mc.maxlen; + r.midi = mc.chunk; + + _midiregions.push_back(r); + //verbose_printf("MIDI %s : r(%d) (%llu, %llu, %llu)\n", str, rindex, zero_ticks, region_pos, midi_len); + //dump_block(*d, 1); + free(str); + } + } + } + } + } + } + + // COMPOUND MIDI regions + for (vector::iterator b = blocks.begin(); + b != blocks.end(); ++b) { + if (b->content_type == 0x262c) { + mindex = 0; + for (vector::iterator c = b->child.begin(); + c != b->child.end(); ++c) { + if (c->content_type == 0x262b) { + for (vector::iterator d = c->child.begin(); + d != c->child.end(); ++d) { + if (d->content_type == 0x2628) { + count = 0; + j = d->offset + 2; + str = parsestring(j); + regionname = std::string(str); + j += 4 + regionname.size(); + parse_three_point(j, start, offset, length); + j = d->offset + d->block_size + 2; + n = u_endian_read2(&_ptfunxored[j], is_bigendian); + + for (vector::iterator e = d->child.begin(); + e != d->child.end(); ++e) { + if (e->content_type == 0x2523) { + // FIXME Compound MIDI region + j = e->offset + 39; + rawindex = u_endian_read4(&_ptfunxored[j], is_bigendian); + j += 12; + start2 = u_endian_read5(&_ptfunxored[j], is_bigendian); + int64_t signedval = (int64_t)start2; + signedval -= ZERO_TICKS; + if (signedval < 0) { + signedval = -signedval; + } + start2 = signedval; + j += 8; + stop2 = u_endian_read5(&_ptfunxored[j], is_bigendian); + signedval = (int64_t)stop2; + signedval -= ZERO_TICKS; + if (signedval < 0) { + signedval = -signedval; + } + stop2 = signedval; + j += 16; + //nn = u_endian_read4(&_ptfunxored[j], is_bigendian); + //verbose_printf("COMPOUND %s : c(%d) r(%d) ?(%d) ?(%d) (%llu %llu)(%llu %llu %llu)\n", str, mindex, rawindex, n, nn, start2, stop2, start, offset, length); + count++; + } + } + if (!count) { + // Plain MIDI region + struct mchunk mc = *(midichunks.begin()+n); + + region_t r (n); + r.name = midiregionname; + r.startpos = (int64_t)0xe8d4a51000ULL; + r.length = mc.maxlen; + r.midi = mc.chunk; + _midiregions.push_back(r); + verbose_printf("%s : MIDI region mr(%d) ?(%d) (%llu %llu %llu)\n", str, mindex, n, start, offset, length); + mindex++; + } + free(str); + } + } + } + } + } + } + + // Put midi regions onto midi tracks + for (vector::iterator b = blocks.begin(); + b != blocks.end(); ++b) { + if (b->content_type == 0x1058) { + //nregions = u_endian_read4(&_ptfunxored[b->offset+2], is_bigendian); + count = 0; + for (vector::iterator c = b->child.begin(); + c != b->child.end(); ++c) { + if (c->content_type == 0x1057) { + str = parsestring(c->offset + 2); + regionname = std::string(str); + free(str); + for (vector::iterator d = c->child.begin(); + d != c->child.end(); ++d) { + if (d->content_type == 0x1056) { + for (vector::iterator e = d->child.begin(); + e != d->child.end(); ++e) { + if (e->content_type == 0x104f) { + // MIDI region->MIDI track + track_t ti; + j = e->offset + 4; + rawindex = u_endian_read4(&_ptfunxored[j], is_bigendian); + j += 4 + 1; + start = u_endian_read5(&_ptfunxored[j], is_bigendian); + tindex = count; + if (!find_miditrack(tindex, ti)) { + verbose_printf("dropped midi t(%d) r(%d)\n", tindex, rawindex); + continue; + } + if (!find_midiregion(rawindex, ti.reg)) { + verbose_printf("dropped midiregion\n"); + continue; + } + //verbose_printf("MIDI : %s : t(%d) r(%d) %llu(%llu)\n", ti.name.c_str(), tindex, rawindex, start, ti.reg.startpos); + int64_t signedstart = (int64_t)(start - ZERO_TICKS); + if (signedstart < 0) + signedstart = -signedstart; + ti.reg.startpos = (uint64_t)signedstart; + if (ti.reg.index != 65535) { + _miditracks.push_back(ti); + } + } + } + } + } + count++; + } + } + } + } + for (std::vector::iterator tr = _miditracks.begin(); + tr != _miditracks.end(); ++tr) { + if ((*tr).reg.index == 65535) { + _miditracks.erase(tr--); + } + } + return true; +} diff --git a/ptformat/ptfformat.h b/ptformat/ptfformat.h deleted file mode 100644 index 5a08596..0000000 --- a/ptformat/ptfformat.h +++ /dev/null @@ -1,283 +0,0 @@ -/* - * libptformat - a library to read ProTools sessions - * - * Copyright (C) 2015 Damien Zammit - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - */ -#ifndef PTFFORMAT_H -#define PTFFORMAT_H - -#include -#include -#include -#include -#include -#include "ptformat/visibility.h" - -#define BITCODE "0010111100101011" -#define ZMARK '\x5a' -#define ZERO_TICKS 0xe8d4a51000ULL -#define MAX_CONTENT_TYPE 0x3000 -#define MAX_CHANNELS_PER_TRACK 8 - -class LIBPTFORMAT_API PTFFormat { -public: - PTFFormat(); - ~PTFFormat(); - - /* Return values: 0 success - -1 could not parse pt session - */ - int load(std::string const& path, int64_t targetsr); - - /* Return values: 0 success - -1 could not decrypt pt session - */ - int unxor(std::string const& path); - - struct wav_t { - std::string filename; - uint16_t index; - - int64_t posabsolute; - int64_t length; - - bool operator <(const struct wav_t& other) const { - return (strcasecmp(this->filename.c_str(), - other.filename.c_str()) < 0); - } - - bool operator ==(const struct wav_t& other) const { - return (this->filename == other.filename || - this->index == other.index); - } - - wav_t (uint16_t idx = 0) : index (idx), posabsolute (0), length (0) {} - }; - - struct midi_ev_t { - uint64_t pos; - uint64_t length; - uint8_t note; - uint8_t velocity; - midi_ev_t () : pos (0), length (0), note (0), velocity (0) {} - }; - - struct region_t { - std::string name; - uint16_t index; - int64_t startpos; - int64_t sampleoffset; - int64_t length; - wav_t wave; - std::vector midi; - - bool operator ==(const region_t& other) const { - return (this->index == other.index); - } - - bool operator <(const region_t& other) const { - return (strcasecmp(this->name.c_str(), - other.name.c_str()) < 0); - } - region_t (uint16_t idx = 0) : index (idx), startpos (0), sampleoffset (0), length (0) {} - }; - - struct track_t { - std::string name; - uint16_t index; - uint8_t playlist; - region_t reg; - - bool operator <(const track_t& other) const { - return (this->index < other.index); - } - - bool operator ==(const track_t& other) const { - return (this->index == other.index); - } - track_t (uint16_t idx = 0) : index (idx), playlist (0) {} - }; - - bool find_track(uint16_t index, track_t& tt) const { - std::vector::const_iterator begin = _tracks.begin(); - std::vector::const_iterator finish = _tracks.end(); - std::vector::const_iterator found; - - track_t t (index); - - if ((found = std::find(begin, finish, t)) != finish) { - tt = *found; - return true; - } - return false; - } - - bool find_region(uint16_t index, region_t& rr) const { - std::vector::const_iterator begin = _regions.begin(); - std::vector::const_iterator finish = _regions.end(); - std::vector::const_iterator found; - - region_t r; - r.index = index; - - if ((found = std::find(begin, finish, r)) != finish) { - rr = *found; - return true; - } - return false; - } - - bool find_miditrack(uint16_t index, track_t& tt) const { - std::vector::const_iterator begin = _miditracks.begin(); - std::vector::const_iterator finish = _miditracks.end(); - std::vector::const_iterator found; - - track_t t (index); - - if ((found = std::find(begin, finish, t)) != finish) { - tt = *found; - return true; - } - return false; - } - - bool find_midiregion(uint16_t index, region_t& rr) const { - std::vector::const_iterator begin = _midiregions.begin(); - std::vector::const_iterator finish = _midiregions.end(); - std::vector::const_iterator found; - - region_t r (index); - - if ((found = std::find(begin, finish, r)) != finish) { - rr = *found; - return true; - } - return false; - } - - bool find_wav(uint16_t index, wav_t& ww) const { - std::vector::const_iterator begin = _audiofiles.begin(); - std::vector::const_iterator finish = _audiofiles.end(); - std::vector::const_iterator found; - - wav_t w (index); - - if ((found = std::find(begin, finish, w)) != finish) { - ww = *found; - return true; - } - return false; - } - - static bool regionexistsin(std::vector const& reg, uint16_t index) { - std::vector::const_iterator begin = reg.begin(); - std::vector::const_iterator finish = reg.end(); - - region_t r (index); - - if (std::find(begin, finish, r) != finish) { - return true; - } - return false; - } - - static bool wavexistsin (std::vector const& wv, uint16_t index) { - std::vector::const_iterator begin = wv.begin(); - std::vector::const_iterator finish = wv.end(); - - wav_t w (index); - - if (std::find(begin, finish, w) != finish) { - return true; - } - return false; - } - - uint8_t version () const { return _version; } - int64_t sessionrate () const { return _sessionrate ; } - - const std::vector& audiofiles () const { return _audiofiles ; } - const std::vector& regions () const { return _regions ; } - const std::vector& midiregions () const { return _midiregions ; } - const std::vector& tracks () const { return _tracks ; } - const std::vector& miditracks () const { return _miditracks ; } - - const unsigned char* unxored_data () const { return _ptfunxored; } - uint64_t unxored_size () const { return _len; } - -private: - - std::vector _audiofiles; - std::vector _regions; - std::vector _midiregions; - std::vector _tracks; - std::vector _miditracks; - - std::string path; - - unsigned char* _ptfunxored; - uint64_t _len; - int64_t _sessionrate; - uint8_t _version; - uint8_t* _product; - int64_t _targetrate; - float _ratefactor; - bool is_bigendian; - - struct block_t { - uint8_t zmark; // 'Z' - uint16_t block_type; // type of block - uint32_t block_size; // size of block - uint16_t content_type; // type of content - uint32_t offset; // offset in file - std::vector child; // vector of child blocks - }; - std::vector blocks; - - bool jumpback(uint32_t *currpos, unsigned char *buf, const uint32_t maxoffset, const unsigned char *needle, const uint32_t needlelen); - bool jumpto(uint32_t *currpos, unsigned char *buf, const uint32_t maxoffset, const unsigned char *needle, const uint32_t needlelen); - bool foundin(std::string haystack, std::string needle); - int64_t foundat(unsigned char *haystack, uint64_t n, const char *needle); - uint16_t u_endian_read2(unsigned char *buf, bool); - uint32_t u_endian_read3(unsigned char *buf, bool); - uint32_t u_endian_read4(unsigned char *buf, bool); - uint64_t u_endian_read5(unsigned char *buf, bool); - uint64_t u_endian_read8(unsigned char *buf, bool); - - char *parsestring(uint32_t pos); - std::string get_content_description(uint16_t ctype); - int parse(void); - void parseblocks(void); - bool parseheader(void); - bool parserest(void); - bool parseaudio(void); - bool parsemidi(void); - void dump(void); - bool parse_block_at(uint32_t pos, struct block_t *b, int level); - void dump_block(struct block_t& b, int level); - bool parse_version(); - void parse_region_info(uint32_t j, block_t& blk, region_t& r); - void parse_three_point(uint32_t j, uint64_t& start, uint64_t& offset, uint64_t& length); - uint8_t gen_xor_delta(uint8_t xor_value, uint8_t mul, bool negative); - void setrates(void); - void cleanup(void); - void free_block(struct block_t& b); - void free_all_blocks(void); -}; - -#endif diff --git a/ptformat/ptformat.h b/ptformat/ptformat.h new file mode 100644 index 0000000..22a4007 --- /dev/null +++ b/ptformat/ptformat.h @@ -0,0 +1,277 @@ +/* + * libptformat - a library to read ProTools sessions + * + * Copyright (C) 2015 Damien Zammit + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#ifndef PTFFORMAT_H +#define PTFFORMAT_H + +#include +#include +#include +#include +#include +#include "ptformat/visibility.h" + +class LIBPTFORMAT_API PTFFormat { +public: + PTFFormat(); + ~PTFFormat(); + + /* Return values: 0 success + -1 could not parse pt session + */ + int load(std::string const& path, int64_t targetsr); + + /* Return values: 0 success + -1 could not decrypt pt session + */ + int unxor(std::string const& path); + + struct wav_t { + std::string filename; + uint16_t index; + + int64_t posabsolute; + int64_t length; + + bool operator <(const struct wav_t& other) const { + return (strcasecmp(this->filename.c_str(), + other.filename.c_str()) < 0); + } + + bool operator ==(const struct wav_t& other) const { + return (this->filename == other.filename || + this->index == other.index); + } + + wav_t (uint16_t idx = 0) : index (idx), posabsolute (0), length (0) {} + }; + + struct midi_ev_t { + uint64_t pos; + uint64_t length; + uint8_t note; + uint8_t velocity; + midi_ev_t () : pos (0), length (0), note (0), velocity (0) {} + }; + + struct region_t { + std::string name; + uint16_t index; + int64_t startpos; + int64_t sampleoffset; + int64_t length; + wav_t wave; + std::vector midi; + + bool operator ==(const region_t& other) const { + return (this->index == other.index); + } + + bool operator <(const region_t& other) const { + return (strcasecmp(this->name.c_str(), + other.name.c_str()) < 0); + } + region_t (uint16_t idx = 0) : index (idx), startpos (0), sampleoffset (0), length (0) {} + }; + + struct track_t { + std::string name; + uint16_t index; + uint8_t playlist; + region_t reg; + + bool operator <(const track_t& other) const { + return (this->index < other.index); + } + + bool operator ==(const track_t& other) const { + return (this->index == other.index); + } + track_t (uint16_t idx = 0) : index (idx), playlist (0) {} + }; + + bool find_track(uint16_t index, track_t& tt) const { + std::vector::const_iterator begin = _tracks.begin(); + std::vector::const_iterator finish = _tracks.end(); + std::vector::const_iterator found; + + track_t t (index); + + if ((found = std::find(begin, finish, t)) != finish) { + tt = *found; + return true; + } + return false; + } + + bool find_region(uint16_t index, region_t& rr) const { + std::vector::const_iterator begin = _regions.begin(); + std::vector::const_iterator finish = _regions.end(); + std::vector::const_iterator found; + + region_t r; + r.index = index; + + if ((found = std::find(begin, finish, r)) != finish) { + rr = *found; + return true; + } + return false; + } + + bool find_miditrack(uint16_t index, track_t& tt) const { + std::vector::const_iterator begin = _miditracks.begin(); + std::vector::const_iterator finish = _miditracks.end(); + std::vector::const_iterator found; + + track_t t (index); + + if ((found = std::find(begin, finish, t)) != finish) { + tt = *found; + return true; + } + return false; + } + + bool find_midiregion(uint16_t index, region_t& rr) const { + std::vector::const_iterator begin = _midiregions.begin(); + std::vector::const_iterator finish = _midiregions.end(); + std::vector::const_iterator found; + + region_t r (index); + + if ((found = std::find(begin, finish, r)) != finish) { + rr = *found; + return true; + } + return false; + } + + bool find_wav(uint16_t index, wav_t& ww) const { + std::vector::const_iterator begin = _audiofiles.begin(); + std::vector::const_iterator finish = _audiofiles.end(); + std::vector::const_iterator found; + + wav_t w (index); + + if ((found = std::find(begin, finish, w)) != finish) { + ww = *found; + return true; + } + return false; + } + + static bool regionexistsin(std::vector const& reg, uint16_t index) { + std::vector::const_iterator begin = reg.begin(); + std::vector::const_iterator finish = reg.end(); + + region_t r (index); + + if (std::find(begin, finish, r) != finish) { + return true; + } + return false; + } + + static bool wavexistsin (std::vector const& wv, uint16_t index) { + std::vector::const_iterator begin = wv.begin(); + std::vector::const_iterator finish = wv.end(); + + wav_t w (index); + + if (std::find(begin, finish, w) != finish) { + return true; + } + return false; + } + + uint8_t version () const { return _version; } + int64_t sessionrate () const { return _sessionrate ; } + + const std::vector& audiofiles () const { return _audiofiles ; } + const std::vector& regions () const { return _regions ; } + const std::vector& midiregions () const { return _midiregions ; } + const std::vector& tracks () const { return _tracks ; } + const std::vector& miditracks () const { return _miditracks ; } + + const unsigned char* unxored_data () const { return _ptfunxored; } + uint64_t unxored_size () const { return _len; } + +private: + + std::vector _audiofiles; + std::vector _regions; + std::vector _midiregions; + std::vector _tracks; + std::vector _miditracks; + + std::string path; + + unsigned char* _ptfunxored; + uint64_t _len; + int64_t _sessionrate; + uint8_t _version; + uint8_t* _product; + int64_t _targetrate; + float _ratefactor; + bool is_bigendian; + + struct block_t { + uint8_t zmark; // 'Z' + uint16_t block_type; // type of block + uint32_t block_size; // size of block + uint16_t content_type; // type of content + uint32_t offset; // offset in file + std::vector child; // vector of child blocks + }; + std::vector blocks; + + bool jumpback(uint32_t *currpos, unsigned char *buf, const uint32_t maxoffset, const unsigned char *needle, const uint32_t needlelen); + bool jumpto(uint32_t *currpos, unsigned char *buf, const uint32_t maxoffset, const unsigned char *needle, const uint32_t needlelen); + bool foundin(std::string const& haystack, std::string const& needle); + int64_t foundat(unsigned char *haystack, uint64_t n, const char *needle); + uint16_t u_endian_read2(unsigned char *buf, bool); + uint32_t u_endian_read3(unsigned char *buf, bool); + uint32_t u_endian_read4(unsigned char *buf, bool); + uint64_t u_endian_read5(unsigned char *buf, bool); + uint64_t u_endian_read8(unsigned char *buf, bool); + + char *parsestring(uint32_t pos); + const std::string get_content_description(uint16_t ctype); + int parse(void); + void parseblocks(void); + bool parseheader(void); + bool parserest(void); + bool parseaudio(void); + bool parsemidi(void); + void dump(void); + bool parse_block_at(uint32_t pos, struct block_t *b, int level); + void dump_block(struct block_t& b, int level); + bool parse_version(); + void parse_region_info(uint32_t j, block_t& blk, region_t& r); + void parse_three_point(uint32_t j, uint64_t& start, uint64_t& offset, uint64_t& length); + uint8_t gen_xor_delta(uint8_t xor_value, uint8_t mul, bool negative); + void setrates(void); + void cleanup(void); + void free_block(struct block_t& b); + void free_all_blocks(void); +}; + +#endif diff --git a/ptftool.cc b/ptftool.cc index 7f90c48..844d4e7 100644 --- a/ptftool.cc +++ b/ptftool.cc @@ -19,7 +19,7 @@ * */ -#include "ptformat/ptfformat.h" +#include "ptformat/ptformat.h" #include // PRIxyy #include #include diff --git a/ptgenmissing.cc b/ptgenmissing.cc index 0e8389b..068496c 100644 --- a/ptgenmissing.cc +++ b/ptgenmissing.cc @@ -19,7 +19,7 @@ * */ -#include "ptformat/ptfformat.h" +#include "ptformat/ptformat.h" #include // PRIx #include #include diff --git a/ptunxor.cc b/ptunxor.cc index 484454e..686c0a5 100644 --- a/ptunxor.cc +++ b/ptunxor.cc @@ -21,7 +21,7 @@ #include #include #include -#include "ptformat/ptfformat.h" +#include "ptformat/ptformat.h" int main(int argc, char** argv) { -- cgit v1.2.3