/* * libptformat - a library to read ProTools sessions * * Copyright (C) 2015-2019 Damien Zammit * Copyright (C) 2015-2019 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 #ifdef HAVE_GLIB # include # define ptf_open g_fopen #else # define ptf_open fopen #endif #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"); } } static uint16_t 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]); } } static uint32_t 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]); } } static uint32_t 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]); } } static uint64_t 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]); } } static uint64_t 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 error decrypting 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 = ptf_open(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 error decrypting pt session -2 error detecting pt session -3 incompatible pt version -4 error parsing 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() { bool failed = true; struct block_t b; if (_ptfunxored[0] != '\x03' && foundat(_ptfunxored, 0x100, BITCODE) != 1) { return failed; } is_bigendian = !!_ptfunxored[0x11]; if (!parse_block_at(0x1f, &b, NULL, 0)) { _version = _ptfunxored[0x40]; if (_version == 0) { _version = _ptfunxored[0x3d]; } if (_version == 0) { _version = _ptfunxored[0x3a] + 2; } if (_version != 0) failed = false; return failed; } else { if (b.content_type == 0x0003) { // old uint16_t skip = parsestring(b.offset + 3).size() + 8; _version = u_endian_read4(&_ptfunxored[b.offset + 3 + skip], is_bigendian); failed = false; } else if (b.content_type == 0x2067) { // new _version = 2 + u_endian_read4(&_ptfunxored[b.offset + 20], is_bigendian); failed = false; } return failed; } } 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, struct block_t *parent, int level) { struct block_t b; int childjump = 0; uint32_t i; uint32_t max = _len; if (_ptfunxored[pos] != ZMARK) return false; if (parent) max = parent->block_size + parent->offset; 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 > max) 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; block->child.clear(); for (i = 1; (i < block->block_size) && (pos + i + childjump < max); i += childjump ? childjump : 1) { int p = pos + i; struct block_t bchild; childjump = 0; if (parse_block_at(p, &bchild, block, 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, NULL, 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; } std::string PTFFormat::parsestring (uint32_t pos) { uint32_t length = u_endian_read4(&_ptfunxored[pos], is_bigendian); pos += 4; return std::string((const char *)&_ptfunxored[pos], length); } bool PTFFormat::parseaudio(void) { bool found = false; uint32_t nwavs = 0; uint32_t i, n; uint32_t pos = 0; 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) { //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++) { wavname = parsestring(pos); pos += wavname.size() + 4; wavtype = std::string((const char*)&_ptfunxored[pos], 4); 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[0] != '\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; } } found = true; wav_t f (n); f.filename = wavname; n++; _audiofiles.push_back(f); } } } } } if (!found) { if (nwavs > 0) { return false; } else { return true; } } // 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; bool region_is_fade = false; 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; regionname = parsestring(j); 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; trackname = parsestring(j); 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; } } } } } // 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; trackname = parsestring(j); 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) { regionname = parsestring(c->offset + 2); 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) { trackname = parsestring(c->offset + 2); for (vector::iterator d = c->child.begin(); d != c->child.end(); ++d) { if (d->content_type == 0x1050) { region_is_fade = (_ptfunxored[d->offset + 46] == 0x01); if (region_is_fade) { verbose_printf("dropped fade region\n"); continue; } 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 * _ratefactor; if (ti.reg.index != 65535) { _tracks.push_back(ti); } } } } } found = true; count++; } } } } for (std::vector::iterator tr = _tracks.begin(); tr != _tracks.end(); /* noop */) { if ((*tr).reg.index == 65535) { tr = _tracks.erase(tr); } else { tr++; } } if (_tracks.begin() == _tracks.end()) return found; /* Sort track entries by index */ std::sort(_tracks.begin(), _tracks.end()); /* Renumber track entries to be gapless */ for (std::vector::iterator tr = _tracks.begin() + 1; tr != _tracks.end(); tr++) { while ((*tr).index == (*(tr-1)).index) { tr++; if (tr == _tracks.end()) { break; } } if (tr == _tracks.end()) { break; } int diffn = (*tr).index - (*(tr-1)).index - 1; if (diffn) { for (std::vector::iterator rest = tr; rest != _tracks.end(); rest++) { (*rest).index -= diffn; } } } /* Renumber track entries to be zero based */ int first = _tracks[0].index; if (first > 0) { for (std::vector::iterator tr = _tracks.begin(); tr != _tracks.end(); tr++) { (*tr).index -= first; } } 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; 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; midiregionname = parsestring(j); 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); } } } } } } // 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; regionname = parsestring(j); 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) (%lu %lu %lu)\n", regionname.c_str(), mindex, n, start, offset, length); mindex++; } } } } } } } // 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) { regionname = parsestring(c->offset + 2); 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 * _ratefactor); if (ti.reg.index != 65535) { _miditracks.push_back(ti); } } } } } count++; } } } } for (std::vector::iterator tr = _miditracks.begin(); tr != _miditracks.end(); /* noop */) { if ((*tr).reg.index == 65535) { tr = _miditracks.erase(tr); } else { tr++; } } return true; }