UNPKG

jzz-midi-smf

Version:

Standard MIDI Files: read / write / play (MIDI 1.0 and MIDI 2.0)

1,505 lines (1,473 loc) 55.1 kB
(function(global, factory) { /* istanbul ignore next */ if (typeof exports === 'object' && typeof module !== 'undefined') { factory.SMF = factory; module.exports = factory; } else if (typeof define === 'function' && define.amd) { define('JZZ.midi.SMF', ['JZZ'], factory); } else { factory(JZZ); } })(this, function(JZZ) { /* istanbul ignore next */ if (JZZ.MIDI.SMF) return; var _ver = '1.9.9'; var _now = JZZ.lib.now; function _error(s) { throw new Error(s); } function _num(n) { var s = ''; if (n > 0x1fffff) s += String.fromCharCode(((n >> 21) & 0x7f) + 0x80); if (n > 0x3fff) s += String.fromCharCode(((n >> 14) & 0x7f) + 0x80); if (n > 0x7f) s += String.fromCharCode(((n >> 7) & 0x7f) + 0x80); s += String.fromCharCode(n & 0x7f); return s; } function _num2(n) { return String.fromCharCode(n >> 8) + String.fromCharCode(n & 0xff); } function _num4(n) { return String.fromCharCode((n >> 24) & 0xff) + String.fromCharCode((n >> 16) & 0xff) + String.fromCharCode((n >> 8) & 0xff) + String.fromCharCode(n & 0xff); } function _num4le(n) { return String.fromCharCode(n & 0xff) + String.fromCharCode((n >> 8) & 0xff) + String.fromCharCode((n >> 16) & 0xff) + String.fromCharCode((n >> 24) & 0xff); } function _u8a2s(u) { var s = ''; var len = u.byteLength; // String.fromCharCode.apply(null, u) throws "RangeError: Maximum call stack size exceeded" on large files for (var i = 0; i < len; i++) s += String.fromCharCode(u[i]); return s; } function _hex(x) { return (x < 16 ? '0' : '') + x.toString(16); } function SMF() { var self = this; if (!(self instanceof SMF)) { self = new SMF(); delete self.ppqn; } var type = 1; var ppqn = 96; var fps; var ppf; if (arguments.length == 1) { if (arguments[0] instanceof SMF) { return arguments[0].copy(); } if (arguments[0] instanceof SYX) { self.type = 0; self.ppqn = ppqn; self.push(new MTrk()); for (var i = 0; i < arguments[0].length; i++) self[0].add(0, arguments[0][i]); return self; } var data; try { if (arguments[0] instanceof ArrayBuffer) { data = _u8a2s(new Uint8Array(arguments[0])); } } catch (err) {/**/} try { if (arguments[0] instanceof Uint8Array || arguments[0] instanceof Int8Array) { data = _u8a2s(new Uint8Array(arguments[0])); } } catch (err) {/**/} try { /* istanbul ignore next */ if (arguments[0] instanceof Buffer) { data = arguments[0].toString('binary'); } } catch (err) {/**/} if (typeof arguments[0] == 'string' && arguments[0] != '0' && arguments[0] != '1' && arguments[0] != '2') { data = arguments[0]; } if (data == '') _error('Empty file'); if (data) { self.load(data); return self; } type = parseInt(arguments[0]); } else if (arguments.length == 2) { type = parseInt(arguments[0]); ppqn = parseInt(arguments[1]); } else if (arguments.length == 3) { type = parseInt(arguments[0]); fps = parseInt(arguments[1]); ppf = parseInt(arguments[2]); } else if (arguments.length) _error('Invalid parameters'); if (isNaN(type) || type < 0 || type > 2) _error('Invalid parameters'); self.type = type; if (typeof fps == 'undefined') { if (isNaN(ppqn) || ppqn < 0 || ppqn > 0xffff) _error('Invalid parameters'); self.ppqn = ppqn; } else { if (fps != 24 && fps != 25 && fps != 29 && fps != 30) _error('Invalid parameters'); if (isNaN(ppf) || ppf < 0 || ppf > 0xff) _error('Invalid parameters'); self.fps = fps; self.ppf = ppf; } return self; } SMF.version = function() { return _ver; }; SMF.num4 = _num4; SMF.prototype = []; SMF.prototype.constructor = SMF; SMF.prototype.copy = function() { var smf = new SMF(); smf.type = this.type; smf.ppqn = this.ppqn; smf.fps = this.fps; smf.ppf = this.ppf; smf.rmi = this.rmi; smf.ntrk = this.ntrk; for (var i = 0; i < this.length; i++) smf.push(this[i].copy()); return smf; }; function _issue(off, msg, data, tick, track) { var w = { off: off, msg: msg, data: data }; if (typeof tick != 'undefined') w.tick = tick; if (typeof track != 'undefined') w.track = track; return w; } SMF.prototype._complain = function(off, msg, data) { if (!this._warn) this._warn = []; this._warn.push(_issue(off, msg, data)); }; SMF.prototype.load = function(s) { var off = 0; if (s.substring(0, 4) == 'RIFF' && s.substring(8, 16) == 'RMIDdata') { this.rmi = true; off = 20; s = s.substring(20, 20 + s.charCodeAt(16) + s.charCodeAt(17) * 0x100 + s.charCodeAt(18) * 0x10000 + s.charCodeAt(19) * 0x1000000); } _loadSMF(this, s, off); }; var MThd0006 = 'MThd' + String.fromCharCode(0) + String.fromCharCode(0) + String.fromCharCode(0) + String.fromCharCode(6); function _loadSMF(self, s, off) { if (s.substring(0, 8) != MThd0006) { var z = s.indexOf(MThd0006); if (z != -1) { s = s.substring(z); self._complain(off, 'Extra leading characters', z); off += z; } else _error('Not a MIDI file'); } self._off = off; self.type = s.charCodeAt(8) * 16 + s.charCodeAt(9); self._off_type = off + 8; self.ntrk = s.charCodeAt(10) * 16 + s.charCodeAt(11); self._off_ntrk = off + 10; if (s.charCodeAt(12) > 0x7f) { self.fps = 0x100 - s.charCodeAt(12); self.ppf = s.charCodeAt(13); self._off_fps = off + 12; self._off_ppf = off + 13; } else{ self.ppqn = s.charCodeAt(12) * 256 + s.charCodeAt(13); self._off_ppqn = off + 12; } if (self.type > 2) self._complain(8 + off, 'Invalid MIDI file type', self.type); else if (self.type == 0 && self.ntrk > 1) self._complain(10 + off, 'Wrong number of tracks for the type 0 MIDI file', self.ntrk); if (!self.ppf && !self.ppqn) _error('Invalid MIDI header'); var n = 0; var p = 14; while (p < s.length - 8) { var offset = p + off; var type = s.substring(p, p + 4); if (type == 'MTrk') n++; var len = (s.charCodeAt(p + 4) << 24) + (s.charCodeAt(p + 5) << 16) + (s.charCodeAt(p + 6) << 8) + s.charCodeAt(p + 7); if (len <= 0) { // broken file len = s.length - p - 8; self._complain(p + off + 4, 'Invalid track length', s.charCodeAt(p + 4) + '/' + s.charCodeAt(p + 5) + '/' + s.charCodeAt(p + 6) + '/' + s.charCodeAt(p + 7)); } p += 8; var data = s.substring(p, p + len); self.push(new Chunk(type, data, offset)); if (type == 'MThd') self._complain(offset, 'Unexpected chunk type', 'MThd'); p += len; } if (n != self.ntrk) { self._complain(off + 10, 'Incorrect number of tracks', self.ntrk); self.ntrk = n; } if (!self.ntrk) _error('No MIDI tracks'); if (!self.type && self.ntrk > 1 || self.type > 2) self.type = 1; if (p < s.length) self._complain(off + p, 'Extra trailing characters', s.length - p); if (p > s.length) self._complain(off + s.length, 'Incomplete data', p - s.length); } function Warn(obj) { if (!(this instanceof Warn)) return new Warn(obj); for (var k in obj) if (obj.hasOwnProperty(k)) this[k] = obj[k]; } Warn.prototype.toString = function() { var a = []; if (typeof this.off != 'undefined') a.push('offset ' + this.off); if (typeof this.track != 'undefined') a.push('track ' + this.track); if (typeof this.tick != 'undefined') a.push('tick ' + this.tick); a.push('--'); a.push(this.msg); if (typeof this.data != 'undefined') a.push('(' + this.data + ')'); return a.join(' '); }; SMF.prototype.tracks = function() { var t = 0; for (var i = 0; i < this.length; i++) if (this[i] instanceof MTrk) t++; return t; }; function _reset_state(w, s) { var i; for (i = 0; i < 16; i++) { if (s[i]) { if (s[i].rm && s[i].rl && s[i].rm[0][2] == 0x7f && s[i].rl[0][2] == 0x7f) { s[i].rm[1] = true; s[i].rl[1] = true; } _check_unused(w, s, i, 'bm'); _check_unused(w, s, i, 'bl'); _check_unused(w, s, i, 'nm'); _check_unused(w, s, i, 'nl'); _check_unused(w, s, i, 'rm'); _check_unused(w, s, i, 'rl'); } s[i] = {}; } } function _update_state(w, s, msg) { if (!msg.length || msg[0] < 0x80) return; if (msg.isGmReset() || msg.isGsReset() || msg.isXgReset()) { _reset_state(w, s); return; } var st = msg[0] >> 4; var ch = msg[0] & 15; var m; if (st == 0xb) { switch (msg[1]) { case 0: _check_unused(w, s, ch, 'bm'); s[ch].bm = [msg, false]; break; case 0x20: _check_unused(w, s, ch, 'bl'); s[ch].bl = [msg, false]; break; case 0x62: _check_unused(w, s, ch, 'nl'); _check_unused(w, s, ch, 'rm'); _check_unused(w, s, ch, 'rl'); s[ch].nl = [msg, false]; break; case 0x63: _check_unused(w, s, ch, 'nm'); _check_unused(w, s, ch, 'rm'); _check_unused(w, s, ch, 'rl'); s[ch].nm = [msg, false]; break; case 0x64: _check_unused(w, s, ch, 'rl'); _check_unused(w, s, ch, 'nm'); _check_unused(w, s, ch, 'nl'); s[ch].rl = [msg, false]; break; case 0x65: _check_unused(w, s, ch, 'rm'); _check_unused(w, s, ch, 'nm'); _check_unused(w, s, ch, 'nl'); s[ch].rm = [msg, false]; break; case 0x6: case 0x26: case 0x60: case 0x61: if (s[ch].rm && s[ch].rl) { s[ch].rm[1] = true; s[ch].rl[1] = true; } if (s[ch].rm && !s[ch].rl && !s[ch].rm[1]) { m = s[ch].rm[0]; w.push(_issue(m._off, 'No matching RPN LSB', m.toString(), m.tt, m.track)); s[ch].rm[1] = true; } if (!s[ch].rm && s[ch].rl && !s[ch].rl[1]) { m = s[ch].rl[0]; w.push(_issue(m._off, 'No matching RPN MSB', m.toString(), m.tt, m.track)); s[ch].rl[1] = true; } if (s[ch].nm && s[ch].nl) { s[ch].nm[1] = true; s[ch].nl[1] = true; } if (s[ch].nm && !s[ch].nl && !s[ch].nm[1]) { m = s[ch].nm[0]; w.push(_issue(m._off, 'No matching NRPN LSB', m.toString(), m.tt, m.track)); s[ch].nm[1] = true; } if (!s[ch].nm && s[ch].nl && !s[ch].nl[1]) { m = s[ch].nl[0]; w.push(_issue(m._off, 'No matching NRPN MSB', m.toString(), m.tt, m.track)); s[ch].nl[1] = true; } if (!s[ch].rm && !s[ch].rl && !s[ch].nm && !s[ch].nl) { w.push(_issue(msg._off, 'RPN/NRPN not set', msg.toString(), msg.tt, msg.track)); } if (s[ch].rm && s[ch].rl && s[ch].rm[0][2] == 0x7f && s[ch].rl[0][2] == 0x7f) { w.push(_issue(msg._off, 'RPN/NRPN not set', msg.toString(), msg.tt, msg.track)); } break; } return; } if (st == 0xc) { if (s[ch].bm) s[ch].bm[1] = true; if (s[ch].bl) s[ch].bl[1] = true; if (s[ch].bl && !s[ch].bm) { m = s[ch].bl[0]; w.push(_issue(m._off, 'No matching Bank Select MSB', m.toString(), m.tt, m.track)); } if (s[ch].bm && !s[ch].bl) { m = s[ch].bm[0]; w.push(_issue(m._off, 'No matching Bank Select LSB', m.toString(), m.tt, m.track)); } } } function _check_unused(w, s, c, x) { if (s[c][x] && !s[c][x][1]) { var str; switch (x) { case 'bm': case 'bl': str = 'Unnecessary Bank Select'; break; case 'nm': case 'nl': str = 'Unnecessary NRPN'; break; case 'rm': case 'rl': str = 'Unnecessary RPN'; break; } var m = s[c][x][0]; w.push(_issue(m._off, str, m.toString(), m.tt, m.track)); delete s[c][x]; } } SMF.prototype.validate = function() { var i, k, z; var w = []; if (this._warn) for (i = 0; i < this._warn.length; i++) w.push(Warn(this._warn[i])); var mm = _sort(this); k = 0; for (i = 0; i < this.length; i++) if (this[i] instanceof MTrk) { this[i]._validate(w, k); k++; } var st = {}; _reset_state(w, st); for (i = 0; i < mm.length; i++) { z = _validate_midi(mm[i], this.type == 1); if (z) { z.track = mm[i].track; w.push(z); } _update_state(w, st, mm[i]); } _reset_state(w, st); w.sort(function(a, b) { return (a.off || 0) - (b.off || 0) || (a.track || 0) - (b.track || 0) || (a.tick || 0) - (b.tick || 0); }); if (w.length) { for (i = 0; i < w.length; i++) w[i] = Warn(w[i]); return w; } }; SMF.prototype.dump = function(rmi) { var s = ''; if (rmi) { s = this.dump(); return 'RIFF' + _num4le(s.length + 12) + 'RMIDdata' + _num4le(s.length) + s; } this.ntrk = 0; for (var i = 0; i < this.length; i++) { if (this[i] instanceof MTrk) this.ntrk++; s += this[i].dump(); } s = (this.ppqn ? _num2(this.ppqn) : String.fromCharCode(0x100 - this.fps) + String.fromCharCode(this.ppf)) + s; s = MThd0006 + String.fromCharCode(0) + String.fromCharCode(this.type) + _num2(this.ntrk) + s; return s; }; SMF.prototype.toBuffer = function(rmi) { return Buffer.from(this.dump(rmi), 'binary'); }; SMF.prototype.toUint8Array = function(rmi) { var str = this.dump(rmi); var buf = new ArrayBuffer(str.length); var arr = new Uint8Array(buf); for (var i = 0; i < str.length; i++) arr[i] = str.charCodeAt(i); return arr; }; SMF.prototype.toArrayBuffer = function(rmi) { return this.toUint8Array(rmi).buffer; }; SMF.prototype.toInt8Array = function(rmi) { return new Int8Array(this.toArrayBuffer(rmi)); }; SMF.prototype.toString = function() { var i; this.ntrk = 0; for (i = 0; i < this.length; i++) if (this[i] instanceof MTrk) this.ntrk++; var a = ['SMF:', ' type: ' + this.type]; if (this.ppqn) a.push(' ppqn: ' + this.ppqn); else a.push(' fps: ' + this.fps, ' ppf: ' + this.ppf); a.push(' tracks: ' + this.ntrk); for (i = 0; i < this.length; i++) { a.push(this[i].toString()); } return a.join('\n'); }; function _var2num(s) { if (!s.length) return 0; // missing last byte if (s.charCodeAt(0) < 0x80) return [1, s.charCodeAt(0)]; var x = s.charCodeAt(0) & 0x7f; x <<= 7; if (s.charCodeAt(1) < 0x80) return [2, x + s.charCodeAt(1)]; x += s.charCodeAt(1) & 0x7f; x <<= 7; if (s.charCodeAt(2) < 0x80) return [3, x + s.charCodeAt(2)]; x += s.charCodeAt(2) & 0x7f; x <<= 7; x += s.charCodeAt(3) & 0x7f; return [4, s.charCodeAt(3) < 0x80 ? x : -x]; } function _msglen(n) { switch (n & 0xf0) { case 0x80: case 0x90: case 0xa0: case 0xb0: case 0xe0: return 2; case 0xc0: case 0xD0: return 1; } switch (n) { case 0xf1: case 0xf3: return 1; case 0xf2: return 2; } return 0; } function _sort(smf) { var i, j; var tt = []; var mm = []; for (i = 0; i < smf.length; i++) if (smf[i] instanceof MTrk) tt.push(smf[i]); if (smf.type != 1) { for (i = 0; i < tt.length; i++) { for (j = 0; j < tt[i].length; j++) { tt[i][j].track = i; mm.push(tt[i][j]); } } } else { var t = 0; var pp = []; for (i = 0; i < tt.length; i++) pp[i] = 0; while (true) { var b = true; var m = 0; for (i = 0; i < tt.length; i++) { while (pp[i] < tt[i].length && tt[i][pp[i]].tt == t) { tt[i][pp[i]].track = i; mm.push(tt[i][pp[i]]); pp[i]++; } if (pp[i] >= tt[i].length) continue; if (b) m = tt[i][pp[i]].tt; b = false; if (m > tt[i][pp[i]].tt) m = tt[i][pp[i]].tt; } t = m; if (b) break; } } return mm; } SMF.prototype.annotate = function() { var mm = _sort(this); var ctxt = JZZ.Context(); for (var i = 0; i < mm.length; i++) { if (mm[i].lbl) mm[i].lbl = undefined; ctxt._read(mm[i]); } return this; }; SMF.prototype.player = function() { var pl = new Player(); pl.ppqn = this.ppqn; pl.fps = this.fps; pl.ppf = this.ppf; var i; var e; var mm = _sort(this); if (this.type == 2) { var tr = 0; var m = 0; var t = 0; for (i = 0; i < mm.length; i++) { e = JZZ.MIDI(mm[i]); if (tr != e.track) { tr = e.track; m = t; } t = e.tt + m; e.tt = t; pl._data.push(e); } } else { for (i = 0; i < mm.length; i++) { e = JZZ.MIDI(mm[i]); pl._data.push(e); } } pl._type = this.type; pl._tracks = this.tracks(); pl._timing(); return pl; }; function Chunk(t, d, off) { if (!(this instanceof Chunk)) return new Chunk(t, d, off); var i; if (this.sub[t]) return this.sub[t](t, d, off); if (typeof t != 'string' || t.length != 4) _error("Invalid chunk type: " + t); for (i = 0; i < t.length; i++) if (t.charCodeAt(i) < 0 || t.charCodeAt(i) > 255) _error("Invalid chunk type: " + t); if (typeof d != 'string') _error("Invalid data type: " + d); for (i = 0; i < d.length; i++) if (d.charCodeAt(i) < 0 || d.charCodeAt(i) > 255) _error("Invalid data character: " + d[i]); this.type = t; this.data = d; this._off = off; } SMF.Chunk = Chunk; Chunk.prototype = []; Chunk.prototype.constructor = Chunk; Chunk.prototype.copy = function() { return new Chunk(this.type, this.data); }; Chunk.prototype.sub = { 'MTrk': function(t, d, off) { return new MTrk(d, off); } }; Chunk.prototype.dump = function() { return this.type + _num4(this.data.length) + this.data; }; Chunk.prototype.toString = function() { return this.type + ': ' + this.data.length + ' bytes'; }; function _validate_msg_data(trk, s, p, m, t, off) { var x = s.substring(p, p + m); if (x.length < m) { trk._complain(off, 'Incomplete track data', m - x.length, t); x = (x + '\x00\x00').substring(0, m); } for (var i = 0; i < m; i++) if (x.charCodeAt(i) > 127) { trk._complain(off + i, 'Bad MIDI value set to 0', x.charCodeAt(i), t); x = x.substring(0, i) + '\x00' + x.substring(i + 1); } return x; } function _validate_number(trk, s, off, t, tt) { var nn = _var2num(s); if (tt) t += nn[1]; if (nn[1] < 0) { nn[1] = -nn[1]; trk._complain(off, "Bad byte sequence", s.charCodeAt(0) + '/' + s.charCodeAt(1) + '/' + s.charCodeAt(2) + '/' + s.charCodeAt(3), t); } else if (nn[0] == 4 && nn[1] < 0x200000) { trk._complain(off, "Long VLQ value", s.charCodeAt(0) + '/' + s.charCodeAt(1) + '/' + s.charCodeAt(2) + '/' + s.charCodeAt(3), t); } else if (nn[0] == 3 && nn[1] < 0x4000) { trk._complain(off, "Long VLQ value", s.charCodeAt(0) + '/' + s.charCodeAt(1) + '/' + s.charCodeAt(2), t); } else if (nn[0] == 2 && nn[1] < 0x80) { trk._complain(off, "Long VLQ value", s.charCodeAt(0) + '/' + s.charCodeAt(1), t); } return nn; } function MTrk(s, off) { if (!(this instanceof MTrk)) return new MTrk(s, off); this._off = off; this._orig = this; this._tick = 0; if(typeof s == 'undefined') { this.push(new Event(0, '\xff\x2f', '')); return; } var t = 0; var p = 0; var w = ''; var st; var m; var rs; off += 8; var offset = p + off; while (p < s.length) { m = _validate_number(this, s.substring(p, p + 4), offset, t, true); p += m[0]; t += m[1]; offset = p + off; if (s.charCodeAt(p) == 0xff) { rs = false; st = s.substring(p, p + 2); if (st.length < 2) { this._complain(offset, 'Incomplete track data', 3 - st.length, t); st = '\xff\x2f'; } p += 2; m = _validate_number(this, s.substring(p, p + 4), offset + 2, t); p += m[0]; this.push (new Event(t, st, s.substring(p, p + m[1]), offset)); p += m[1]; } else if (s.charCodeAt(p) == 0xf0 || s.charCodeAt(p) == 0xf7) { rs = false; st = s.substring(p, p + 1); p += 1; m = _validate_number(this, s.substring(p, p + 4), offset + 1, t); p += m[0]; this.push(new Event(t, st, s.substring(p, p + m[1]), offset)); p += m[1]; } else if (s.charCodeAt(p) & 0x80) { rs = true; w = s.substring(p, p + 1); p += 1; m = _msglen(w.charCodeAt(0)); if (w.charCodeAt(0) > 0xf0) this._complain(offset, 'Unexpected MIDI message', w.charCodeAt(0).toString(16), t); this.push(new Event(t, w, _validate_msg_data(this, s, p, m, t, offset + 1), offset)); p += m; } else if (w.charCodeAt(0) & 0x80) { // running status if (!rs) this._complain(offset, 'Interrupted running status', w.charCodeAt(0).toString(16), t); rs = true; m = _msglen(w.charCodeAt(0)); if (w.charCodeAt(0) > 0xf0) this._complain(offset, 'Unexpected MIDI message', w.charCodeAt(0).toString(16), t); this.push(new Event(t, w, _validate_msg_data(this, s, p, m, t, offset), offset)); p += m; } } } SMF.MTrk = MTrk; MTrk.prototype = []; MTrk.prototype.constructor = MTrk; MTrk.prototype.type = 'MTrk'; MTrk.prototype.copy = function() { var trk = new MTrk(); trk.length = 0; for (var i = 0; i < this.length; i++) trk.push(new JZZ.MIDI(this[i])); return trk; }; function _shortmsg(msg) { var s = msg.toString(); if (s.length > 80) { s = s.substring(0, 78); s = s.substring(0, s.lastIndexOf(' ')) + ' ...'; } return s; } function _metaevent_len(msg, name, len) { if (msg.dd.length < len) return _issue(msg._off, 'Invalid ' + name + ' meta event: ' + (msg.dd.length ? 'data too short' : 'no data'), _shortmsg(msg), msg.tt); if (msg.dd.length > len) return _issue(msg._off, 'Invalid ' + name + ' meta event: data too long', _shortmsg(msg), msg.tt); } function _timing_first_track(msg, name) { return _issue(msg._off, name + ' meta events must be in the first track', _shortmsg(msg), msg.tt); } function _validate_midi(msg, t1) { var issue; if (typeof msg.ff != 'undefined') { if (msg.ff > 0x7f) return _issue(msg._off, 'Invalid meta event', _shortmsg(msg), msg.tt); else if (msg.ff == 0) { issue = _metaevent_len(msg, 'Sequence Number', 2); if (issue) return issue; } else if (msg.ff < 10) { if (!msg.dd.length) return _issue(msg._off, 'Invalid Text meta event: no data', _shortmsg(msg), msg.tt); } else if (msg.ff == 32) { issue = _metaevent_len(msg, 'Channel Prefix', 1); if (issue) return issue; if (msg.dd.charCodeAt(0) > 15) return _issue(msg._off, 'Invalid Channel Prefix meta event: incorrect data', _shortmsg(msg), msg.tt); } else if (msg.ff == 33) { issue = _metaevent_len(msg, 'MIDI Port', 1); if (issue) return issue; if (msg.dd.charCodeAt(0) > 127) return _issue(msg._off, 'Invalid MIDI Port meta event: incorrect data', _shortmsg(msg), msg.tt); } else if (msg.ff == 47) { issue = _metaevent_len(msg, 'End of Track', 0); if (issue) return issue; } else if (msg.ff == 81) { issue = _metaevent_len(msg, 'Tempo', 3); if (issue) return issue; if (t1 && msg.track) return _timing_first_track(msg, 'Tempo'); } else if (msg.ff == 84) { issue = _metaevent_len(msg, 'SMPTE', 5); if (issue) return issue; if ((msg.dd.charCodeAt(0) & 0x1f) >= 24 || msg.dd.charCodeAt(1) >= 60 || msg.dd.charCodeAt(2) >= 60 || msg.dd.charCodeAt(3) >= 30 || msg.dd.charCodeAt(4) >= 200 || msg.dd.charCodeAt(4) % 25) return _issue(msg._off, 'Invalid SMPTE meta event: incorrect data', _shortmsg(msg), msg.tt); else if ((msg.dd.charCodeAt(0) >> 5) > 3) return _issue(msg._off, 'Invalid SMPTE meta event: incorrect format', msg.dd.charCodeAt(0) >> 5, msg.tt); if (t1 && msg.track) return _timing_first_track(msg, 'SMPTE'); } else if (msg.ff == 88) { issue = _metaevent_len(msg, 'Time Signature', 4); if (issue) return issue; if (msg.dd.charCodeAt(1) > 8) return _issue(msg._off, 'Invalid Time Signature meta event: incorrect data', _shortmsg(msg), msg.tt); if (t1 && msg.track) return _timing_first_track(msg, 'Time Signature'); } else if (msg.ff == 89) { issue = _metaevent_len(msg, 'Key Signature', 2); if (issue) return issue; if (msg.dd.charCodeAt(1) > 1 || msg.dd.charCodeAt(0) > 255 || (msg.dd.charCodeAt(0) > 7 && msg.dd.charCodeAt(0) < 249)) return _issue(msg._off, 'Invalid Key Signature meta event: incorrect data', msg.toString(), msg.tt); } else if (msg.ff == 127) { // Sequencer Specific meta event } else { return _issue(msg._off, 'Unknown meta event', _shortmsg(msg), msg.tt); } } else { // } } MTrk.prototype._validate = function(w, k) { var i, z; if (this._warn) for (i = 0; i < this._warn.length; i++) { z = Warn(this._warn[i]); z.track = k; w.push(z); } }; MTrk.prototype._complain = function(off, msg, data, tick) { if (!this._warn) this._warn = []; this._warn.push(_issue(off, msg, data, tick)); }; MTrk.prototype.dump = function() { var s = ''; var t = 0; var m = ''; var i, j; for (i = 0; i < this.length; i++) { s += _num(this[i].tt - t); t = this[i].tt; if (typeof this[i].dd != 'undefined') { s += '\xff'; s += String.fromCharCode(this[i].ff); s += _num(this[i].dd.length); s += this[i].dd; } else if (this[i][0] == 0xf0 || this[i][0] == 0xf7) { s += String.fromCharCode(this[i][0]); s += _num(this[i].length - 1); for (j = 1; j < this[i].length; j++) s += String.fromCharCode(this[i][j]); } else { if (this[i][0] != m) { m = this[i][0]; s += String.fromCharCode(this[i][0]); } for (j = 1; j < this[i].length; j++) s += String.fromCharCode(this[i][j]); } } return 'MTrk' + _num4(s.length) + s; }; MTrk.prototype.toString = function() { var a = ['MTrk:']; for (var i = 0; i < this.length; i++) { a.push(this[i].tt + ': ' + this[i].toString()); } return a.join('\n '); }; function _msg(msg) { if (msg.length || msg.isSMF()) return msg; _error('Not a MIDI message'); } MTrk.prototype.add = function(t, msg) { t = parseInt(t); if(isNaN(t) || t < 0) _error('Invalid parameter'); var i, j; var a = []; try { a.push(JZZ.MIDI(msg)); } catch (e) { for (i = 0; i < msg.length; i++) a.push(JZZ.MIDI(msg[i])); } if (!a.length) _error('Not a MIDI message'); for (i = 0; i < a.length; i++) _msg(a[i]); if (this[this._orig.length - 1].tt < t) this[this._orig.length - 1].tt = t; // end of track if (msg.ff == 0x2f || msg[0] > 0xf0 && msg[0] != 0xf7) return this; for (i = 0; i < this._orig.length - 1; i++) { if (this._orig[i].tt > t) break; } for (j = 0; j < a.length; j++) { msg = a[j]; msg.tt = t; this._orig.splice(i, 0, msg); i++; } return this; }; MTrk.prototype._sxid = 0x7f; MTrk.prototype._image = function() { var F = function() {}; F.prototype = this._orig; var img = new F(); img._ch = this._ch; img._sxid = this._sxid; img._tick = this._tick; return img; }; MTrk.prototype.send = function(msg) { this._orig.add(this._tick, msg); return this; }; MTrk.prototype.tick = function(t) { if (t != parseInt(t) || t < 0) throw RangeError('Bad tick value: ' + t); if (!t) return this; var img = this._image(); img._tick = this._tick + t; return img; }; MTrk.prototype.sxId = function(id) { if (typeof id == 'undefined') id = MTrk.prototype._sxid; if (id == this._sxid) return this; if (id != parseInt(id) || id < 0 || id > 0x7f) throw RangeError('Bad MIDI value: ' + id); var img = this._image(); img._sxid = id; return img; }; MTrk.prototype.ch = function(c) { if (c == this._ch || typeof c == 'undefined' && typeof this._ch == 'undefined') return this; if (typeof c != 'undefined') { if (c != parseInt(c) || c < 0 || c > 15) throw RangeError('Bad channel value: ' + c + ' (must be from 0 to 15)'); } var img = this._image(); img._ch = c; return img; }; MTrk.prototype.note = function(c, n, v, t) { this.noteOn(c, n, v); if (typeof this._ch == 'undefined') { if (t > 0) this.tick(t).noteOff(c, n); } else { if (v > 0) this.tick(v).noteOff(c); } return this; }; JZZ.lib.copyMidiHelpers(MTrk); function Event(t, s, d, off) { var midi; if (s.charCodeAt(0) == 0xff) { midi = JZZ.MIDI.smf(s.charCodeAt(1), d); } else { var a = [s.charCodeAt(0)]; for (var i = 0; i < d.length; i++) a.push(d.charCodeAt(i)); midi = JZZ.MIDI(a); } if (typeof off != 'undefined') midi._off = off; midi.tt = t; return midi; } function Player() { var self = new JZZ.Widget(); self._info.name = 'MIDI Player'; self._info.manufacturer = 'Jazz-Soft'; self._info.version = _ver; self.playing = false; self._loop = 0; self._data = []; self._hdr = []; self._pos = 0; self._tick = (function(x) { return function(){ x.tick(); }; })(self); for (var k in Player.prototype) if (Player.prototype.hasOwnProperty(k)) self[k] = Player.prototype[k]; return self; } Player.prototype.onEnd = function() {}; Player.prototype.loop = function(n) { if (n == parseInt(n) && n > 0) this._loop = n; else this._loop = n ? -1 : 0; }; Player.prototype.play = function() { this.event = undefined; this.playing = true; this.paused = false; this._ptr = 0; this._pos = 0; this._p0 = 0; this._t0 = _now(); this._list = this._hdr; this.tick(); }; Player.prototype.stop = function() { this._pos = 0; this.playing = false; this.event = 'stop'; this.paused = undefined; }; Player.prototype.pause = function() { this.event = 'pause'; }; Player.prototype.resume = function() { if (this.playing) return; if (this.paused) { this.event = undefined; this._t0 = _now(); this.playing = true; this.paused = false; this.tick(); } else this.play(); }; Player.prototype.sndOff = function() { var c; for (c = 0; c < 16; c++) this._emit(JZZ.MIDI.allSoundOff(c)); for (c = 0; c < 16; c++) this._emit(JZZ.MIDI.resetAllControllers(c)); }; function _filter(e) { this._receive(e); } Player.prototype._filter = _filter; Player.prototype.filter = function(f) { this._filter = f instanceof Function ? f : _filter; }; Player.prototype._receive = function(e) { if (e.isTempo() && this.ppqn) { this._mul = this.ppqn * (e.isMidi2 ? 100000.0 : 1000.0) / (e.getTempo() || 1); this.mul = this._mul * this._speed; this._t0 = _now(); this._p0 = this._pos; } this._emit(e); }; Player.prototype.tick = function() { var t = _now(); var e; this._pos = this._p0 + (t - this._t0) * this.mul; for(; this._ptr < this._list.length; this._ptr++) { e = this._list[this._ptr]; if (e.tt > this._pos) break; this._filter(e); } if (this._ptr >= this._list.length) { if (this._list == this._hdr) { this._list = this._data; this._ptr = 0; this._p0 = 0; this._t0 = t; } else { if (this._loop && this._loop != -1) this._loop--; if (this._loop) { this._ptr = 0; this._p0 = 0; this._t0 = t; } else this.stop(); this.onEnd(); } } if (this.event == 'stop') { this.playing = false; this.paused = false; this._pos = 0; this._ptr = 0; this.sndOff(); this.event = undefined; } if (this.event == 'pause') { this.playing = false; this.paused = true; if (this._pos >= this._duration) this._pos = this._duration - 1; this._p0 = this._pos; this.sndOff(); this.event = undefined; } if (this.playing) JZZ.lib.schedule(this._tick); }; Player.prototype.trim = function() { var i, j, e; var data = []; j = 0; for (i = 0; i < this._data.length; i++) { e = this._data[i]; if (e.length || e.ff == 1 || e.ff == 5) { for (; j <= i; j++) data.push(this._data[j]); } } var dt = (i ? this._data[i - 1].tt : 0) - (j ? this._data[j - 1].tt : 0); this._data = data; this._timing(); return dt; }; Player.prototype._timing = function() { var i, m, t, e; this._duration = this._data.length ? this._data[this._data.length - 1].tt : 0; this._ttt = []; if (this.ppqn) { this._mul = this.ppqn / 500.0; // 120 bpm m = this._mul; for (i = 0; i < this._hdr.length; i++) { e = this._hdr[i]; if (e.isTempo()) m = this.ppqn * 100000.0 / (e.getTempo() || 1); } t = 0; this._durationMS = 0; this._ttt.push({ t: 0, m: m, ms: 0 }); for (i = 0; i < this._data.length; i++) { e = this._data[i]; if (e.isTempo()) { this._durationMS += (e.tt - t) / m; t = e.tt; m = this.ppqn * (e.isMidi2 ? 100000.0 : 1000.0) / (e.getTempo() || 1); this._ttt.push({ t: t, m: m, ms: this._durationMS }); } } this._durationMS += (this._duration - t) / m; } else { this._mul = this.fps * this.ppf / 1000.0; // 1s = fps*ppf ticks this._ttt.push({ t: 0, m: this._mul, ms: 0 }); this._durationMS = this._duration / this._mul; } this._speed = 1; this.mul = this._mul; this._ttt.push({ t: this._duration, m: 0, ms: this._durationMS }); if (!this._durationMS) this._durationMS = 1; }; Player.prototype.speed = function(x) { if (typeof x != 'undefined') { if (isNaN(parseFloat(x)) || x <= 0) x = 1; this._speed = x; this.mul = this._mul * this._speed; this._p0 = this._pos - (_now() - this._t0) * this.mul; } return this._speed; }; Player.prototype.type = function() { return this._type; }; Player.prototype.tracks = function() { return this._tracks; }; Player.prototype.duration = function() { return this._duration; }; Player.prototype.durationMS = function() { return this._durationMS; }; Player.prototype.position = function() { return this._pos; }; Player.prototype.positionMS = function() { return this.tick2ms(this._pos); }; Player.prototype.jump = function(t) { if (isNaN(parseFloat(t))) _error('Not a number: ' + t); if (t < 0) t = 0.0; if (t >= this._duration) t = this._duration - 1; this._goto(t); }; Player.prototype.jumpMS = function(ms) { if (isNaN(parseFloat(ms))) _error('Not a number: ' + ms); if (ms < 0) ms = 0.0; if (ms >= this._durationMS) ms = this._durationMS - 1; this._goto(this._ms2t(ms)); }; Player.prototype._t2ms = function(t) { if (!t) return 0.0; var i; for (i = 0; this._ttt[i].t < t; i++) ; i--; return this._ttt[i].ms + (t - this._ttt[i].t) / this._ttt[i].m; }; Player.prototype._ms2t = function(ms) { if (!ms) return 0.0; var i; for (i = 0; this._ttt[i].ms < ms; i++) ; i--; return this._ttt[i].t + (ms - this._ttt[i].ms) * this._ttt[i].m; }; Player.prototype._goto = function(t) { this._pos = t; if (!this.playing) this.paused = !!t; this._toPos(); if (this.playing) this.sndOff(); }; Player.prototype._toPos = function() { var i, e; for(i = 0; i < this._hdr.length; i++) { e = this._hdr[i]; if (e.isTempo()) this._mul = this.ppqn * 100000.0 / (e.getTempo() || 1); } for(this._ptr = 0; this._ptr < this._data.length; this._ptr++) { e = this._data[this._ptr]; if (e.tt >= this._pos) break; if (e.isTempo() && this.ppqn) this._mul = this.ppqn * (e.isMidi2 ? 100000.0 : 1000.0) / (e.getTempo() || 1); } this._list = this._data; this.mul = this._mul * this._speed; this._t0 = _now(); this._p0 = this._pos; }; Player.prototype.tick2ms = function(t) { if (isNaN(parseFloat(t))) _error('Not a number: ' + t); if (t <= 0) return 0.0; if (t >= this._duration) return this._durationMS; return this._t2ms(t); }; Player.prototype.ms2tick = function(t) { if (isNaN(parseFloat(t))) _error('Not a number: ' + t); if (t <= 0) return 0.0; if (t >= this._durationMS) return this._duration; return this._ms2t(t); }; JZZ.MIDI.SMF = SMF; function _not_a_syx() { _error('Not a SYX file'); } function SYX(arg) { var self = this instanceof SYX ? this : new SYX(); self._orig = self; if (typeof arg != 'undefined') { if (arg instanceof SMF) { self.copy(arg.player()._data); return self; } if (arg instanceof SYX) { self.copy(arg); return self; } try { if (arg instanceof ArrayBuffer) { arg = _u8a2s(new Uint8Array(arg)); } } catch (err) {/**/} try { if (arg instanceof Uint8Array || arg instanceof Int8Array) { arg = _u8a2s(new Uint8Array(arg)); } } catch (err) {/**/} try { /* istanbul ignore next */ if (arg instanceof Buffer) { arg = arg.toString('binary'); } } catch (err) {/**/} if (typeof arg != 'string') { arg = String.fromCharCode.apply(null, arg); } var x; var msg = []; var i = 0; var off = 0; if (!arg.length) _error('Empty file'); while (i < arg.length) { if (arg.charCodeAt(i) != 0xf0) _not_a_syx(); while (i < arg.length) { x = arg.charCodeAt(i); msg.push(x); if (x == 0xf7) { msg = JZZ.MIDI(msg); msg._off = off; self.push(JZZ.MIDI(msg)); msg = []; off = i + 1; break; } i++; } i++; } if (msg.length) _not_a_syx(); return self; } return self; } SYX.version = function() { return _ver; }; SYX.prototype = []; SYX.prototype.constructor = SYX; SYX.prototype.copy = function(data) { for (var i = 0; i < data.length; i++) if (!data[i].isSMF()) { if (data[i].isFullSysEx()) this.push(JZZ.MIDI(data[i])); else _not_a_syx(); } }; SYX.prototype.validate = function() { return []; }; SYX.prototype.dump = function() { var i, j, s = ''; for (i = 0; i < this.length; i++) for (j = 0; j < this[i].length; j++) s += String.fromCharCode(this[i][j]); return s; }; SYX.prototype.toBuffer = function() { return Buffer.from(this.dump(), 'binary'); }; SYX.prototype.toUint8Array = function() { var str = this.dump(); var buf = new ArrayBuffer(str.length); var arr = new Uint8Array(buf); for (var i = 0; i < str.length; i++) arr[i] = str.charCodeAt(i); return arr; }; SYX.prototype.toArrayBuffer = function() { return this.toUint8Array().buffer; }; SYX.prototype.toInt8Array = function() { return new Int8Array(this.toArrayBuffer()); }; SYX.prototype.toString = function() { var i; var a = ['SYX:']; for (i = 0; i < this.length; i++) { a.push(this[i].toString()); } return a.join('\n '); }; SYX.prototype.annotate = function() { var ctxt = JZZ.Context(); for (var i = 0; i < this.length; i++) { if (this[i].lbl) this[i].lbl = undefined; ctxt._read(this[i]); } return this; }; SYX.prototype.player = function() { var pl = new Player(); pl.ppqn = 96; var i; for (i = 0; i < this.length; i++) { var e = JZZ.MIDI(this[i]); e.tt = 0; pl._data.push(e); } pl._type = 'syx'; pl._tracks = 1; pl._timing(); pl.sndOff = function() {}; return pl; }; SYX.prototype._sxid = 0x7f; SYX.prototype._image = function() { var F = function() {}; F.prototype = this._orig; var img = new F(); img._ch = this._ch; img._sxid = this._sxid; return img; }; SYX.prototype.add = function(msg) { msg = JZZ.MIDI(msg); if (msg.isFullSysEx()) this._orig.push(msg); return this; }; SYX.prototype.send = function(msg) { return this.add(msg); }; SYX.prototype.sxId = function(id) { if (typeof id == 'undefined') id = SYX.prototype._sxid; if (id == this._sxid) return this; if (id != parseInt(id) || id < 0 || id > 0x7f) throw RangeError('Bad MIDI value: ' + id); var img = this._image(); img._sxid = id; return img; }; SYX.prototype.ch = function(c) { if (c == this._ch || typeof c == 'undefined' && typeof this._ch == 'undefined') return this; if (typeof c != 'undefined') { if (c != parseInt(c) || c < 0 || c > 15) throw RangeError('Bad channel value: ' + c + ' (must be from 0 to 15)'); } var img = this._image(); img._ch = c; return img; }; JZZ.lib.copyMidiHelpers(SYX); JZZ.MIDI.SYX = SYX; function Clip(arg) { var self = this instanceof Clip ? this : new Clip(); self._orig = self; self._tick = 0; self.ppqn = 96; if (typeof arg != 'undefined') { if (arg instanceof Clip) { _copyClip(self, arg); return self; } try { if (arg instanceof ArrayBuffer) { arg = _u8a2s(new Uint8Array(arg)); } } catch (err) {/**/} try { if (arg instanceof Uint8Array || arg instanceof Int8Array) { arg = _u8a2s(new Uint8Array(arg)); } } catch (err) {/**/} try { /* istanbul ignore next */ if (arg instanceof Buffer) { arg = arg.toString('binary'); } } catch (err) {/**/} if (typeof arg != 'string') { arg = String.fromCharCode.apply(null, arg); } _loadClip(self, arg, 0); return self; } if (!self.header) self.header = new ClipHdr(); if (!self.length) { var msg = JZZ.UMP.umpEndClip(); msg.tt = 0; self.push(msg); } return self; } Clip.version = function() { return _ver; }; Clip.prototype = []; Clip.prototype.constructor = Clip; Clip.prototype._sxid = 0x7f; var SMF2CLIP = 'SMF2CLIP'; Clip.prototype._image = function() { var F = function() {}; F.prototype = this._orig; var img = new F(); img._gr = this._gr; img._ch = this._ch; img._sxid = this._sxid; img._tick = this._tick; return img; }; Clip.prototype.send = function(msg) { return this.add(this._tick, msg); }; Clip.prototype.tick = function(t) { if (t != parseInt(t) || t < 0) throw RangeError('Bad tick value: ' + t); if (!t) return this; var img = this._image(); img._tick = this._tick + t; return img; }; function _ump(msg) { if (!msg || !msg.length) _error('Not a MIDI message'); var i; var a = []; try { a.push(JZZ.UMP(msg)); } catch (e) { for (i = 0; i < msg.length; i++) { if (!msg[i] || !msg[i].length) _error('Not a MIDI message'); a.push(JZZ.UMP(msg[i])); } } return a; } Clip.prototype.add = function(t, msg) { var i, j, d, e; t = parseInt(t); if(isNaN(t) || t < 0) _error('Invalid parameter'); var arr = _ump(msg); var self = this; if (this.length) e = this._orig[this._orig.length - 1]; if (e && !e.isEndClip()) e = undefined; if (e && e.tt < t) e.tt = t; for (i = 0; i < arr.length; i++) { msg = arr[i]; if (msg.isStartClip() || msg.isEndClip()) continue; if (msg.isDelta()) { d = msg.getDelta(); t += d; if (e && e.tt < t) e.tt = t; self = self.tick(msg.getDelta()); continue; } msg.tt = t; for (j = 0; j < this._orig.length; j++) if (this._orig[j].tt > t || this._orig[j] == e) break; this._orig.splice(j, 0, msg); } return self; }; Clip.prototype.sxId = function(id) { if (typeof id == 'undefined') id = Clip.prototype._sxid; if (id == this._sxid) return this; if (id != parseInt(id) || id < 0 || id > 0x7f) throw RangeError('Bad MIDI value: ' + id); var img = this._image(); img._sxid = id; return img; }; Clip.prototype.gr = function(g) { if (g == this._gr || typeof g == 'undefined' && typeof this._gr == 'undefined') return this; if (typeof g != 'undefined') { if (g != parseInt(g) || g < 0 || g > 15) throw RangeError('Bad channel value: ' + g + ' (must be from 0 to 15)'); } var img = this._image(); img._gr = g; return img; }; Clip.prototype.ch = function(c) { if (c == this._ch || typeof c == 'undefined' && typeof this._ch == 'undefined') return this; if (typeof c != 'undefined') { if (c != parseInt(c) || c < 0 || c > 15) throw RangeError('Bad channel value: ' + c + ' (must be from 0 to 15)'); } var img = this._image(); img._ch = c; return img; }; function ClipHdr() { this._orig = this; this._tick = 0; } ClipHdr.prototype = []; ClipHdr.prototype.constructor = ClipHdr; ClipHdr.prototype._image = Clip.prototype._image; ClipHdr.prototype.send = Clip.prototype.send; ClipHdr.prototype.tick = Clip.prototype.tick; ClipHdr.prototype.gr = Clip.prototype.gr; ClipHdr.prototype.ch = Clip.prototype.ch; ClipHdr.prototype.sxId = Clip.prototype.sxId; ClipHdr.prototype.add = Clip.prototype.add; function _copyClip(clip, x) { var i, m; clip.length = 0; clip.header = new ClipHdr(); clip.ppqn = x.ppqn; for (i = 0; i < x.header.length; i++) { m = new JZZ.UMP(x.header[i]); m.tt = x.header[i].tt; clip.header.push(m); } for (i = 0; i < x.length; i++) { m = new JZZ.UMP(x[i]); m.tt = x[i].tt; clip.push(m); } } function _loadClip(clip, s, off) { if (!s.length) _error('Empty clip'); if (s.substring(0, 8) != SMF2CLIP) { var z = s.indexOf(SMF2CLIP); if (z != -1) { off += z; clip._complain(off, 'Extra leading characters', off); } else _error('Not a clip'); } off += 8; var a, i, m, t, len, prev; clip.length = 0; clip.header = new ClipHdr(); clip.ppqn = -1; var inHdr = true; var ended = false; var tt = 0; while (off < s.length) { t = s.charCodeAt(off) >> 4; len = [4, 4, 4, 8, 8, 16, 4, 4, 8, 8, 8, 12, 12, 16, 16, 16][t]; a = []; if (s.length < off + len) { for (i = off; i < s.length; i++) a.push(_hex(s.charCodeAt(i))); clip._complain(off, 'Incomplete message', a.join(' ')); off += len; break; } for (i = 0; i < len; i++) a.push(s.charCodeAt(off + i)); prev = m; m = JZZ.UMP(a); if (m.isDelta()) { if (prev && prev.isDelta()) clip._complain(off, 'Consequential Delta Ticks message'); tt += m.getDelta(); } else { m.tt = tt; m.off = off; if (prev && !prev.isDelta()) { clip._complain(off, "Missing Delta Ticks message", m.toString(), tt); } if (inHdr) { if (m.isStartClip()) { tt = 0; inHdr = false; } else if (m.isTicksPQN()) { if (clip.ppqn != -1) clip._complain(off, 'Multiple Ticks PQN message'); clip.ppqn = m.getTicksPQN(); if (!clip.ppqn) { clip._complain(off, 'Bad Ticks PQN value: 0'); clip.ppqn = 96; } } else if (