summaryrefslogtreecommitdiff
path: root/libs/ptformat/ptformat.cc
diff options
context:
space:
mode:
Diffstat (limited to 'libs/ptformat/ptformat.cc')
-rw-r--r--libs/ptformat/ptformat.cc1350
1 files changed, 1350 insertions, 0 deletions
diff --git a/libs/ptformat/ptformat.cc b/libs/ptformat/ptformat.cc
new file mode 100644
index 0000000000..b6b13b745f
--- /dev/null
+++ b/libs/ptformat/ptformat.cc
@@ -0,0 +1,1350 @@
+/*
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <string>
+#include <string.h>
+#include <assert.h>
+
+#ifdef HAVE_GLIB
+# include <glib/gstdio.h>
+# 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");
+ }
+}
+
+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 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() {
+ 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, 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;
+ memset(&block->child, 0, sizeof(block->child));
+
+ 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<PTFFormat::block_t>::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<PTFFormat::block_t>::iterator c = b.child.begin();
+ c != b.child.end(); ++c) {
+ free_block(*c);
+ }
+
+ b.child.clear();
+}
+
+void
+PTFFormat::free_all_blocks(void)
+{
+ for (vector<PTFFormat::block_t>::iterator b = blocks.begin();
+ b != blocks.end(); ++b) {
+ free_block(*b);
+ }
+
+ blocks.clear();
+}
+
+void
+PTFFormat::dump(void) {
+ for (vector<PTFFormat::block_t>::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<PTFFormat::block_t>::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<PTFFormat::block_t>::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<PTFFormat::block_t>::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<PTFFormat::block_t>::iterator b = blocks.begin();
+ b != blocks.end(); ++b) {
+ if (b->content_type == 0x1004) {
+
+ vector<PTFFormat::wav_t>::iterator wav = _audiofiles.begin();
+
+ for (vector<PTFFormat::block_t>::iterator c = b->child.begin();
+ c != b->child.end(); ++c) {
+ if (c->content_type == 0x1003) {
+ for (vector<PTFFormat::block_t>::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<midi_ev_t> 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<PTFFormat::block_t>::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<PTFFormat::block_t>::iterator c = b->child.begin();
+ c != b->child.end(); ++c) {
+ if (c->content_type == 0x1008 || c->content_type == 0x2629) {
+ vector<PTFFormat::block_t>::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<PTFFormat::block_t>::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<PTFFormat::block_t>::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<PTFFormat::block_t>::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<PTFFormat::block_t>::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<PTFFormat::block_t>::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<PTFFormat::block_t>::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<PTFFormat::block_t>::iterator d = c->child.begin();
+ d != c->child.end(); ++d) {
+ if (d->content_type == 0x100f) {
+ for (vector<PTFFormat::block_t>::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<PTFFormat::block_t>::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<PTFFormat::block_t>::iterator d = c->child.begin();
+ d != c->child.end(); ++d) {
+ if (d->content_type == 0x1050) {
+ for (vector<PTFFormat::block_t>::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<track_t>::iterator tr = _tracks.begin();
+ tr != _tracks.end(); /* noop */) {
+ if ((*tr).reg.index == 65535) {
+ tr = _tracks.erase(tr);
+ } else {
+ tr++;
+ }
+ }
+ return found;
+}
+
+struct mchunk {
+ mchunk (uint64_t zt, uint64_t ml, std::vector<PTFFormat::midi_ev_t> const& c)
+ : zero (zt)
+ , maxlen (ml)
+ , chunk (c)
+ {}
+ uint64_t zero;
+ uint64_t maxlen;
+ std::vector<PTFFormat::midi_ev_t> 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<mchunk> midichunks;
+ midi_ev_t m;
+
+ char *str;
+ std::string regionname, trackname;
+ rindex = 0;
+
+ // Parse MIDI events
+ for (vector<PTFFormat::block_t>::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_ev_t> 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<PTFFormat::block_t>::iterator c = b->child.begin();
+ c != b->child.end(); ++c) {
+ if ((c->content_type == 0x2001) || (c->content_type == 0x2633)) {
+ for (vector<PTFFormat::block_t>::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<PTFFormat::block_t>::iterator b = blocks.begin();
+ b != blocks.end(); ++b) {
+ if (b->content_type == 0x262c) {
+ mindex = 0;
+ for (vector<PTFFormat::block_t>::iterator c = b->child.begin();
+ c != b->child.end(); ++c) {
+ if (c->content_type == 0x262b) {
+ for (vector<PTFFormat::block_t>::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<PTFFormat::block_t>::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<PTFFormat::block_t>::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<PTFFormat::block_t>::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<PTFFormat::block_t>::iterator d = c->child.begin();
+ d != c->child.end(); ++d) {
+ if (d->content_type == 0x1056) {
+ for (vector<PTFFormat::block_t>::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<track_t>::iterator tr = _miditracks.begin();
+ tr != _miditracks.end(); /* noop */) {
+ if ((*tr).reg.index == 65535) {
+ tr = _miditracks.erase(tr);
+ } else {
+ tr++;
+ }
+ }
+ return true;
+}