├── .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 |
16 | NOTE: For better sound quality, you may want to install the
17 | Jazz-Plugin and browser extension from
18 | Chrome Web Store
19 | or Mozilla Add-ons
20 | or Apple App Store.
21 |
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 | 
7 | 
8 | 
9 | 
10 | 
11 | 
12 | 
13 | [](https://www.npmjs.com/package/jzz-midi-smf)
14 | [](https://www.npmjs.com/package/jzz-midi-smf)
15 | [](https://github.com/jazz-soft/JZZ-midi-SMF/actions)
16 | [](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 | [](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 |
--------------------------------------------------------------------------------