├── .gitattributes ├── .gitignore ├── test.mid ├── test.syx ├── .github ├── FUNDING.yml └── workflows │ └── build.yml ├── test ├── midi │ ├── 0.mid │ ├── 1.mid │ ├── 2.mid │ ├── 3.mid │ ├── 4.mid │ ├── 5.mid │ ├── 6.mid │ ├── 7.mid │ └── 8.mid └── mocha.js ├── .npmignore ├── Gruntfile.js ├── eslint.config.mjs ├── test.js ├── LICENSE.md ├── package.json ├── test.html ├── README.md ├── minified └── JZZ.midi.SMF.js └── javascript └── JZZ.midi.SMF.js /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json 3 | .nyc_output 4 | -------------------------------------------------------------------------------- /test.mid: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazz-soft/JZZ-midi-SMF/HEAD/test.mid -------------------------------------------------------------------------------- /test.syx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazz-soft/JZZ-midi-SMF/HEAD/test.syx -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: jazz-soft 2 | custom: https://paypal.me/jazzsoft 3 | -------------------------------------------------------------------------------- /test/midi/0.mid: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazz-soft/JZZ-midi-SMF/HEAD/test/midi/0.mid -------------------------------------------------------------------------------- /test/midi/1.mid: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazz-soft/JZZ-midi-SMF/HEAD/test/midi/1.mid -------------------------------------------------------------------------------- /test/midi/2.mid: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazz-soft/JZZ-midi-SMF/HEAD/test/midi/2.mid -------------------------------------------------------------------------------- /test/midi/3.mid: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazz-soft/JZZ-midi-SMF/HEAD/test/midi/3.mid -------------------------------------------------------------------------------- /test/midi/4.mid: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazz-soft/JZZ-midi-SMF/HEAD/test/midi/4.mid -------------------------------------------------------------------------------- /test/midi/5.mid: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazz-soft/JZZ-midi-SMF/HEAD/test/midi/5.mid -------------------------------------------------------------------------------- /test/midi/6.mid: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazz-soft/JZZ-midi-SMF/HEAD/test/midi/6.mid -------------------------------------------------------------------------------- /test/midi/7.mid: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazz-soft/JZZ-midi-SMF/HEAD/test/midi/7.mid -------------------------------------------------------------------------------- /test/midi/8.mid: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazz-soft/JZZ-midi-SMF/HEAD/test/midi/8.mid -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .* 2 | minified 3 | test* 4 | coverage 5 | Gruntfile.js 6 | eslint.config.mjs 7 | *.html -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | on: [push, pull_request] 3 | jobs: 4 | build: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - uses: actions/checkout@v4 8 | - uses: actions/setup-node@v4 9 | with: 10 | node-version: '20.x' 11 | - run: npm install 12 | - run: npm test 13 | - run: npm run coverage 14 | - uses: coverallsapp/github-action@v2 15 | with: 16 | github-token: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | grunt.initConfig({ 3 | pkg: grunt.file.readJSON('package.json'), 4 | jshint: { 5 | all: ['javascript/*.js'] 6 | }, 7 | uglify: { 8 | javascript: { 9 | expand: true, 10 | cwd: 'javascript', 11 | src: '*.js', 12 | dest: 'minified' 13 | } 14 | } 15 | }); 16 | grunt.task.registerTask('version', 'Check version consistency', function() { 17 | var pkg = grunt.file.readJSON('package.json'); 18 | var JZZ = require('jzz'); 19 | require('.')(JZZ); 20 | var ver = JZZ.MIDI.SMF.version(); 21 | if (ver == pkg.version) { 22 | grunt.log.ok('Version:', ver); 23 | } 24 | else { 25 | grunt.log.error('Version:', ver, '!=', pkg.version); 26 | return false; 27 | } 28 | }); 29 | grunt.loadNpmTasks('grunt-contrib-uglify'); 30 | grunt.loadNpmTasks('grunt-contrib-jshint'); 31 | grunt.registerTask('default', ['jshint', 'uglify', 'version']); 32 | }; 33 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import globals from "globals"; 2 | import js from "@eslint/js"; 3 | 4 | export default [ 5 | js.configs.recommended, 6 | { 7 | ignores: ["**/.instrumented/*"] 8 | }, 9 | { 10 | files: ["javascript/*.js", "test/*.js"], 11 | languageOptions: { 12 | ecmaVersion: 2015, 13 | globals: { 14 | ...globals.browser, 15 | ...globals.node, 16 | define: "readonly", 17 | JZZ: "readonly" 18 | } 19 | }, 20 | rules: { 21 | "no-prototype-builtins": "off", 22 | "no-unused-vars": ["error", { caughtErrors: "none"}] 23 | } 24 | }, 25 | { 26 | files: ["test/*.js"], 27 | languageOptions: { 28 | globals: { 29 | describe: "readonly", 30 | it: "readonly", 31 | before: "readonly", 32 | after: "readonly" 33 | } 34 | } 35 | } 36 | ]; -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | var JZZ = require('jzz'); 2 | require('jzz-midi-gm')(JZZ); 3 | require('.')(JZZ); 4 | 5 | var file = process.argv[2]; 6 | if (typeof file == 'undefined') file = 'test.mid'; 7 | var data = require('fs').readFileSync(file, 'binary'); 8 | var smf; 9 | try { 10 | smf = new JZZ.MIDI.Clip(data); 11 | } 12 | catch (e) { 13 | try { 14 | smf = new JZZ.MIDI.SYX(data); 15 | } 16 | catch (e) { 17 | smf = new JZZ.MIDI.SMF(data); 18 | } 19 | } 20 | smf.annotate(); 21 | console.log(smf.toString()); 22 | var player = smf.player(); 23 | var t = Math.round(player.durationMS() / 10); 24 | var h = Math.floor(t / 360000); 25 | var m = Math.floor((t % 360000) / 6000); 26 | var s = (t % 6000) / 100; 27 | var time = []; 28 | if (h) time.push(h); 29 | time.push(h && m < 10 ? '0' + m : m); 30 | time.push(s < 10 ? '0' + s : s); 31 | console.log('Total time:', time.join(':')); 32 | 33 | JZZ().or('Cannot start MIDI engine!').openMidiOut().or('Cannot open MIDI Out!').and(function() { 34 | var m2m1 = JZZ.M2M1(); 35 | m2m1.connect(this); 36 | player.connect(m2m1); 37 | player.loop(3); 38 | player.play(); 39 | }); 40 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018-25 Jazz-Soft 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jzz-midi-smf", 3 | "version": "1.9.9", 4 | "description": "Standard MIDI Files: read / write / play (MIDI 1.0 and MIDI 2.0)", 5 | "main": "javascript/JZZ.midi.SMF.js", 6 | "scripts": { 7 | "test": "nyc mocha", 8 | "lint": "eslint javascript test", 9 | "coverage": "nyc report --reporter=lcov" 10 | }, 11 | "keywords": [ 12 | "midi", 13 | "midi2", 14 | "midi-file", 15 | "midi-clip", 16 | "smf", 17 | "syx" 18 | ], 19 | "author": "jazz-soft (https://jazz-soft.net/)", 20 | "dependencies": { 21 | "jzz": "^1.9.5" 22 | }, 23 | "devDependencies": { 24 | "eslint": "^9.35.0", 25 | "grunt": "^1.6.1", 26 | "grunt-contrib-jshint": "^3.2.0", 27 | "grunt-contrib-uglify": "^5.2.2", 28 | "jzz-gui-player": "^1.7.8", 29 | "jzz-midi-gm": "^1.4.0", 30 | "jzz-synth-tiny": "^1.4.3", 31 | "mocha": "^11.7.2", 32 | "nyc": "^17.1.0", 33 | "test-midi-files": "^1.1.1" 34 | }, 35 | "runkitExampleFilename": "runkit.js", 36 | "repository": { 37 | "type": "git", 38 | "url": "https://github.com/jazz-soft/JZZ-midi-SMF.git" 39 | }, 40 | "homepage": "https://jazz-soft.net/doc/JZZ/midifile.html", 41 | "bugs": "https://github.com/jazz-soft/JZZ-midi-SMF/issues", 42 | "license": "MIT" 43 | } 44 | -------------------------------------------------------------------------------- /test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | JZZ.midi.SMF Test 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |

JZZ.midi.SMF Test

14 |

Please enable JavaScript!

15 |

22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 |

32 | 33 | 34 | 35 |

36 | 37 |

 38 | 
 39 | 
139 | 
140 | 
141 | 
142 | 


--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
  1 | # JZZ-midi-SMF
  2 | 
  3 | Standard MIDI Files: read / write / play  
  4 | (MIDI 1.0 and MIDI 2.0)
  5 | 
  6 | ![Node.js](https://jazz-soft.github.io/img/nodejs.jpg)
  7 | ![Firefox](https://jazz-soft.github.io/img/firefox.jpg)
  8 | ![Chrome](https://jazz-soft.github.io/img/chrome.jpg)
  9 | ![Opera](https://jazz-soft.github.io/img/opera.jpg)
 10 | ![Safari](https://jazz-soft.github.io/img/safari.jpg)
 11 | ![Internet Explorer](https://jazz-soft.github.io/img/msie.jpg)
 12 | ![Edge](https://jazz-soft.github.io/img/edgc.jpg)  
 13 | [![npm](https://img.shields.io/npm/v/jzz-midi-smf.svg)](https://www.npmjs.com/package/jzz-midi-smf)
 14 | [![npm](https://img.shields.io/npm/dt/jzz-midi-smf.svg)](https://www.npmjs.com/package/jzz-midi-smf)
 15 | [![build](https://github.com/jazz-soft/JZZ-midi-SMF/actions/workflows/build.yml/badge.svg)](https://github.com/jazz-soft/JZZ-midi-SMF/actions)
 16 | [![Coverage Status](https://coveralls.io/repos/github/jazz-soft/JZZ-midi-SMF/badge.svg?branch=master)](https://coveralls.io/github/jazz-soft/JZZ-midi-SMF?branch=master)
 17 | 
 18 | ## Install
 19 | 
 20 | `npm install jzz-midi-smf --save`  
 21 | or `yarn add jzz-midi-smf`  
 22 | or get the full development version and minified scripts from [**GitHub**](https://github.com/jazz-soft/JZZ-midi-SMF)
 23 | 
 24 | ## Usage
 25 | 
 26 | ##### Plain HTML
 27 | 
 28 | ```html
 29 | 
 30 | 
 31 | //...
 32 | ```
 33 | 
 34 | ##### CDN (jsdelivr)
 35 | 
 36 | ```html
 37 | 
 38 | 
 39 | //...
 40 | ```
 41 | 
 42 | ##### CDN (unpkg)
 43 | 
 44 | ```html
 45 | 
 46 | 
 47 | //...
 48 | ```
 49 | 
 50 | ##### CommonJS
 51 | 
 52 | ```js
 53 | var JZZ = require('jzz');
 54 | require('jzz-midi-smf')(JZZ);
 55 | //...
 56 | ```
 57 | 
 58 | ##### TypeScript / ES6
 59 | 
 60 | ```ts
 61 | import { JZZ } from 'jzz';
 62 | import { SMF } from 'jzz-midi-smf';
 63 | SMF(JZZ);
 64 | //...
 65 | ```
 66 | 
 67 | ##### AMD
 68 | 
 69 | ```js
 70 | require(['JZZ', 'JZZ.midi.SMF'], function(JZZ, dummy) {
 71 |   // ...
 72 | });
 73 | ```
 74 | 
 75 | ## MIDI files
 76 | Supported file formats: `.mid`, `.kar`, `.rmi`
 77 | 
 78 | Please check the [**API Reference**](https://jazz-soft.net/doc/JZZ/midifile.html) !
 79 | 
 80 | ##### Playing MIDI file
 81 | 
 82 | ```js
 83 | var midiout = JZZ().openMidiOut();
 84 | var data = require('fs').readFileSync('file.mid', 'binary');
 85 | // data can be String, Buffer, ArrayBuffer, Uint8Array or Int8Array
 86 | var smf = new JZZ.MIDI.SMF(data);
 87 | var player = smf.player();
 88 | player.connect(midiout);
 89 | player.play();
 90 | //...
 91 | player.speed(0.5); // play twice slower
 92 | ```
 93 | 
 94 | ##### Viewing the contents of MIDI file
 95 | 
 96 | ```js
 97 | console.log(smf.toString());
 98 | ```
 99 | 
100 | ##### Validating MIDI file
101 | 
102 | ```js
103 | var warn = smf.validate();
104 | if (warn) console.log(warn);
105 | ```
106 | 
107 | ##### Creating MIDI file from scratch
108 | 
109 | ```js
110 | var smf = new JZZ.MIDI.SMF(0, 96); // type 0, 96 ticks per quarter note
111 | var trk = new JZZ.MIDI.SMF.MTrk();
112 | smf.push(trk);
113 | // add contents:
114 | trk.add(0, JZZ.MIDI.smfSeqName('This is a sequence name'))
115 |    .add(0, JZZ.MIDI.smfBPM(90)) // tempo 90 bpm
116 |    .add(96, JZZ.MIDI.noteOn(0, 'C6', 127))
117 |    .add(96, JZZ.MIDI.noteOn(0, 'Eb6', 127))
118 |    .add(96, JZZ.MIDI.noteOn(0, 'G6', 127))
119 |    .add(192, JZZ.MIDI.noteOff(0, 'C6'))
120 |    .add(192, JZZ.MIDI.noteOff(0, 'Eb6'))
121 |    .add(192, JZZ.MIDI.noteOff(0, 'G6'))
122 |    .add(288, JZZ.MIDI.smfEndOfTrack());
123 | // or an alternative way:
124 | trk.smfSeqName('This is a sequence name').smfBPM(90).tick(96)
125 |    .noteOn(0, 'C6', 127).noteOn(0, 'Eb6', 127).noteOn(0, 'G6', 127)
126 |    .tick(96).noteOff(0, 'C6').noteOff(0, 'Eb6').noteOff(0, 'G6')
127 |    .tick(96).smfEndOfTrack();
128 | // or even shorter:
129 | trk.smfSeqName('This is a sequence name').smfBPM(90).tick(96)
130 |    .ch(0).note('C6', 127, 96).note('Eb6', 127, 96).note('G6', 127, 96)
131 |    .tick(192).smfEndOfTrack();
132 | ```
133 | 
134 | ##### Exporting MIDI file data as JSON or any custom format
135 | 
136 | One easy thing to remember: `SMF` is an `Array` of `Track`s and `Track` is an `Array` of MIDI events:
137 | 
138 | ```js
139 | for (var i = 0; i < smf.length; i++) {
140 |   for (var j = 0; j < smf[i].length; j++) {
141 |     console.log('track:', i, 'tick:', smf[i][j].tt, smf[i][j].toString());
142 |     // or do whatever else with the message
143 |   }
144 | }
145 | ```
146 | 
147 | ##### Transposing MIDI file
148 | 
149 | ```js
150 | for (var i = 0; i < smf.length; i++) {
151 |   if (smf[i] instanceof JZZ.MIDI.SMF.MTrk) {
152 |     for (var j = 0; j < smf[i].length; j++) {
153 |       var note = smf[i][j].getNote();
154 |       if (typeof note != 'undefined') {
155 |         if (smf[i][j].getChannel() != 9) { // skip the percussion channel
156 |           smf[i][j].setNote(note + 12);    // transpose one octave up
157 |         }
158 |       }
159 |     }
160 |   }
161 | }
162 | ```
163 | 
164 | ##### Getting the info
165 | 
166 | ```js
167 | var player = smf.player();
168 | var dump = smf.dump();
169 | console.log('Type:', player.type());
170 | console.log('Number of tracks:', player.tracks());
171 | console.log('Size:', dump.length, 'bytes');
172 | console.log('Duration:', player.duration(), 'ticks');
173 | console.log('Total time:', player.durationMS(), 'milliseconds');
174 | ```
175 | 
176 | ##### Saving MIDI file
177 | 
178 | ```js
179 | require('fs').writeFileSync('out.mid', smf.dump(), 'binary');
180 | ```
181 | 
182 | ## SYX files
183 | 
184 | ##### All calls are almost identical to those for the SMF files:
185 | 
186 | ```js
187 | var data = require('fs').readFileSync('file.syx', 'binary');
188 | var syx = new JZZ.MIDI.SYX(data);
189 | syx.send([0xf0, 0x7e, 0x7f, 0x06, 0x01, 0xf7]);
190 | syx.sxMasterVolumeF(0.5);
191 | var player = syx.player();
192 | player.connect(midiout);
193 | player.play();
194 | require('fs').writeFileSync('out.syx', syx.dump(), 'binary');
195 | ```
196 | 
197 | ## MIDI 2.0 Clips
198 | 
199 | ##### Playing MIDI 2.0 clip
200 | 
201 | ```js
202 | var midiout = JZZ().openMidiOut();
203 | var data = require('fs').readFileSync('file.midi2', 'binary');
204 | var clip = new JZZ.MIDI.Clip(data);
205 | var player = clip.player();
206 | // since the majority of today's MIDI devices only support MIDI 1.0,
207 | // we can use a converter:
208 | var conv = JZZ.M2M1();
209 | conv.connect(midiout);
210 | player.connect(conv);
211 | player.play();
212 | ```
213 | 
214 | ##### Creating MIDI 2.0 clip from scratch
215 | 
216 | ```js
217 | var clip = new JZZ.MIDI.Clip();
218 | clip.gr(0).ch(0).noteOn('C5').tick(96).noteOff('C5');
219 | require('fs').writeFileSync('out.midi2', clip.dump(), 'binary');
220 | ```
221 | 
222 | ##### Most of other calls - same as above
223 | 
224 | ```js
225 | var player = clip.player();
226 | var dump = clip.dump();
227 | console.log(clip.toString());
228 | console.log('Size:', dump.length, 'bytes');
229 | console.log('Duration:', player.duration(), 'ticks');
230 | console.log('Total time:', player.durationMS(), 'milliseconds');
231 | ```
232 | 
233 | ## Live DEMOs (source code included)
234 | 
235 | [**Read MIDI file**](https://jazz-soft.net/demo/ReadMidiFile.html) - from file, URL, Base64  
236 | [**Write MIDI file**](https://jazz-soft.net/demo/WriteMidiFile.html) - create MIDI file from scratch  
237 | [**MIDI Player**](https://jazz-soft.net/demo/PlayMidiFile.html) - various ways to play MIDI file  
238 | [**Karaoke**](https://jazz-soft.net/demo/Karaoke.html) - playing MIDI files in *.kar* format
239 | 
240 | ## By popular demand
241 | ##### Boilerplate code for bulk MIDI file editing
242 | ```
243 | const fs = require('fs');
244 | const JZZ = require('jzz');
245 | require('jzz-midi-smf')(JZZ);
246 | 
247 | if (process.argv.length != 4) {
248 |   console.log('Usage: node ' + process.argv[1] + '  ');
249 |   process.exit(1);
250 | }
251 | 
252 | var old_midi = new JZZ.MIDI.SMF(fs.readFileSync(process.argv[2], 'binary'));
253 | var new_midi = new JZZ.MIDI.SMF(old_midi); // copy all settings from the old file
254 | new_midi.length = 0; // remove all tracks
255 | 
256 | for (var i = 0; i < old_midi.length; i++) {
257 |   var old_track = old_midi[i];
258 |   if (!(old_track instanceof JZZ.MIDI.SMF.MTrk)) continue;
259 |   var new_track = new JZZ.MIDI.SMF.MTrk();
260 |   new_midi.push(new_track);
261 |   for (var j = 0; j < old_track.length; j++) {
262 |     var old_msg = old_track[j];
263 |     var tick = old_msg.tt; // change it if you like, e.g. tick = old_msg.tt / 2;
264 |     if (true) { // add your own condition
265 |       new_track.add(tick, old_msg);
266 |     }
267 |     else if (false) { // add new messages or don't add anything
268 |       var new_msg = JZZ.MIDI.whatever(READ_THE_REFERENCE);
269 |       new_track.add(tick, new_msg);
270 |     }
271 |   }
272 | }
273 | fs.writeFileSync(process.argv[3], new_midi.dump(), 'binary');
274 | ```
275 | 
276 | ## See also
277 | [**Test MIDI Files**](https://github.com/jazz-soft/test-midi-files) - these may be useful if you write a MIDI application...  
278 | 
279 | ## More information
280 | 
281 | Please visit [**https://jazz-soft.net**](https://jazz-soft.net) for more information.  
282 | 
283 | ## Thanks for your support!
284 | [![Stargazers for @jazz-soft/JZZ-midi-SMF](https://reporoster.com/stars/jazz-soft/JZZ-midi-SMF)](https://github.com/jazz-soft/JZZ-midi-SMF/stargazers)


--------------------------------------------------------------------------------
/test/mocha.js:
--------------------------------------------------------------------------------
  1 | const assert = require('assert');
  2 | const fs = require('fs');
  3 | const JZZ = require('jzz');
  4 | require('..')(JZZ);
  5 | const RawClip = require('test-midi-files').RawClip; // must be after require('..')...
  6 | const version = require('../package.json').version;
  7 | 
  8 | function Sample(done, list) {
  9 |   this.done = done;
 10 |   this.list = list.slice();
 11 |   this.count = 0;
 12 |   this.compare = function(msg) {
 13 |     if (this.count < this.list.length) assert.equal(msg.slice().toString(), this.list[this.count].toString());
 14 |     this.count++;
 15 |     if (this.count == this.list.length) this.done();
 16 |   };
 17 | }
 18 | 
 19 | describe('Info', function() {
 20 |   it('version ' + version, function() {
 21 |     assert.equal(JZZ.MIDI.SMF.version(), version);
 22 |   });
 23 | });
 24 | 
 25 | describe('functions', function() {
 26 |   var smf = JZZ.MIDI.SMF(1, 100); // test without the 'new' keyword
 27 |   var trk = JZZ.MIDI.SMF.MTrk(); // test without the 'new' keyword
 28 |   smf.push(trk);
 29 |   trk.tick(0).tick(200).smfBPM(60).tick(200).smfEndOfTrack();
 30 |   var chk = JZZ.MIDI.SMF.Chunk('fake', '');
 31 |   var player = smf.player();
 32 | 
 33 |   it('constructor', function() {
 34 |     JZZ.MIDI.SMF('0');
 35 |     assert.throws(function() {
 36 |       JZZ.MIDI.SMF(0, 24, 16, 0);
 37 |     });
 38 |     assert.throws(function() {
 39 |       JZZ.MIDI.SMF(-1, 24, 16);
 40 |     });
 41 |     assert.throws(function() {
 42 |       JZZ.MIDI.SMF(0, 31, 16);
 43 |     });
 44 |     assert.throws(function() {
 45 |       JZZ.MIDI.SMF(0, 24, 300);
 46 |     });
 47 |     assert.throws(function() {
 48 |       JZZ.MIDI.SMF(0, -1);
 49 |     });
 50 |     assert.throws(function() {
 51 |       JZZ.MIDI.SMF('');
 52 |     });
 53 |     assert.throws(function() {
 54 |       JZZ.MIDI.SMF('not a midi file');
 55 |     });
 56 |     assert.throws(function() { // No MIDI tracks
 57 |       JZZ.MIDI.SMF(JZZ.MIDI.SMF().dump());
 58 |     });
 59 |     assert.throws(function() { // Invalid MIDI header
 60 |       var dump = JZZ.MIDI.SMF(0, 24, 16).dump();
 61 |       JZZ.MIDI.SMF(dump.substring(0, 13) + '\0' + dump.substring(14));
 62 |     });
 63 |     assert.throws(function() {
 64 |       JZZ.MIDI.SMF.Chunk();
 65 |     });
 66 |     assert.throws(function() {
 67 |       JZZ.MIDI.SMF.Chunk('xxxЯ');
 68 |     });
 69 |     assert.throws(function() {
 70 |       JZZ.MIDI.SMF.Chunk('xxxx');
 71 |     });
 72 |     assert.throws(function() {
 73 |       JZZ.MIDI.SMF.Chunk('xxxx', 'xxxЯ');
 74 |     });
 75 |   });
 76 |   it('type', function() {
 77 |     assert.equal(player.type(), 1);
 78 |   });
 79 |   it('tracks', function() {
 80 |     assert.equal(player.tracks(), 1);
 81 |   });
 82 |   it('duration', function() {
 83 |     assert.equal(player.duration(), 400);
 84 |     assert.equal(player.durationMS(), 3000);
 85 |   });
 86 |   it('tick2ms', function() {
 87 |     assert.equal(player.tick2ms(-1), 0);
 88 |     assert.equal(player.tick2ms(100), 500);
 89 |     assert.equal(player.tick2ms(1000), 3000);
 90 |     assert.throws(function() { player.tick2ms(); });
 91 |   });
 92 |   it('ms2tick', function() {
 93 |     assert.equal(player.ms2tick(-1), 0);
 94 |     assert.equal(player.ms2tick(500), 100);
 95 |     assert.equal(player.ms2tick(2000), 300);
 96 |     assert.equal(player.ms2tick(5000), 400);
 97 |     assert.throws(function() { player.ms2tick(); });
 98 |   });
 99 |   it('jump', function() {
100 |     assert.throws(function() { player.jump(); });
101 |     assert.throws(function() { player.jumpMS(); });
102 |     assert.equal(player._t2ms(0), 0);
103 |   });
104 |   it('type', function() {
105 |     assert.equal(chk.type, 'fake');
106 |     assert.equal(trk.type, 'MTrk');
107 |   });
108 |   it('add', function() {
109 |     var t = new JZZ.MIDI.SMF.MTrk();
110 |     t.add(10, JZZ.MIDI.bank(0, 1, 2));
111 |     assert.equal(t[0].toString(), 'b0 00 01 -- Bank Select MSB');
112 |     assert.equal(t[1].toString(), 'b0 20 02 -- Bank Select LSB');
113 |   });
114 |   it('throw', function() {
115 |     assert.throws(function() { trk.add(); });
116 |     assert.throws(function() { trk.add(0); });
117 |     assert.throws(function() { trk.add(0, 'dummy'); });
118 |     assert.throws(function() { trk.add(0, []); });
119 |     assert.throws(function() { trk.add(0, {}); });
120 |     assert.throws(function() { trk.add(0, [[0x80, 0, 0], []]); });
121 |     assert.throws(function() { trk.tick(); });
122 |     assert.throws(function() { trk.ch(-1); });
123 |     assert.throws(function() { trk.sxId(-1); });
124 |   });
125 |   it('validate', function() {
126 |     var smf = new JZZ.MIDI.SMF(1);
127 |     var trk = new JZZ.MIDI.SMF.MTrk();
128 |     smf.push(trk);
129 |     smf.validate();
130 |     trk.smfBPM(90).smfTimeSignature('3/4').smfSMPTE(JZZ.SMPTE())
131 |       .smf(0, 'xx').smf(0, 'xxx').smf(5, '')
132 |       .smf(32, 'x').smf(32, 'xx').smf(32, '\x01')
133 |       .smf(33, 'x').smf(33, 'xx')
134 |       .smf(81, '').smf(81, 'x').smf(81, 'xxxx')
135 |       .smf(84, 'xxxxx').smf(84, 'xxxxxx')
136 |       .smf(88, 'xxxx').smf(88, 'xxxxx')
137 |       .smf(89, 'xx').smf(89, 'xxx')
138 |       .smf(127, '').smf(126, '')
139 |       .sxGM().sxGS().send([0xf0, 0x43, 0x10, 0x4c, 0x00, 0x00, 0x7e, 0x00, 0xf7])
140 |     ;
141 |     trk = new JZZ.MIDI.SMF.MTrk();
142 |     smf.push(trk);
143 |     trk.smfBPM(90).smfTimeSignature('3/4').smfSMPTE(JZZ.SMPTE())
144 |       .ch(1)
145 |       .dataMSB(1).dataLSB(2).dataIncr().dataDecr()
146 |       .rpn(1).nrpn(1);
147 |     var val = smf.validate();
148 |     for (var i = 0; i < val.length; i++) val[i].toString();
149 |     smf = new JZZ.MIDI.SMF(0);
150 |     smf.push(JZZ.MIDI.SMF.MTrk());
151 |     smf.push(JZZ.MIDI.SMF.MTrk());
152 |     smf[1].send([0xf0, 0xf7, 0xf7, 0xf7, 0xf7, 0xf7, 0xf7, 0xf7])
153 |       .bank(0, 0).bank(0, 0).program(0, 0)
154 |       .bankMSB(1, 0).program(1, 0).bankLSB(2, 0).program(2, 0)
155 |       .rpn(0, 0).dataIncr(0).rpn(0, 0)
156 |       .rpnMSB(1, 0).dataMSB(1, 0).rpnLSB(2, 0).dataLSB(2, 0)
157 |       .nrpn(0, 0).dataDecr(0).nrpn(0, 0)
158 |       .nrpnMSB(1, 0).dataMSB(1, 0).nrpnLSB(2, 0).dataLSB(2, 0)
159 |       .dataIncr(3).rpn(4, 0x7f, 0x7f).dataDecr(4)
160 |     ;
161 |     var dump = smf.dump();
162 |     dump = dump.substring(0, 9) + '\x03' + dump.substring(10);
163 |     dump = dump.replace('MTrk', 'MThd')
164 |     dump = dump.replace('\xf0\x07\xf7\xf7\xf7\xf7\xf7\xf7\xf7', '\xf1\x00\x00\xf3\x00\x00\xf2\x00\x00');
165 |     smf = JZZ.MIDI.SMF(dump);
166 |     smf.validate();
167 |     //console.log(smf.validate());
168 |     //console.log(smf.toString());
169 |   });
170 |   it('trim', function() {
171 |     var smf = new JZZ.MIDI.SMF(1);
172 |     var trk = new JZZ.MIDI.SMF.MTrk();
173 |     smf.push(trk);
174 |     assert.equal(smf.player().trim(), 0);
175 |     trk.tick(100).smfBPM(120);
176 |     assert.equal(smf.player().trim(), 100);
177 |     trk.tick(100).smfText('the end');
178 |     assert.equal(smf.player().trim(), 0);
179 |     trk.length = 0;
180 |     assert.equal(smf.player().trim(), 0);
181 |   });
182 |   it('num4', function() {
183 |     assert.equal(JZZ.MIDI.SMF.num4(100000000), '\x05\xf5\xe1\x00');
184 |   });
185 | });
186 | 
187 | describe('integration: read / write / play', function() {
188 |   //console.log('JZZ.MIDI.SMF v' + JZZ.MIDI.SMF.version());
189 |   it('MIDI file type 0; fps/ppf', function(done) {
190 |     // new file
191 |     var smf = new JZZ.MIDI.SMF(0, 24, 16);
192 |     var trk = new JZZ.MIDI.SMF.MTrk();
193 |     smf.push(trk);
194 |     trk.add(0, JZZ.MIDI.smfBPM(90))
195 |        .add(0, JZZ.MIDI.program(0, 8).label('dummy'))
196 |        .add(10, JZZ.MIDI.noteOn(0, 'C#6', 127))
197 |        .add(20, JZZ.MIDI.noteOff(0, 'C#6'))
198 |        .add(30, JZZ.MIDI.smfEndOfTrack());
199 |     var more = new JZZ.MIDI.SMF.Chunk('More', 'Ignore this...');
200 |     smf.push(more);
201 |     smf.annotate();
202 |     smf.toString();
203 |     //console.log(smf.toString());
204 |     // write and read
205 |     smf = new JZZ.MIDI.SMF(smf.dump(true));
206 |     // read from Buffer
207 |     smf = new JZZ.MIDI.SMF(smf.toBuffer());
208 |     // read from ArrayBuffer
209 |     smf = new JZZ.MIDI.SMF(smf.toArrayBuffer());
210 |     // read from Int8Array
211 |     smf = new JZZ.MIDI.SMF(smf.toInt8Array());
212 |     // copy
213 |     smf = new JZZ.MIDI.SMF(smf);
214 |     // player
215 |     var player = smf.player();
216 | 
217 |     assert.equal(player.duration(), 30);
218 |     assert.equal(player.durationMS(), 78.125);
219 |     assert.equal(player.position(), 0);
220 |     assert.equal(player.positionMS(), 0);
221 |     player.jump(-1);
222 |     assert.equal(player.position(), 0);
223 |     player.jump(1000000);
224 |     assert.equal(player.position(), 29);
225 |     player.jump(20);
226 |     assert.equal(player.position(), 20);
227 |     player.jumpMS(-1);
228 |     assert.equal(player.positionMS(), 0);
229 |     player.jumpMS(1000000);
230 |     assert.equal(player.positionMS(), 77.125);
231 |     player.jumpMS(50);
232 |     assert.equal(player.positionMS(), 50);
233 |     player.jump(0);
234 |     player.speed(2);
235 |     assert.equal(player.speed(), 2);
236 |     player.speed(-1);
237 |     assert.equal(player.speed(), 1);
238 |     player.filter(function() {});
239 |     player.filter();
240 | 
241 |     var sample = new Sample(done, [
242 |       [], [0xc0, 0x08], [0x90, 0x49, 0x7f], [0x80, 0x49, 0x40], [],
243 |       [], [0xc0, 0x08], [0x90, 0x49, 0x7f], [0x80, 0x49, 0x40], [],
244 |       [0xb0, 0x78, 0x00], [0xb1, 0x78, 0x00], [0xb2, 0x78, 0x00], [0xb3, 0x78, 0x00],
245 |       [0xb4, 0x78, 0x00], [0xb5, 0x78, 0x00], [0xb6, 0x78, 0x00], [0xb7, 0x78, 0x00],
246 |       [0xb8, 0x78, 0x00], [0xb9, 0x78, 0x00], [0xba, 0x78, 0x00], [0xbb, 0x78, 0x00],
247 |       [0xbc, 0x78, 0x00], [0xbd, 0x78, 0x00], [0xbe, 0x78, 0x00], [0xbf, 0x78, 0x00]
248 |     ]);
249 |     player.connect(function(msg) { sample.compare(msg); });
250 |     player.loop(true);
251 |     player.loop(false);
252 |     player.loop(2);
253 |     player.play();
254 |     player.resume();
255 |   });
256 | 
257 |   it('MIDI file type 1; ppqn', function(done) {
258 |     var smf = new JZZ.MIDI.SMF(1, 96);
259 |     var trk = new JZZ.MIDI.SMF.MTrk();
260 |     smf.push(trk);
261 |     trk.smfBPM(90);
262 |     trk = new JZZ.MIDI.SMF.MTrk;
263 |     smf.push(trk);
264 |     trk.sxIdRequest(); // insert a sysex
265 |     trk.tick(200).note(0, 'C5', 127, 10)
266 |        .tick(20000).note(0, 'E5', 127, 10)
267 |        .tick(3000000).ch(1).ch(1).ch(0).note('E5', 127, 10).ch().sxId(1).sxId(1).sxId()
268 |        .tick(200).smfEndOfTrack().add(0, [0x99, 0x40, 0x7f]);
269 |     var str = smf.dump();
270 |     str = str.substring(0, str.length - 1); // make a fixable corrupted file
271 |     smf = new JZZ.MIDI.SMF(str);
272 |     smf = new JZZ.MIDI.SMF(smf.dump() + ' ');
273 |     smf.annotate().annotate();
274 |     smf.toString();
275 |     //console.log(smf.toString());
276 |     var player = smf.player();
277 |     var sample = new Sample(function() { player.stop(); done(); }, [
278 |       [0xb0, 0x78, 0x00], [0xb1, 0x78, 0x00], [0xb2, 0x78, 0x00], [0xb3, 0x78, 0x00],
279 |       [0xb4, 0x78, 0x00], [0xb5, 0x78, 0x00], [0xb6, 0x78, 0x00], [0xb7, 0x78, 0x00],
280 |       [0xb8, 0x78, 0x00], [0xb9, 0x78, 0x00], [0xba, 0x78, 0x00], [0xbb, 0x78, 0x00],
281 |       [0xbc, 0x78, 0x00], [0xbd, 0x78, 0x00], [0xbe, 0x78, 0x00], [0xbf, 0x78, 0x00],
282 |       [0xb0, 0x79, 0x00], [0xb1, 0x79, 0x00], [0xb2, 0x79, 0x00], [0xb3, 0x79, 0x00],
283 |       [0xb4, 0x79, 0x00], [0xb5, 0x79, 0x00], [0xb6, 0x79, 0x00], [0xb7, 0x79, 0x00],
284 |       [0xb8, 0x79, 0x00], [0xb9, 0x79, 0x00], [0xba, 0x79, 0x00], [0xbb, 0x79, 0x00],
285 |       [0xbc, 0x79, 0x00], [0xbd, 0x79, 0x00], [0xbe, 0x79, 0x00], [0xbf, 0x79, 0x00],
286 |       [0x90, 0x40, 0x7f], [0x80, 0x40, 0x40]
287 |     ]);
288 |     player.connect(function(msg) { sample.compare(msg); });
289 |     player.jump(3020300);
290 |     player.resume();
291 |     player.jump(3020200);
292 |   });
293 | 
294 |   it('MIDI file type 2; ppqn', function(done) {
295 |     var smf = new JZZ.MIDI.SMF(2, 96);
296 |     var trk = new JZZ.MIDI.SMF.MTrk;
297 |     smf.push(trk);
298 |     trk.smfBPM(90).tick(20).ch(0).clock().note('C#5', 127, 20).add(0, [0xc0, 0x0c]);
299 |     trk = new JZZ.MIDI.SMF.MTrk;
300 |     smf.push(trk);
301 |     trk.ch(0).note('F#5', 127, 20);
302 |     smf.annotate();
303 |     smf.toString();
304 |     //console.log(smf.toString());
305 |     var player = smf.player();
306 |     var sample = new Sample(done, [
307 |       [], [0xc0, 0x0c],
308 |       [0xb0, 0x78, 0x00], [0xb1, 0x78, 0x00], [0xb2, 0x78, 0x00], [0xb3, 0x78, 0x00],
309 |       [0xb4, 0x78, 0x00], [0xb5, 0x78, 0x00], [0xb6, 0x78, 0x00], [0xb7, 0x78, 0x00],
310 |       [0xb8, 0x78, 0x00], [0xb9, 0x78, 0x00], [0xba, 0x78, 0x00], [0xbb, 0x78, 0x00],
311 |       [0xbc, 0x78, 0x00], [0xbd, 0x78, 0x00], [0xbe, 0x78, 0x00], [0xbf, 0x78, 0x00],
312 |       [0xb0, 0x79, 0x00], [0xb1, 0x79, 0x00], [0xb2, 0x79, 0x00], [0xb3, 0x79, 0x00],
313 |       [0xb4, 0x79, 0x00], [0xb5, 0x79, 0x00], [0xb6, 0x79, 0x00], [0xb7, 0x79, 0x00],
314 |       [0xb8, 0x79, 0x00], [0xb9, 0x79, 0x00], [0xba, 0x79, 0x00], [0xbb, 0x79, 0x00],
315 |       [0xbc, 0x79, 0x00], [0xbd, 0x79, 0x00], [0xbe, 0x79, 0x00], [0xbf, 0x79, 0x00],
316 |       [0x90, 0x3d, 0x7f], [0x80, 0x3d, 0x40], [],
317 |       [0x90, 0x42, 0x7f], [0x80, 0x42, 0x40]
318 |     ]);
319 |     player.connect(function(msg) { sample.compare(msg); });
320 |     player.resume();
321 |     player.pause();
322 |     player.pause();
323 |     setTimeout(function() { player.resume(); }, 100);
324 |   });
325 | });
326 | 
327 | describe('MIDI files', function() {
328 |   function load(fname) {
329 |     return fs.readFileSync(__dirname + '/midi/' + fname, 'binary');
330 |   }
331 |   it('0.mid', function() {
332 |     var smf = new JZZ.MIDI.SMF(load('0.mid'));
333 |     var val = smf.validate();
334 |     for (var i = 0; i < val.length; i++) val[i].toString();
335 |   });
336 |   it('1.mid', function() {
337 |     var smf = new JZZ.MIDI.SMF(load('1.mid'));
338 |     smf.validate();
339 |   });
340 |   it('2.mid', function() {
341 |     var smf = new JZZ.MIDI.SMF(load('2.mid'));
342 |     var val = smf.validate();
343 |     for (var i = 0; i < val.length; i++) val[i].toString();
344 |   });
345 |   it('3.mid', function() {
346 |     var smf = new JZZ.MIDI.SMF(load('3.mid'));
347 |     smf.validate();
348 |     smf.player().trim();
349 |   });
350 |   it('4.mid', function() {
351 |     var smf = new JZZ.MIDI.SMF(load('4.mid'));
352 |     var val = smf.validate();
353 |     for (var i = 0; i < val.length; i++) val[i].toString();
354 |   });
355 |   it('5.mid', function() {
356 |     var smf = new JZZ.MIDI.SMF(load('5.mid'));
357 |     var val = smf.validate();
358 |     for (var i = 0; i < val.length; i++) val[i].toString();
359 |   });
360 |   it('6.mid', function() {
361 |     var smf = new JZZ.MIDI.SMF(load('6.mid'));
362 |     var val = smf.validate();
363 |     for (var i = 0; i < val.length; i++) val[i].toString();
364 |   });
365 |   it('7.mid', function() {
366 |     var smf = new JZZ.MIDI.SMF(load('7.mid'));
367 |     smf.validate();
368 |   });
369 |   it('8.mid', function() {
370 |     var smf = new JZZ.MIDI.SMF(load('8.mid'));
371 |     smf.validate();
372 |   });
373 | });
374 | 
375 | describe('SYX', function() {
376 |   it('version', function() {
377 |     assert.equal(JZZ.MIDI.SYX.version(), JZZ.MIDI.SMF.version());
378 |   });
379 |   it('constructor', function() {
380 |     assert.equal(JZZ.MIDI.SMF.version(), JZZ.MIDI.SMF.version());
381 |     var syx = JZZ.MIDI.SYX(JZZ.MIDI.sxIdRequest());
382 |     assert.equal(syx[0].toString(), 'f0 7e 7f 06 01 f7');
383 |     assert.equal(syx.toString(), 'SYX:\n  f0 7e 7f 06 01 f7');
384 |     syx = JZZ.MIDI.SYX([0xf0, 0x7e, 0x7f, 0x06, 0x01, 0xf7, 0xf0, 0x7e, 0x01, 0x06, 0x01, 0xf7]);
385 |     assert.equal(syx[0].toString(), 'f0 7e 7f 06 01 f7');
386 |     assert.equal(syx[1].toString(), 'f0 7e 01 06 01 f7');
387 |     assert.throws(function() {
388 |       JZZ.MIDI.SYX([0x7e, 0x7f, 0x06, 0x01, 0xf7]);
389 |     });
390 |     assert.throws(function() { JZZ.MIDI.SYX(''); });
391 |     assert.throws(function() {
392 |       JZZ.MIDI.SYX([0xf0, 0x7e, 0x7f, 0x06, 0x01]);
393 |     });
394 |     assert.throws(function() {
395 |       var smf = JZZ.MIDI.SMF();
396 |       smf.push(JZZ.MIDI.SMF.MTrk());
397 |       smf[0].note(0, 60, 127).ch(0).note(60, 27);
398 |       JZZ.MIDI.SYX(smf);
399 |     });
400 |     syx = JZZ.MIDI.SYX(syx.dump());
401 |     syx = JZZ.MIDI.SYX(syx.toBuffer());
402 |     syx = JZZ.MIDI.SYX(syx.toArrayBuffer());
403 |     syx = JZZ.MIDI.SYX(syx.toInt8Array());
404 |     syx = JZZ.MIDI.SYX(syx.toUint8Array());
405 |     syx = JZZ.MIDI.SYX(JZZ.MIDI.SMF(syx));
406 |     syx = JZZ.MIDI.SYX(syx);
407 |     assert.equal(syx[0].toString(), 'f0 7e 7f 06 01 f7');
408 |     assert.equal(syx[1].toString(), 'f0 7e 01 06 01 f7');
409 |     syx.validate();
410 |   });
411 |   it('player', function(done) {
412 |     var syx = JZZ.MIDI.SYX(JZZ.MIDI.sxIdRequest());
413 |     var player = syx.player();
414 |     var sample = new Sample(done, [
415 |       [0xf0, 0x7e, 0x7f, 0x06, 0x01, 0xf7]
416 |     ]);
417 |     player.connect(function(msg) { sample.compare(msg); });
418 |     player.play();
419 |   });
420 |   it('helpers', function() {
421 |     var syx = new JZZ.MIDI.SYX();
422 |     syx.send(JZZ.MIDI([0xf0, 0xf7]).label('dummy'))
423 |       .noteOn(0, 'C7', 127).ch().ch(0).ch(0).noteOff('C7').ch()
424 |       .sxMasterVolumeF(0).sxId().sxId(5).sxId(5).sxMasterVolumeF(1).add([0xf0, 0xf7]).sxId().annotate();
425 |     assert.equal(syx[0].toString(), 'f0 f7');
426 |     assert.equal(syx[1].toString(), 'f0 7f 7f 04 01 00 00 f7 (Master Volume)');
427 |     assert.equal(syx[2].toString(), 'f0 7f 05 04 01 7f 7f f7 (Master Volume)');
428 |     assert.equal(syx[3].toString(), 'f0 f7');
429 |     assert.throws(function() { syx.ch(-1); });
430 |     assert.throws(function() { syx.sxId(-1); });
431 |   });
432 | });
433 | 
434 | describe('SMF2', function() {
435 |   it('version', function() {
436 |     assert.equal(JZZ.MIDI.Clip.version(), JZZ.MIDI.SMF.version());
437 |   });
438 |   it('empty', function() {
439 |     assert.throws(function() { JZZ.MIDI.Clip(''); });
440 |     var clip = new RawClip();
441 |     clip = JZZ.MIDI.Clip(clip.dump());
442 |     clip = JZZ.MIDI.Clip(clip);
443 |     assert.equal(clip.toString(), 'SMF2CLIP\nHeader\n  0: 00300060 -- Ticks Per Quarter Note\nData\n  0: f0200000 00000000 00000000 00000000 -- Start of Clip\n  0: f0210000 00000000 00000000 00000000 -- End of Clip');
444 |     clip = RawClip(clip.dump());
445 |     assert.equal(clip[0].getDelta(), 0);
446 |     assert.equal(clip[2].getDelta(), 0);
447 |     assert.equal(clip[3].isStartClip(), true);
448 |     assert.equal(clip[4].getDelta(), 0);
449 |     assert.equal(clip[5].isEndClip(), true);
450 |   });
451 |   it('errors', function() {
452 |     assert.throws(function() { new JZZ.MIDI.Clip('dummy'); });
453 |     assert.throws(function() { new JZZ.MIDI.Clip(['dummy']); });
454 |     var clip = JZZ.MIDI.Clip('xSMF2CLIP');
455 |     assert.throws(function() { clip.gr(20); });
456 |     assert.throws(function() { clip.ch(20); });
457 |     assert.throws(function() { clip.sxId(256); });
458 |     assert.throws(function() { clip.tick(); });
459 |     assert.throws(function() { clip.add(-1, JZZ.MIDI2.noop()); });
460 |     assert.throws(function() { clip.header.add(-1, JZZ.MIDI2.noop()); });
461 |   });
462 |   it('warnings', function() {
463 |     var clip = new JZZ.MIDI.Clip('abc' + (new RawClip()).dump() + "abc");
464 |     var val = clip.validate();
465 |     assert.equal(val[0].toString(), 'offset 3 -- Extra leading characters (3)');
466 |     assert.equal(val[1].toString(), 'offset 11 -- Incomplete message (61 62 63)');
467 |     assert.equal(val[2].toString(), 'offset 15 -- Missing Ticks PQN message');
468 |     assert.equal(val[3].toString(), 'offset 15 -- No Start of Clip message');
469 |     clip = new RawClip();
470 |     clip.umpTicksPQN(96).umpDelta(0).umpTicksPQN(0).umpDelta(0).umpDelta(0).umpStartClip().noteOn(0, 0, 'C5').noteOff(0, 0, 'C5');
471 |     clip = new JZZ.MIDI.Clip(clip.dump());
472 |     val = clip.validate();
473 |     assert.equal(val[0].toString(), 'offset 16 -- Multiple Ticks PQN message');
474 |     assert.equal(val[1].toString(), 'offset 16 -- Bad Ticks PQN value: 0');
475 |     assert.equal(val[2].toString(), 'offset 24 -- Consequential Delta Ticks message');
476 |     assert.equal(val[3].toString(), 'offset 44 tick 0 -- Missing Delta Ticks message (20903c7f -- Note On)');
477 |     clip = new RawClip();
478 |     clip.umpTicksPQN(96).umpDelta(0).umpEndClip().umpDelta(0).umpStartClip().umpDelta(0).umpStartClip().umpDelta(0).umpEndClip().umpDelta(0).umpEndClip();
479 |     clip = new JZZ.MIDI.Clip(clip.dump());
480 |     val = clip.validate();
481 |     assert.equal(val[0].toString(), 'offset 16 -- Unexpected End of Clip message');
482 |     assert.equal(val[1].toString(), 'offset 56 -- Repeated Start of Clip message');
483 |     assert.equal(val[2].toString(), 'offset 96 -- Repeated End of Clip message');
484 |     clip = new JZZ.MIDI.Clip();
485 |     val = clip.validate();
486 |     assert.equal(typeof val, 'undefined');
487 |     var a = JZZ.UMP.umpText(0, 'text text text text text text');
488 |     clip.send(a[0]).send(a[1]).send(a[2]);
489 |     clip = new JZZ.MIDI.Clip(clip.dump());
490 |     val = clip.validate();
491 |     assert.equal(typeof val, 'undefined');
492 |     clip = new JZZ.MIDI.Clip();
493 |     clip.send(a[0]).send(a[1]).send(a[2]).tick(96).send(a[2]).send(a[1]).send(a[0]);
494 |     clip = new JZZ.MIDI.Clip(clip.dump());
495 |     val = clip.validate();
496 |     assert.equal(val[0].toString(), 'offset 100 tick 96 -- Missing series start (d0d00200 20746578 74000000 00000000 -- Text)');
497 |     assert.equal(val[3].toString(), 'offset 140 tick 96 -- Missing series end (d0500200 74657874 20746578 74207465 -- Text)');
498 |     a = JZZ.UMP.umpData(0, 'data data data');
499 |     clip = new JZZ.MIDI.Clip();
500 |     clip.send(a[1]).send(a[0]);
501 |     clip = new JZZ.MIDI.Clip(clip.dump());
502 |     val = clip.validate();
503 |     assert.equal(val[0].toString(), 'offset 40 tick 0 -- Missing series start (50310061 00000000 00000000 00000000 -- Data)');
504 |     assert.equal(val[1].toString(), 'offset 60 tick 0 -- Missing series end (501d0064 61746120 64617461 20646174 -- Data)');
505 |     a = JZZ.UMP.sxMidiSoft(0, 4, 'karaoke');
506 |     clip = new JZZ.MIDI.Clip();
507 |     clip.send(a[1]).send(a[0]);
508 |     clip = new JZZ.MIDI.Clip(clip.dump());
509 |     val = clip.validate();
510 |     assert.equal(val[0].toString(), 'offset 40 tick 0 -- Missing series start (30366172 616f6b65 -- SysEx)');
511 |     assert.equal(val[1].toString(), 'offset 52 tick 0 -- Missing series end (30160020 2400046b -- SysEx)');
512 |   });
513 |   it('add', function() {
514 |     var clip = new JZZ.MIDI.Clip();
515 |     clip.header.add(0, JZZ.UMP.umpText(0, 'text text text'));
516 |     clip.add(0, JZZ.UMP.umpText(0, 'text text text'));
517 |     assert.throws(function() { clip.add(0); });
518 |     assert.throws(function() { clip.add(0, []); });
519 |     assert.throws(function() { clip.add(0, [[]]); });
520 |     assert.throws(function() { clip.add(0, [undefined]); });
521 |     //console.log(clip.toString());
522 |   });
523 |   it('tick', function() {
524 |     var clip = new JZZ.MIDI.Clip();
525 |     clip.tick(96).umpDelta(96).tick(96).umpEndClip();
526 |     //console.log(clip.toString());
527 |     assert.equal(clip[0].tt, 288);
528 |   });
529 |   it('dump', function() {
530 |     var clip = new JZZ.MIDI.Clip();
531 |     clip.header.tick(1).program(0, 1, 2).umpEndClip().noop();
532 |     clip.header.umpDelta(0).noop();
533 |     clip.gr(0).gr(0).ch(1).ch(1).sxId(2).sxId(2).noteOn('C5').tick(96).noteOff('C5').gr().gr().ch().ch().sxId().sxId();
534 |     clip.noop();
535 |     clip = JZZ.MIDI.Clip(clip.dump());
536 |     clip = JZZ.MIDI.Clip(clip.toBuffer());
537 |     clip = JZZ.MIDI.Clip(clip.toArrayBuffer());
538 |     clip = JZZ.MIDI.Clip(clip.toInt8Array());
539 |     clip = JZZ.MIDI.Clip(clip.toUint8Array());
540 |     clip = JZZ.MIDI.Clip(clip);
541 |     clip.annotate();
542 |     //console.log(clip.toString());
543 |   });
544 |   it('player', function(done) {
545 |     var clip = JZZ.MIDI.Clip();
546 |     clip.header.gr(0).umpBPM(2400).ch(0).program(8);
547 |     clip.gr(0).gr(0).ch(1).ch(1).sxId(2).sxId(2).umpBPM(2400).noteOn('C5').tick(96).noteOff('C5').gr().gr().ch().ch().sxId().sxId();
548 |     clip.toString();
549 |     var player = clip.player();
550 |     assert.equal(player.durationMS(), 25);
551 |     var sample = new Sample(done, [
552 |       [0xd0, 0x10, 0x00, 0x00, 0x00, 0x26, 0x25, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
553 |       [0x20, 0xc0, 0x08, 0x00],
554 |       [0xd0, 0x10, 0x00, 0x00, 0x00, 0x26, 0x25, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
555 |       [0x20, 0x91, 0x3c, 0x7f], [0x20, 0x81, 0x3c, 0x40],
556 |       [0xf0, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
557 |     ]);
558 |     player.connect(function(msg) { sample.compare(msg); });
559 |     player.jump(90);
560 |     player.play();
561 |   });
562 | });
563 | 


--------------------------------------------------------------------------------
/minified/JZZ.midi.SMF.js:
--------------------------------------------------------------------------------
1 | !function(t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t.SMF=t:"function"==typeof define&&define.amd?define("JZZ.midi.SMF",["JZZ"],t):t(JZZ)}(function(m){var e,i,c,y;function v(t){throw new Error(t)}function o(t){var r="";return 2097151>21&127))),16383>14&127))),127>7&127))),r+=String.fromCharCode(127&t)}function n(t){return String.fromCharCode(t>>8)+String.fromCharCode(255&t)}function s(t){return String.fromCharCode(t>>24&255)+String.fromCharCode(t>>16&255)+String.fromCharCode(t>>8&255)+String.fromCharCode(255&t)}function h(t){return String.fromCharCode(255&t)+String.fromCharCode(t>>8&255)+String.fromCharCode(t>>16&255)+String.fromCharCode(t>>24&255)}function C(t){for(var r="",i=t.byteLength,e=0;e=i[a].length||(s&&(h=i[a][o[a]].tt),s=!1,h>i[a][o[a]].tt&&(h=i[a][o[a]].tt))}if(n=h,s)break}}return e}function I(t,r,i){if(!(this instanceof I))return new I(t,r,i);var e;if(this.sub[t])return this.sub[t](t,r,i);for("string"==typeof t&&4==t.length||v("Invalid chunk type: "+t),e=0;ei?u(t._off,"Invalid "+r+" meta event: data too long",S(t),t.tt):void 0}function b(t,r){return u(t._off,r+" meta events must be in the first track",S(t),t.tt)}function w(t,r,i,e){var n;if(255==r.charCodeAt(0))n=m.MIDI.smf(r.charCodeAt(1),i);else{for(var o=[r.charCodeAt(0)],s=0;s>4,u=[4,4,4,8,8,16,4,4,8,8,8,12,12,16,16,16][d],p=[],h.lengthe.length&&i._complain(n+e.length,"Incomplete data",s-e.length)},c="MThd"+String.fromCharCode(0)+String.fromCharCode(0)+String.fromCharCode(0)+String.fromCharCode(6),d.prototype.toString=function(){var t=[];return void 0!==this.off&&t.push("offset "+this.off),void 0!==this.track&&t.push("track "+this.track),void 0!==this.tick&&t.push("tick "+this.tick),t.push("--"),t.push(this.msg),void 0!==this.data&&t.push("("+this.data+")"),t.join(" ")},a.prototype.tracks=function(){for(var t=0,r=0;r>5?u(t._off,"Invalid SMPTE meta event: incorrect format",t.dd.charCodeAt(0)>>5,t.tt):r&&t.track?b(t,"SMPTE"):void 0):88==t.ff?M(t,"Time Signature",4)||(8>4,f=15&h[0];if(11==p)switch(h[1]){case 0:_(o,s,f,"bm"),s[f].bm=[h,!1];break;case 32:_(o,s,f,"bl"),s[f].bl=[h,!1];break;case 98:_(o,s,f,"nl"),_(o,s,f,"rm"),_(o,s,f,"rl"),s[f].nl=[h,!1];break;case 99:_(o,s,f,"nm"),_(o,s,f,"rm"),_(o,s,f,"rl"),s[f].nm=[h,!1];break;case 100:_(o,s,f,"rl"),_(o,s,f,"nm"),_(o,s,f,"nl"),s[f].rl=[h,!1];break;case 101:_(o,s,f,"rm"),_(o,s,f,"nm"),_(o,s,f,"nl"),s[f].rm=[h,!1];break;case 6:case 38:case 96:case 97:s[f].rm&&s[f].rl&&(s[f].rm[1]=!0,s[f].rl[1]=!0),!s[f].rm||s[f].rl||s[f].rm[1]||(a=s[f].rm[0],o.push(u(a._off,"No matching RPN LSB",a.toString(),a.tt,a.track)),s[f].rm[1]=!0),s[f].rm||!s[f].rl||s[f].rl[1]||(a=s[f].rl[0],o.push(u(a._off,"No matching RPN MSB",a.toString(),a.tt,a.track)),s[f].rl[1]=!0),s[f].nm&&s[f].nl&&(s[f].nm[1]=!0,s[f].nl[1]=!0),!s[f].nm||s[f].nl||s[f].nm[1]||(a=s[f].nm[0],o.push(u(a._off,"No matching NRPN LSB",a.toString(),a.tt,a.track)),s[f].nm[1]=!0),s[f].nm||!s[f].nl||s[f].nl[1]||(a=s[f].nl[0],o.push(u(a._off,"No matching NRPN MSB",a.toString(),a.tt,a.track)),s[f].nl[1]=!0),s[f].rm||s[f].rl||s[f].nm||s[f].nl||o.push(u(h._off,"RPN/NRPN not set",h.toString(),h.tt,h.track)),s[f].rm&&s[f].rl&&127==s[f].rm[0][2]&&127==s[f].rl[0][2]&&o.push(u(h._off,"RPN/NRPN not set",h.toString(),h.tt,h.track))}else 12==p&&(s[f].bm&&(s[f].bm[1]=!0),s[f].bl&&(s[f].bl[1]=!0),s[f].bl&&!s[f].bm&&(a=s[f].bl[0],o.push(u(a._off,"No matching Bank Select MSB",a.toString(),a.tt,a.track))),s[f].bm)&&!s[f].bl&&(a=s[f].bm[0],o.push(u(a._off,"No matching Bank Select LSB",a.toString(),a.tt,a.track)))}}if(l(t,n),t.sort(function(t,r){return(t.off||0)-(r.off||0)||(t.track||0)-(r.track||0)||(t.tick||0)-(r.tick||0)}),t.length){for(e=0;et);i++);for(e=0;ethis._pos);this._ptr++)this._filter(t);this._ptr>=this._list.length&&(this._list==this._hdr?(this._list=this._data,this._ptr=0,this._p0=0,this._t0=r):(this._loop&&-1!=this._loop&&this._loop--,this._loop?(this._ptr=0,this._p0=0,this._t0=r):this.stop(),this.onEnd())),"stop"==this.event&&(this.playing=!1,this.paused=!1,this._pos=0,this._ptr=0,this.sndOff(),this.event=void 0),"pause"==this.event&&(this.playing=!1,this.paused=!0,this._pos>=this._duration&&(this._pos=this._duration-1),this._p0=this._pos,this.sndOff(),this.event=void 0),this.playing&&m.lib.schedule(this._tick)},N.prototype.trim=function(){for(var t,r=[],i=0,e=0;e=this._duration&&(t=this._duration-1),this._goto(t)},N.prototype.jumpMS=function(t){isNaN(parseFloat(t))&&v("Not a number: "+t),(t=t<0?0:t)>=this._durationMS&&(t=this._durationMS-1),this._goto(this._ms2t(t))},N.prototype._t2ms=function(t){if(!t)return 0;for(var r=0;this._ttt[r].t=this._pos);this._ptr++)t.isTempo()&&this.ppqn&&(this._mul=this.ppqn*(t.isMidi2?1e5:1e3)/(t.getTempo()||1));this._list=this._data,this.mul=this._mul*this._speed,this._t0=i(),this._p0=this._pos},N.prototype.tick2ms=function(t){return isNaN(parseFloat(t))&&v("Not a number: "+t),t<=0?0:t>=this._duration?this._durationMS:this._t2ms(t)},N.prototype.ms2tick=function(t){return isNaN(parseFloat(t))&&v("Not a number: "+t),t<=0?0:t>=this._durationMS?this._duration:this._ms2t(t)},m.MIDI.SMF=a,x.version=function(){return e},((x.prototype=[]).constructor=x).prototype.copy=function(t){for(var r=0;rt||this._orig[e]==n);e++);this._orig.splice(e,0,r)}return s},P.prototype.sxId=function(t){if((t=void 0===t?P.prototype._sxid:t)==this._sxid)return this;if(t!=parseInt(t)||t<0||127>6),e.isData()&&(r="d"+(15&e[0]),i=e[1]>>4&3),e.isSX()&&(r="s"+(15&e[0]),i=e[1]>>4&3),r&&(s[r]?0!=i&&1!=i||o._complain(s[r].off,"Missing series end",s[r].toString(),s[r].tt):2!=i&&3!=i||o._complain(e.off,"Missing series start",e.toString(),e.tt),s[r]=0==i||3==i?void 0:e);for(i=Object.keys(s),h=0;h 0x1fffff) s += String.fromCharCode(((n >> 21) & 0x7f) + 0x80);
  26 |     if (n > 0x3fff) s += String.fromCharCode(((n >> 14) & 0x7f) + 0x80);
  27 |     if (n > 0x7f) s += String.fromCharCode(((n >> 7) & 0x7f) + 0x80);
  28 |     s += String.fromCharCode(n & 0x7f);
  29 |     return s;
  30 |   }
  31 |   function _num2(n) {
  32 |     return String.fromCharCode(n >> 8) + String.fromCharCode(n & 0xff);
  33 |   }
  34 |   function _num4(n) {
  35 |     return String.fromCharCode((n >> 24) & 0xff) + String.fromCharCode((n >> 16) & 0xff) + String.fromCharCode((n >> 8) & 0xff) + String.fromCharCode(n & 0xff);
  36 |   }
  37 |   function _num4le(n) {
  38 |     return String.fromCharCode(n & 0xff) + String.fromCharCode((n >> 8) & 0xff) + String.fromCharCode((n >> 16) & 0xff) + String.fromCharCode((n >> 24) & 0xff);
  39 |   }
  40 |   function _u8a2s(u) {
  41 |     var s = '';
  42 |     var len = u.byteLength;
  43 |     // String.fromCharCode.apply(null, u) throws "RangeError: Maximum call stack size exceeded" on large files
  44 |     for (var i = 0; i < len; i++) s += String.fromCharCode(u[i]);
  45 |     return s;
  46 |   }
  47 |   function _hex(x) { return (x < 16 ? '0' : '') + x.toString(16); }
  48 | 
  49 |   function SMF() {
  50 |     var self = this;
  51 |     if (!(self instanceof SMF)) {
  52 |       self = new SMF();
  53 |       delete self.ppqn;
  54 |     }
  55 |     var type = 1;
  56 |     var ppqn = 96;
  57 |     var fps;
  58 |     var ppf;
  59 |     if (arguments.length == 1) {
  60 |       if (arguments[0] instanceof SMF) {
  61 |         return arguments[0].copy();
  62 |       }
  63 |       if (arguments[0] instanceof SYX) {
  64 |         self.type = 0;
  65 |         self.ppqn = ppqn;
  66 |         self.push(new MTrk());
  67 |         for (var i = 0; i < arguments[0].length; i++) self[0].add(0, arguments[0][i]);
  68 |         return self;
  69 |       }
  70 |       var data;
  71 |       try {
  72 |         if (arguments[0] instanceof ArrayBuffer) {
  73 |           data = _u8a2s(new Uint8Array(arguments[0]));
  74 |         }
  75 |       }
  76 |       catch (err) {/**/}
  77 |       try {
  78 |         if (arguments[0] instanceof Uint8Array || arguments[0] instanceof Int8Array) {
  79 |           data = _u8a2s(new Uint8Array(arguments[0]));
  80 |         }
  81 |       }
  82 |       catch (err) {/**/}
  83 |       try {
  84 |         /* istanbul ignore next */
  85 |         if (arguments[0] instanceof Buffer) {
  86 |           data = arguments[0].toString('binary');
  87 |         }
  88 |       }
  89 |       catch (err) {/**/}
  90 |       if (typeof arguments[0] == 'string' && arguments[0] != '0' && arguments[0] != '1' && arguments[0] != '2') {
  91 |         data = arguments[0];
  92 |       }
  93 |       if (data == '') _error('Empty file');
  94 |       if (data) {
  95 |         self.load(data);
  96 |         return self;
  97 |       }
  98 |       type = parseInt(arguments[0]);
  99 |     }
 100 |     else if (arguments.length == 2) {
 101 |       type = parseInt(arguments[0]);
 102 |       ppqn = parseInt(arguments[1]);
 103 |     }
 104 |     else if (arguments.length == 3) {
 105 |       type = parseInt(arguments[0]);
 106 |       fps = parseInt(arguments[1]);
 107 |       ppf = parseInt(arguments[2]);
 108 |     }
 109 |     else if (arguments.length) _error('Invalid parameters');
 110 |     if (isNaN(type) || type < 0 || type > 2) _error('Invalid parameters');
 111 |     self.type = type;
 112 |     if (typeof fps == 'undefined') {
 113 |       if (isNaN(ppqn) || ppqn < 0 || ppqn > 0xffff) _error('Invalid parameters');
 114 |       self.ppqn = ppqn;
 115 |     }
 116 |     else {
 117 |       if (fps != 24 && fps != 25 && fps != 29 && fps != 30) _error('Invalid parameters');
 118 |       if (isNaN(ppf) || ppf < 0 || ppf > 0xff) _error('Invalid parameters');
 119 |       self.fps = fps;
 120 |       self.ppf = ppf;
 121 |     }
 122 |     return self;
 123 |   }
 124 |   SMF.version = function() { return _ver; };
 125 |   SMF.num4 = _num4;
 126 | 
 127 |   SMF.prototype = [];
 128 |   SMF.prototype.constructor = SMF;
 129 |   SMF.prototype.copy = function() {
 130 |     var smf = new SMF();
 131 |     smf.type = this.type;
 132 |     smf.ppqn = this.ppqn;
 133 |     smf.fps = this.fps;
 134 |     smf.ppf = this.ppf;
 135 |     smf.rmi = this.rmi;
 136 |     smf.ntrk = this.ntrk;
 137 |     for (var i = 0; i < this.length; i++) smf.push(this[i].copy());
 138 |     return smf;
 139 |   };
 140 | 
 141 |   function _issue(off, msg, data, tick, track) {
 142 |     var w = { off: off, msg: msg, data: data };
 143 |     if (typeof tick != 'undefined') w.tick = tick;
 144 |     if (typeof track != 'undefined') w.track = track;
 145 |     return w;
 146 |   }
 147 |   SMF.prototype._complain = function(off, msg, data) {
 148 |     if (!this._warn) this._warn = [];
 149 |     this._warn.push(_issue(off, msg, data));
 150 |   };
 151 |   SMF.prototype.load = function(s) {
 152 |     var off = 0;
 153 |     if (s.substring(0, 4) == 'RIFF' && s.substring(8, 16) == 'RMIDdata') {
 154 |       this.rmi = true;
 155 |       off = 20;
 156 |       s = s.substring(20, 20 + s.charCodeAt(16) + s.charCodeAt(17) * 0x100 + s.charCodeAt(18) * 0x10000 + s.charCodeAt(19) * 0x1000000);
 157 |     }
 158 |     _loadSMF(this, s, off);
 159 |   };
 160 | 
 161 |   var MThd0006 = 'MThd' + String.fromCharCode(0) + String.fromCharCode(0) + String.fromCharCode(0) + String.fromCharCode(6);
 162 |   function _loadSMF(self, s, off) {
 163 |     if (s.substring(0, 8) != MThd0006) {
 164 |       var z = s.indexOf(MThd0006);
 165 |       if (z != -1) {
 166 |         s = s.substring(z);
 167 |         self._complain(off, 'Extra leading characters', z);
 168 |         off += z;
 169 |       }
 170 |       else _error('Not a MIDI file');
 171 |     }
 172 |     self._off = off;
 173 |     self.type = s.charCodeAt(8) * 16 + s.charCodeAt(9);
 174 |     self._off_type = off + 8;
 175 |     self.ntrk = s.charCodeAt(10) * 16 + s.charCodeAt(11);
 176 |     self._off_ntrk = off + 10;
 177 |     if (s.charCodeAt(12) > 0x7f) {
 178 |       self.fps = 0x100 - s.charCodeAt(12);
 179 |       self.ppf = s.charCodeAt(13);
 180 |       self._off_fps = off + 12;
 181 |       self._off_ppf = off + 13;
 182 |     }
 183 |     else{
 184 |       self.ppqn = s.charCodeAt(12) * 256 + s.charCodeAt(13);
 185 |       self._off_ppqn = off + 12;
 186 |     }
 187 |     if (self.type > 2) self._complain(8 + off, 'Invalid MIDI file type', self.type);
 188 |     else if (self.type == 0 && self.ntrk > 1) self._complain(10 + off, 'Wrong number of tracks for the type 0 MIDI file', self.ntrk);
 189 |     if (!self.ppf && !self.ppqn) _error('Invalid MIDI header');
 190 |     var n = 0;
 191 |     var p = 14;
 192 |     while (p < s.length - 8) {
 193 |       var offset = p + off;
 194 |       var type = s.substring(p, p + 4);
 195 |       if (type == 'MTrk') n++;
 196 |       var len = (s.charCodeAt(p + 4) << 24) + (s.charCodeAt(p + 5) << 16) + (s.charCodeAt(p + 6) << 8) + s.charCodeAt(p + 7);
 197 |       if (len <= 0) { // broken file
 198 |         len = s.length - p - 8;
 199 |         self._complain(p + off + 4, 'Invalid track length', s.charCodeAt(p + 4) + '/' + s.charCodeAt(p + 5) + '/' + s.charCodeAt(p + 6) + '/' + s.charCodeAt(p + 7));
 200 |       }
 201 |       p += 8;
 202 |       var data = s.substring(p, p + len);
 203 |       self.push(new Chunk(type, data, offset));
 204 |       if (type == 'MThd') self._complain(offset, 'Unexpected chunk type', 'MThd');
 205 |       p += len;
 206 |     }
 207 |     if (n != self.ntrk) {
 208 |       self._complain(off + 10, 'Incorrect number of tracks', self.ntrk);
 209 |       self.ntrk = n;
 210 |     }
 211 |     if (!self.ntrk)  _error('No MIDI tracks');
 212 |     if (!self.type && self.ntrk > 1 || self.type > 2)  self.type = 1;
 213 |     if (p < s.length) self._complain(off + p, 'Extra trailing characters', s.length - p);
 214 |     if (p > s.length) self._complain(off + s.length, 'Incomplete data', p - s.length);
 215 |   }
 216 | 
 217 |   function Warn(obj) {
 218 |     if (!(this instanceof Warn)) return new Warn(obj);
 219 |     for (var k in obj) if (obj.hasOwnProperty(k)) this[k] = obj[k];
 220 |   }
 221 |   Warn.prototype.toString = function() {
 222 |     var a = [];
 223 |     if (typeof this.off != 'undefined') a.push('offset ' + this.off);
 224 |     if (typeof this.track != 'undefined') a.push('track ' + this.track);
 225 |     if (typeof this.tick != 'undefined') a.push('tick ' + this.tick);
 226 |     a.push('--');
 227 |     a.push(this.msg);
 228 |     if (typeof this.data != 'undefined') a.push('(' + this.data + ')');
 229 |     return a.join(' ');
 230 |   };
 231 | 
 232 |   SMF.prototype.tracks = function() {
 233 |     var t = 0;
 234 |     for (var i = 0; i < this.length; i++) if (this[i] instanceof MTrk) t++;
 235 |     return t;
 236 |   };
 237 | 
 238 |   function _reset_state(w, s) {
 239 |     var i;
 240 |     for (i = 0; i < 16; i++) {
 241 |       if (s[i]) {
 242 |         if (s[i].rm && s[i].rl && s[i].rm[0][2] == 0x7f && s[i].rl[0][2] == 0x7f) {
 243 |           s[i].rm[1] = true;
 244 |           s[i].rl[1] = true;
 245 |         }
 246 |         _check_unused(w, s, i, 'bm');
 247 |         _check_unused(w, s, i, 'bl');
 248 |         _check_unused(w, s, i, 'nm');
 249 |         _check_unused(w, s, i, 'nl');
 250 |         _check_unused(w, s, i, 'rm');
 251 |         _check_unused(w, s, i, 'rl');
 252 |       }
 253 |       s[i] = {};
 254 |     }
 255 |   }
 256 |   function _update_state(w, s, msg) {
 257 |     if (!msg.length || msg[0] < 0x80) return;
 258 |     if (msg.isGmReset() || msg.isGsReset() || msg.isXgReset()) {
 259 |       _reset_state(w, s);
 260 |       return;
 261 |     }
 262 |     var st = msg[0] >> 4;
 263 |     var ch = msg[0] & 15;
 264 |     var m;
 265 |     if (st == 0xb) {
 266 |       switch (msg[1]) {
 267 |         case 0:
 268 |           _check_unused(w, s, ch, 'bm');
 269 |           s[ch].bm = [msg, false];
 270 |           break;
 271 |         case 0x20:
 272 |           _check_unused(w, s, ch, 'bl');
 273 |           s[ch].bl = [msg, false];
 274 |           break;
 275 |         case 0x62:
 276 |           _check_unused(w, s, ch, 'nl');
 277 |           _check_unused(w, s, ch, 'rm');
 278 |           _check_unused(w, s, ch, 'rl');
 279 |           s[ch].nl = [msg, false];
 280 |           break;
 281 |         case 0x63:
 282 |           _check_unused(w, s, ch, 'nm');
 283 |           _check_unused(w, s, ch, 'rm');
 284 |           _check_unused(w, s, ch, 'rl');
 285 |           s[ch].nm = [msg, false];
 286 |           break;
 287 |         case 0x64:
 288 |           _check_unused(w, s, ch, 'rl');
 289 |           _check_unused(w, s, ch, 'nm');
 290 |           _check_unused(w, s, ch, 'nl');
 291 |           s[ch].rl = [msg, false];
 292 |           break;
 293 |         case 0x65:
 294 |           _check_unused(w, s, ch, 'rm');
 295 |           _check_unused(w, s, ch, 'nm');
 296 |           _check_unused(w, s, ch, 'nl');
 297 |           s[ch].rm = [msg, false];
 298 |           break;
 299 |         case 0x6: case 0x26: case 0x60: case 0x61:
 300 |           if (s[ch].rm && s[ch].rl) {
 301 |             s[ch].rm[1] = true;
 302 |             s[ch].rl[1] = true;
 303 |           }
 304 |           if (s[ch].rm && !s[ch].rl && !s[ch].rm[1]) {
 305 |             m = s[ch].rm[0];
 306 |             w.push(_issue(m._off, 'No matching RPN LSB', m.toString(), m.tt, m.track));
 307 |             s[ch].rm[1] = true;
 308 |           }
 309 |           if (!s[ch].rm && s[ch].rl && !s[ch].rl[1]) {
 310 |             m = s[ch].rl[0];
 311 |             w.push(_issue(m._off, 'No matching RPN MSB', m.toString(), m.tt, m.track));
 312 |             s[ch].rl[1] = true;
 313 |           }
 314 |           if (s[ch].nm && s[ch].nl) {
 315 |             s[ch].nm[1] = true;
 316 |             s[ch].nl[1] = true;
 317 |           }
 318 |           if (s[ch].nm && !s[ch].nl && !s[ch].nm[1]) {
 319 |             m = s[ch].nm[0];
 320 |             w.push(_issue(m._off, 'No matching NRPN LSB', m.toString(), m.tt, m.track));
 321 |             s[ch].nm[1] = true;
 322 |           }
 323 |           if (!s[ch].nm && s[ch].nl && !s[ch].nl[1]) {
 324 |             m = s[ch].nl[0];
 325 |             w.push(_issue(m._off, 'No matching NRPN MSB', m.toString(), m.tt, m.track));
 326 |             s[ch].nl[1] = true;
 327 |           }
 328 |           if (!s[ch].rm && !s[ch].rl && !s[ch].nm && !s[ch].nl) {
 329 |             w.push(_issue(msg._off, 'RPN/NRPN not set', msg.toString(), msg.tt, msg.track));
 330 |           }
 331 |           if (s[ch].rm && s[ch].rl && s[ch].rm[0][2] == 0x7f && s[ch].rl[0][2] == 0x7f) {
 332 |             w.push(_issue(msg._off, 'RPN/NRPN not set', msg.toString(), msg.tt, msg.track));
 333 |           }
 334 |           break;
 335 |       }
 336 |       return;
 337 |     }
 338 |     if (st == 0xc) {
 339 |       if (s[ch].bm) s[ch].bm[1] = true;
 340 |       if (s[ch].bl) s[ch].bl[1] = true;
 341 |       if (s[ch].bl && !s[ch].bm) {
 342 |         m = s[ch].bl[0];
 343 |         w.push(_issue(m._off, 'No matching Bank Select MSB', m.toString(), m.tt, m.track));
 344 |       }
 345 |       if (s[ch].bm && !s[ch].bl) {
 346 |         m = s[ch].bm[0];
 347 |         w.push(_issue(m._off, 'No matching Bank Select LSB', m.toString(), m.tt, m.track));
 348 |       }
 349 |     }
 350 |   }
 351 |   function _check_unused(w, s, c, x) {
 352 |     if (s[c][x] && !s[c][x][1]) {
 353 |       var str;
 354 |       switch (x) {
 355 |         case 'bm': case 'bl': str = 'Unnecessary Bank Select'; break;
 356 |         case 'nm': case 'nl': str = 'Unnecessary NRPN'; break;
 357 |         case 'rm': case 'rl': str = 'Unnecessary RPN'; break;
 358 |       }
 359 |       var m = s[c][x][0];
 360 |       w.push(_issue(m._off, str, m.toString(), m.tt, m.track));
 361 |       delete s[c][x];
 362 |     }
 363 |   }
 364 |   SMF.prototype.validate = function() {
 365 |     var i, k, z;
 366 |     var w = [];
 367 |     if (this._warn) for (i = 0; i < this._warn.length; i++) w.push(Warn(this._warn[i]));
 368 |     var mm = _sort(this);
 369 |     k = 0;
 370 |     for (i = 0; i < this.length; i++) if (this[i] instanceof MTrk) {
 371 |       this[i]._validate(w, k);
 372 |       k++;
 373 |     }
 374 |     var st = {};
 375 |     _reset_state(w, st);
 376 |     for (i = 0; i < mm.length; i++) {
 377 |       z = _validate_midi(mm[i], this.type == 1);
 378 |       if (z) {
 379 |         z.track = mm[i].track;
 380 |         w.push(z);
 381 |       }
 382 |       _update_state(w, st, mm[i]);
 383 |     }
 384 |     _reset_state(w, st);
 385 |     w.sort(function(a, b) {
 386 |       return (a.off || 0) - (b.off || 0) || (a.track || 0) - (b.track || 0) || (a.tick || 0) - (b.tick || 0);
 387 |     });
 388 |     if (w.length) {
 389 |       for (i = 0; i < w.length; i++) w[i] = Warn(w[i]);
 390 |       return w;
 391 |     }
 392 |   };
 393 |   SMF.prototype.dump = function(rmi) {
 394 |     var s = '';
 395 |     if (rmi) {
 396 |       s = this.dump();
 397 |       return 'RIFF' + _num4le(s.length + 12) + 'RMIDdata' + _num4le(s.length) + s;
 398 |     }
 399 |     this.ntrk = 0;
 400 |     for (var i = 0; i < this.length; i++) {
 401 |       if (this[i] instanceof MTrk) this.ntrk++;
 402 |       s += this[i].dump();
 403 |     }
 404 |     s = (this.ppqn ? _num2(this.ppqn) : String.fromCharCode(0x100 - this.fps) + String.fromCharCode(this.ppf)) + s;
 405 |     s = MThd0006 + String.fromCharCode(0) + String.fromCharCode(this.type) + _num2(this.ntrk) + s;
 406 |     return s;
 407 |   };
 408 |   SMF.prototype.toBuffer = function(rmi) {
 409 |     return Buffer.from(this.dump(rmi), 'binary');
 410 |   };
 411 |   SMF.prototype.toUint8Array = function(rmi) {
 412 |     var str = this.dump(rmi);
 413 |     var buf = new ArrayBuffer(str.length);
 414 |     var arr = new Uint8Array(buf);
 415 |     for (var i = 0; i < str.length; i++) arr[i] = str.charCodeAt(i);
 416 |     return arr;
 417 |   };
 418 |   SMF.prototype.toArrayBuffer = function(rmi) {
 419 |     return this.toUint8Array(rmi).buffer;
 420 |   };
 421 |   SMF.prototype.toInt8Array = function(rmi) {
 422 |     return new Int8Array(this.toArrayBuffer(rmi));
 423 |   };
 424 |   SMF.prototype.toString = function() {
 425 |     var i;
 426 |     this.ntrk = 0;
 427 |     for (i = 0; i < this.length; i++) if (this[i] instanceof MTrk) this.ntrk++;
 428 |     var a = ['SMF:', '  type: ' + this.type];
 429 |     if (this.ppqn) a.push('  ppqn: ' + this.ppqn);
 430 |     else a.push('  fps: ' + this.fps, '  ppf: ' + this.ppf);
 431 |     a.push('  tracks: ' + this.ntrk);
 432 |     for (i = 0; i < this.length; i++) {
 433 |       a.push(this[i].toString());
 434 |     }
 435 |     return a.join('\n');
 436 |   };
 437 | 
 438 |   function _var2num(s) {
 439 |     if (!s.length) return 0; // missing last byte
 440 |     if (s.charCodeAt(0) < 0x80) return [1, s.charCodeAt(0)];
 441 |     var x = s.charCodeAt(0) & 0x7f;
 442 |     x <<= 7;
 443 |     if (s.charCodeAt(1) < 0x80) return [2, x + s.charCodeAt(1)];
 444 |     x += s.charCodeAt(1) & 0x7f;
 445 |     x <<= 7;
 446 |     if (s.charCodeAt(2) < 0x80) return [3, x + s.charCodeAt(2)];
 447 |     x += s.charCodeAt(2) & 0x7f;
 448 |     x <<= 7;
 449 |     x += s.charCodeAt(3) & 0x7f;
 450 |     return [4, s.charCodeAt(3) < 0x80 ? x : -x];
 451 |   }
 452 |   function _msglen(n) {
 453 |     switch (n & 0xf0) {
 454 |       case 0x80: case 0x90: case 0xa0: case 0xb0: case 0xe0: return 2;
 455 |       case 0xc0: case 0xD0: return 1;
 456 |     }
 457 |     switch (n) {
 458 |       case 0xf1: case 0xf3: return 1;
 459 |       case 0xf2: return 2;
 460 |     }
 461 |     return 0;
 462 |   }
 463 | 
 464 |   function _sort(smf) {
 465 |     var i, j;
 466 |     var tt = [];
 467 |     var mm = [];
 468 |     for (i = 0; i < smf.length; i++) if (smf[i] instanceof MTrk) tt.push(smf[i]);
 469 |     if (smf.type != 1) {
 470 |       for (i = 0; i < tt.length; i++) {
 471 |         for (j = 0; j < tt[i].length; j++) {
 472 |           tt[i][j].track = i;
 473 |           mm.push(tt[i][j]);
 474 |         }
 475 |       }
 476 |     }
 477 |     else {
 478 |       var t = 0;
 479 |       var pp = [];
 480 |       for (i = 0; i < tt.length; i++) pp[i] = 0;
 481 |       while (true) {
 482 |         var b = true;
 483 |         var m = 0;
 484 |         for (i = 0; i < tt.length; i++) {
 485 |           while (pp[i] < tt[i].length && tt[i][pp[i]].tt == t) {
 486 |             tt[i][pp[i]].track = i;
 487 |             mm.push(tt[i][pp[i]]);
 488 |             pp[i]++;
 489 |           }
 490 |           if (pp[i] >= tt[i].length) continue;
 491 |           if (b) m = tt[i][pp[i]].tt;
 492 |           b = false;
 493 |           if (m > tt[i][pp[i]].tt) m = tt[i][pp[i]].tt;
 494 |         }
 495 |         t = m;
 496 |         if (b) break;
 497 |       }
 498 |     }
 499 |     return mm;
 500 |   }
 501 | 
 502 |   SMF.prototype.annotate = function() {
 503 |     var mm = _sort(this);
 504 |     var ctxt = JZZ.Context();
 505 |     for (var i = 0; i < mm.length; i++) {
 506 |       if (mm[i].lbl) mm[i].lbl = undefined;
 507 |       ctxt._read(mm[i]);
 508 |     }
 509 |     return this;
 510 |   };
 511 | 
 512 |   SMF.prototype.player = function() {
 513 |     var pl = new Player();
 514 |     pl.ppqn = this.ppqn;
 515 |     pl.fps = this.fps;
 516 |     pl.ppf = this.ppf;
 517 |     var i;
 518 |     var e;
 519 |     var mm = _sort(this);
 520 |     if (this.type == 2) {
 521 |       var tr = 0;
 522 |       var m = 0;
 523 |       var t = 0;
 524 |       for (i = 0; i < mm.length; i++) {
 525 |         e = JZZ.MIDI(mm[i]);
 526 |         if (tr != e.track) {
 527 |           tr = e.track;
 528 |           m = t;
 529 |         }
 530 |         t = e.tt + m;
 531 |         e.tt = t;
 532 |         pl._data.push(e);
 533 |       }
 534 |     }
 535 |     else {
 536 |       for (i = 0; i < mm.length; i++) {
 537 |         e = JZZ.MIDI(mm[i]);
 538 |         pl._data.push(e);
 539 |       }
 540 |     }
 541 |     pl._type = this.type;
 542 |     pl._tracks = this.tracks();
 543 |     pl._timing();
 544 |     return pl;
 545 |   };
 546 | 
 547 |   function Chunk(t, d, off) {
 548 |     if (!(this instanceof Chunk)) return new Chunk(t, d, off);
 549 |     var i;
 550 |     if (this.sub[t]) return this.sub[t](t, d, off);
 551 |     if (typeof t != 'string' || t.length != 4) _error("Invalid chunk type: " + t);
 552 |     for (i = 0; i < t.length; i++) if (t.charCodeAt(i) < 0 || t.charCodeAt(i) > 255) _error("Invalid chunk type: " + t);
 553 |     if (typeof d != 'string') _error("Invalid data type: " + d);
 554 |     for (i = 0; i < d.length; i++) if (d.charCodeAt(i) < 0 || d.charCodeAt(i) > 255) _error("Invalid data character: " + d[i]);
 555 |     this.type = t;
 556 |     this.data = d;
 557 |     this._off = off;
 558 |   }
 559 |   SMF.Chunk = Chunk;
 560 |   Chunk.prototype = [];
 561 |   Chunk.prototype.constructor = Chunk;
 562 |   Chunk.prototype.copy = function() { return new Chunk(this.type, this.data); };
 563 | 
 564 |   Chunk.prototype.sub = {
 565 |     'MTrk': function(t, d, off) { return new MTrk(d, off); }
 566 |   };
 567 |   Chunk.prototype.dump = function() {
 568 |     return this.type + _num4(this.data.length) + this.data;
 569 |   };
 570 |   Chunk.prototype.toString = function() {
 571 |     return this.type + ': ' + this.data.length + ' bytes';
 572 |   };
 573 | 
 574 |   function _validate_msg_data(trk, s, p, m, t, off) {
 575 |     var x = s.substring(p, p + m);
 576 |     if (x.length < m) {
 577 |       trk._complain(off, 'Incomplete track data', m - x.length, t);
 578 |       x = (x + '\x00\x00').substring(0, m);
 579 |     }
 580 |     for (var i = 0; i < m; i++) if (x.charCodeAt(i) > 127) {
 581 |       trk._complain(off + i, 'Bad MIDI value set to 0', x.charCodeAt(i), t);
 582 |       x = x.substring(0, i) + '\x00' + x.substring(i + 1);
 583 |     }
 584 |     return x;
 585 |   }
 586 |   function _validate_number(trk, s, off, t, tt) {
 587 |     var nn = _var2num(s);
 588 |     if (tt) t += nn[1];
 589 |     if (nn[1] < 0) {
 590 |       nn[1] = -nn[1];
 591 |       trk._complain(off, "Bad byte sequence", s.charCodeAt(0) + '/' + s.charCodeAt(1) + '/' + s.charCodeAt(2) + '/' + s.charCodeAt(3), t);
 592 |     }
 593 |     else if (nn[0] == 4 && nn[1] < 0x200000) {
 594 |       trk._complain(off, "Long VLQ value", s.charCodeAt(0) + '/' + s.charCodeAt(1) + '/' + s.charCodeAt(2) + '/' + s.charCodeAt(3), t);
 595 |     }
 596 |     else if (nn[0] == 3 && nn[1] < 0x4000) {
 597 |       trk._complain(off, "Long VLQ value", s.charCodeAt(0) + '/' + s.charCodeAt(1) + '/' + s.charCodeAt(2), t);
 598 |     }
 599 |     else if (nn[0] == 2 && nn[1] < 0x80) {
 600 |       trk._complain(off, "Long VLQ value", s.charCodeAt(0) + '/' + s.charCodeAt(1), t);
 601 |     }
 602 |     return nn;
 603 |   }
 604 | 
 605 |   function MTrk(s, off) {
 606 |     if (!(this instanceof MTrk)) return new MTrk(s, off);
 607 |     this._off = off;
 608 |     this._orig = this;
 609 |     this._tick = 0;
 610 |     if(typeof s == 'undefined') {
 611 |       this.push(new Event(0, '\xff\x2f', ''));
 612 |       return;
 613 |     }
 614 |     var t = 0;
 615 |     var p = 0;
 616 |     var w = '';
 617 |     var st;
 618 |     var m;
 619 |     var rs;
 620 |     off += 8;
 621 |     var offset = p + off;
 622 |     while (p < s.length) {
 623 |       m = _validate_number(this, s.substring(p, p + 4), offset, t, true);
 624 |       p += m[0];
 625 |       t += m[1];
 626 |       offset = p + off;
 627 |       if (s.charCodeAt(p) == 0xff) {
 628 |         rs = false;
 629 |         st = s.substring(p, p + 2);
 630 |         if (st.length < 2) {
 631 |           this._complain(offset, 'Incomplete track data', 3 - st.length, t);
 632 |           st = '\xff\x2f';
 633 |         }
 634 |         p += 2;
 635 |         m = _validate_number(this, s.substring(p, p + 4), offset + 2, t);
 636 |         p += m[0];
 637 |         this.push (new Event(t, st, s.substring(p, p + m[1]), offset));
 638 |         p += m[1];
 639 |       }
 640 |       else if (s.charCodeAt(p) == 0xf0 || s.charCodeAt(p) == 0xf7) {
 641 |         rs = false;
 642 |         st = s.substring(p, p + 1);
 643 |         p += 1;
 644 |         m = _validate_number(this, s.substring(p, p + 4), offset + 1, t);
 645 |         p += m[0];
 646 |         this.push(new Event(t, st, s.substring(p, p + m[1]), offset));
 647 |         p += m[1];
 648 |       }
 649 |       else if (s.charCodeAt(p) & 0x80) {
 650 |         rs = true;
 651 |         w = s.substring(p, p + 1);
 652 |         p += 1;
 653 |         m = _msglen(w.charCodeAt(0));
 654 |         if (w.charCodeAt(0) > 0xf0) this._complain(offset, 'Unexpected MIDI message', w.charCodeAt(0).toString(16), t);
 655 |         this.push(new Event(t, w, _validate_msg_data(this, s, p, m, t, offset + 1), offset));
 656 |         p += m;
 657 |       }
 658 |       else if (w.charCodeAt(0) & 0x80) { // running status
 659 |         if (!rs) this._complain(offset, 'Interrupted running status', w.charCodeAt(0).toString(16), t);
 660 |         rs = true;
 661 |         m = _msglen(w.charCodeAt(0));
 662 |         if (w.charCodeAt(0) > 0xf0) this._complain(offset, 'Unexpected MIDI message', w.charCodeAt(0).toString(16), t);
 663 |         this.push(new Event(t, w, _validate_msg_data(this, s, p, m, t, offset), offset));
 664 |         p += m;
 665 |       }
 666 |     }
 667 |   }
 668 |   SMF.MTrk = MTrk;
 669 | 
 670 |   MTrk.prototype = [];
 671 |   MTrk.prototype.constructor = MTrk;
 672 |   MTrk.prototype.type = 'MTrk';
 673 |   MTrk.prototype.copy = function() {
 674 |     var trk = new MTrk();
 675 |     trk.length = 0;
 676 |     for (var i = 0; i < this.length; i++) trk.push(new JZZ.MIDI(this[i]));
 677 |     return trk;
 678 |   };
 679 |   function _shortmsg(msg) {
 680 |     var s = msg.toString();
 681 |     if (s.length > 80) {
 682 |       s = s.substring(0, 78);
 683 |       s = s.substring(0, s.lastIndexOf(' ')) + ' ...';
 684 |     }
 685 |     return s;
 686 |   }
 687 |   function _metaevent_len(msg, name, len) {
 688 |     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);
 689 |     if (msg.dd.length > len) return _issue(msg._off, 'Invalid ' + name + ' meta event: data too long', _shortmsg(msg), msg.tt);
 690 |   }
 691 |   function _timing_first_track(msg, name) {
 692 |     return _issue(msg._off, name + ' meta events must be in the first track', _shortmsg(msg), msg.tt);
 693 |   }
 694 |   function _validate_midi(msg, t1) {
 695 |     var issue;
 696 |     if (typeof msg.ff != 'undefined') {
 697 |       if (msg.ff > 0x7f) return _issue(msg._off, 'Invalid meta event', _shortmsg(msg), msg.tt);
 698 |       else if (msg.ff == 0) {
 699 |         issue = _metaevent_len(msg, 'Sequence Number', 2); if (issue) return issue;
 700 |       }
 701 |       else if (msg.ff < 10) {
 702 |         if (!msg.dd.length) return _issue(msg._off, 'Invalid Text meta event: no data', _shortmsg(msg), msg.tt);
 703 |       }
 704 |       else if (msg.ff == 32) {
 705 |         issue = _metaevent_len(msg, 'Channel Prefix', 1); if (issue) return issue;
 706 |         if (msg.dd.charCodeAt(0) > 15) return _issue(msg._off, 'Invalid Channel Prefix meta event: incorrect data', _shortmsg(msg), msg.tt);
 707 |       }
 708 |       else if (msg.ff == 33) {
 709 |         issue = _metaevent_len(msg, 'MIDI Port', 1); if (issue) return issue;
 710 |         if (msg.dd.charCodeAt(0) > 127) return _issue(msg._off, 'Invalid MIDI Port meta event: incorrect data', _shortmsg(msg), msg.tt);
 711 |       }
 712 |       else if (msg.ff == 47) {
 713 |         issue = _metaevent_len(msg, 'End of Track', 0); if (issue) return issue;
 714 |       }
 715 |       else if (msg.ff == 81) {
 716 |         issue = _metaevent_len(msg, 'Tempo', 3); if (issue) return issue;
 717 |         if (t1 && msg.track) return _timing_first_track(msg, 'Tempo');
 718 |       }
 719 |       else if (msg.ff == 84) {
 720 |         issue = _metaevent_len(msg, 'SMPTE', 5); if (issue) return issue;
 721 |         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);
 722 |         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);
 723 |         if (t1 && msg.track) return _timing_first_track(msg, 'SMPTE');
 724 |       }
 725 |       else if (msg.ff == 88) {
 726 |         issue = _metaevent_len(msg, 'Time Signature', 4); if (issue) return issue;
 727 |         if (msg.dd.charCodeAt(1) > 8) return _issue(msg._off, 'Invalid Time Signature meta event: incorrect data', _shortmsg(msg), msg.tt);
 728 |         if (t1 && msg.track) return _timing_first_track(msg, 'Time Signature');
 729 |       }
 730 |       else if (msg.ff == 89) {
 731 |         issue = _metaevent_len(msg, 'Key Signature', 2); if (issue) return issue;
 732 |         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);
 733 |       }
 734 |       else if (msg.ff == 127) {
 735 |         // Sequencer Specific meta event
 736 |       }
 737 |       else {
 738 |         return _issue(msg._off, 'Unknown meta event', _shortmsg(msg), msg.tt);
 739 |       }
 740 |     }
 741 |     else {
 742 |       //
 743 |     }
 744 |   }
 745 |   MTrk.prototype._validate = function(w, k) {
 746 |     var i, z;
 747 |     if (this._warn) for (i = 0; i < this._warn.length; i++) {
 748 |       z = Warn(this._warn[i]);
 749 |       z.track = k;
 750 |       w.push(z);
 751 |     }
 752 |   };
 753 |   MTrk.prototype._complain = function(off, msg, data, tick) {
 754 |     if (!this._warn) this._warn = [];
 755 |     this._warn.push(_issue(off, msg, data, tick));
 756 |   };
 757 |   MTrk.prototype.dump = function() {
 758 |     var s = '';
 759 |     var t = 0;
 760 |     var m = '';
 761 |     var i, j;
 762 |     for (i = 0; i < this.length; i++) {
 763 |       s += _num(this[i].tt - t);
 764 |       t = this[i].tt;
 765 |       if (typeof this[i].dd != 'undefined') {
 766 |         s += '\xff';
 767 |         s += String.fromCharCode(this[i].ff);
 768 |         s += _num(this[i].dd.length);
 769 |         s += this[i].dd;
 770 |       }
 771 |       else if (this[i][0] == 0xf0 || this[i][0] == 0xf7) {
 772 |         s += String.fromCharCode(this[i][0]);
 773 |         s += _num(this[i].length - 1);
 774 |         for (j = 1; j < this[i].length; j++) s += String.fromCharCode(this[i][j]);
 775 |       }
 776 |       else {
 777 |         if (this[i][0] != m) {
 778 |           m = this[i][0];
 779 |           s += String.fromCharCode(this[i][0]);
 780 |         }
 781 |         for (j = 1; j < this[i].length; j++) s += String.fromCharCode(this[i][j]);
 782 |       }
 783 |     }
 784 |     return 'MTrk' + _num4(s.length) + s;
 785 |   };
 786 |   MTrk.prototype.toString = function() {
 787 |     var a = ['MTrk:'];
 788 |     for (var i = 0; i < this.length; i++) {
 789 |       a.push(this[i].tt + ': ' + this[i].toString());
 790 |     }
 791 |     return a.join('\n  ');
 792 |   };
 793 |   function _msg(msg) {
 794 |     if (msg.length || msg.isSMF()) return msg;
 795 |     _error('Not a MIDI message');
 796 |   }
 797 |   MTrk.prototype.add = function(t, msg) {
 798 |     t = parseInt(t);
 799 |     if(isNaN(t) || t < 0) _error('Invalid parameter');
 800 |     var i, j;
 801 |     var a = [];
 802 |     try {
 803 |       a.push(JZZ.MIDI(msg));
 804 |     }
 805 |     catch (e) {
 806 |       for (i = 0; i < msg.length; i++) a.push(JZZ.MIDI(msg[i]));
 807 |     }
 808 |     if (!a.length) _error('Not a MIDI message');
 809 |     for (i = 0; i < a.length; i++) _msg(a[i]);
 810 |     if (this[this._orig.length - 1].tt < t) this[this._orig.length - 1].tt = t; // end of track
 811 |     if (msg.ff == 0x2f || msg[0] > 0xf0 && msg[0] != 0xf7) return this;
 812 |     for (i = 0; i < this._orig.length - 1; i++) {
 813 |       if (this._orig[i].tt > t) break;
 814 |     }
 815 |     for (j = 0; j < a.length; j++) {
 816 |       msg = a[j];
 817 |       msg.tt = t;
 818 |       this._orig.splice(i, 0, msg);
 819 |       i++;
 820 |     }
 821 |     return this;
 822 |   };
 823 | 
 824 |   MTrk.prototype._sxid = 0x7f;
 825 |   MTrk.prototype._image = function() {
 826 |     var F = function() {}; F.prototype = this._orig;
 827 |     var img = new F();
 828 |     img._ch = this._ch;
 829 |     img._sxid = this._sxid;
 830 |     img._tick = this._tick;
 831 |     return img;
 832 |   };
 833 |   MTrk.prototype.send = function(msg) { this._orig.add(this._tick, msg); return this; };
 834 |   MTrk.prototype.tick = function(t) {
 835 |     if (t != parseInt(t) || t < 0) throw RangeError('Bad tick value: ' + t);
 836 |     if (!t) return this;
 837 |     var img = this._image();
 838 |     img._tick = this._tick + t;
 839 |     return img;
 840 |   };
 841 |   MTrk.prototype.sxId = function(id) {
 842 |     if (typeof id == 'undefined') id = MTrk.prototype._sxid;
 843 |     if (id == this._sxid) return this;
 844 |     if (id != parseInt(id) || id < 0 || id > 0x7f) throw RangeError('Bad MIDI value: ' + id);
 845 |     var img = this._image();
 846 |     img._sxid = id;
 847 |     return img;
 848 |   };
 849 |   MTrk.prototype.ch = function(c) {
 850 |     if (c == this._ch || typeof c == 'undefined' && typeof this._ch == 'undefined') return this;
 851 |     if (typeof c != 'undefined') {
 852 |       if (c != parseInt(c) || c < 0 || c > 15) throw RangeError('Bad channel value: ' + c  + ' (must be from 0 to 15)');
 853 |     }
 854 |     var img = this._image();
 855 |     img._ch = c;
 856 |     return img;
 857 |   };
 858 |   MTrk.prototype.note = function(c, n, v, t) {
 859 |     this.noteOn(c, n, v);
 860 |     if (typeof this._ch == 'undefined') {
 861 |       if (t > 0) this.tick(t).noteOff(c, n);
 862 |     }
 863 |     else {
 864 |       if (v > 0) this.tick(v).noteOff(c);
 865 |     }
 866 |     return this;
 867 |   };
 868 |   JZZ.lib.copyMidiHelpers(MTrk);
 869 | 
 870 |   function Event(t, s, d, off) {
 871 |     var midi;
 872 |     if (s.charCodeAt(0) == 0xff) {
 873 |       midi = JZZ.MIDI.smf(s.charCodeAt(1), d);
 874 |     }
 875 |     else {
 876 |       var a = [s.charCodeAt(0)];
 877 |       for (var i = 0; i < d.length; i++) a.push(d.charCodeAt(i));
 878 |       midi = JZZ.MIDI(a);
 879 |     }
 880 |     if (typeof off != 'undefined') midi._off = off;
 881 |     midi.tt = t;
 882 |     return midi;
 883 |   }
 884 | 
 885 |   function Player() {
 886 |     var self = new JZZ.Widget();
 887 |     self._info.name = 'MIDI Player';
 888 |     self._info.manufacturer = 'Jazz-Soft';
 889 |     self._info.version = _ver;
 890 |     self.playing = false;
 891 |     self._loop = 0;
 892 |     self._data = [];
 893 |     self._hdr = [];
 894 |     self._pos = 0;
 895 |     self._tick = (function(x) { return function(){ x.tick(); }; })(self);
 896 |     for (var k in Player.prototype) if (Player.prototype.hasOwnProperty(k)) self[k] = Player.prototype[k];
 897 |     return self;
 898 |   }
 899 |   Player.prototype.onEnd = function() {};
 900 |   Player.prototype.loop = function(n) {
 901 |     if (n == parseInt(n) && n > 0) this._loop = n;
 902 |     else this._loop = n ? -1 : 0;
 903 |   };
 904 |   Player.prototype.play = function() {
 905 |     this.event = undefined;
 906 |     this.playing = true;
 907 |     this.paused = false;
 908 |     this._ptr = 0;
 909 |     this._pos = 0;
 910 |     this._p0 = 0;
 911 |     this._t0 = _now();
 912 |     this._list = this._hdr;
 913 |     this.tick();
 914 |   };
 915 |   Player.prototype.stop = function() {
 916 |     this._pos = 0;
 917 |     this.playing = false;
 918 |     this.event = 'stop';
 919 |     this.paused = undefined;
 920 |   };
 921 |   Player.prototype.pause = function() {
 922 |     this.event = 'pause';
 923 |   };
 924 |   Player.prototype.resume = function() {
 925 |     if (this.playing) return;
 926 |     if (this.paused) {
 927 |       this.event = undefined;
 928 |       this._t0 = _now();
 929 |       this.playing = true;
 930 |       this.paused = false;
 931 |       this.tick();
 932 |     }
 933 |     else this.play();
 934 |   };
 935 |   Player.prototype.sndOff = function() {
 936 |     var c;
 937 |     for (c = 0; c < 16; c++) this._emit(JZZ.MIDI.allSoundOff(c));
 938 |     for (c = 0; c < 16; c++) this._emit(JZZ.MIDI.resetAllControllers(c));
 939 |   };
 940 |   function _filter(e) { this._receive(e); }
 941 |   Player.prototype._filter = _filter;
 942 |   Player.prototype.filter = function(f) {
 943 |     this._filter = f instanceof Function ? f : _filter;
 944 |   };
 945 |   Player.prototype._receive = function(e) {
 946 |     if (e.isTempo() && this.ppqn) {
 947 |       this._mul = this.ppqn * (e.isMidi2 ? 100000.0 : 1000.0) / (e.getTempo() || 1);
 948 |       this.mul = this._mul * this._speed;
 949 |       this._t0 = _now();
 950 |       this._p0 = this._pos;
 951 |     }
 952 |     this._emit(e);
 953 |   };
 954 |   Player.prototype.tick = function() {
 955 |     var t = _now();
 956 |     var e;
 957 |     this._pos = this._p0 + (t - this._t0) * this.mul;
 958 |     for(; this._ptr < this._list.length; this._ptr++) {
 959 |       e = this._list[this._ptr];
 960 |       if (e.tt > this._pos) break;
 961 |       this._filter(e);
 962 |     }
 963 |     if (this._ptr >= this._list.length) {
 964 |       if (this._list == this._hdr) {
 965 |         this._list = this._data;
 966 |         this._ptr = 0;
 967 |         this._p0 = 0;
 968 |         this._t0 = t;
 969 |       }
 970 |       else {
 971 |         if (this._loop && this._loop != -1) this._loop--;
 972 |         if (this._loop) {
 973 |           this._ptr = 0;
 974 |           this._p0 = 0;
 975 |           this._t0 = t;
 976 |         }
 977 |         else this.stop();
 978 |         this.onEnd();
 979 |       }
 980 |     }
 981 |     if (this.event == 'stop') {
 982 |       this.playing = false;
 983 |       this.paused = false;
 984 |       this._pos = 0;
 985 |       this._ptr = 0;
 986 |       this.sndOff();
 987 |       this.event = undefined;
 988 |     }
 989 |     if (this.event == 'pause') {
 990 |       this.playing = false;
 991 |       this.paused = true;
 992 |       if (this._pos >= this._duration) this._pos = this._duration - 1;
 993 |       this._p0 = this._pos;
 994 |       this.sndOff();
 995 |       this.event = undefined;
 996 |     }
 997 |     if (this.playing) JZZ.lib.schedule(this._tick);
 998 |   };
 999 |   Player.prototype.trim = function() {
1000 |     var i, j, e;
1001 |     var data = [];
1002 |     j = 0;
1003 |     for (i = 0; i < this._data.length; i++) {
1004 |       e = this._data[i];
1005 |       if (e.length || e.ff == 1 || e.ff == 5) {
1006 |         for (; j <= i; j++) data.push(this._data[j]);
1007 |       }
1008 |     }
1009 |     var dt = (i ? this._data[i - 1].tt : 0) - (j ? this._data[j - 1].tt : 0);
1010 |     this._data = data;
1011 |     this._timing();
1012 |     return dt;
1013 |   };
1014 |   Player.prototype._timing = function() {
1015 |     var i, m, t, e;
1016 |     this._duration = this._data.length ? this._data[this._data.length - 1].tt : 0;
1017 |     this._ttt = [];
1018 |     if (this.ppqn) {
1019 |       this._mul = this.ppqn / 500.0; // 120 bpm
1020 |       m = this._mul;
1021 |       for (i = 0; i < this._hdr.length; i++) {
1022 |         e = this._hdr[i];
1023 |         if (e.isTempo()) m = this.ppqn * 100000.0 / (e.getTempo() || 1);
1024 |       }
1025 |       t = 0;
1026 |       this._durationMS = 0;
1027 |       this._ttt.push({ t: 0, m: m, ms: 0 });
1028 |       for (i = 0; i < this._data.length; i++) {
1029 |         e = this._data[i];
1030 |         if (e.isTempo()) {
1031 |           this._durationMS += (e.tt - t) / m;
1032 |           t = e.tt;
1033 |           m = this.ppqn * (e.isMidi2 ? 100000.0 : 1000.0) / (e.getTempo() || 1);
1034 |           this._ttt.push({ t: t, m: m, ms: this._durationMS });
1035 |         }
1036 |       }
1037 |       this._durationMS += (this._duration - t) / m;
1038 |     }
1039 |     else {
1040 |       this._mul = this.fps * this.ppf / 1000.0; // 1s = fps*ppf ticks
1041 |       this._ttt.push({ t: 0, m: this._mul, ms: 0 });
1042 |       this._durationMS = this._duration / this._mul;
1043 |     }
1044 |     this._speed = 1;
1045 |     this.mul = this._mul;
1046 |     this._ttt.push({ t: this._duration, m: 0, ms: this._durationMS });
1047 |     if (!this._durationMS) this._durationMS = 1;
1048 |   };
1049 |   Player.prototype.speed = function(x) {
1050 |     if (typeof x != 'undefined') {
1051 |       if (isNaN(parseFloat(x)) || x <= 0) x = 1;
1052 |       this._speed = x;
1053 |       this.mul = this._mul * this._speed;
1054 |       this._p0 = this._pos - (_now() - this._t0) * this.mul;
1055 |     }
1056 |     return this._speed;
1057 |   };
1058 |   Player.prototype.type = function() { return this._type; };
1059 |   Player.prototype.tracks = function() { return this._tracks; };
1060 |   Player.prototype.duration = function() { return this._duration; };
1061 |   Player.prototype.durationMS = function() { return this._durationMS; };
1062 |   Player.prototype.position = function() { return this._pos; };
1063 |   Player.prototype.positionMS = function() { return this.tick2ms(this._pos); };
1064 |   Player.prototype.jump = function(t) {
1065 |     if (isNaN(parseFloat(t))) _error('Not a number: ' + t);
1066 |     if (t < 0) t = 0.0;
1067 |     if (t >= this._duration) t = this._duration - 1;
1068 |     this._goto(t);
1069 |   };
1070 |   Player.prototype.jumpMS = function(ms) {
1071 |     if (isNaN(parseFloat(ms))) _error('Not a number: ' + ms);
1072 |     if (ms < 0) ms = 0.0;
1073 |     if (ms >= this._durationMS) ms = this._durationMS - 1;
1074 |     this._goto(this._ms2t(ms));
1075 |   };
1076 |   Player.prototype._t2ms = function(t) {
1077 |     if (!t) return 0.0;
1078 |     var i;
1079 |     for (i = 0; this._ttt[i].t < t; i++) ;
1080 |     i--;
1081 |     return this._ttt[i].ms + (t - this._ttt[i].t) / this._ttt[i].m;
1082 |   };
1083 |   Player.prototype._ms2t = function(ms) {
1084 |     if (!ms) return 0.0;
1085 |     var i;
1086 |     for (i = 0; this._ttt[i].ms < ms; i++) ;
1087 |     i--;
1088 |     return this._ttt[i].t + (ms - this._ttt[i].ms) * this._ttt[i].m;
1089 |   };
1090 |   Player.prototype._goto = function(t) {
1091 |     this._pos = t;
1092 |     if (!this.playing) this.paused = !!t;
1093 |     this._toPos();
1094 |     if (this.playing) this.sndOff();
1095 |   };
1096 |   Player.prototype._toPos = function() {
1097 |     var i, e;
1098 |     for(i = 0; i < this._hdr.length; i++) {
1099 |       e = this._hdr[i];
1100 |       if (e.isTempo()) this._mul = this.ppqn * 100000.0 / (e.getTempo() || 1);
1101 |     }
1102 |     for(this._ptr = 0; this._ptr < this._data.length; this._ptr++) {
1103 |       e = this._data[this._ptr];
1104 |       if (e.tt >= this._pos) break;
1105 |       if (e.isTempo() && this.ppqn) this._mul = this.ppqn * (e.isMidi2 ? 100000.0 : 1000.0) / (e.getTempo() || 1);
1106 |     }
1107 |     this._list = this._data;
1108 |     this.mul = this._mul * this._speed;
1109 |     this._t0 = _now();
1110 |     this._p0 = this._pos;
1111 |   };
1112 |   Player.prototype.tick2ms = function(t) {
1113 |     if (isNaN(parseFloat(t))) _error('Not a number: ' + t);
1114 |     if (t <= 0) return 0.0;
1115 |     if (t >= this._duration) return this._durationMS;
1116 |     return this._t2ms(t);
1117 |   };
1118 |   Player.prototype.ms2tick = function(t) {
1119 |     if (isNaN(parseFloat(t))) _error('Not a number: ' + t);
1120 |     if (t <= 0) return 0.0;
1121 |     if (t >= this._durationMS) return this._duration;
1122 |     return this._ms2t(t);
1123 |   };
1124 |   JZZ.MIDI.SMF = SMF;
1125 | 
1126 |   function _not_a_syx() { _error('Not a SYX file'); }
1127 | 
1128 |   function SYX(arg) {
1129 |     var self = this instanceof SYX ? this : new SYX();
1130 |     self._orig = self;
1131 |     if (typeof arg != 'undefined') {
1132 |       if (arg instanceof SMF) {
1133 |         self.copy(arg.player()._data);
1134 |         return self;
1135 |       }
1136 |       if (arg instanceof SYX) {
1137 |         self.copy(arg);
1138 |         return self;
1139 |       }
1140 |       try {
1141 |         if (arg instanceof ArrayBuffer) {
1142 |           arg = _u8a2s(new Uint8Array(arg));
1143 |         }
1144 |       }
1145 |       catch (err) {/**/}
1146 |       try {
1147 |         if (arg instanceof Uint8Array || arg instanceof Int8Array) {
1148 |           arg = _u8a2s(new Uint8Array(arg));
1149 |         }
1150 |       }
1151 |       catch (err) {/**/}
1152 |       try {
1153 |         /* istanbul ignore next */
1154 |         if (arg instanceof Buffer) {
1155 |           arg = arg.toString('binary');
1156 |         }
1157 |       }
1158 |       catch (err) {/**/}
1159 |       if (typeof arg != 'string') {
1160 |         arg = String.fromCharCode.apply(null, arg);
1161 |       }
1162 |       var x;
1163 |       var msg = [];
1164 |       var i = 0;
1165 |       var off = 0;
1166 |       if (!arg.length) _error('Empty file');
1167 |       while (i < arg.length) {
1168 |         if (arg.charCodeAt(i) != 0xf0) _not_a_syx();
1169 |         while (i < arg.length) {
1170 |           x = arg.charCodeAt(i);
1171 |           msg.push(x);
1172 |           if (x == 0xf7) {
1173 |             msg = JZZ.MIDI(msg);
1174 |             msg._off = off;
1175 |             self.push(JZZ.MIDI(msg));
1176 |             msg = [];
1177 |             off = i + 1;
1178 |             break;
1179 |           }
1180 |           i++;
1181 |         }
1182 |         i++;
1183 |       }
1184 |       if (msg.length) _not_a_syx();
1185 |       return self;
1186 |     }
1187 |     return self;
1188 |   }
1189 |   SYX.version = function() { return _ver; };
1190 |   SYX.prototype = [];
1191 |   SYX.prototype.constructor = SYX;
1192 | 
1193 |   SYX.prototype.copy = function(data) {
1194 |     for (var i = 0; i < data.length; i++) if (!data[i].isSMF()) {
1195 |       if (data[i].isFullSysEx()) this.push(JZZ.MIDI(data[i]));
1196 |       else _not_a_syx();
1197 |     }
1198 |   };
1199 |   SYX.prototype.validate = function() { return []; };
1200 |   SYX.prototype.dump = function() {
1201 |     var i, j, s = '';
1202 |     for (i = 0; i < this.length; i++) for (j = 0; j < this[i].length; j++) s += String.fromCharCode(this[i][j]);
1203 |     return s;
1204 |   };
1205 |   SYX.prototype.toBuffer = function() {
1206 |     return Buffer.from(this.dump(), 'binary');
1207 |   };
1208 |   SYX.prototype.toUint8Array = function() {
1209 |     var str = this.dump();
1210 |     var buf = new ArrayBuffer(str.length);
1211 |     var arr = new Uint8Array(buf);
1212 |     for (var i = 0; i < str.length; i++) arr[i] = str.charCodeAt(i);
1213 |     return arr;
1214 |   };
1215 |   SYX.prototype.toArrayBuffer = function() {
1216 |     return this.toUint8Array().buffer;
1217 |   };
1218 |   SYX.prototype.toInt8Array = function() {
1219 |     return new Int8Array(this.toArrayBuffer());
1220 |   };
1221 |   SYX.prototype.toString = function() {
1222 |     var i;
1223 |     var a = ['SYX:'];
1224 |     for (i = 0; i < this.length; i++) {
1225 |       a.push(this[i].toString());
1226 |     }
1227 |     return a.join('\n  ');
1228 |   };
1229 |   SYX.prototype.annotate = function() {
1230 |     var ctxt = JZZ.Context();
1231 |     for (var i = 0; i < this.length; i++) {
1232 |       if (this[i].lbl) this[i].lbl = undefined;
1233 |       ctxt._read(this[i]);
1234 |     }
1235 |     return this;
1236 |   };
1237 |   SYX.prototype.player = function() {
1238 |     var pl = new Player();
1239 |     pl.ppqn = 96;
1240 |     var i;
1241 |     for (i = 0; i < this.length; i++) {
1242 |       var e = JZZ.MIDI(this[i]);
1243 |       e.tt = 0;
1244 |       pl._data.push(e);
1245 |     }
1246 |     pl._type = 'syx';
1247 |     pl._tracks = 1;
1248 |     pl._timing();
1249 |     pl.sndOff = function() {};
1250 |     return pl;
1251 |   };
1252 | 
1253 |   SYX.prototype._sxid = 0x7f;
1254 |   SYX.prototype._image = function() {
1255 |     var F = function() {}; F.prototype = this._orig;
1256 |     var img = new F();
1257 |     img._ch = this._ch;
1258 |     img._sxid = this._sxid;
1259 |     return img;
1260 |   };
1261 |   SYX.prototype.add = function(msg) {
1262 |     msg = JZZ.MIDI(msg);
1263 |     if (msg.isFullSysEx()) this._orig.push(msg);
1264 |     return this;
1265 |   };
1266 |   SYX.prototype.send = function(msg) { return this.add(msg); };
1267 |   SYX.prototype.sxId = function(id) {
1268 |     if (typeof id == 'undefined') id = SYX.prototype._sxid;
1269 |     if (id == this._sxid) return this;
1270 |     if (id != parseInt(id) || id < 0 || id > 0x7f) throw RangeError('Bad MIDI value: ' + id);
1271 |     var img = this._image();
1272 |     img._sxid = id;
1273 |     return img;
1274 |   };
1275 |   SYX.prototype.ch = function(c) {
1276 |     if (c == this._ch || typeof c == 'undefined' && typeof this._ch == 'undefined') return this;
1277 |     if (typeof c != 'undefined') {
1278 |       if (c != parseInt(c) || c < 0 || c > 15) throw RangeError('Bad channel value: ' + c  + ' (must be from 0 to 15)');
1279 |     }
1280 |     var img = this._image();
1281 |     img._ch = c;
1282 |     return img;
1283 |   };
1284 |   JZZ.lib.copyMidiHelpers(SYX);
1285 | 
1286 |   JZZ.MIDI.SYX = SYX;
1287 | 
1288 |   function Clip(arg) {
1289 |     var self = this instanceof Clip ? this : new Clip();
1290 |     self._orig = self;
1291 |     self._tick = 0;
1292 |     self.ppqn = 96;
1293 |     if (typeof arg != 'undefined') {
1294 |       if (arg instanceof Clip) {
1295 |         _copyClip(self, arg);
1296 |         return self;
1297 |       }
1298 |       try {
1299 |         if (arg instanceof ArrayBuffer) {
1300 |           arg = _u8a2s(new Uint8Array(arg));
1301 |         }
1302 |       }
1303 |       catch (err) {/**/}
1304 |       try {
1305 |         if (arg instanceof Uint8Array || arg instanceof Int8Array) {
1306 |           arg = _u8a2s(new Uint8Array(arg));
1307 |         }
1308 |       }
1309 |       catch (err) {/**/}
1310 |       try {
1311 |         /* istanbul ignore next */
1312 |         if (arg instanceof Buffer) {
1313 |           arg = arg.toString('binary');
1314 |         }
1315 |       }
1316 |       catch (err) {/**/}
1317 |       if (typeof arg != 'string') {
1318 |         arg = String.fromCharCode.apply(null, arg);
1319 |       }
1320 |       _loadClip(self, arg, 0);
1321 |       return self;
1322 |     }
1323 |     if (!self.header) self.header = new ClipHdr();
1324 |     if (!self.length) {
1325 |       var msg = JZZ.UMP.umpEndClip();
1326 |       msg.tt = 0;
1327 |       self.push(msg);
1328 |     }
1329 |     return self;
1330 |   }
1331 |   Clip.version = function() { return _ver; };
1332 |   Clip.prototype = [];
1333 |   Clip.prototype.constructor = Clip;
1334 |   Clip.prototype._sxid = 0x7f;
1335 |   var SMF2CLIP = 'SMF2CLIP';
1336 | 
1337 |   Clip.prototype._image = function() {
1338 |     var F = function() {}; F.prototype = this._orig;
1339 |     var img = new F();
1340 |     img._gr = this._gr;
1341 |     img._ch = this._ch;
1342 |     img._sxid = this._sxid;
1343 |     img._tick = this._tick;
1344 |     return img;
1345 |   };
1346 |   Clip.prototype.send = function(msg) { return this.add(this._tick, msg); };
1347 |   Clip.prototype.tick = function(t) {
1348 |     if (t != parseInt(t) || t < 0) throw RangeError('Bad tick value: ' + t);
1349 |     if (!t) return this;
1350 |     var img = this._image();
1351 |     img._tick = this._tick + t;
1352 |     return img;
1353 |   };
1354 |   function _ump(msg) {
1355 |     if (!msg || !msg.length) _error('Not a MIDI message');
1356 |     var i;
1357 |     var a = [];
1358 |     try {
1359 |       a.push(JZZ.UMP(msg));
1360 |     }
1361 |     catch (e) {
1362 |       for (i = 0; i < msg.length; i++) {
1363 |         if (!msg[i] || !msg[i].length) _error('Not a MIDI message');
1364 |         a.push(JZZ.UMP(msg[i]));
1365 |       }
1366 |     }
1367 |     return a;
1368 |   }
1369 |   Clip.prototype.add = function(t, msg) {
1370 |     var i, j, d, e;
1371 |     t = parseInt(t);
1372 |     if(isNaN(t) || t < 0) _error('Invalid parameter');
1373 |     var arr = _ump(msg);
1374 |     var self = this;
1375 |     if (this.length) e = this._orig[this._orig.length - 1];
1376 |     if (e && !e.isEndClip()) e = undefined;
1377 |     if (e && e.tt < t) e.tt = t;
1378 |     for (i = 0; i < arr.length; i++) {
1379 |       msg = arr[i];
1380 |       if (msg.isStartClip() || msg.isEndClip()) continue;
1381 |       if (msg.isDelta()) {
1382 |         d = msg.getDelta();
1383 |         t += d;
1384 |         if (e && e.tt < t) e.tt = t;
1385 |         self = self.tick(msg.getDelta());
1386 |         continue;
1387 |       }
1388 |       msg.tt = t;
1389 |       for (j = 0; j < this._orig.length; j++) if (this._orig[j].tt > t || this._orig[j] == e) break;
1390 |       this._orig.splice(j, 0, msg);
1391 |     }
1392 |     return self;
1393 |   };
1394 |   Clip.prototype.sxId = function(id) {
1395 |     if (typeof id == 'undefined') id = Clip.prototype._sxid;
1396 |     if (id == this._sxid) return this;
1397 |     if (id != parseInt(id) || id < 0 || id > 0x7f) throw RangeError('Bad MIDI value: ' + id);
1398 |     var img = this._image();
1399 |     img._sxid = id;
1400 |     return img;
1401 |   };
1402 |   Clip.prototype.gr = function(g) {
1403 |     if (g == this._gr || typeof g == 'undefined' && typeof this._gr == 'undefined') return this;
1404 |     if (typeof g != 'undefined') {
1405 |       if (g != parseInt(g) || g < 0 || g > 15) throw RangeError('Bad channel value: ' + g  + ' (must be from 0 to 15)');
1406 |     }
1407 |     var img = this._image();
1408 |     img._gr = g;
1409 |     return img;
1410 |   };
1411 |   Clip.prototype.ch = function(c) {
1412 |     if (c == this._ch || typeof c == 'undefined' && typeof this._ch == 'undefined') return this;
1413 |     if (typeof c != 'undefined') {
1414 |       if (c != parseInt(c) || c < 0 || c > 15) throw RangeError('Bad channel value: ' + c  + ' (must be from 0 to 15)');
1415 |     }
1416 |     var img = this._image();
1417 |     img._ch = c;
1418 |     return img;
1419 |   };
1420 | 
1421 |   function ClipHdr() {
1422 |     this._orig = this;
1423 |     this._tick = 0;
1424 |   }
1425 |   ClipHdr.prototype = [];
1426 |   ClipHdr.prototype.constructor = ClipHdr;
1427 |   ClipHdr.prototype._image = Clip.prototype._image;
1428 |   ClipHdr.prototype.send = Clip.prototype.send;
1429 |   ClipHdr.prototype.tick = Clip.prototype.tick;
1430 |   ClipHdr.prototype.gr = Clip.prototype.gr;
1431 |   ClipHdr.prototype.ch = Clip.prototype.ch;
1432 |   ClipHdr.prototype.sxId = Clip.prototype.sxId;
1433 |   ClipHdr.prototype.add = Clip.prototype.add;
1434 | 
1435 |   function _copyClip(clip, x) {
1436 |     var i, m;
1437 |     clip.length = 0;
1438 |     clip.header = new ClipHdr();
1439 |     clip.ppqn = x.ppqn;
1440 |     for (i = 0; i < x.header.length; i++) {
1441 |       m = new JZZ.UMP(x.header[i]);
1442 |       m.tt = x.header[i].tt;
1443 |       clip.header.push(m);
1444 |     }
1445 |     for (i = 0; i < x.length; i++) {
1446 |       m = new JZZ.UMP(x[i]);
1447 |       m.tt = x[i].tt;
1448 |       clip.push(m);
1449 |     }
1450 |   }
1451 |   function _loadClip(clip, s, off) {
1452 |     if (!s.length) _error('Empty clip');
1453 |     if (s.substring(0, 8) != SMF2CLIP) {
1454 |       var z = s.indexOf(SMF2CLIP);
1455 |       if (z != -1) {
1456 |         off += z;
1457 |         clip._complain(off, 'Extra leading characters', off);
1458 |       }
1459 |       else _error('Not a clip');
1460 |     }
1461 |     off += 8;
1462 |     var a, i, m, t, len, prev;
1463 |     clip.length = 0;
1464 |     clip.header = new ClipHdr();
1465 |     clip.ppqn = -1;
1466 |     var inHdr = true;
1467 |     var ended = false;
1468 |     var tt = 0;
1469 |     while (off < s.length) {
1470 |       t = s.charCodeAt(off) >> 4;
1471 |       len = [4, 4, 4, 8, 8, 16, 4, 4, 8, 8, 8, 12, 12, 16, 16, 16][t];
1472 |       a = [];
1473 |       if (s.length < off + len) {
1474 |         for (i = off; i < s.length; i++) a.push(_hex(s.charCodeAt(i)));
1475 |         clip._complain(off, 'Incomplete message', a.join(' '));
1476 |         off += len;
1477 |         break;
1478 |       }
1479 |       for (i = 0; i < len; i++) a.push(s.charCodeAt(off + i));
1480 |       prev = m;
1481 |       m = JZZ.UMP(a);
1482 |       if (m.isDelta()) {
1483 |         if (prev && prev.isDelta())  clip._complain(off, 'Consequential Delta Ticks message');
1484 |         tt += m.getDelta();
1485 |       }
1486 |       else {
1487 |         m.tt = tt;
1488 |         m.off = off;
1489 |         if (prev && !prev.isDelta()) {
1490 |           clip._complain(off, "Missing Delta Ticks message", m.toString(), tt);
1491 |         }
1492 |         if (inHdr) {
1493 |           if (m.isStartClip()) {
1494 |             tt = 0;
1495 |             inHdr = false;
1496 |           }
1497 |           else if (m.isTicksPQN()) {
1498 |             if (clip.ppqn != -1) clip._complain(off, 'Multiple Ticks PQN message');
1499 |             clip.ppqn = m.getTicksPQN();
1500 |             if (!clip.ppqn) {
1501 |               clip._complain(off, 'Bad Ticks PQN value: 0');
1502 |               clip.ppqn = 96;
1503 |             }
1504 |           }
1505 |           else if (m.isEndClip()) {
1506 |             clip._complain(off, 'Unexpected End of Clip message');
1507 |           }
1508 |           else clip.header.push(m);
1509 |         }
1510 |         else {
1511 |           if (m.isStartClip()) {
1512 |             clip._complain(off, 'Repeated Start of Clip message');
1513 |           }
1514 |           else if (m.isEndClip()) {
1515 |             if (ended) clip._complain(off, 'Repeated End of Clip message');
1516 |             ended = true;
1517 |           }
1518 |           else clip.push(m);
1519 |         }
1520 |       }
1521 |       off += len;
1522 |     }
1523 |     m = JZZ.UMP.umpEndClip();
1524 |     m.tt = tt;
1525 |     clip.push(m);
1526 |     if (clip.ppqn == -1) {
1527 |       clip._complain(off, 'Missing Ticks PQN message');
1528 |       clip.ppqn = 96;
1529 |     }
1530 |     if (inHdr) clip._complain(off, 'No Start of Clip message');
1531 |     else if (!ended) clip._complain(off, 'No End of Clip message');
1532 |   }
1533 |   Clip.prototype._complain = function(off, msg, data, tick) {
1534 |     if (!this._warn) this._warn = [];
1535 |     var w = { off: off, msg: msg, data: data };
1536 |     if (typeof tick != 'undefined') w.tick = tick;
1537 |     this._warn.push(w);
1538 |   };
1539 |   function _validate_clip(clip) {
1540 |     var i, k, d, m;
1541 |     var p = {};
1542 |     for (i = 0; i < clip.length; i++) {
1543 |       m = clip[i];
1544 |       k = undefined;
1545 |       if (m.isFlex()) {
1546 |         k = (m[0] & 0xf) + (m[1] & 0x3f) * 16;
1547 |         k = 'f' + k;
1548 |         d = m[1] >> 6;
1549 |       }
1550 |       if (m.isData()) {
1551 |         k = 'd' + (m[0] & 0xf);
1552 |         d = (m[1] >> 4) & 3;
1553 |       }
1554 |       if (m.isSX()) {
1555 |         k = 's' + (m[0] & 0xf);
1556 |         d = (m[1] >> 4) & 3;
1557 |       }
1558 |       if (k) {
1559 |         if (p[k]) {
1560 |           if (d == 0 || d == 1) clip._complain(p[k].off, 'Missing series end', p[k].toString(), p[k].tt);
1561 |         }
1562 |         else {
1563 |           if (d == 2 || d == 3) clip._complain(m.off, 'Missing series start', m.toString(), m.tt);
1564 |         }
1565 |         p[k] = (d == 0 || d == 3) ? undefined : m;
1566 |       }
1567 |     }
1568 |     d = Object.keys(p);
1569 |     for (i = 0; i < d.length; i++) {
1570 |       m = p[d[i]];
1571 |       if (m) clip._complain(m.off, 'Missing series end', m.toString(), m.tt);
1572 |     }
1573 |   }
1574 |   Clip.prototype.validate = function() {
1575 |     var i;
1576 |     var w = [];
1577 |     _validate_clip(this);
1578 |     if (this._warn) for (i = 0; i < this._warn.length; i++) w.push(Warn(this._warn[i]));
1579 |     if (w.length) {
1580 |       for (i = 0; i < w.length; i++) w[i] = Warn(w[i]);
1581 |       w.sort(function(a, b) {
1582 |         return (a.off || 0) - (b.off || 0) || (a.tick || 0) - (b.tick || 0);
1583 |       });
1584 |       return w;
1585 |     }
1586 |   };
1587 | 
1588 |   Clip.prototype.dump = function() {
1589 |     var i, tt;
1590 |     var a = [SMF2CLIP];
1591 |     a.push(JZZ.UMP.umpDelta(0).dump());
1592 |     a.push(JZZ.UMP.umpTicksPQN(this.ppqn).dump());
1593 |     tt = 0;
1594 |     for (i = 0; i < this.header.length; i++) {
1595 |       a.push(JZZ.UMP.umpDelta(this.header[i].tt - tt).dump());
1596 |       a.push(this.header[i].dump());
1597 |       tt = this.header[i].tt;
1598 |     }
1599 |     a.push(JZZ.UMP.umpDelta(0).dump());
1600 |     a.push(JZZ.UMP.umpStartClip().dump());
1601 |     tt = 0;
1602 |     for (i = 0; i < this.length; i++) {
1603 |       a.push(JZZ.UMP.umpDelta(this[i].tt - tt).dump());
1604 |       a.push(this[i].dump());
1605 |       tt = this[i].tt;
1606 |     }
1607 |     return a.join('');
1608 |   };
1609 |   Clip.prototype.toBuffer = function() {
1610 |     return Buffer.from(this.dump(), 'binary');
1611 |   };
1612 |   Clip.prototype.toUint8Array = function() {
1613 |     var str = this.dump();
1614 |     var buf = new ArrayBuffer(str.length);
1615 |     var arr = new Uint8Array(buf);
1616 |     for (var i = 0; i < str.length; i++) arr[i] = str.charCodeAt(i);
1617 |     return arr;
1618 |   };
1619 |   Clip.prototype.toArrayBuffer = function() {
1620 |     return this.toUint8Array().buffer;
1621 |   };
1622 |   Clip.prototype.toInt8Array = function() {
1623 |     return new Int8Array(this.toArrayBuffer());
1624 |   };
1625 |   Clip.prototype.toString = function() {
1626 |     var i;
1627 |     var a = [SMF2CLIP, 'Header'];
1628 |     a.push('  0: ' + JZZ.UMP.umpTicksPQN(this.ppqn));
1629 |     for (i = 0; i < this.header.length; i++) a.push('  ' + this.header[i].tt + ': ' + this.header[i]);
1630 |     a.push('Data', '  0: ' + JZZ.UMP.umpStartClip());
1631 |     for (i = 0; i < this.length; i++) a.push('  ' + this[i].tt + ': ' + this[i]);
1632 |     return a.join('\n');
1633 |   };
1634 |   Clip.prototype.annotate = function() {
1635 |     var i, ctxt;
1636 |     ctxt = JZZ.Context();
1637 |     for (i = 0; i < this.header.length; i++) {
1638 |       if (this.header[i].lbl) this.header[i].lbl = undefined;
1639 |       ctxt._read(this.header[i]);
1640 |     }
1641 |     ctxt = JZZ.Context();
1642 |     for (i = 0; i < this.length; i++) {
1643 |       if (this[i].lbl) this[i].lbl = undefined;
1644 |       ctxt._read(this[i]);
1645 |     }
1646 |     return this;
1647 |   };
1648 |   Clip.prototype.player = function() {
1649 |     var pl = new Player();
1650 |     pl.ppqn = this.ppqn;
1651 |     var i, e;
1652 |     for (i = 0; i < this.header.length; i++) {
1653 |       e = JZZ.MIDI2(this.header[i]);
1654 |       pl._hdr.push(e);
1655 |     }
1656 |     for (i = 0; i < this.length; i++) {
1657 |       e = JZZ.MIDI2(this[i]);
1658 |       pl._data.push(e);
1659 |     }
1660 |     pl._type = 'clip';
1661 |     pl._tracks = 1;
1662 |     pl._timing();
1663 |     pl.sndOff = function() {};
1664 |     return pl;
1665 |   };
1666 | 
1667 |   JZZ.lib.copyMidi2Helpers(Clip);
1668 |   JZZ.lib.copyMidi2Helpers(ClipHdr);
1669 | 
1670 |   JZZ.MIDI.Clip = Clip;
1671 | 
1672 | });
1673 | 


--------------------------------------------------------------------------------