├── .editorconfig
├── .eslintignore
├── .eslintrc.js
├── .github
└── workflows
│ ├── lint.js.yml
│ └── node.js.yml
├── .gitignore
├── .npmignore
├── .nvmrc
├── .travis.yml
├── LICENSE
├── README.md
├── browser
└── midiwriter.js
├── examples
├── chopin-prelude-e-minor.js
├── hot-cross-buns.js
├── mauro.giuliani-op.47-main-theme.js
├── notes-by-start-tick.js
└── zelda-main-theme.js
├── jsdoc.json
├── package-lock.json
├── package.json
├── postinstall.js
├── rollup.config.js
├── runkit.js
├── src
├── abstract-event.ts
├── chunks
│ ├── chunk.ts
│ ├── header.ts
│ └── track.ts
├── constants.ts
├── main.ts
├── meta-events
│ ├── copyright-event.ts
│ ├── cue-point-event.ts
│ ├── end-track-event.ts
│ ├── instrument-name-event.ts
│ ├── key-signature-event.ts
│ ├── lyric-event.ts
│ ├── marker-event.ts
│ ├── meta-event.ts
│ ├── tempo-event.ts
│ ├── text-event.ts
│ ├── time-signature-event.ts
│ └── track-name-event.ts
├── midi-events
│ ├── controller-change-event.ts
│ ├── midi-event.ts
│ ├── note-event.ts
│ ├── note-off-event.ts
│ ├── note-on-event.ts
│ ├── pitch-bend-event.ts
│ └── program-change-event.ts
├── utils.ts
├── vexflow.ts
└── writer.ts
├── test
├── main.js
├── vexflow.js
└── writer.js
├── tsconfig.json
└── typedoc.json
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | end_of_line = lf
7 | indent_size = 4
8 | indent_style = tab
9 | insert_final_newline = true
10 | trim_trailing_whitespace = true
11 |
12 | [*.json]
13 | insert_final_newline = false
14 | indent_size = 2
15 | indent_style = space
16 |
17 | [*.md]
18 | indent_size = 2
19 | indent_style = space
20 | trim_trailing_whitespace = false
21 |
22 | [*.yml]
23 | indent_size = 2
24 | indent_style = space
25 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | **/__tests__/**
2 | **/coverage/**
3 | **/node_modules/**
4 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "env": {
3 | "browser": true,
4 | "es6": true,
5 | "node": true
6 | },
7 | "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
8 | "parser": '@typescript-eslint/parser',
9 | "plugins": ['@typescript-eslint'],
10 | "globals": {
11 | "Atomics": "readonly",
12 | "SharedArrayBuffer": "readonly"
13 | },
14 | "parserOptions": {
15 | "ecmaVersion": 2020,
16 | "sourceType": "module"
17 | }
18 | };
--------------------------------------------------------------------------------
/.github/workflows/lint.js.yml:
--------------------------------------------------------------------------------
1 | # This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
3 |
4 | name: Lint
5 |
6 | on:
7 | push:
8 | branches: [ master ]
9 | pull_request:
10 | branches: [ master ]
11 |
12 | jobs:
13 | build:
14 |
15 | runs-on: ubuntu-latest
16 |
17 | strategy:
18 | matrix:
19 | node-version: [16.x]
20 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/
21 |
22 | steps:
23 | - uses: actions/checkout@v2
24 | - name: Use Node.js ${{ matrix.node-version }}
25 | uses: actions/setup-node@v2
26 | with:
27 | node-version: ${{ matrix.node-version }}
28 | cache: 'npm'
29 | - run: npm ci
30 | - run: npm run lint:js
31 |
--------------------------------------------------------------------------------
/.github/workflows/node.js.yml:
--------------------------------------------------------------------------------
1 | # This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
3 |
4 | name: Tests
5 |
6 | on:
7 | push:
8 | branches: [ master ]
9 | pull_request:
10 | branches: [ master ]
11 |
12 | jobs:
13 | build:
14 |
15 | runs-on: ubuntu-latest
16 |
17 | strategy:
18 | matrix:
19 | node-version: [16.x]
20 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/
21 |
22 | steps:
23 | - uses: actions/checkout@v2
24 | - name: Use Node.js ${{ matrix.node-version }}
25 | uses: actions/setup-node@v2
26 | with:
27 | node-version: ${{ matrix.node-version }}
28 | cache: 'npm'
29 | - run: npm ci
30 | - run: npm test
31 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | npm-debug.log
3 | node_modules
4 | build
5 | browser/types
6 | index.html
7 | index.js
8 | yarn.lock
9 |
10 | # test artifacts
11 | test.mid.mid
12 | .nyc_output/
13 |
14 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | docs
2 | src
3 | .nyc_output
4 | .github
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | v18.14.2
2 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "stable"
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Garrett Grimm
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ♬ MidiWriterJS
2 | ===============
3 | [](https://www.npmjs.com/package/midi-writer-js)
4 | 
5 | 
6 | [](https://npm.runkit.com/midi-writer-js)
7 |
8 | MidiWriterJS is a JavaScript library providing an API for generating expressive multi-track MIDI files.
9 |
10 | Note that the `master` branch is in active development so if you're looking for a tried and true stable version please use the latest release.
11 |
12 | [Source Documentation](https://grimmdude.com/MidiWriterJS/docs/)
13 |
14 | Install
15 | ------------
16 | ```sh
17 | npm install midi-writer-js
18 | ```
19 | Getting Started
20 | ------------
21 |
22 | ```javascript
23 | import MidiWriter from 'midi-writer-js';
24 |
25 | // Start with a new track
26 | const track = new MidiWriter.Track();
27 |
28 | // Define an instrument (optional):
29 | track.addEvent(new MidiWriter.ProgramChangeEvent({instrument: 1}));
30 |
31 | // Add some notes:
32 | const note = new MidiWriter.NoteEvent({pitch: ['C4', 'D4', 'E4'], duration: '4'});
33 | track.addEvent(note);
34 |
35 | // Generate a data URI
36 | const write = new MidiWriter.Writer(track);
37 | console.log(write.dataUri());
38 | ```
39 | Documentation
40 | ------------
41 |
42 | ### `MidiWriter.Track()`
43 |
44 | - `addEvent({event}, mapFunction)`
45 | - `setTempo(tempo)`
46 | - `addText(text)`
47 | - `addCopyright(text)`
48 | - `addTrackName(text)`
49 | - `addInstrumentName(text)`
50 | - `addMarker(text)`
51 | - `addCuePoint(text)`
52 | - `addLyric(text)`
53 | - `setTimeSignature(numerator, denominator)`
54 |
55 | ### `MidiWriter.NoteEvent({options})`
56 |
57 | The MIDI spec defines that each note must have a `NoteOnEvent` and `NoteOffEvent` (or `NoteOnEvent` with zero velocity) event, marking the beginning and end of the sounding note. While it's possible to manually add these events to a track with `Track.addEvent()`, the `NoteEvent` provides a more intuitive interface for doing this with a single, "pseudo" event. Under the hood, the `NoteEvent` event generates the relevant `NoteOnEvent` and `NoteOffEvent` events.
58 |
59 | Each MIDI event has a `delta` property, which is used to define the number of ticks to wait after the previous event. This can be challenging to calculate if you're not necessarily adding events in a serial fashion. Because of this, you can alternatively use the `tick` property to define the exact tick where the event should fall.
60 |
61 | The `NoteEvent` supports these options:
62 |
63 |
64 |
65 |
66 | Name |
67 | Type |
68 | Default |
69 | Description |
70 |
71 |
72 |
73 |
74 | pitch |
75 | string or array |
76 | |
77 | Each pitch can be a string or valid MIDI note code. Format for string is C#4 . Pro tip: You can use the output from tonal functions to build scales, chords, intervals, etc. in this parameter. |
78 |
79 |
80 | duration |
81 | string or array |
82 | |
83 |
84 | How long the note should sound.
85 |
86 | 1 : whole
87 | 2 : half
88 | d2 : dotted half
89 | dd2 : double dotted half
90 | 4 : quarter
91 | 4t : quarter triplet
92 | d4 : dotted quarter
93 | dd4 : double dotted quarter
94 | 8 : eighth
95 | 8t : eighth triplet
96 | d8 : dotted eighth
97 | dd8 : double dotted eighth
98 | 16 : sixteenth
99 | 16t : sixteenth triplet
100 | 32 : thirty-second
101 | 64 : sixty-fourth
102 | Tn : where n is an explicit number of ticks (T128 = 1 beat)
103 |
104 | If an array of durations is passed then the sum of the durations will be used.
105 | |
106 |
107 |
108 | wait |
109 | string or array |
110 | 0 |
111 | How long to wait before sounding note (rest). Takes same values as duration. |
112 |
113 |
114 | sequential |
115 | boolean |
116 | false |
117 | If true then array of pitches will be played sequentially as opposed to simulatanously. |
118 |
119 |
120 | velocity |
121 | number |
122 | 50 |
123 | How loud the note should sound, values 1-100. |
124 |
125 |
126 | repeat |
127 | number |
128 | 1 |
129 | How many times this event should be repeated. |
130 |
131 |
132 | channel |
133 | number |
134 | 1 |
135 | MIDI channel to use. |
136 |
137 |
138 | grace |
139 | string or array |
140 | |
141 | Grace note to be applied to note event. Takes same value format as pitch |
142 |
143 |
144 | tick |
145 | number |
146 | |
147 | Specific tick where this event should be played. If this parameter is supplied then wait is disregarded if also supplied. |
148 |
149 |
150 |
151 |
152 |
153 | ### `MidiWriter.Writer(tracks)`
154 | The `Writer` class provides a few ways to output the file:
155 | - `buildFile()` *Uint8Array*
156 | - `base64()` *string*
157 | - `dataUri()` *string*
158 | - `stdout()` *file stream (cli)*
159 |
160 | ### Hot Cross Buns
161 | Here's an example of how everyone's favorite song "Hot Cross Buns" could be written. Note use of the mapping function passed as the second argument of `addEvent()`. This can be used to apply specific properties to all events. With some
162 | street smarts you could also use it for programmatic crescendos and other property 'animation'.
163 | ```javascript
164 | import MidiWriter from 'midi-writer-js';
165 |
166 | const track = new MidiWriter.Track();
167 |
168 | track.addEvent([
169 | new MidiWriter.NoteEvent({pitch: ['E4','D4'], duration: '4'}),
170 | new MidiWriter.NoteEvent({pitch: ['C4'], duration: '2'}),
171 | new MidiWriter.NoteEvent({pitch: ['E4','D4'], duration: '4'}),
172 | new MidiWriter.NoteEvent({pitch: ['C4'], duration: '2'}),
173 | new MidiWriter.NoteEvent({pitch: ['C4', 'C4', 'C4', 'C4', 'D4', 'D4', 'D4', 'D4'], duration: '8'}),
174 | new MidiWriter.NoteEvent({pitch: ['E4','D4'], duration: '4'}),
175 | new MidiWriter.NoteEvent({pitch: ['C4'], duration: '2'})
176 | ], function(event, index) {
177 | return {sequential: true};
178 | }
179 | );
180 |
181 | const write = new MidiWriter.Writer(track);
182 | console.log(write.dataUri());
183 | ```
184 |
185 | ### VexFlow Integration
186 | MidiWriterJS can export MIDI from VexFlow voices, though this feature is still experimental. Current usage is to use `MidiWriter.VexFlow.trackFromVoice(voice)` to create a MidiWriterJS `Track` object:
187 | ```javascript
188 |
189 | // ...VexFlow code defining notes
190 | const voice = create_4_4_voice().addTickables(notes);
191 |
192 | const vexWriter = new MidiWriter.VexFlow();
193 | const track = vexWriter.trackFromVoice(voice);
194 | const writer = new MidiWriter.Writer([track]);
195 | console.log(writer.dataUri());
196 | ```
197 |
198 |
199 | ## Demos
200 | * [Example with Magenta player](https://codepen.io/dirkk0/pen/rNZLXjZ) by Dirk Krause [@dirkk0](https://github.com/dirkk0)
201 |
--------------------------------------------------------------------------------
/browser/midiwriter.js:
--------------------------------------------------------------------------------
1 | var MidiWriter = (function () {
2 | 'use strict';
3 |
4 | /**
5 | * MIDI file format constants.
6 | * @return {Constants}
7 | */
8 | var Constants = {
9 | VERSION: '3.1.1',
10 | HEADER_CHUNK_TYPE: [0x4d, 0x54, 0x68, 0x64],
11 | HEADER_CHUNK_LENGTH: [0x00, 0x00, 0x00, 0x06],
12 | HEADER_CHUNK_FORMAT0: [0x00, 0x00],
13 | HEADER_CHUNK_FORMAT1: [0x00, 0x01],
14 | HEADER_CHUNK_DIVISION: [0x00, 0x80],
15 | TRACK_CHUNK_TYPE: [0x4d, 0x54, 0x72, 0x6b],
16 | META_EVENT_ID: 0xFF,
17 | META_SMTPE_OFFSET: 0x54
18 | };
19 |
20 | // src/utils.ts
21 | var fillStr = (s, n) => Array(Math.abs(n) + 1).join(s);
22 |
23 | // src/named.ts
24 | function isNamed(src) {
25 | return src !== null && typeof src === "object" && typeof src.name === "string" ? true : false;
26 | }
27 |
28 | // src/pitch.ts
29 | function isPitch(pitch) {
30 | return pitch !== null && typeof pitch === "object" && typeof pitch.step === "number" && typeof pitch.alt === "number" ? true : false;
31 | }
32 | var FIFTHS = [0, 2, 4, -1, 1, 3, 5];
33 | var STEPS_TO_OCTS = FIFTHS.map(
34 | (fifths) => Math.floor(fifths * 7 / 12)
35 | );
36 | function encode(pitch) {
37 | const { step, alt, oct, dir = 1 } = pitch;
38 | const f = FIFTHS[step] + 7 * alt;
39 | if (oct === void 0) {
40 | return [dir * f];
41 | }
42 | const o = oct - STEPS_TO_OCTS[step] - 4 * alt;
43 | return [dir * f, dir * o];
44 | }
45 |
46 | // src/note.ts
47 | var NoNote = { empty: true, name: "", pc: "", acc: "" };
48 | var cache = /* @__PURE__ */ new Map();
49 | var stepToLetter = (step) => "CDEFGAB".charAt(step);
50 | var altToAcc = (alt) => alt < 0 ? fillStr("b", -alt) : fillStr("#", alt);
51 | var accToAlt = (acc) => acc[0] === "b" ? -acc.length : acc.length;
52 | function note(src) {
53 | const stringSrc = JSON.stringify(src);
54 | const cached = cache.get(stringSrc);
55 | if (cached) {
56 | return cached;
57 | }
58 | const value = typeof src === "string" ? parse(src) : isPitch(src) ? note(pitchName(src)) : isNamed(src) ? note(src.name) : NoNote;
59 | cache.set(stringSrc, value);
60 | return value;
61 | }
62 | var REGEX = /^([a-gA-G]?)(#{1,}|b{1,}|x{1,}|)(-?\d*)\s*(.*)$/;
63 | function tokenizeNote(str) {
64 | const m = REGEX.exec(str);
65 | return [m[1].toUpperCase(), m[2].replace(/x/g, "##"), m[3], m[4]];
66 | }
67 | var mod = (n, m) => (n % m + m) % m;
68 | var SEMI = [0, 2, 4, 5, 7, 9, 11];
69 | function parse(noteName) {
70 | const tokens = tokenizeNote(noteName);
71 | if (tokens[0] === "" || tokens[3] !== "") {
72 | return NoNote;
73 | }
74 | const letter = tokens[0];
75 | const acc = tokens[1];
76 | const octStr = tokens[2];
77 | const step = (letter.charCodeAt(0) + 3) % 7;
78 | const alt = accToAlt(acc);
79 | const oct = octStr.length ? +octStr : void 0;
80 | const coord = encode({ step, alt, oct });
81 | const name = letter + acc + octStr;
82 | const pc = letter + acc;
83 | const chroma = (SEMI[step] + alt + 120) % 12;
84 | const height = oct === void 0 ? mod(SEMI[step] + alt, 12) - 12 * 99 : SEMI[step] + alt + 12 * (oct + 1);
85 | const midi = height >= 0 && height <= 127 ? height : null;
86 | const freq = oct === void 0 ? null : Math.pow(2, (height - 69) / 12) * 440;
87 | return {
88 | empty: false,
89 | acc,
90 | alt,
91 | chroma,
92 | coord,
93 | freq,
94 | height,
95 | letter,
96 | midi,
97 | name,
98 | oct,
99 | pc,
100 | step
101 | };
102 | }
103 | function pitchName(props) {
104 | const { step, alt, oct } = props;
105 | const letter = stepToLetter(step);
106 | if (!letter) {
107 | return "";
108 | }
109 | const pc = letter + altToAcc(alt);
110 | return oct || oct === 0 ? pc + oct : pc;
111 | }
112 |
113 | // index.ts
114 | function isMidi(arg) {
115 | return +arg >= 0 && +arg <= 127;
116 | }
117 | function toMidi(note$1) {
118 | if (isMidi(note$1)) {
119 | return +note$1;
120 | }
121 | const n = note(note$1);
122 | return n.empty ? null : n.midi;
123 | }
124 |
125 | /**
126 | * Static utility functions used throughout the library.
127 | */
128 | var Utils = /** @class */ (function () {
129 | function Utils() {
130 | }
131 | /**
132 | * Gets MidiWriterJS version number.
133 | * @return {string}
134 | */
135 | Utils.version = function () {
136 | return Constants.VERSION;
137 | };
138 | /**
139 | * Convert a string to an array of bytes
140 | * @param {string} string
141 | * @return {array}
142 | */
143 | Utils.stringToBytes = function (string) {
144 | return string.split('').map(function (char) { return char.charCodeAt(0); });
145 | };
146 | /**
147 | * Checks if argument is a valid number.
148 | * @param {*} n - Value to check
149 | * @return {boolean}
150 | */
151 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
152 | Utils.isNumeric = function (n) {
153 | return !isNaN(parseFloat(n)) && isFinite(n);
154 | };
155 | /**
156 | * Returns the correct MIDI number for the specified pitch.
157 | * Uses Tonal Midi - https://github.com/danigb/tonal/tree/master/packages/midi
158 | * @param {(string|number)} pitch - 'C#4' or midi note code
159 | * @param {string} middleC
160 | * @return {number}
161 | */
162 | Utils.getPitch = function (pitch, middleC) {
163 | if (middleC === void 0) { middleC = 'C4'; }
164 | return 60 - toMidi(middleC) + toMidi(pitch);
165 | };
166 | /**
167 | * Translates number of ticks to MIDI timestamp format, returning an array of
168 | * hex strings with the time values. Midi has a very particular time to express time,
169 | * take a good look at the spec before ever touching this function.
170 | * Thanks to https://github.com/sergi/jsmidi
171 | *
172 | * @param {number} ticks - Number of ticks to be translated
173 | * @return {array} - Bytes that form the MIDI time value
174 | */
175 | Utils.numberToVariableLength = function (ticks) {
176 | ticks = Math.round(ticks);
177 | var buffer = ticks & 0x7F;
178 | // eslint-disable-next-line no-cond-assign
179 | while (ticks = ticks >> 7) {
180 | buffer <<= 8;
181 | buffer |= ((ticks & 0x7F) | 0x80);
182 | }
183 | var bList = [];
184 | // eslint-disable-next-line no-constant-condition
185 | while (true) {
186 | bList.push(buffer & 0xff);
187 | if (buffer & 0x80)
188 | buffer >>= 8;
189 | else {
190 | break;
191 | }
192 | }
193 | return bList;
194 | };
195 | /**
196 | * Counts number of bytes in string
197 | * @param {string} s
198 | * @return {number}
199 | */
200 | Utils.stringByteCount = function (s) {
201 | return encodeURI(s).split(/%..|./).length - 1;
202 | };
203 | /**
204 | * Get an int from an array of bytes.
205 | * @param {array} bytes
206 | * @return {number}
207 | */
208 | Utils.numberFromBytes = function (bytes) {
209 | var hex = '';
210 | var stringResult;
211 | bytes.forEach(function (byte) {
212 | stringResult = byte.toString(16);
213 | // ensure string is 2 chars
214 | if (stringResult.length == 1)
215 | stringResult = "0" + stringResult;
216 | hex += stringResult;
217 | });
218 | return parseInt(hex, 16);
219 | };
220 | /**
221 | * Takes a number and splits it up into an array of bytes. Can be padded by passing a number to bytesNeeded
222 | * @param {number} number
223 | * @param {number} bytesNeeded
224 | * @return {array} - Array of bytes
225 | */
226 | Utils.numberToBytes = function (number, bytesNeeded) {
227 | bytesNeeded = bytesNeeded || 1;
228 | var hexString = number.toString(16);
229 | if (hexString.length & 1) { // Make sure hex string is even number of chars
230 | hexString = '0' + hexString;
231 | }
232 | // Split hex string into an array of two char elements
233 | var hexArray = hexString.match(/.{2}/g);
234 | // Now parse them out as integers
235 | var intArray = hexArray.map(function (item) { return parseInt(item, 16); });
236 | // Prepend empty bytes if we don't have enough
237 | if (intArray.length < bytesNeeded) {
238 | while (bytesNeeded - intArray.length > 0) {
239 | intArray.unshift(0);
240 | }
241 | }
242 | return intArray;
243 | };
244 | /**
245 | * Converts value to array if needed.
246 | * @param {any} value
247 | * @return {array}
248 | */
249 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
250 | Utils.toArray = function (value) {
251 | if (Array.isArray(value))
252 | return value;
253 | return [value];
254 | };
255 | /**
256 | * Converts velocity to value 0-127
257 | * @param {number} velocity - Velocity value 1-100
258 | * @return {number}
259 | */
260 | Utils.convertVelocity = function (velocity) {
261 | // Max passed value limited to 100
262 | velocity = velocity > 100 ? 100 : velocity;
263 | return Math.round(velocity / 100 * 127);
264 | };
265 | /**
266 | * Gets the total number of ticks of a specified duration.
267 | * Note: type=='note' defaults to quarter note, type==='rest' defaults to 0
268 | * @param {(string|array)} duration
269 | * @return {number}
270 | */
271 | Utils.getTickDuration = function (duration) {
272 | if (Array.isArray(duration)) {
273 | // Recursively execute this method for each item in the array and return the sum of tick durations.
274 | return duration.map(function (value) {
275 | return Utils.getTickDuration(value);
276 | }).reduce(function (a, b) {
277 | return a + b;
278 | }, 0);
279 | }
280 | duration = duration.toString();
281 | if (duration.toLowerCase().charAt(0) === 't') {
282 | // If duration starts with 't' then the number that follows is an explicit tick count
283 | var ticks = parseInt(duration.substring(1));
284 | if (isNaN(ticks) || ticks < 0) {
285 | throw new Error(duration + ' is not a valid duration.');
286 | }
287 | return ticks;
288 | }
289 | // Need to apply duration here. Quarter note == Constants.HEADER_CHUNK_DIVISION
290 | var quarterTicks = Utils.numberFromBytes(Constants.HEADER_CHUNK_DIVISION);
291 | var tickDuration = quarterTicks * Utils.getDurationMultiplier(duration);
292 | return Utils.getRoundedIfClose(tickDuration);
293 | };
294 | /**
295 | * Due to rounding errors in JavaScript engines,
296 | * it's safe to round when we're very close to the actual tick number
297 | *
298 | * @static
299 | * @param {number} tick
300 | * @return {number}
301 | */
302 | Utils.getRoundedIfClose = function (tick) {
303 | var roundedTick = Math.round(tick);
304 | return Math.abs(roundedTick - tick) < 0.000001 ? roundedTick : tick;
305 | };
306 | /**
307 | * Due to low precision of MIDI,
308 | * we need to keep track of rounding errors in deltas.
309 | * This function will calculate the rounding error for a given duration.
310 | *
311 | * @static
312 | * @param {number} tick
313 | * @return {number}
314 | */
315 | Utils.getPrecisionLoss = function (tick) {
316 | var roundedTick = Math.round(tick);
317 | return roundedTick - tick;
318 | };
319 | /**
320 | * Gets what to multiple ticks/quarter note by to get the specified duration.
321 | * Note: type=='note' defaults to quarter note, type==='rest' defaults to 0
322 | * @param {string} duration
323 | * @return {number}
324 | */
325 | Utils.getDurationMultiplier = function (duration) {
326 | // Need to apply duration here.
327 | // Quarter note == Constants.HEADER_CHUNK_DIVISION ticks.
328 | if (duration === '0')
329 | return 0;
330 | var match = duration.match(/^(?d+)?(?\d+)(?:t(?\d*))?/);
331 | if (match) {
332 | var base = Number(match.groups.base);
333 | // 1 or any power of two:
334 | var isValidBase = base === 1 || ((base & (base - 1)) === 0);
335 | if (isValidBase) {
336 | // how much faster or slower is this note compared to a quarter?
337 | var ratio = base / 4;
338 | var durationInQuarters = 1 / ratio;
339 | var _a = match.groups, dotted = _a.dotted, tuplet = _a.tuplet;
340 | if (dotted) {
341 | var thisManyDots = dotted.length;
342 | var divisor = Math.pow(2, thisManyDots);
343 | durationInQuarters = durationInQuarters + (durationInQuarters * ((divisor - 1) / divisor));
344 | }
345 | if (typeof tuplet === 'string') {
346 | var fitInto = durationInQuarters * 2;
347 | // default to triplet:
348 | var thisManyNotes = Number(tuplet || '3');
349 | durationInQuarters = fitInto / thisManyNotes;
350 | }
351 | return durationInQuarters;
352 | }
353 | }
354 | throw new Error(duration + ' is not a valid duration.');
355 | };
356 | return Utils;
357 | }());
358 |
359 | /**
360 | * Holds all data for a "controller change" MIDI event
361 | * @param {object} fields {controllerNumber: integer, controllerValue: integer, delta: integer}
362 | * @return {ControllerChangeEvent}
363 | */
364 | var ControllerChangeEvent = /** @class */ (function () {
365 | function ControllerChangeEvent(fields) {
366 | this.channel = fields.channel - 1 || 0;
367 | this.controllerValue = fields.controllerValue;
368 | this.controllerNumber = fields.controllerNumber;
369 | this.delta = fields.delta || 0x00;
370 | this.name = 'ControllerChangeEvent';
371 | this.status = 0xB0;
372 | this.data = Utils.numberToVariableLength(fields.delta).concat(this.status | this.channel, this.controllerNumber, this.controllerValue);
373 | }
374 | return ControllerChangeEvent;
375 | }());
376 |
377 | /**
378 | * Object representation of a tempo meta event.
379 | * @param {object} fields {text: string, delta: integer}
380 | * @return {CopyrightEvent}
381 | */
382 | var CopyrightEvent = /** @class */ (function () {
383 | function CopyrightEvent(fields) {
384 | this.delta = fields.delta || 0x00;
385 | this.name = 'CopyrightEvent';
386 | this.text = fields.text;
387 | this.type = 0x02;
388 | var textBytes = Utils.stringToBytes(this.text);
389 | // Start with zero time delta
390 | this.data = Utils.numberToVariableLength(this.delta).concat(Constants.META_EVENT_ID, this.type, Utils.numberToVariableLength(textBytes.length), // Size
391 | textBytes);
392 | }
393 | return CopyrightEvent;
394 | }());
395 |
396 | /**
397 | * Object representation of a cue point meta event.
398 | * @param {object} fields {text: string, delta: integer}
399 | * @return {CuePointEvent}
400 | */
401 | var CuePointEvent = /** @class */ (function () {
402 | function CuePointEvent(fields) {
403 | this.delta = fields.delta || 0x00;
404 | this.name = 'CuePointEvent';
405 | this.text = fields.text;
406 | this.type = 0x07;
407 | var textBytes = Utils.stringToBytes(this.text);
408 | // Start with zero time delta
409 | this.data = Utils.numberToVariableLength(this.delta).concat(Constants.META_EVENT_ID, this.type, Utils.numberToVariableLength(textBytes.length), // Size
410 | textBytes);
411 | }
412 | return CuePointEvent;
413 | }());
414 |
415 | /**
416 | * Object representation of a end track meta event.
417 | * @param {object} fields {delta: integer}
418 | * @return {EndTrackEvent}
419 | */
420 | var EndTrackEvent = /** @class */ (function () {
421 | function EndTrackEvent(fields) {
422 | this.delta = (fields === null || fields === void 0 ? void 0 : fields.delta) || 0x00;
423 | this.name = 'EndTrackEvent';
424 | this.type = [0x2F, 0x00];
425 | // Start with zero time delta
426 | this.data = Utils.numberToVariableLength(this.delta).concat(Constants.META_EVENT_ID, this.type);
427 | }
428 | return EndTrackEvent;
429 | }());
430 |
431 | /**
432 | * Object representation of an instrument name meta event.
433 | * @param {object} fields {text: string, delta: integer}
434 | * @return {InstrumentNameEvent}
435 | */
436 | var InstrumentNameEvent = /** @class */ (function () {
437 | function InstrumentNameEvent(fields) {
438 | this.delta = fields.delta || 0x00;
439 | this.name = 'InstrumentNameEvent';
440 | this.text = fields.text;
441 | this.type = 0x04;
442 | var textBytes = Utils.stringToBytes(this.text);
443 | // Start with zero time delta
444 | this.data = Utils.numberToVariableLength(this.delta).concat(Constants.META_EVENT_ID, this.type, Utils.numberToVariableLength(textBytes.length), // Size
445 | textBytes);
446 | }
447 | return InstrumentNameEvent;
448 | }());
449 |
450 | /**
451 | * Object representation of a key signature meta event.
452 | * @return {KeySignatureEvent}
453 | */
454 | var KeySignatureEvent = /** @class */ (function () {
455 | function KeySignatureEvent(sf, mi) {
456 | this.name = 'KeySignatureEvent';
457 | this.type = 0x59;
458 | var mode = mi || 0;
459 | sf = sf || 0;
460 | // Function called with string notation
461 | if (typeof mi === 'undefined') {
462 | var fifths = [
463 | ['Cb', 'Gb', 'Db', 'Ab', 'Eb', 'Bb', 'F', 'C', 'G', 'D', 'A', 'E', 'B', 'F#', 'C#'],
464 | ['ab', 'eb', 'bb', 'f', 'c', 'g', 'd', 'a', 'e', 'b', 'f#', 'c#', 'g#', 'd#', 'a#']
465 | ];
466 | var _sflen = sf.length;
467 | var note = sf || 'C';
468 | if (sf[0] === sf[0].toLowerCase())
469 | mode = 1;
470 | if (_sflen > 1) {
471 | switch (sf.charAt(_sflen - 1)) {
472 | case 'm':
473 | mode = 1;
474 | note = sf.charAt(0).toLowerCase();
475 | note = note.concat(sf.substring(1, _sflen - 1));
476 | break;
477 | case '-':
478 | mode = 1;
479 | note = sf.charAt(0).toLowerCase();
480 | note = note.concat(sf.substring(1, _sflen - 1));
481 | break;
482 | case 'M':
483 | mode = 0;
484 | note = sf.charAt(0).toUpperCase();
485 | note = note.concat(sf.substring(1, _sflen - 1));
486 | break;
487 | case '+':
488 | mode = 0;
489 | note = sf.charAt(0).toUpperCase();
490 | note = note.concat(sf.substring(1, _sflen - 1));
491 | break;
492 | }
493 | }
494 | var fifthindex = fifths[mode].indexOf(note);
495 | sf = fifthindex === -1 ? 0 : fifthindex - 7;
496 | }
497 | // Start with zero time delta
498 | this.data = Utils.numberToVariableLength(0x00).concat(Constants.META_EVENT_ID, this.type, [0x02], // Size
499 | Utils.numberToBytes(sf, 1), // Number of sharp or flats ( < 0 flat; > 0 sharp)
500 | Utils.numberToBytes(mode, 1));
501 | }
502 | return KeySignatureEvent;
503 | }());
504 |
505 | /**
506 | * Object representation of a lyric meta event.
507 | * @param {object} fields {text: string, delta: integer}
508 | * @return {LyricEvent}
509 | */
510 | var LyricEvent = /** @class */ (function () {
511 | function LyricEvent(fields) {
512 | this.delta = fields.delta || 0x00;
513 | this.name = 'LyricEvent';
514 | this.text = fields.text;
515 | this.type = 0x05;
516 | var textBytes = Utils.stringToBytes(this.text);
517 | // Start with zero time delta
518 | this.data = Utils.numberToVariableLength(this.delta).concat(Constants.META_EVENT_ID, this.type, Utils.numberToVariableLength(textBytes.length), // Size
519 | textBytes);
520 | }
521 | return LyricEvent;
522 | }());
523 |
524 | /**
525 | * Object representation of a marker meta event.
526 | * @param {object} fields {text: string, delta: integer}
527 | * @return {MarkerEvent}
528 | */
529 | var MarkerEvent = /** @class */ (function () {
530 | function MarkerEvent(fields) {
531 | this.delta = fields.delta || 0x00;
532 | this.name = 'MarkerEvent';
533 | this.text = fields.text;
534 | this.type = 0x06;
535 | var textBytes = Utils.stringToBytes(this.text);
536 | // Start with zero time delta
537 | this.data = Utils.numberToVariableLength(this.delta).concat(Constants.META_EVENT_ID, this.type, Utils.numberToVariableLength(textBytes.length), // Size
538 | textBytes);
539 | }
540 | return MarkerEvent;
541 | }());
542 |
543 | /**
544 | * Holds all data for a "note on" MIDI event
545 | * @param {object} fields {data: []}
546 | * @return {NoteOnEvent}
547 | */
548 | var NoteOnEvent = /** @class */ (function () {
549 | function NoteOnEvent(fields) {
550 | this.name = 'NoteOnEvent';
551 | this.channel = fields.channel || 1;
552 | this.pitch = fields.pitch;
553 | this.wait = fields.wait || 0;
554 | this.velocity = fields.velocity || 50;
555 | this.tick = fields.tick || null;
556 | this.delta = null;
557 | this.data = fields.data;
558 | this.status = 0x90;
559 | }
560 | /**
561 | * Builds int array for this event.
562 | * @param {Track} track - parent track
563 | * @return {NoteOnEvent}
564 | */
565 | NoteOnEvent.prototype.buildData = function (track, precisionDelta, options) {
566 | if (options === void 0) { options = {}; }
567 | this.data = [];
568 | // Explicitly defined startTick event
569 | if (this.tick) {
570 | this.tick = Utils.getRoundedIfClose(this.tick);
571 | // If this is the first event in the track then use event's starting tick as delta.
572 | if (track.tickPointer == 0) {
573 | this.delta = this.tick;
574 | }
575 | }
576 | else {
577 | this.delta = Utils.getTickDuration(this.wait);
578 | this.tick = Utils.getRoundedIfClose(track.tickPointer + this.delta);
579 | }
580 | this.deltaWithPrecisionCorrection = Utils.getRoundedIfClose(this.delta - precisionDelta);
581 | this.data = Utils.numberToVariableLength(this.deltaWithPrecisionCorrection)
582 | .concat(this.status | this.channel - 1, Utils.getPitch(this.pitch, options.middleC), Utils.convertVelocity(this.velocity));
583 | return this;
584 | };
585 | return NoteOnEvent;
586 | }());
587 |
588 | /**
589 | * Holds all data for a "note off" MIDI event
590 | * @param {object} fields {data: []}
591 | * @return {NoteOffEvent}
592 | */
593 | var NoteOffEvent = /** @class */ (function () {
594 | function NoteOffEvent(fields) {
595 | this.name = 'NoteOffEvent';
596 | this.channel = fields.channel || 1;
597 | this.pitch = fields.pitch;
598 | this.velocity = fields.velocity || 50;
599 | this.tick = fields.tick || null;
600 | this.data = fields.data;
601 | this.delta = fields.delta || Utils.getTickDuration(fields.duration);
602 | this.status = 0x80;
603 | }
604 | /**
605 | * Builds int array for this event.
606 | * @param {Track} track - parent track
607 | * @return {NoteOffEvent}
608 | */
609 | NoteOffEvent.prototype.buildData = function (track, precisionDelta, options) {
610 | if (options === void 0) { options = {}; }
611 | if (this.tick === null) {
612 | this.tick = Utils.getRoundedIfClose(this.delta + track.tickPointer);
613 | }
614 | this.deltaWithPrecisionCorrection = Utils.getRoundedIfClose(this.delta - precisionDelta);
615 | this.data = Utils.numberToVariableLength(this.deltaWithPrecisionCorrection)
616 | .concat(this.status | this.channel - 1, Utils.getPitch(this.pitch, options.middleC), Utils.convertVelocity(this.velocity));
617 | return this;
618 | };
619 | return NoteOffEvent;
620 | }());
621 |
622 | /**
623 | * Wrapper for noteOnEvent/noteOffEvent objects that builds both events.
624 | * @param {object} fields - {pitch: '[C4]', duration: '4', wait: '4', velocity: 1-100}
625 | * @return {NoteEvent}
626 | */
627 | var NoteEvent = /** @class */ (function () {
628 | function NoteEvent(fields) {
629 | this.data = [];
630 | this.name = 'NoteEvent';
631 | this.pitch = Utils.toArray(fields.pitch);
632 | this.channel = fields.channel || 1;
633 | this.duration = fields.duration || '4';
634 | this.grace = fields.grace;
635 | this.repeat = fields.repeat || 1;
636 | this.sequential = fields.sequential || false;
637 | this.tick = fields.startTick || fields.tick || null;
638 | this.velocity = fields.velocity || 50;
639 | this.wait = fields.wait || 0;
640 | this.tickDuration = Utils.getTickDuration(this.duration);
641 | this.restDuration = Utils.getTickDuration(this.wait);
642 | this.events = []; // Hold actual NoteOn/NoteOff events
643 | }
644 | /**
645 | * Builds int array for this event.
646 | * @return {NoteEvent}
647 | */
648 | NoteEvent.prototype.buildData = function () {
649 | var _this = this;
650 | // Reset data array
651 | this.data = [];
652 | // Apply grace note(s) and subtract ticks (currently 1 tick per grace note) from tickDuration so net value is the same
653 | if (this.grace) {
654 | var graceDuration_1 = 1;
655 | this.grace = Utils.toArray(this.grace);
656 | this.grace.forEach(function () {
657 | var noteEvent = new NoteEvent({ pitch: _this.grace, duration: 'T' + graceDuration_1 });
658 | _this.data = _this.data.concat(noteEvent.data);
659 | });
660 | }
661 | // fields.pitch could be an array of pitches.
662 | // If so create note events for each and apply the same duration.
663 | // By default this is a chord if it's an array of notes that requires one NoteOnEvent.
664 | // If this.sequential === true then it's a sequential string of notes that requires separate NoteOnEvents.
665 | if (!this.sequential) {
666 | // Handle repeat
667 | for (var j = 0; j < this.repeat; j++) {
668 | // Note on
669 | this.pitch.forEach(function (p, i) {
670 | var noteOnNew;
671 | if (i == 0) {
672 | noteOnNew = new NoteOnEvent({
673 | channel: _this.channel,
674 | wait: _this.wait,
675 | delta: Utils.getTickDuration(_this.wait),
676 | velocity: _this.velocity,
677 | pitch: p,
678 | tick: _this.tick,
679 | });
680 | }
681 | else {
682 | // Running status (can ommit the note on status)
683 | //noteOn = new NoteOnEvent({data: [0, Utils.getPitch(p), Utils.convertVelocity(this.velocity)]});
684 | noteOnNew = new NoteOnEvent({
685 | channel: _this.channel,
686 | wait: 0,
687 | delta: 0,
688 | velocity: _this.velocity,
689 | pitch: p,
690 | tick: _this.tick,
691 | });
692 | }
693 | _this.events.push(noteOnNew);
694 | });
695 | // Note off
696 | this.pitch.forEach(function (p, i) {
697 | var noteOffNew;
698 | if (i == 0) {
699 | //noteOff = new NoteOffEvent({data: Utils.numberToVariableLength(tickDuration).concat(this.getNoteOffStatus(), Utils.getPitch(p), Utils.convertVelocity(this.velocity))});
700 | noteOffNew = new NoteOffEvent({
701 | channel: _this.channel,
702 | duration: _this.duration,
703 | velocity: _this.velocity,
704 | pitch: p,
705 | tick: _this.tick !== null ? Utils.getTickDuration(_this.duration) + _this.tick : null,
706 | });
707 | }
708 | else {
709 | // Running status (can omit the note off status)
710 | //noteOff = new NoteOffEvent({data: [0, Utils.getPitch(p), Utils.convertVelocity(this.velocity)]});
711 | noteOffNew = new NoteOffEvent({
712 | channel: _this.channel,
713 | duration: 0,
714 | velocity: _this.velocity,
715 | pitch: p,
716 | tick: _this.tick !== null ? Utils.getTickDuration(_this.duration) + _this.tick : null,
717 | });
718 | }
719 | _this.events.push(noteOffNew);
720 | });
721 | }
722 | }
723 | else {
724 | // Handle repeat
725 | for (var j = 0; j < this.repeat; j++) {
726 | this.pitch.forEach(function (p, i) {
727 | var noteOnNew = new NoteOnEvent({
728 | channel: _this.channel,
729 | wait: (i > 0 ? 0 : _this.wait),
730 | delta: (i > 0 ? 0 : Utils.getTickDuration(_this.wait)),
731 | velocity: _this.velocity,
732 | pitch: p,
733 | tick: _this.tick,
734 | });
735 | var noteOffNew = new NoteOffEvent({
736 | channel: _this.channel,
737 | duration: _this.duration,
738 | velocity: _this.velocity,
739 | pitch: p,
740 | });
741 | _this.events.push(noteOnNew, noteOffNew);
742 | });
743 | }
744 | }
745 | return this;
746 | };
747 | return NoteEvent;
748 | }());
749 |
750 | /**
751 | * Holds all data for a "Pitch Bend" MIDI event
752 | * [ -1.0, 0, 1.0 ] -> [ 0, 8192, 16383]
753 | * @param {object} fields { bend : float, channel : int, delta: int }
754 | * @return {PitchBendEvent}
755 | */
756 | var PitchBendEvent = /** @class */ (function () {
757 | function PitchBendEvent(fields) {
758 | this.channel = fields.channel || 0;
759 | this.delta = fields.delta || 0x00;
760 | this.name = 'PitchBendEvent';
761 | this.status = 0xE0;
762 | var bend14 = this.scale14bits(fields.bend);
763 | var lsbValue = bend14 & 0x7f;
764 | var msbValue = (bend14 >> 7) & 0x7f;
765 | this.data = Utils.numberToVariableLength(this.delta).concat(this.status | this.channel, lsbValue, msbValue);
766 | }
767 | PitchBendEvent.prototype.scale14bits = function (zeroOne) {
768 | if (zeroOne <= 0) {
769 | return Math.floor(16384 * (zeroOne + 1) / 2);
770 | }
771 | return Math.floor(16383 * (zeroOne + 1) / 2);
772 | };
773 | return PitchBendEvent;
774 | }());
775 |
776 | /**
777 | * Holds all data for a "program change" MIDI event
778 | * @param {object} fields {instrument: integer, delta: integer}
779 | * @return {ProgramChangeEvent}
780 | */
781 | var ProgramChangeEvent = /** @class */ (function () {
782 | function ProgramChangeEvent(fields) {
783 | this.channel = fields.channel || 0;
784 | this.delta = fields.delta || 0x00;
785 | this.instrument = fields.instrument;
786 | this.status = 0xC0;
787 | this.name = 'ProgramChangeEvent';
788 | // delta time defaults to 0.
789 | this.data = Utils.numberToVariableLength(this.delta).concat(this.status | this.channel, this.instrument);
790 | }
791 | return ProgramChangeEvent;
792 | }());
793 |
794 | /**
795 | * Object representation of a tempo meta event.
796 | * @param {object} fields {bpm: integer, delta: integer}
797 | * @return {TempoEvent}
798 | */
799 | var TempoEvent = /** @class */ (function () {
800 | function TempoEvent(fields) {
801 | this.bpm = fields.bpm;
802 | this.delta = fields.delta || 0x00;
803 | this.tick = fields.tick;
804 | this.name = 'TempoEvent';
805 | this.type = 0x51;
806 | var tempo = Math.round(60000000 / this.bpm);
807 | // Start with zero time delta
808 | this.data = Utils.numberToVariableLength(this.delta).concat(Constants.META_EVENT_ID, this.type, [0x03], // Size
809 | Utils.numberToBytes(tempo, 3));
810 | }
811 | return TempoEvent;
812 | }());
813 |
814 | /**
815 | * Object representation of a tempo meta event.
816 | * @param {object} fields {text: string, delta: integer}
817 | * @return {TextEvent}
818 | */
819 | var TextEvent = /** @class */ (function () {
820 | function TextEvent(fields) {
821 | this.delta = fields.delta || 0x00;
822 | this.text = fields.text;
823 | this.name = 'TextEvent';
824 | this.type = 0x01;
825 | var textBytes = Utils.stringToBytes(this.text);
826 | // Start with zero time delta
827 | this.data = Utils.numberToVariableLength(fields.delta).concat(Constants.META_EVENT_ID, this.type, Utils.numberToVariableLength(textBytes.length), // Size
828 | textBytes);
829 | }
830 | return TextEvent;
831 | }());
832 |
833 | /**
834 | * Object representation of a time signature meta event.
835 | * @return {TimeSignatureEvent}
836 | */
837 | var TimeSignatureEvent = /** @class */ (function () {
838 | function TimeSignatureEvent(numerator, denominator, midiclockspertick, notespermidiclock) {
839 | this.name = 'TimeSignatureEvent';
840 | this.type = 0x58;
841 | // Start with zero time delta
842 | this.data = Utils.numberToVariableLength(0x00).concat(Constants.META_EVENT_ID, this.type, [0x04], // Size
843 | Utils.numberToBytes(numerator, 1), // Numerator, 1 bytes
844 | Utils.numberToBytes(Math.log2(denominator), 1), // Denominator is expressed as pow of 2, 1 bytes
845 | Utils.numberToBytes(midiclockspertick || 24, 1), // MIDI Clocks per tick, 1 bytes
846 | Utils.numberToBytes(notespermidiclock || 8, 1));
847 | }
848 | return TimeSignatureEvent;
849 | }());
850 |
851 | /**
852 | * Object representation of a tempo meta event.
853 | * @param {object} fields {text: string, delta: integer}
854 | * @return {TrackNameEvent}
855 | */
856 | var TrackNameEvent = /** @class */ (function () {
857 | function TrackNameEvent(fields) {
858 | this.delta = fields.delta || 0x00;
859 | this.name = 'TrackNameEvent';
860 | this.text = fields.text;
861 | this.type = 0x03;
862 | var textBytes = Utils.stringToBytes(this.text);
863 | // Start with zero time delta
864 | this.data = Utils.numberToVariableLength(this.delta).concat(Constants.META_EVENT_ID, this.type, Utils.numberToVariableLength(textBytes.length), // Size
865 | textBytes);
866 | }
867 | return TrackNameEvent;
868 | }());
869 |
870 | /**
871 | * Holds all data for a track.
872 | * @param {object} fields {type: number, data: array, size: array, events: array}
873 | * @return {Track}
874 | */
875 | var Track = /** @class */ (function () {
876 | function Track() {
877 | this.type = Constants.TRACK_CHUNK_TYPE;
878 | this.data = [];
879 | this.size = [];
880 | this.events = [];
881 | this.explicitTickEvents = [];
882 | // If there are any events with an explicit tick defined then we will create a "sub" track for those
883 | // and merge them in and the end.
884 | this.tickPointer = 0; // Each time an event is added this will increase
885 | }
886 | /**
887 | * Adds any event type to the track.
888 | * Events without a specific startTick property are assumed to be added in order of how they should output.
889 | * Events with a specific startTick property are set aside for now will be merged in during build process.
890 | *
891 | * TODO: Don't put startTick events in their own array. Just lump everything together and sort it out during buildData();
892 | * @param {(NoteEvent|ProgramChangeEvent)} events - Event object or array of Event objects.
893 | * @param {Function} mapFunction - Callback which can be used to apply specific properties to all events.
894 | * @return {Track}
895 | */
896 | Track.prototype.addEvent = function (events, mapFunction) {
897 | var _this = this;
898 | Utils.toArray(events).forEach(function (event, i) {
899 | if (event instanceof NoteEvent) {
900 | // Handle map function if provided
901 | if (typeof mapFunction === 'function') {
902 | var properties = mapFunction(i, event);
903 | if (typeof properties === 'object') {
904 | Object.assign(event, properties);
905 | }
906 | }
907 | // If this note event has an explicit startTick then we need to set aside for now
908 | if (event.tick !== null) {
909 | _this.explicitTickEvents.push(event);
910 | }
911 | else {
912 | // Push each on/off event to track's event stack
913 | event.buildData().events.forEach(function (e) { return _this.events.push(e); });
914 | }
915 | }
916 | else {
917 | _this.events.push(event);
918 | }
919 | });
920 | return this;
921 | };
922 | /**
923 | * Builds int array of all events.
924 | * @param {object} options
925 | * @return {Track}
926 | */
927 | Track.prototype.buildData = function (options) {
928 | var _this = this;
929 | if (options === void 0) { options = {}; }
930 | // Reset
931 | this.data = [];
932 | this.size = [];
933 | this.tickPointer = 0;
934 | var precisionLoss = 0;
935 | this.events.forEach(function (event) {
936 | // Build event & add to total tick duration
937 | if (event instanceof NoteOnEvent || event instanceof NoteOffEvent) {
938 | var built = event.buildData(_this, precisionLoss, options);
939 | precisionLoss = Utils.getPrecisionLoss(event.deltaWithPrecisionCorrection || 0);
940 | _this.data = _this.data.concat(built.data);
941 | _this.tickPointer = Utils.getRoundedIfClose(event.tick);
942 | }
943 | else if (event instanceof TempoEvent) {
944 | _this.tickPointer = Utils.getRoundedIfClose(event.tick);
945 | _this.data = _this.data.concat(event.data);
946 | }
947 | else {
948 | _this.data = _this.data.concat(event.data);
949 | }
950 | });
951 | this.mergeExplicitTickEvents();
952 | // If the last event isn't EndTrackEvent, then tack it onto the data.
953 | if (!this.events.length || !(this.events[this.events.length - 1] instanceof EndTrackEvent)) {
954 | this.data = this.data.concat((new EndTrackEvent).data);
955 | }
956 | this.size = Utils.numberToBytes(this.data.length, 4); // 4 bytes long
957 | return this;
958 | };
959 | Track.prototype.mergeExplicitTickEvents = function () {
960 | var _this = this;
961 | if (!this.explicitTickEvents.length)
962 | return;
963 | // First sort asc list of events by startTick
964 | this.explicitTickEvents.sort(function (a, b) { return a.tick - b.tick; });
965 | // Now this.explicitTickEvents is in correct order, and so is this.events naturally.
966 | // For each explicit tick event, splice it into the main list of events and
967 | // adjust the delta on the following events so they still play normally.
968 | this.explicitTickEvents.forEach(function (noteEvent) {
969 | // Convert NoteEvent to it's respective NoteOn/NoteOff events
970 | // Note that as we splice in events the delta for the NoteOff ones will
971 | // Need to change based on what comes before them after the splice.
972 | noteEvent.buildData().events.forEach(function (e) { return e.buildData(_this); });
973 | // Merge each event individually into this track's event list.
974 | noteEvent.events.forEach(function (event) { return _this.mergeSingleEvent(event); });
975 | });
976 | // Hacky way to rebuild track with newly spliced events. Need better solution.
977 | this.explicitTickEvents = [];
978 | this.buildData();
979 | };
980 | /**
981 | * Merges another track's events with this track.
982 | * @param {Track} track
983 | * @return {Track}
984 | */
985 | Track.prototype.mergeTrack = function (track) {
986 | var _this = this;
987 | // First build this track to populate each event's tick property
988 | this.buildData();
989 | // Then build track to be merged so that tick property is populated on all events & merge each event.
990 | track.buildData().events.forEach(function (event) { return _this.mergeSingleEvent(event); });
991 | return this;
992 | };
993 | /**
994 | * Merges a single event into this track's list of events based on event.tick property.
995 | * @param {AbstractEvent} - event
996 | * @return {Track}
997 | */
998 | Track.prototype.mergeSingleEvent = function (event) {
999 | // There are no events yet, so just add it in.
1000 | if (!this.events.length) {
1001 | this.addEvent(event);
1002 | return;
1003 | }
1004 | // Find index of existing event we need to follow with
1005 | var lastEventIndex;
1006 | for (var i = 0; i < this.events.length; i++) {
1007 | if (this.events[i].tick > event.tick)
1008 | break;
1009 | lastEventIndex = i;
1010 | }
1011 | var splicedEventIndex = lastEventIndex + 1;
1012 | // Need to adjust the delta of this event to ensure it falls on the correct tick.
1013 | event.delta = event.tick - this.events[lastEventIndex].tick;
1014 | // Splice this event at lastEventIndex + 1
1015 | this.events.splice(splicedEventIndex, 0, event);
1016 | // Now adjust delta of all following events
1017 | for (var i = splicedEventIndex + 1; i < this.events.length; i++) {
1018 | // Since each existing event should have a tick value at this point we just need to
1019 | // adjust delta to that the event still falls on the correct tick.
1020 | this.events[i].delta = this.events[i].tick - this.events[i - 1].tick;
1021 | }
1022 | };
1023 | /**
1024 | * Removes all events matching specified type.
1025 | * @param {string} eventName - Event type
1026 | * @return {Track}
1027 | */
1028 | Track.prototype.removeEventsByName = function (eventName) {
1029 | var _this = this;
1030 | this.events.forEach(function (event, index) {
1031 | if (event.name === eventName) {
1032 | _this.events.splice(index, 1);
1033 | }
1034 | });
1035 | return this;
1036 | };
1037 | /**
1038 | * Sets tempo of the MIDI file.
1039 | * @param {number} bpm - Tempo in beats per minute.
1040 | * @param {number} tick - Start tick.
1041 | * @return {Track}
1042 | */
1043 | Track.prototype.setTempo = function (bpm, tick) {
1044 | if (tick === void 0) { tick = 0; }
1045 | return this.addEvent(new TempoEvent({ bpm: bpm, tick: tick }));
1046 | };
1047 | /**
1048 | * Sets time signature.
1049 | * @param {number} numerator - Top number of the time signature.
1050 | * @param {number} denominator - Bottom number of the time signature.
1051 | * @param {number} midiclockspertick - Defaults to 24.
1052 | * @param {number} notespermidiclock - Defaults to 8.
1053 | * @return {Track}
1054 | */
1055 | Track.prototype.setTimeSignature = function (numerator, denominator, midiclockspertick, notespermidiclock) {
1056 | return this.addEvent(new TimeSignatureEvent(numerator, denominator, midiclockspertick, notespermidiclock));
1057 | };
1058 | /**
1059 | * Sets key signature.
1060 | * @param {*} sf -
1061 | * @param {*} mi -
1062 | * @return {Track}
1063 | */
1064 | Track.prototype.setKeySignature = function (sf, mi) {
1065 | return this.addEvent(new KeySignatureEvent(sf, mi));
1066 | };
1067 | /**
1068 | * Adds text to MIDI file.
1069 | * @param {string} text - Text to add.
1070 | * @return {Track}
1071 | */
1072 | Track.prototype.addText = function (text) {
1073 | return this.addEvent(new TextEvent({ text: text }));
1074 | };
1075 | /**
1076 | * Adds copyright to MIDI file.
1077 | * @param {string} text - Text of copyright line.
1078 | * @return {Track}
1079 | */
1080 | Track.prototype.addCopyright = function (text) {
1081 | return this.addEvent(new CopyrightEvent({ text: text }));
1082 | };
1083 | /**
1084 | * Adds Sequence/Track Name.
1085 | * @param {string} text - Text of track name.
1086 | * @return {Track}
1087 | */
1088 | Track.prototype.addTrackName = function (text) {
1089 | return this.addEvent(new TrackNameEvent({ text: text }));
1090 | };
1091 | /**
1092 | * Sets instrument name of track.
1093 | * @param {string} text - Name of instrument.
1094 | * @return {Track}
1095 | */
1096 | Track.prototype.addInstrumentName = function (text) {
1097 | return this.addEvent(new InstrumentNameEvent({ text: text }));
1098 | };
1099 | /**
1100 | * Adds marker to MIDI file.
1101 | * @param {string} text - Marker text.
1102 | * @return {Track}
1103 | */
1104 | Track.prototype.addMarker = function (text) {
1105 | return this.addEvent(new MarkerEvent({ text: text }));
1106 | };
1107 | /**
1108 | * Adds cue point to MIDI file.
1109 | * @param {string} text - Text of cue point.
1110 | * @return {Track}
1111 | */
1112 | Track.prototype.addCuePoint = function (text) {
1113 | return this.addEvent(new CuePointEvent({ text: text }));
1114 | };
1115 | /**
1116 | * Adds lyric to MIDI file.
1117 | * @param {string} text - Lyric text to add.
1118 | * @return {Track}
1119 | */
1120 | Track.prototype.addLyric = function (text) {
1121 | return this.addEvent(new LyricEvent({ text: text }));
1122 | };
1123 | /**
1124 | * Channel mode messages
1125 | * @return {Track}
1126 | */
1127 | Track.prototype.polyModeOn = function () {
1128 | var event = new NoteOnEvent({ data: [0x00, 0xB0, 0x7E, 0x00] });
1129 | return this.addEvent(event);
1130 | };
1131 | /**
1132 | * Sets a pitch bend.
1133 | * @param {float} bend - Bend value ranging [-1,1], zero meaning no bend.
1134 | * @return {Track}
1135 | */
1136 | Track.prototype.setPitchBend = function (bend) {
1137 | return this.addEvent(new PitchBendEvent({ bend: bend }));
1138 | };
1139 | /**
1140 | * Adds a controller change event
1141 | * @param {number} number - Control number.
1142 | * @param {number} value - Control value.
1143 | * @param {number} channel - Channel to send controller change event on (1-based).
1144 | * @param {number} delta - Track tick offset for cc event.
1145 | * @return {Track}
1146 | */
1147 | Track.prototype.controllerChange = function (number, value, channel, delta) {
1148 | return this.addEvent(new ControllerChangeEvent({ controllerNumber: number, controllerValue: value, channel: channel, delta: delta }));
1149 | };
1150 | return Track;
1151 | }());
1152 |
1153 | var VexFlow = /** @class */ (function () {
1154 | function VexFlow() {
1155 | }
1156 | /**
1157 | * Support for converting VexFlow voice into MidiWriterJS track
1158 | * @return MidiWriter.Track object
1159 | */
1160 | VexFlow.prototype.trackFromVoice = function (voice, options) {
1161 | var _this = this;
1162 | if (options === void 0) { options = { addRenderedAccidentals: false }; }
1163 | var track = new Track;
1164 | var wait = [];
1165 | voice.tickables.forEach(function (tickable) {
1166 | if (tickable.noteType === 'n') {
1167 | track.addEvent(new NoteEvent({
1168 | pitch: tickable.keys.map(function (pitch, index) { return _this.convertPitch(pitch, index, tickable, options.addRenderedAccidentals); }),
1169 | duration: _this.convertDuration(tickable),
1170 | wait: wait
1171 | }));
1172 | // reset wait
1173 | wait = [];
1174 | }
1175 | else if (tickable.noteType === 'r') {
1176 | // move on to the next tickable and add this to the stack
1177 | // of the `wait` property for the next note event
1178 | wait.push(_this.convertDuration(tickable));
1179 | }
1180 | });
1181 | // There may be outstanding rests at the end of the track,
1182 | // pad with a ghost note (zero duration and velocity), just to capture the wait.
1183 | if (wait.length > 0) {
1184 | track.addEvent(new NoteEvent({ pitch: '[c4]', duration: '0', wait: wait, velocity: '0' }));
1185 | }
1186 | return track;
1187 | };
1188 | /**
1189 | * Converts VexFlow pitch syntax to MidiWriterJS syntax
1190 | * @param pitch string
1191 | * @param index pitch index
1192 | * @param note struct from Vexflow
1193 | * @param addRenderedAccidentals adds Vexflow rendered accidentals
1194 | */
1195 | VexFlow.prototype.convertPitch = function (pitch, index, note, addRenderedAccidentals) {
1196 | var _a;
1197 | if (addRenderedAccidentals === void 0) { addRenderedAccidentals = false; }
1198 | // Splits note name from octave
1199 | var pitchParts = pitch.split('/');
1200 | // Retrieves accidentals from pitch
1201 | // Removes natural accidentals since they are not accepted in Tonal Midi
1202 | var accidentals = pitchParts[0].substring(1).replace('n', '');
1203 | if (addRenderedAccidentals) {
1204 | (_a = note.getAccidentals()) === null || _a === void 0 ? void 0 : _a.forEach(function (accidental) {
1205 | if (accidental.index === index) {
1206 | if (accidental.type === 'n') {
1207 | accidentals = '';
1208 | }
1209 | else {
1210 | accidentals += accidental.type;
1211 | }
1212 | }
1213 | });
1214 | }
1215 | return pitchParts[0][0] + accidentals + pitchParts[1];
1216 | };
1217 | /**
1218 | * Converts VexFlow duration syntax to MidiWriterJS syntax
1219 | * @param note struct from VexFlow
1220 | */
1221 | VexFlow.prototype.convertDuration = function (note) {
1222 | return 'd'.repeat(note.dots) + this.convertBaseDuration(note.duration) + (note.tuplet ? 't' + note.tuplet.num_notes : '');
1223 | };
1224 | /**
1225 | * Converts VexFlow base duration syntax to MidiWriterJS syntax
1226 | * @param duration Vexflow duration
1227 | * @returns MidiWriterJS duration
1228 | */
1229 | VexFlow.prototype.convertBaseDuration = function (duration) {
1230 | switch (duration) {
1231 | case 'w':
1232 | return '1';
1233 | case 'h':
1234 | return '2';
1235 | case 'q':
1236 | return '4';
1237 | default:
1238 | return duration;
1239 | }
1240 | };
1241 | return VexFlow;
1242 | }());
1243 |
1244 | /**
1245 | * Object representation of a header chunk section of a MIDI file.
1246 | * @param {number} numberOfTracks - Number of tracks
1247 | * @return {Header}
1248 | */
1249 | var Header = /** @class */ (function () {
1250 | function Header(numberOfTracks) {
1251 | this.type = Constants.HEADER_CHUNK_TYPE;
1252 | var trackType = numberOfTracks > 1 ? Constants.HEADER_CHUNK_FORMAT1 : Constants.HEADER_CHUNK_FORMAT0;
1253 | this.data = trackType.concat(Utils.numberToBytes(numberOfTracks, 2), // two bytes long,
1254 | Constants.HEADER_CHUNK_DIVISION);
1255 | this.size = [0, 0, 0, this.data.length];
1256 | }
1257 | return Header;
1258 | }());
1259 |
1260 | /**
1261 | * Object that puts together tracks and provides methods for file output.
1262 | * @param {array|Track} tracks - A single {Track} object or an array of {Track} objects.
1263 | * @param {object} options - {middleC: 'C4'}
1264 | * @return {Writer}
1265 | */
1266 | var Writer = /** @class */ (function () {
1267 | function Writer(tracks, options) {
1268 | if (options === void 0) { options = {}; }
1269 | // Ensure tracks is an array
1270 | this.tracks = Utils.toArray(tracks);
1271 | this.options = options;
1272 | }
1273 | /**
1274 | * Builds array of data from chunkschunks.
1275 | * @return {array}
1276 | */
1277 | Writer.prototype.buildData = function () {
1278 | var _this = this;
1279 | var data = [];
1280 | data.push(new Header(this.tracks.length));
1281 | // For each track add final end of track event and build data
1282 | this.tracks.forEach(function (track) {
1283 | data.push(track.buildData(_this.options));
1284 | });
1285 | return data;
1286 | };
1287 | /**
1288 | * Builds the file into a Uint8Array
1289 | * @return {Uint8Array}
1290 | */
1291 | Writer.prototype.buildFile = function () {
1292 | var build = [];
1293 | // Data consists of chunks which consists of data
1294 | this.buildData().forEach(function (d) { return build = build.concat(d.type, d.size, d.data); });
1295 | return new Uint8Array(build);
1296 | };
1297 | /**
1298 | * Convert file buffer to a base64 string. Different methods depending on if browser or node.
1299 | * @return {string}
1300 | */
1301 | Writer.prototype.base64 = function () {
1302 | if (typeof btoa === 'function') {
1303 | var binary = '';
1304 | var bytes = this.buildFile();
1305 | var len = bytes.byteLength;
1306 | for (var i = 0; i < len; i++) {
1307 | binary += String.fromCharCode(bytes[i]);
1308 | }
1309 | return btoa(binary);
1310 | }
1311 | return Buffer.from(this.buildFile()).toString('base64');
1312 | };
1313 | /**
1314 | * Get the data URI.
1315 | * @return {string}
1316 | */
1317 | Writer.prototype.dataUri = function () {
1318 | return 'data:audio/midi;base64,' + this.base64();
1319 | };
1320 | /**
1321 | * Set option on instantiated Writer.
1322 | * @param {string} key
1323 | * @param {any} value
1324 | * @return {Writer}
1325 | */
1326 | Writer.prototype.setOption = function (key, value) {
1327 | this.options[key] = value;
1328 | return this;
1329 | };
1330 | /**
1331 | * Output to stdout
1332 | * @return {string}
1333 | */
1334 | Writer.prototype.stdout = function () {
1335 | return process.stdout.write(Buffer.from(this.buildFile()));
1336 | };
1337 | return Writer;
1338 | }());
1339 |
1340 | var main = {
1341 | Constants: Constants,
1342 | ControllerChangeEvent: ControllerChangeEvent,
1343 | CopyrightEvent: CopyrightEvent,
1344 | CuePointEvent: CuePointEvent,
1345 | EndTrackEvent: EndTrackEvent,
1346 | InstrumentNameEvent: InstrumentNameEvent,
1347 | KeySignatureEvent: KeySignatureEvent,
1348 | LyricEvent: LyricEvent,
1349 | MarkerEvent: MarkerEvent,
1350 | NoteOnEvent: NoteOnEvent,
1351 | NoteOffEvent: NoteOffEvent,
1352 | NoteEvent: NoteEvent,
1353 | PitchBendEvent: PitchBendEvent,
1354 | ProgramChangeEvent: ProgramChangeEvent,
1355 | TempoEvent: TempoEvent,
1356 | TextEvent: TextEvent,
1357 | TimeSignatureEvent: TimeSignatureEvent,
1358 | Track: Track,
1359 | TrackNameEvent: TrackNameEvent,
1360 | Utils: Utils,
1361 | VexFlow: VexFlow,
1362 | Writer: Writer
1363 | };
1364 |
1365 | return main;
1366 |
1367 | })();
1368 |
--------------------------------------------------------------------------------
/examples/chopin-prelude-e-minor.js:
--------------------------------------------------------------------------------
1 | var MidiWriter = require('..');
2 |
3 | var tracks = [];
4 | tracks[0] = new MidiWriter.Track();
5 |
6 | // You can chain track methods.
7 | tracks[0]
8 | .setTempo(60)
9 | .addEvent([
10 | // addEvent() accepts an array of event objects like this...
11 | new MidiWriter.ProgramChangeEvent({instrument : 1}),
12 | new MidiWriter.NoteEvent({pitch: ['B4'], duration: 'd2'}),
13 | new MidiWriter.NoteEvent({pitch: ['C5'], duration: '4'}),
14 | new MidiWriter.NoteEvent({pitch: ['B4'], duration: 'd2'}),
15 | new MidiWriter.NoteEvent({pitch: ['C5'], duration: '4'}),
16 | new MidiWriter.NoteEvent({pitch: ['B4'], duration: 'd2'}),
17 | new MidiWriter.NoteEvent({pitch: ['C5'], duration: '4'}),
18 | new MidiWriter.NoteEvent({pitch: ['B4'], duration: 'd2'}),
19 | new MidiWriter.NoteEvent({pitch: ['Bb4'], duration: '4'}),
20 | new MidiWriter.NoteEvent({pitch: ['A4'], duration: 'd2'}),
21 | new MidiWriter.NoteEvent({pitch: ['B4'], duration: '4'}),
22 | new MidiWriter.NoteEvent({pitch: ['A4'], duration: 'd2'}),
23 | new MidiWriter.NoteEvent({pitch: ['B4'], duration: '4'}),
24 | new MidiWriter.NoteEvent({pitch: ['A4'], duration: 'd2'}),
25 | new MidiWriter.NoteEvent({pitch: ['B4'], duration: 'd8'}),
26 | new MidiWriter.NoteEvent({pitch: ['A4'], duration: '16'}),
27 | new MidiWriter.NoteEvent({pitch: ['A4'], duration: 'd2'}),
28 | new MidiWriter.NoteEvent({pitch: ['Ab4'], duration: '2'}),
29 | new MidiWriter.NoteEvent({pitch: ['A4', 'B4', 'D5', 'C5', 'E4', 'A4'], duration: '8', sequential:true}),
30 | new MidiWriter.NoteEvent({pitch: ['Gb4'], duration: 'd2'}),
31 | new MidiWriter.NoteEvent({pitch: ['A4'], duration: '4'}),
32 | new MidiWriter.NoteEvent({pitch: ['Gb4'], duration: 'd2'}),
33 | new MidiWriter.NoteEvent({pitch: ['A4'], duration: '4'}),
34 | new MidiWriter.NoteEvent({pitch: ['G4', 'Gb4', 'C4', 'B3', 'Eb4', 'Gb4'], duration: '8', sequential:true}),
35 | new MidiWriter.NoteEvent({pitch: ['D5', 'C5', 'B4'], duration: '8t', sequential:true}),
36 | new MidiWriter.NoteEvent({pitch: ['B4'], duration: 'd2'}),
37 | new MidiWriter.NoteEvent({pitch: ['C5'], duration: '4'}),
38 | new MidiWriter.NoteEvent({pitch: ['B4'], duration: 'd2'}),
39 | new MidiWriter.NoteEvent({pitch: ['C5'], duration: '4'}),
40 | new MidiWriter.NoteEvent({pitch: ['B4'], duration: 'd2'}),
41 | new MidiWriter.NoteEvent({pitch: ['C5'], duration: '4'}),
42 | new MidiWriter.NoteEvent({pitch: ['B4'], duration: 'd8'}),
43 | new MidiWriter.NoteEvent({pitch: ['Bb4'], duration: '16'}),
44 | new MidiWriter.NoteEvent({pitch: ['Bb4'], duration: '4'}),
45 | new MidiWriter.NoteEvent({pitch: ['G5'], duration: '4'}),
46 | new MidiWriter.NoteEvent({pitch: ['Gb5'], duration: 'd8'}),
47 | new MidiWriter.NoteEvent({pitch: ['E5'], duration: '16'}),
48 | new MidiWriter.NoteEvent({pitch: ['E5', 'D#5', 'C6', 'D#5', 'D#5', 'E5', 'G5', 'B4'], duration: '8', sequential:true}),
49 | new MidiWriter.NoteEvent({pitch: ['D5', 'C5'], duration: '8', sequential:true}),
50 | new MidiWriter.NoteEvent({pitch: ['E5', 'E4', 'A4'], duration: '8t', sequential:true}),
51 | new MidiWriter.NoteEvent({pitch: ['Gb4'], duration: 'd4'}),
52 | new MidiWriter.NoteEvent({pitch: ['A4'], duration: '8'}),
53 | new MidiWriter.NoteEvent({pitch: ['Gb4'], duration: 'd2'}),
54 | new MidiWriter.NoteEvent({pitch: ['A4'], duration: '4'}),
55 | new MidiWriter.NoteEvent({pitch: ['Gb4'], duration: 'd2'}),
56 | new MidiWriter.NoteEvent({pitch: ['Gb4'], duration: 'd8'}),
57 | new MidiWriter.NoteEvent({pitch: ['E4'], duration: '16'}),
58 | new MidiWriter.NoteEvent({pitch: ['E4'], duration: 'd2'}),
59 | new MidiWriter.NoteEvent({pitch: ['Gb4'], duration: '4'}),
60 | new MidiWriter.NoteEvent({pitch: ['E4'], duration: 'd2'}),
61 | new MidiWriter.NoteEvent({pitch: ['Gb4'], duration: '4'}),
62 | new MidiWriter.NoteEvent({pitch: ['E4'], duration: '2'}),
63 | new MidiWriter.NoteEvent({pitch: ['C6', 'G5', 'D5', 'C5'], duration: '2', wait: '2'}),
64 | new MidiWriter.NoteEvent({pitch: ['C6', 'G5', 'D5', 'C5'], duration: '2'}),
65 | new MidiWriter.NoteEvent({pitch: ['C6', 'G5', 'E5', 'C5'], duration: '4'})
66 | ], function(index, event) {
67 | return {velocity: 100};
68 | }
69 | );
70 |
71 | // You can optionally pass a single event object to addEvent() if you wish
72 | tracks[1] = new MidiWriter.Track();
73 | tracks[1].addEvent(new MidiWriter.ProgramChangeEvent({instrument : 1}));
74 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['E4', 'B3', 'G3'], duration: '8', repeat: 8}));
75 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['E4', 'A3', 'F#3'], duration: '8', repeat: 4}));
76 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['D#4', 'A3', 'F#3'], duration: '8', repeat: 4}));
77 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['D#4', 'A3', 'F3'], duration: '8', repeat: 4}));
78 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['D4', 'A3', 'F3'], duration: '8', repeat: 2}));
79 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['D4', 'G#3', 'F3'], duration: '8', repeat: 2}));
80 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['D4', 'G#3', 'E3'], duration: '8', repeat: 4}));
81 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['D4', 'G3', 'E3'], duration: '8', repeat: 2}));
82 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['C#4', 'G3', 'E3'], duration: '8', repeat: 2}));
83 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['C4', 'G3', 'E3'], duration: '8', repeat: 4}));
84 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['C4', 'F#3', 'E3'], duration: '8', repeat: 8}));
85 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['C4', 'F#3', 'D#3'], duration: '8', repeat: 4}));
86 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['C4', 'F#3', 'D3'], duration: '8', repeat: 8}));
87 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['C4', 'F3', 'D3'], duration: '8', repeat: 4}));
88 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['B4', 'F3', 'D3'], duration: '8', repeat: 4}));
89 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['B4', 'E3', 'C3'], duration: '8', repeat: 2}));
90 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['A4', 'E3', 'C3'], duration: '8', repeat: 6}));
91 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['A4', 'E3', 'B3'], duration: '8', repeat: 2}));
92 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['A4', 'D#3', 'B3'], duration: '8', repeat: 2}));
93 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['A4', 'E3', 'C3'], duration: '8', repeat: 4}));
94 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['A4', 'D#3', 'B3'], duration: '8', repeat: 4}));
95 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['A4', 'E3', 'C3'], duration: '8', repeat: 4}));
96 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['A4', 'D#3', 'B3'], duration: '4'}));
97 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['E4', 'B3', 'G3'], duration: '8', wait: 'd2'}));
98 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['E4', 'B3', 'G3'], duration: '8'}));
99 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['E4', 'B3', 'G3'], duration: '8'}));
100 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['E4', 'B3', 'G3'], duration: '8'}));
101 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['E4', 'B3', 'G3'], duration: '8'}));
102 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['E4', 'B3', 'G3'], duration: '8'}));
103 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['E4', 'B3', 'G3'], duration: '8'}));
104 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['E4', 'B3', 'G3'], duration: '8'}));
105 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['E4', 'A3', 'F#3'], duration: '8', repeat: 4}));
106 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['D#4', 'A3', 'F3'], duration: '8', repeat: 4}));
107 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['D#4', 'G#3', 'F3'], duration: '8', repeat: 2}));
108 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['D4', 'G#3', 'F3'], duration: '8', repeat: 2}));
109 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['D4', 'G3', 'E3'], duration: '8', repeat: 4}));
110 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['D4', 'G3', 'E3'], duration: '8', repeat: 2}));
111 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['C#4', 'G3', 'E3'], duration: '8', repeat: 2}));
112 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['A#4', 'E3', 'C#3'], duration: '8', repeat: 2}));
113 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['A4', 'E3', 'C3'], duration: '8', repeat: 2}));
114 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['B3', 'B2'], duration: '8'}));
115 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['A5', 'F#5', 'C4', 'A4'], duration: '8', repeat: 3}));
116 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['F#5', 'D#5', 'B4', 'G4'], duration: '8'}));
117 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['E5', 'B4', 'G4'], duration: '8', repeat: 3}));
118 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['E5', 'C4', 'A4'], duration: '8', repeat: 2}));
119 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['A3'], duration: '8'}));
120 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['C4', 'F#3', 'E3'], duration: '8'}));
121 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['B4', 'E3', 'B3'], duration: '8', repeat: 2}));
122 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['A4', 'E3', 'C3'], duration: '8', repeat: 2}));
123 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['B3', 'E3', 'B2'], duration: '8', repeat: 4}));
124 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['A3', 'E3', 'C3'], duration: '8', repeat: 4}));
125 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['B3', 'E3', 'B2'], duration: '8', repeat: 4}));
126 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['B3', 'D#3', 'B3'], duration: '8', repeat: 2}));
127 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['A3', 'D#3', 'B3'], duration: '8', repeat: 2}));
128 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['G3', 'E3', 'C3'], duration: '8', repeat: 4}));
129 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Bb3', 'E3', 'C3'], duration: '8', repeat: 2}));
130 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['A3', 'E3', 'C3'], duration: '8', repeat: 2}));
131 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['A3', 'E3', 'B3'], duration: '8', repeat: 2}));
132 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['G#3', 'E3', 'B3'], duration: '8', repeat: 2}));
133 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['G3', 'E3', 'B3'], duration: '8', repeat: 4}));
134 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['G3', 'C3', 'A#3'], duration: '2'}));
135 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['B3', 'B2'], duration: '2', wait: '2'}));
136 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['B3', 'F#3', 'B2'], duration: '2'}));
137 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['E2', 'E1'], duration: '1'}));
138 |
139 | var write = new MidiWriter.Writer(tracks);
140 | console.log(write.dataUri());
141 | //write.stdout();
142 |
143 | module.exports = write;
--------------------------------------------------------------------------------
/examples/hot-cross-buns.js:
--------------------------------------------------------------------------------
1 | var MidiWriter = require('..');
2 |
3 | var track = new MidiWriter.Track();
4 |
5 | track.addEvent([
6 | new MidiWriter.NoteEvent({pitch: ['E4','D4'], duration: '4'}),
7 | new MidiWriter.NoteEvent({pitch: 'C4', duration: '2'}),
8 | new MidiWriter.NoteEvent({pitch: ['E4','D4'], duration: '4'}),
9 | new MidiWriter.NoteEvent({pitch: 'C4', duration: '2'}),
10 | new MidiWriter.NoteEvent({pitch: ['C4', 'C4', 'C4', 'C4', 'D4', 'D4', 'D4', 'D4'], duration: '8'}),
11 | new MidiWriter.NoteEvent({pitch: ['E4','D4'], duration: '4'}),
12 | new MidiWriter.NoteEvent({pitch: 'C4', duration: '2'})
13 | ], function(event, index) {
14 | return {sequential:true};
15 | }
16 | );
17 |
18 |
19 | var write = new MidiWriter.Writer(track);
20 | //console.log(track);
21 | //console.log(write.base64())
22 | console.log(write.dataUri());
23 |
24 | module.exports = write;
--------------------------------------------------------------------------------
/examples/mauro.giuliani-op.47-main-theme.js:
--------------------------------------------------------------------------------
1 | /**
2 | VARIATIONS
3 | Faciles
4 | Pour la Guitare
5 | Sur un air National Autrichien
6 | Composées
7 | PAR
8 | MAURO GIULIANI
9 | Oev: 47 Prix 3f
10 | à Paris
11 | chez RICHAULT...
12 | Boulevard Poissonièr No 16, au 1er
13 | [N.] 1303. R.
14 |
15 | This is the main theme, the pubblication comes along with 12
16 | variations, it´s a pretty good start for classical guitarists.
17 | **/
18 | var MidiWriter = require('..');
19 | var tracks = [];
20 |
21 | tracks[0] = new MidiWriter.Track();
22 | tracks[0].setTimeSignature(3, 4);
23 | tracks[0].setTempo(100);
24 |
25 | var notes;
26 |
27 | // melody
28 | tracks[1] = new MidiWriter.Track();
29 | notes = new MidiWriter.NoteEvent({pitch:['C#5', 'E5'], duration: '2'});
30 | tracks[1].addEvent(notes);
31 | notes = new MidiWriter.NoteEvent({pitch:['C#5', 'E5'], duration: '4'});
32 | tracks[1].addEvent(notes);
33 | notes = new MidiWriter.NoteEvent({pitch:['C#5', 'E5'], duration: '2'});
34 | tracks[1].addEvent(notes);
35 | notes = new MidiWriter.NoteEvent({pitch:['A4', 'C#5'], duration: '4'});
36 | tracks[1].addEvent(notes);
37 | notes = new MidiWriter.NoteEvent({pitch:['B4', 'D5'], duration: '2'});
38 | tracks[1].addEvent(notes);
39 | notes = new MidiWriter.NoteEvent({pitch:['G#4', 'E5'], duration: '4'});
40 | tracks[1].addEvent(notes);
41 | notes = new MidiWriter.NoteEvent({pitch:['A4', 'C#5'], duration: '2'});
42 | tracks[1].addEvent(notes);
43 | notes = new MidiWriter.NoteEvent({pitch:['A4'], duration: '4'});
44 | tracks[1].addEvent(notes);
45 | notes = new MidiWriter.NoteEvent({pitch:['C#5', 'E5'], duration: '2'});
46 | tracks[1].addEvent(notes);
47 | notes = new MidiWriter.NoteEvent({pitch:['C#5', 'E5'], duration: '4'});
48 | tracks[1].addEvent(notes);
49 | notes = new MidiWriter.NoteEvent({pitch:['C#5', 'E5'], duration: '2'});
50 | tracks[1].addEvent(notes);
51 | notes = new MidiWriter.NoteEvent({pitch:['A4', 'C#5'], duration: '4'});
52 | tracks[1].addEvent(notes);
53 | notes = new MidiWriter.NoteEvent({pitch:['B4', 'D5'], duration: '2'});
54 | tracks[1].addEvent(notes);
55 | notes = new MidiWriter.NoteEvent({pitch:['G#4', 'E5'], duration: '4'});
56 | tracks[1].addEvent(notes);
57 | notes = new MidiWriter.NoteEvent({pitch:['A4', 'C#5'], duration: '2'});
58 | tracks[1].addEvent(notes);
59 | // note how the previous rest is handled: it became the wait
60 | notes = new MidiWriter.NoteEvent({wait: '4', pitch:['E5', 'E5'], duration: '4'});
61 | tracks[1].addEvent(notes);
62 | notes = new MidiWriter.NoteEvent({pitch:['D#5', 'F#5'], duration: '4'});
63 | tracks[1].addEvent(notes);
64 | notes = new MidiWriter.NoteEvent({pitch:['D5', 'G#5'], duration: '4'});
65 | tracks[1].addEvent(notes);
66 | notes = new MidiWriter.NoteEvent({pitch:['C#5', 'A5'], duration: '2'});
67 | tracks[1].addEvent(notes);
68 | notes = new MidiWriter.NoteEvent({pitch:['E5'], duration: '4'});
69 | tracks[1].addEvent(notes);
70 | notes = new MidiWriter.NoteEvent({pitch:['E5', 'E5'], duration: '4'});
71 | tracks[1].addEvent(notes);
72 | notes = new MidiWriter.NoteEvent({pitch:['D#5', 'F#5'], duration: '4'});
73 | tracks[1].addEvent(notes);
74 | notes = new MidiWriter.NoteEvent({pitch:['D5', 'G#5'], duration: '4'});
75 | tracks[1].addEvent(notes);
76 | notes = new MidiWriter.NoteEvent({pitch:['C#5', 'A5'], duration: '2'});
77 | tracks[1].addEvent(notes);
78 | notes = new MidiWriter.NoteEvent({wait: '4', pitch:['C#5', 'E5'], duration: '2'});
79 | tracks[1].addEvent(notes);
80 | notes = new MidiWriter.NoteEvent({pitch:['C#5', 'E5'], duration: '4'});
81 | tracks[1].addEvent(notes);
82 | notes = new MidiWriter.NoteEvent({pitch:['C#5', 'E5'], duration: '4'});
83 | tracks[1].addEvent(notes);
84 | notes = new MidiWriter.NoteEvent({pitch:['A5'], duration: '4'});
85 | tracks[1].addEvent(notes);
86 | notes = new MidiWriter.NoteEvent({pitch:['A4', 'C#5'], duration: '4'});
87 | tracks[1].addEvent(notes);
88 | notes = new MidiWriter.NoteEvent({pitch:['C#5', 'E5'], duration: '4'});
89 | tracks[1].addEvent(notes);
90 | notes = new MidiWriter.NoteEvent({pitch:['B4', 'D5'], duration: '4'});
91 | tracks[1].addEvent(notes);
92 | notes = new MidiWriter.NoteEvent({pitch:['G#4', 'B4'], duration: '4'});
93 | tracks[1].addEvent(notes);
94 | notes = new MidiWriter.NoteEvent({pitch:['A4'], duration: '2'});
95 | tracks[1].addEvent(notes);
96 |
97 | // bass
98 | tracks[2] = new MidiWriter.Track();
99 | notes = new MidiWriter.NoteEvent({pitch:['A3'], duration: '2'});
100 | tracks[2].addEvent(notes);
101 | notes = new MidiWriter.NoteEvent({wait: '4', pitch: ['A3'], duration: '2'});
102 | tracks[2].addEvent(notes);
103 | notes = new MidiWriter.NoteEvent({wait: '4', pitch: ['E3'], duration: '2'});
104 | tracks[2].addEvent(notes);
105 | notes = new MidiWriter.NoteEvent({wait: '4', pitch: ['A3'], duration: '2'});
106 | tracks[2].addEvent(notes);
107 | notes = new MidiWriter.NoteEvent({wait: '4', pitch: ['A3'], duration: '2'});
108 | tracks[2].addEvent(notes);
109 | notes = new MidiWriter.NoteEvent({wait: '4', pitch: ['A3'], duration: '2'});
110 | tracks[2].addEvent(notes);
111 | notes = new MidiWriter.NoteEvent({wait: '4', pitch: ['E3'], duration: '2'});
112 | tracks[2].addEvent(notes);
113 | notes = new MidiWriter.NoteEvent({wait: '4', pitch: ['A3'], duration: '2'});
114 | tracks[2].addEvent(notes);
115 | notes = new MidiWriter.NoteEvent({wait: '4', pitch: ['E3'], duration: '2'});
116 | tracks[2].addEvent(notes);
117 | notes = new MidiWriter.NoteEvent({wait: '4', pitch: ['A3'], duration: '2'});
118 | tracks[2].addEvent(notes);
119 | notes = new MidiWriter.NoteEvent({wait: '4', pitch: ['E3'], duration: '2'});
120 | tracks[2].addEvent(notes);
121 | notes = new MidiWriter.NoteEvent({wait: '4', pitch: ['A3'], duration: '2'});
122 | tracks[2].addEvent(notes);
123 | notes = new MidiWriter.NoteEvent({wait: '4', pitch: ['A3'], duration: '2'});
124 | tracks[2].addEvent(notes);
125 | notes = new MidiWriter.NoteEvent({wait: '4', pitch: ['A3'], duration: '2'});
126 | tracks[2].addEvent(notes);
127 | notes = new MidiWriter.NoteEvent({wait: '4', pitch: ['E3'], duration: '2'});
128 | tracks[2].addEvent(notes);
129 | notes = new MidiWriter.NoteEvent({wait: '4', pitch: ['A3'], duration: '2'});
130 | tracks[2].addEvent(notes);
131 |
132 |
133 | var write = new MidiWriter.Writer(tracks);
134 |
135 | console.log(write.dataUri());
136 | module.exports = write;
--------------------------------------------------------------------------------
/examples/notes-by-start-tick.js:
--------------------------------------------------------------------------------
1 | var MidiWriter = require('..');
2 |
3 | var track = new MidiWriter.Track();
4 |
5 | track.addEvent([
6 | new MidiWriter.NoteEvent({
7 | pitch: 'C4',
8 | duration: 'T50',
9 | tick: 0
10 | }),
11 | new MidiWriter.NoteEvent({
12 | pitch: 'E4',
13 | duration: 'T50',
14 | tick: 50
15 | }),
16 | new MidiWriter.NoteEvent({
17 | pitch: ['G4', 'B4'],
18 | duration: 'T50',
19 | tick: 100
20 | }),
21 | new MidiWriter.NoteEvent({
22 | pitch: 'C5',
23 | duration: 'T50',
24 | tick: 150
25 | }),
26 | new MidiWriter.NoteEvent({
27 | pitch: 'D5',
28 | duration: 'T50',
29 | tick: 200
30 | }),
31 | new MidiWriter.NoteEvent({
32 | pitch: 'F5',
33 | duration: 'T50',
34 | tick: 250
35 | }),
36 | new MidiWriter.NoteEvent({
37 | pitch: 'A5',
38 | duration: 'T50',
39 | tick: 300
40 | }),
41 | ]);
42 |
43 | var write = new MidiWriter.Writer(track);
44 | console.log(write.dataUri());
45 | module.exports = write;
46 |
--------------------------------------------------------------------------------
/examples/zelda-main-theme.js:
--------------------------------------------------------------------------------
1 | var MidiWriter = require('..');
2 | var tracks = [];
3 |
4 | // Lead Instrument
5 | tracks[0] = new MidiWriter.Track();
6 | tracks[0].addEvent(new MidiWriter.ProgramChangeEvent({instrument : 1}));
7 |
8 | // Bass
9 | tracks[1] = new MidiWriter.Track();
10 | tracks[1].addEvent(new MidiWriter.ProgramChangeEvent({instrument : 1}));
11 |
12 | // Intro
13 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['Bb4'], duration: '2', velocity:100}));
14 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['Bb4'], duration: '8', velocity:100}));
15 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['F4'], duration: '8', velocity:100}));
16 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['F4'], duration: '8', velocity:100}));
17 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['Bb4'], duration: '8', velocity:100}));
18 |
19 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['Ab4'], duration: '16', velocity:100}));
20 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['Gb4'], duration: '16', velocity:100}));
21 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['Ab4'], duration: 'd2', velocity:100}));
22 |
23 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['Bb4'], duration: '2', wait:'8', velocity:100}));
24 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['Bb4'], duration: '8', velocity:100}));
25 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['F4'], duration: '8', velocity:100}));
26 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['F4'], duration: '8', velocity:100}));
27 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['Bb4'], duration: '8', velocity:100}));
28 |
29 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['A4'], duration: '16', velocity:100}));
30 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['G4'], duration: '16', velocity:100}));
31 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['A4'], duration: 'd4', velocity:100}));
32 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['A4'], duration: '1', wait:'2', velocity:1}));
33 |
34 |
35 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Gb2'], duration: '4', velocity:50}));
36 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['F3'], duration: '4', velocity:50}));
37 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Bb3'], duration: '2', velocity:50}));
38 |
39 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['F2'], duration: '4', velocity:50}));
40 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Eb3'], duration: '4', velocity:50}));
41 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Ab3'], duration: '2', velocity:50}));
42 |
43 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Eb2'], duration: '4', velocity:50}));
44 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Db3'], duration: '4', velocity:50}));
45 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Bb3'], duration: '2', velocity:50}));
46 |
47 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Gb2'], duration: '4', velocity:50}));
48 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['F3'], duration: '4', velocity:50}));
49 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Bb3'], duration: '2', velocity:50}));
50 |
51 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Bb2'], duration: '8', velocity:50}));
52 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Bb2'], duration: '16', velocity:50}));
53 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['F2'], duration: '16', velocity:50}));
54 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Bb2'], duration: '8', velocity:50}));
55 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Bb2'], duration: '16', velocity:50}));
56 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['F2'], duration: '16', velocity:50}));
57 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Bb2'], duration: '8', velocity:50}));
58 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Bb2'], duration: '16', velocity:50}));
59 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['F2'], duration: '16', velocity:50}));
60 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Bb2'], duration: '16', velocity:50}));
61 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['F2'], duration: '16', velocity:50}));
62 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Bb2'], duration: '16', velocity:50}));
63 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['F2'], duration: '16', velocity:50}));
64 |
65 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Bb2'], duration: '8', velocity:50}));
66 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Bb2'], duration: '16', velocity:50}));
67 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['F2'], duration: '16', velocity:50}));
68 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Bb2'], duration: '8', velocity:50}));
69 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Bb2'], duration: '16', velocity:50}));
70 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['F2'], duration: '16', velocity:50}));
71 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Bb2'], duration: '8', velocity:50}));
72 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Bb2'], duration: '16', velocity:50}));
73 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['F2'], duration: '16', velocity:50}));
74 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Bb2'], duration: '16', velocity:50}));
75 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['F2'], duration: '16', velocity:50}));
76 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Bb2'], duration: '16', velocity:50}));
77 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['F2'], duration: '16', velocity:50}));
78 |
79 | // Main theme part 1
80 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['Bb4'], duration: '4', wait:'1', velocity:100}));
81 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['F4'], duration: 'd4', velocity:100}));
82 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['Bb4'], duration: '8', velocity:100}));
83 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['Bb4', 'C5', 'D5', 'Eb5'], duration: '16', sequential: 'true', velocity:100}));
84 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['F5'], duration: '2', velocity:100}));
85 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['F5', 'F5'], duration: '8', sequential: 'true', wait:'8', velocity:100}));
86 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['Gb5', 'Ab5'], duration: '16', sequential: 'true', velocity:100}));
87 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['Bb5'], duration: '2', velocity:100}));
88 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['Bb5', 'Bb5'], duration: '8', sequential: 'true', wait:'8', velocity:100}));
89 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['Ab5', 'Gb5'], duration: '16', sequential: 'true', velocity:100}));
90 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['Ab5'], duration: 'd8', sequential: 'true', velocity:100}));
91 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['Gb5'], duration: '16', sequential: 'true', velocity:100}));
92 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['F5'], duration: '2', velocity:100}));
93 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['F5'], duration: '4', velocity:100}));
94 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['Eb5'], duration: 'd8', velocity:100}));
95 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['F5'], duration: '16', velocity:100}));
96 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['Gb5'], duration: '2', velocity:100}));
97 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['F5', 'Eb5'], duration: '8', sequential: 'true', velocity:100}));
98 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['Db5'], duration: 'd8', velocity:100}));
99 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['Eb5'], duration: '16', velocity:100}));
100 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['F5'], duration: '2', velocity:100}));
101 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['Eb5', 'Db5'], duration: '8', sequential: 'true', velocity:100}));
102 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['C5'], duration: 'd8', velocity:100}));
103 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['D5'], duration: '16', velocity:100}));
104 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['E5'], duration: '2', velocity:100}));
105 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['G5'], duration: '4', velocity:100}));
106 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['F5'], duration: '8', velocity:100}));
107 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['F4', 'F4'], duration: '16', sequential: 'true', velocity:100}));
108 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['F4'], duration: '8', velocity:100}));
109 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['F4', 'F4'], duration: '16', sequential: 'true', velocity:100}));
110 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['F4'], duration: '8', velocity:100}));
111 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['F4', 'F4'], duration: '16', sequential: 'true', velocity:100}));
112 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['F4'], duration: '8', velocity:100}));
113 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['F4'], duration: '8', velocity:100}));
114 |
115 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Bb2'], duration: '8', velocity:50}));
116 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Bb2'], duration: '16', velocity:50}));
117 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['F2'], duration: '16', velocity:50}));
118 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Bb2'], duration: '8', velocity:50}));
119 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Bb2'], duration: '16', velocity:50}));
120 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['F2'], duration: '16', velocity:50}));
121 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Bb2'], duration: '8', velocity:50}));
122 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Bb2'], duration: '16', velocity:50}));
123 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['F2'], duration: '16', velocity:50}));
124 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Bb2'], duration: '16', velocity:50}));
125 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['F2'], duration: '16', velocity:50}));
126 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Bb2'], duration: '16', velocity:50}));
127 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['F2'], duration: '16', velocity:50}));
128 |
129 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Ab2'], duration: '8', velocity:50}));
130 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Ab2'], duration: '16', velocity:50}));
131 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Eb2'], duration: '16', velocity:50}));
132 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Ab2'], duration: '8', velocity:50}));
133 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Ab2'], duration: '16', velocity:50}));
134 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Eb2'], duration: '16', velocity:50}));
135 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Ab2'], duration: '8', velocity:50}));
136 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Ab2'], duration: '16', velocity:50}));
137 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Eb2'], duration: '16', velocity:50}));
138 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Ab2'], duration: '16', velocity:50}));
139 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Eb2'], duration: '16', velocity:50}));
140 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Ab2'], duration: '16', velocity:50}));
141 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Eb2'], duration: '16', velocity:50}));
142 |
143 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Gb2'], duration: '8', velocity:50}));
144 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Gb2'], duration: '16', velocity:50}));
145 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Db2'], duration: '16', velocity:50}));
146 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Gb2'], duration: '8', velocity:50}));
147 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Gb2'], duration: '16', velocity:50}));
148 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Db2'], duration: '16', velocity:50}));
149 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Gb2'], duration: '8', velocity:50}));
150 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Gb2'], duration: '16', velocity:50}));
151 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Db2'], duration: '16', velocity:50}));
152 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Gb2'], duration: '16', velocity:50}));
153 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Db2'], duration: '16', velocity:50}));
154 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Gb2'], duration: '16', velocity:50}));
155 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Db2'], duration: '16', velocity:50}));
156 |
157 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Db3'], duration: '8', velocity:50}));
158 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Db3'], duration: '16', velocity:50}));
159 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Ab2'], duration: '16', velocity:50}));
160 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Db3'], duration: '8', velocity:50}));
161 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Db3'], duration: '16', velocity:50}));
162 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Ab2'], duration: '16', velocity:50}));
163 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Db3'], duration: '8', velocity:50}));
164 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Db3'], duration: '16', velocity:50}));
165 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Ab2'], duration: '16', velocity:50}));
166 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Db3'], duration: '16', velocity:50}));
167 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Ab2'], duration: '16', velocity:50}));
168 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Db3'], duration: '16', velocity:50}));
169 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Ab2'], duration: '16', velocity:50}));
170 |
171 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['B2'], duration: '8', velocity:50}));
172 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['B2'], duration: '16', velocity:50}));
173 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Gb2'], duration: '16', velocity:50}));
174 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['B2'], duration: '8', velocity:50}));
175 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['B2'], duration: '16', velocity:50}));
176 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Gb2'], duration: '16', velocity:50}));
177 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['B2'], duration: '8', velocity:50}));
178 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['B2'], duration: '16', velocity:50}));
179 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Gb2'], duration: '16', velocity:50}));
180 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['B2'], duration: '16', velocity:50}));
181 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Gb2'], duration: '16', velocity:50}));
182 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['B2'], duration: '16', velocity:50}));
183 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Gb2'], duration: '16', velocity:50}));
184 |
185 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Bb2'], duration: '8', velocity:50}));
186 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Bb2'], duration: '16', velocity:50}));
187 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['F2'], duration: '16', velocity:50}));
188 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Bb2'], duration: '8', velocity:50}));
189 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Bb2'], duration: '16', velocity:50}));
190 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['F2'], duration: '16', velocity:50}));
191 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Bb2'], duration: '8', velocity:50}));
192 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Bb2'], duration: '16', velocity:50}));
193 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['F2'], duration: '16', velocity:50}));
194 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Bb2'], duration: '16', velocity:50}));
195 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['F2'], duration: '16', velocity:50}));
196 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Bb2'], duration: '16', velocity:50}));
197 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['F2'], duration: '16', velocity:50}));
198 |
199 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['C3'], duration: '8', velocity:50}));
200 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['C3'], duration: '16', velocity:50}));
201 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['G2'], duration: '16', velocity:50}));
202 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['C3'], duration: '8', velocity:50}));
203 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['C3'], duration: '16', velocity:50}));
204 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['G2'], duration: '16', velocity:50}));
205 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['C3'], duration: '8', velocity:50}));
206 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['C3'], duration: '16', velocity:50}));
207 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['G2'], duration: '16', velocity:50}));
208 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['C3'], duration: '16', velocity:50}));
209 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['G2'], duration: '16', velocity:50}));
210 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['C3'], duration: '16', velocity:50}));
211 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['G2'], duration: '16', velocity:50}));
212 |
213 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['F2'], duration: '8', velocity:50}));
214 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['A3'], duration: '16', velocity:50}));
215 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['A3'], duration: '16', velocity:50}));
216 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Ab3'], duration: '8', velocity:50}));
217 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Ab3'], duration: '16', velocity:50}));
218 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Ab3'], duration: '16', velocity:50}));
219 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['G3'], duration: '8', velocity:50}));
220 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['G3'], duration: '16', velocity:50}));
221 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['G3'], duration: '16', velocity:50}));
222 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Gb3'], duration: '8', velocity:50}));
223 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['F2'], duration: '8', velocity:50}));
224 |
225 | // Main theme part 2
226 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['Bb4'], duration: '4', velocity:100}));
227 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['F4'], duration: 'd4', velocity:100}));
228 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['Bb4'], duration: '8', velocity:100}));
229 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['Bb4', 'C5', 'D5', 'Eb5'], duration: '16', sequential: 'true', velocity:100}));
230 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['F5'], duration: '2', velocity:100}));
231 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['F5', 'F5'], duration: '8', sequential: 'true', wait:'8', velocity:100}));
232 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['Gb5', 'Ab5'], duration: '16', sequential: 'true', velocity:100}));
233 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['Bb5'], duration: 'd2', velocity:100}));
234 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['Db6'], duration: '4', velocity:100}));
235 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['C6'], duration: '4', velocity:100}));
236 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['A5'], duration: '2', velocity:100}));
237 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['F5'], duration: '4', velocity:100}));
238 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['Gb5'], duration: 'd2', velocity:100}));
239 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['Bb5'], duration: '4', velocity:100}));
240 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['A5'], duration: '4', velocity:100}));
241 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['F5'], duration: '2', velocity:100}));
242 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['F5'], duration: '4', velocity:100}));
243 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['Gb5'], duration: 'd2', velocity:100}));
244 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['Bb5'], duration: '4', velocity:100}));
245 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['A5'], duration: '4', velocity:100}));
246 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['F5'], duration: '2', velocity:100}));
247 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['D5'], duration: '4', velocity:100}));
248 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['Eb5'], duration: 'd2', velocity:100}));
249 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['Gb5'], duration: '4', velocity:100}));
250 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['F5'], duration: '4', velocity:100}));
251 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['Db5'], duration: '2', velocity:100}));
252 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['Bb4'], duration: '4', velocity:100}));
253 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['C5'], duration: 'd8', velocity:100}));
254 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['D5'], duration: '16', velocity:100}));
255 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['E5'], duration: '2', velocity:100}));
256 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['G5'], duration: '4', velocity:100}));
257 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['F5'], duration: '8', velocity:100}));
258 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['F4', 'F4'], duration: '16', sequential: 'true', velocity:100}));
259 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['F4'], duration: '8', velocity:100}));
260 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['F4', 'F4'], duration: '16', sequential: 'true', velocity:100}));
261 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['F4'], duration: '8', velocity:100}));
262 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['F4', 'F4'], duration: '16', sequential: 'true', velocity:100}));
263 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['F4'], duration: '8', velocity:100}));
264 | tracks[0].addEvent(new MidiWriter.NoteEvent({pitch: ['F4'], duration: '8', velocity:100}));
265 |
266 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Bb2'], duration: '8', velocity:50}));
267 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Bb2'], duration: '16', velocity:50}));
268 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['F2'], duration: '16', velocity:50}));
269 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Bb2'], duration: '8', velocity:50}));
270 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Bb2'], duration: '16', velocity:50}));
271 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['F2'], duration: '16', velocity:50}));
272 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Bb2'], duration: '8', velocity:50}));
273 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Bb2'], duration: '16', velocity:50}));
274 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['F2'], duration: '16', velocity:50}));
275 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Bb2'], duration: '16', velocity:50}));
276 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['F2'], duration: '16', velocity:50}));
277 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Bb2'], duration: '16', velocity:50}));
278 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['F2'], duration: '16', velocity:50}));
279 |
280 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Ab2'], duration: '8', velocity:50}));
281 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Ab2'], duration: '16', velocity:50}));
282 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Eb2'], duration: '16', velocity:50}));
283 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Ab2'], duration: '8', velocity:50}));
284 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Ab2'], duration: '16', velocity:50}));
285 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Eb2'], duration: '16', velocity:50}));
286 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Ab2'], duration: '8', velocity:50}));
287 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Ab2'], duration: '16', velocity:50}));
288 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Eb2'], duration: '16', velocity:50}));
289 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Ab2'], duration: '16', velocity:50}));
290 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Eb2'], duration: '16', velocity:50}));
291 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Ab2'], duration: '16', velocity:50}));
292 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Eb2'], duration: '16', velocity:50}));
293 |
294 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Gb2'], duration: '8', velocity:50}));
295 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Gb2'], duration: '16', velocity:50}));
296 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Gb2'], duration: '16', velocity:50}));
297 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Gb2'], duration: '8', velocity:50}));
298 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Gb2'], duration: '16', velocity:50}));
299 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Gb2'], duration: '16', velocity:50}));
300 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Gb2'], duration: '8', velocity:50}));
301 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Gb2'], duration: '16', velocity:50}));
302 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Gb2'], duration: '16', velocity:50}));
303 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Gb2'], duration: '16', velocity:50}));
304 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Gb2'], duration: '16', velocity:50}));
305 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Gb2'], duration: '16', velocity:50}));
306 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Gb2'], duration: '16', velocity:50}));
307 |
308 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['F2'], duration: '8', velocity:50}));
309 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['F2'], duration: '16', velocity:50}));
310 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['F2'], duration: '16', velocity:50}));
311 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['F2'], duration: '8', velocity:50}));
312 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['F2'], duration: '16', velocity:50}));
313 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['F2'], duration: '16', velocity:50}));
314 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['F2'], duration: '8', velocity:50}));
315 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['F2'], duration: '16', velocity:50}));
316 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['F2'], duration: '16', velocity:50}));
317 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['F2'], duration: '16', velocity:50}));
318 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['F2'], duration: '16', velocity:50}));
319 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['F2'], duration: '16', velocity:50}));
320 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['F2'], duration: '16', velocity:50}));
321 |
322 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['E2'], duration: '8', velocity:50}));
323 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['E2'], duration: '16', velocity:50}));
324 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['E2'], duration: '16', velocity:50}));
325 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['E2'], duration: '8', velocity:50}));
326 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['E2'], duration: '16', velocity:50}));
327 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['E2'], duration: '16', velocity:50}));
328 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['E2'], duration: '8', velocity:50}));
329 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['E2'], duration: '16', velocity:50}));
330 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['E2'], duration: '16', velocity:50}));
331 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['E2'], duration: '16', velocity:50}));
332 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['E2'], duration: '16', velocity:50}));
333 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['E2'], duration: '16', velocity:50}));
334 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['E2'], duration: '16', velocity:50}));
335 |
336 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['F2'], duration: '8', velocity:50}));
337 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['F2'], duration: '16', velocity:50}));
338 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['F2'], duration: '16', velocity:50}));
339 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['F2'], duration: '8', velocity:50}));
340 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['F2'], duration: '16', velocity:50}));
341 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['F2'], duration: '16', velocity:50}));
342 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['F2'], duration: '8', velocity:50}));
343 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['F2'], duration: '16', velocity:50}));
344 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['F2'], duration: '16', velocity:50}));
345 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['F2'], duration: '16', velocity:50}));
346 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['F2'], duration: '16', velocity:50}));
347 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['F2'], duration: '16', velocity:50}));
348 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['F2'], duration: '16', velocity:50}));
349 |
350 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['E2'], duration: '8', velocity:50}));
351 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['E2'], duration: '16', velocity:50}));
352 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['E2'], duration: '16', velocity:50}));
353 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['E2'], duration: '8', velocity:50}));
354 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['E2'], duration: '16', velocity:50}));
355 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['E2'], duration: '16', velocity:50}));
356 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['E2'], duration: '8', velocity:50}));
357 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['E2'], duration: '16', velocity:50}));
358 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['E2'], duration: '16', velocity:50}));
359 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['E2'], duration: '16', velocity:50}));
360 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['E2'], duration: '16', velocity:50}));
361 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['E2'], duration: '16', velocity:50}));
362 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['E2'], duration: '16', velocity:50}));
363 |
364 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['F2'], duration: '8', velocity:50}));
365 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['F2'], duration: '16', velocity:50}));
366 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['F2'], duration: '16', velocity:50}));
367 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['F2'], duration: '8', velocity:50}));
368 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['F2'], duration: '16', velocity:50}));
369 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['F2'], duration: '16', velocity:50}));
370 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['F2'], duration: '8', velocity:50}));
371 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['F2'], duration: '16', velocity:50}));
372 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['F2'], duration: '16', velocity:50}));
373 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['F2'], duration: '16', velocity:50}));
374 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['F2'], duration: '16', velocity:50}));
375 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['F2'], duration: '16', velocity:50}));
376 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['F2'], duration: '16', velocity:50}));
377 |
378 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['B2'], duration: '8', velocity:50}));
379 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['B2'], duration: '16', velocity:50}));
380 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['B2'], duration: '16', velocity:50}));
381 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['B2'], duration: '8', velocity:50}));
382 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['B2'], duration: '16', velocity:50}));
383 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['B2'], duration: '16', velocity:50}));
384 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['B2'], duration: '8', velocity:50}));
385 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['B2'], duration: '16', velocity:50}));
386 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['B2'], duration: '16', velocity:50}));
387 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['B2'], duration: '16', velocity:50}));
388 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['B2'], duration: '16', velocity:50}));
389 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['B2'], duration: '16', velocity:50}));
390 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['B2'], duration: '16', velocity:50}));
391 |
392 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Bb2'], duration: '8', velocity:50}));
393 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Bb2'], duration: '16', velocity:50}));
394 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Bb2'], duration: '16', velocity:50}));
395 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Bb2'], duration: '8', velocity:50}));
396 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Bb2'], duration: '16', velocity:50}));
397 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Bb2'], duration: '16', velocity:50}));
398 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Bb2'], duration: '8', velocity:50}));
399 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Bb2'], duration: '16', velocity:50}));
400 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Bb2'], duration: '16', velocity:50}));
401 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Bb2'], duration: '16', velocity:50}));
402 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Bb2'], duration: '16', velocity:50}));
403 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Bb2'], duration: '16', velocity:50}));
404 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Bb2'], duration: '16', velocity:50}));
405 |
406 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['C3'], duration: '8', velocity:50}));
407 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['C3'], duration: '16', velocity:50}));
408 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['C3'], duration: '16', velocity:50}));
409 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['C3'], duration: '8', velocity:50}));
410 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['C3'], duration: '16', velocity:50}));
411 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['C3'], duration: '16', velocity:50}));
412 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['C3'], duration: '8', velocity:50}));
413 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['C3'], duration: '16', velocity:50}));
414 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['C3'], duration: '16', velocity:50}));
415 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['C3'], duration: '16', velocity:50}));
416 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['C3'], duration: '16', velocity:50}));
417 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['C3'], duration: '16', velocity:50}));
418 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['C3'], duration: '16', velocity:50}));
419 |
420 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['F2'], duration: '8', velocity:50}));
421 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['A3'], duration: '16', velocity:50}));
422 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['A3'], duration: '16', velocity:50}));
423 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Ab3'], duration: '8', velocity:50}));
424 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Ab3'], duration: '16', velocity:50}));
425 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Ab3'], duration: '16', velocity:50}));
426 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['G3'], duration: '8', velocity:50}));
427 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['G3'], duration: '16', velocity:50}));
428 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['G3'], duration: '16', velocity:50}));
429 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['Gb3'], duration: '8', velocity:50}));
430 | tracks[1].addEvent(new MidiWriter.NoteEvent({pitch: ['F2'], duration: '8', velocity:50}));
431 |
432 | var write = new MidiWriter.Writer(tracks);
433 |
434 | console.log(write.dataUri());
435 | module.exports = write;
436 |
--------------------------------------------------------------------------------
/jsdoc.json:
--------------------------------------------------------------------------------
1 | {
2 | "opts": {
3 | "template": "node_modules/minami"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "midi-writer-js",
3 | "version": "3.1.1",
4 | "description": "A library providing an API for generating MIDI files.",
5 | "main": "build/index.js",
6 | "types": "build/types/main.d.ts",
7 | "dependencies": {
8 | "@tonaljs/midi": "^4.9.0"
9 | },
10 | "devDependencies": {
11 | "@babel/core": "^7.9.6",
12 | "@babel/plugin-transform-destructuring": "^7.9.5",
13 | "@babel/preset-env": "^7.9.6",
14 | "@rollup/plugin-babel": "^5.0.0",
15 | "@rollup/plugin-node-resolve": "^15.0.1",
16 | "@rollup/plugin-replace": "^2.4.2",
17 | "@rollup/plugin-typescript": "^11.0.0",
18 | "@types/node": "^18.15.3",
19 | "@typescript-eslint/eslint-plugin": "^5.55.0",
20 | "@typescript-eslint/parser": "^5.55.0",
21 | "eslint": "^7.32.0",
22 | "eslint-config-standard": "^12.0.0",
23 | "eslint-plugin-import": "^2.20.2",
24 | "eslint-plugin-node": "^9.2.0",
25 | "eslint-plugin-promise": "^4.1.1",
26 | "eslint-plugin-standard": "^4.0.1",
27 | "jsdoc": "^3.6.4",
28 | "minami": "^1.2.3",
29 | "mocha": "^9.0.1",
30 | "nyc": "^15.0.1",
31 | "rollup": "^2.9.0",
32 | "tslib": "^2.5.0",
33 | "typedoc": "^0.25.1",
34 | "typescript": "^5.0.2",
35 | "watch": "^1.0.2"
36 | },
37 | "directories": {
38 | "lib": "src",
39 | "example": "examples",
40 | "test": "test"
41 | },
42 | "scripts": {
43 | "build": "mkdir -p build && rollup -c",
44 | "docs": "npx typedoc --options typedoc.json",
45 | "lint:js": "eslint 'src/**/**.ts'",
46 | "prepublishOnly": "npm test",
47 | "pretest": "npm run build",
48 | "test": "nyc --reporter=text mocha --no-config --no-package",
49 | "watch": "watch 'npm run build' src",
50 | "postinstall": "node postinstall.js"
51 | },
52 | "runkitExampleFilename": "runkit.js",
53 | "repository": {
54 | "type": "git",
55 | "url": "git+https://github.com/grimmdude/MidiWriterJS.git"
56 | },
57 | "keywords": [
58 | "midi",
59 | "generator",
60 | "music"
61 | ],
62 | "author": "Garrett Grimm",
63 | "license": "MIT",
64 | "bugs": {
65 | "url": "https://github.com/grimmdude/MidiWriterJS/issues"
66 | },
67 | "homepage": "https://github.com/grimmdude/MidiWriterJS#readme",
68 | "publishConfig": {
69 | "@grimmdude:registry": "https://npm.pkg.github.com"
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/postinstall.js:
--------------------------------------------------------------------------------
1 | console.log('\x1b[36m%s\x1b[0m', "Please give MidiWriterJS a ⭐ on GitHub if you found useful, thx!");
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import typescript from '@rollup/plugin-typescript';
2 | import replace from "@rollup/plugin-replace";
3 | import {nodeResolve} from '@rollup/plugin-node-resolve';
4 |
5 | export default [
6 | {
7 | input: "src/main.ts",
8 | output: [
9 | {
10 | file: "browser/midiwriter.js",
11 | format: "iife",
12 | name: "MidiWriter",
13 | },
14 | {
15 | file: "build/index.browser.js",
16 | format: "es",
17 | name: "MidiWriter",
18 | },
19 | ],
20 | plugins: [
21 | typescript(),
22 | replace({
23 | "process.browser": true,
24 | "preventAssignment": true
25 | }),
26 | nodeResolve(),
27 | ],
28 | },
29 | {
30 | input: 'src/main.ts',
31 | output: {
32 | file: 'build/index.js',
33 | format: 'cjs',
34 | exports: 'default',
35 | },
36 | external: ['tonal-midi', 'fs'],
37 | plugins: [
38 | typescript(),
39 | nodeResolve()
40 | ]
41 | }
42 | ];
--------------------------------------------------------------------------------
/runkit.js:
--------------------------------------------------------------------------------
1 | const rkmidi = require('runkit-midi');
2 | const MidiWriter = require('midi-writer-js');
3 | const track = new MidiWriter.Track();
4 | track.addEvent([
5 | new MidiWriter.NoteEvent({pitch: ['E4','D4'], duration: '4'}),
6 | new MidiWriter.NoteEvent({pitch: ['C4'], duration: '2'}),
7 | new MidiWriter.NoteEvent({pitch: ['E4','D4'], duration: '4'}),
8 | new MidiWriter.NoteEvent({pitch: ['C4'], duration: '2'}),
9 | new MidiWriter.NoteEvent({pitch: ['C4', 'C4', 'C4', 'C4', 'D4', 'D4', 'D4', 'D4'], duration: '8'}),
10 | new MidiWriter.NoteEvent({pitch: ['E4','D4'], duration: '4'}),
11 | new MidiWriter.NoteEvent({pitch: ['C4'], duration: '2'})
12 | ], function(event, index) {
13 | return {sequential: true};
14 | }
15 | );
16 |
17 | const write = new MidiWriter.Writer(track);
18 | rkmidi(write.buildFile());
--------------------------------------------------------------------------------
/src/abstract-event.ts:
--------------------------------------------------------------------------------
1 | interface AbstractEvent {
2 | data: number[];
3 | delta: number;
4 | name: string;
5 | tick?: number;
6 | }
7 |
8 | export {AbstractEvent};
--------------------------------------------------------------------------------
/src/chunks/chunk.ts:
--------------------------------------------------------------------------------
1 | interface Chunk {
2 | type: number[];
3 | size: number[];
4 | }
5 |
6 | export {Chunk};
7 |
--------------------------------------------------------------------------------
/src/chunks/header.ts:
--------------------------------------------------------------------------------
1 | import {Chunk} from './chunk';
2 | import {Constants} from '../constants';
3 | import {Utils} from '../utils';
4 |
5 | /**
6 | * Object representation of a header chunk section of a MIDI file.
7 | * @param {number} numberOfTracks - Number of tracks
8 | * @return {Header}
9 | */
10 | class Header implements Chunk {
11 | data: number[];
12 | type: number[];
13 | size: number[];
14 |
15 | constructor(numberOfTracks) {
16 | this.type = Constants.HEADER_CHUNK_TYPE;
17 |
18 | const trackType = numberOfTracks > 1? Constants.HEADER_CHUNK_FORMAT1 : Constants.HEADER_CHUNK_FORMAT0;
19 |
20 | this.data = trackType.concat(
21 | Utils.numberToBytes(numberOfTracks, 2), // two bytes long,
22 | Constants.HEADER_CHUNK_DIVISION
23 | );
24 |
25 | this.size = [0, 0, 0, this.data.length];
26 | }
27 | }
28 |
29 | export {Header};
30 |
--------------------------------------------------------------------------------
/src/chunks/track.ts:
--------------------------------------------------------------------------------
1 | import { AbstractEvent } from '../abstract-event';
2 | import {Chunk} from './chunk';
3 | import {Constants} from '../constants';
4 | import {ControllerChangeEvent} from '../midi-events/controller-change-event';
5 | import {CopyrightEvent} from '../meta-events/copyright-event';
6 | import {CuePointEvent} from '../meta-events/cue-point-event';
7 | import {EndTrackEvent} from '../meta-events/end-track-event';
8 | import {InstrumentNameEvent} from '../meta-events/instrument-name-event';
9 | import {KeySignatureEvent} from '../meta-events/key-signature-event';
10 | import {LyricEvent} from '../meta-events/lyric-event';
11 | import {MarkerEvent} from '../meta-events/marker-event';
12 | import {NoteEvent} from '../midi-events/note-event';
13 | import {NoteOnEvent} from '../midi-events/note-on-event';
14 | import {NoteOffEvent} from '../midi-events/note-off-event';
15 | import {PitchBendEvent} from '../midi-events/pitch-bend-event';
16 | import {TempoEvent} from '../meta-events/tempo-event';
17 | import {TextEvent} from '../meta-events/text-event';
18 | import {TimeSignatureEvent} from '../meta-events/time-signature-event';
19 | import {TrackNameEvent} from '../meta-events/track-name-event';
20 | import {Utils} from '../utils';
21 |
22 | /**
23 | * Holds all data for a track.
24 | * @param {object} fields {type: number, data: array, size: array, events: array}
25 | * @return {Track}
26 | */
27 | class Track implements Chunk {
28 | data: number[];
29 | events: AbstractEvent[];
30 | explicitTickEvents: NoteEvent[];
31 | size: number[];
32 | type: number[];
33 | tickPointer: number;
34 |
35 | constructor() {
36 | this.type = Constants.TRACK_CHUNK_TYPE;
37 | this.data = [];
38 | this.size = [];
39 | this.events = [];
40 | this.explicitTickEvents = [];
41 |
42 | // If there are any events with an explicit tick defined then we will create a "sub" track for those
43 | // and merge them in and the end.
44 | this.tickPointer = 0; // Each time an event is added this will increase
45 | }
46 |
47 | /**
48 | * Adds any event type to the track.
49 | * Events without a specific startTick property are assumed to be added in order of how they should output.
50 | * Events with a specific startTick property are set aside for now will be merged in during build process.
51 | *
52 | * TODO: Don't put startTick events in their own array. Just lump everything together and sort it out during buildData();
53 | * @param {(NoteEvent|ProgramChangeEvent)} events - Event object or array of Event objects.
54 | * @param {Function} mapFunction - Callback which can be used to apply specific properties to all events.
55 | * @return {Track}
56 | */
57 | addEvent(events: (AbstractEvent|AbstractEvent[]), mapFunction?: (i: number, event: AbstractEvent) => object): Track {
58 | Utils.toArray(events).forEach((event, i) => {
59 | if (event instanceof NoteEvent) {
60 | // Handle map function if provided
61 | if (typeof mapFunction === 'function') {
62 | const properties = mapFunction(i, event);
63 |
64 | if (typeof properties === 'object') {
65 | Object.assign(event, properties);
66 | }
67 | }
68 |
69 | // If this note event has an explicit startTick then we need to set aside for now
70 | if (event.tick !== null) {
71 | this.explicitTickEvents.push(event);
72 |
73 | } else {
74 | // Push each on/off event to track's event stack
75 | event.buildData().events.forEach((e) => this.events.push(e));
76 | }
77 |
78 | } else {
79 | this.events.push(event);
80 | }
81 | });
82 |
83 | return this;
84 | }
85 |
86 | /**
87 | * Builds int array of all events.
88 | * @param {object} options
89 | * @return {Track}
90 | */
91 | buildData(options = {}) {
92 | // Reset
93 | this.data = [];
94 | this.size = [];
95 | this.tickPointer = 0;
96 |
97 | let precisionLoss = 0;
98 |
99 | this.events.forEach((event) => {
100 | // Build event & add to total tick duration
101 | if (event instanceof NoteOnEvent || event instanceof NoteOffEvent) {
102 | const built = event.buildData(this, precisionLoss, options);
103 | precisionLoss = Utils.getPrecisionLoss(event.deltaWithPrecisionCorrection || 0);
104 | this.data = this.data.concat(built.data);
105 | this.tickPointer = Utils.getRoundedIfClose(event.tick);
106 |
107 | } else if (event instanceof TempoEvent) {
108 | this.tickPointer = Utils.getRoundedIfClose(event.tick);
109 | this.data = this.data.concat(event.data);
110 |
111 | } else {
112 | this.data = this.data.concat(event.data);
113 | }
114 | });
115 |
116 | this.mergeExplicitTickEvents();
117 |
118 | // If the last event isn't EndTrackEvent, then tack it onto the data.
119 | if (!this.events.length || !(this.events[this.events.length - 1] instanceof EndTrackEvent)) {
120 | this.data = this.data.concat((new EndTrackEvent).data);
121 | }
122 |
123 | this.size = Utils.numberToBytes(this.data.length, 4); // 4 bytes long
124 | return this;
125 | }
126 |
127 | mergeExplicitTickEvents() {
128 | if (!this.explicitTickEvents.length) return;
129 |
130 | // First sort asc list of events by startTick
131 | this.explicitTickEvents.sort((a, b) => a.tick - b.tick);
132 |
133 | // Now this.explicitTickEvents is in correct order, and so is this.events naturally.
134 | // For each explicit tick event, splice it into the main list of events and
135 | // adjust the delta on the following events so they still play normally.
136 | this.explicitTickEvents.forEach((noteEvent) => {
137 | // Convert NoteEvent to it's respective NoteOn/NoteOff events
138 | // Note that as we splice in events the delta for the NoteOff ones will
139 | // Need to change based on what comes before them after the splice.
140 | noteEvent.buildData().events.forEach((e) => e.buildData(this));
141 |
142 | // Merge each event individually into this track's event list.
143 | noteEvent.events.forEach((event) => this.mergeSingleEvent(event));
144 | });
145 |
146 | // Hacky way to rebuild track with newly spliced events. Need better solution.
147 | this.explicitTickEvents = [];
148 | this.buildData();
149 | }
150 |
151 | /**
152 | * Merges another track's events with this track.
153 | * @param {Track} track
154 | * @return {Track}
155 | */
156 | mergeTrack(track: Track): Track {
157 | // First build this track to populate each event's tick property
158 | this.buildData();
159 |
160 | // Then build track to be merged so that tick property is populated on all events & merge each event.
161 | track.buildData().events.forEach((event) => this.mergeSingleEvent(event));
162 | return this;
163 | }
164 |
165 | /**
166 | * Merges a single event into this track's list of events based on event.tick property.
167 | * @param {AbstractEvent} - event
168 | * @return {Track}
169 | */
170 | mergeSingleEvent(event: AbstractEvent): Track {
171 | // There are no events yet, so just add it in.
172 | if (!this.events.length) {
173 | this.addEvent(event);
174 | return;
175 | }
176 |
177 | // Find index of existing event we need to follow with
178 | let lastEventIndex;
179 |
180 | for (let i = 0; i < this.events.length; i++) {
181 | if (this.events[i].tick > event.tick) break;
182 | lastEventIndex = i;
183 | }
184 |
185 | const splicedEventIndex = lastEventIndex + 1;
186 |
187 | // Need to adjust the delta of this event to ensure it falls on the correct tick.
188 | event.delta = event.tick - this.events[lastEventIndex].tick;
189 |
190 | // Splice this event at lastEventIndex + 1
191 | this.events.splice(splicedEventIndex, 0, event);
192 |
193 | // Now adjust delta of all following events
194 | for (let i = splicedEventIndex + 1; i < this.events.length; i++) {
195 | // Since each existing event should have a tick value at this point we just need to
196 | // adjust delta to that the event still falls on the correct tick.
197 | this.events[i].delta = this.events[i].tick - this.events[i - 1].tick;
198 | }
199 | }
200 |
201 | /**
202 | * Removes all events matching specified type.
203 | * @param {string} eventName - Event type
204 | * @return {Track}
205 | */
206 | removeEventsByName(eventName: string): Track {
207 | this.events.forEach((event, index) => {
208 | if (event.name === eventName) {
209 | this.events.splice(index, 1);
210 | }
211 | });
212 |
213 | return this;
214 | }
215 |
216 | /**
217 | * Sets tempo of the MIDI file.
218 | * @param {number} bpm - Tempo in beats per minute.
219 | * @param {number} tick - Start tick.
220 | * @return {Track}
221 | */
222 | setTempo(bpm: number, tick = 0): Track {
223 | return this.addEvent(new TempoEvent({bpm, tick}));
224 | }
225 |
226 | /**
227 | * Sets time signature.
228 | * @param {number} numerator - Top number of the time signature.
229 | * @param {number} denominator - Bottom number of the time signature.
230 | * @param {number} midiclockspertick - Defaults to 24.
231 | * @param {number} notespermidiclock - Defaults to 8.
232 | * @return {Track}
233 | */
234 | setTimeSignature(numerator: number, denominator: number, midiclockspertick: number, notespermidiclock: number): Track {
235 | return this.addEvent(new TimeSignatureEvent(numerator, denominator, midiclockspertick, notespermidiclock));
236 | }
237 |
238 | /**
239 | * Sets key signature.
240 | * @param {*} sf -
241 | * @param {*} mi -
242 | * @return {Track}
243 | */
244 | setKeySignature(sf, mi) {
245 | return this.addEvent(new KeySignatureEvent(sf, mi));
246 | }
247 |
248 | /**
249 | * Adds text to MIDI file.
250 | * @param {string} text - Text to add.
251 | * @return {Track}
252 | */
253 | addText(text: string): Track {
254 | return this.addEvent(new TextEvent({text}));
255 | }
256 |
257 | /**
258 | * Adds copyright to MIDI file.
259 | * @param {string} text - Text of copyright line.
260 | * @return {Track}
261 | */
262 | addCopyright(text: string): Track {
263 | return this.addEvent(new CopyrightEvent({text}));
264 | }
265 |
266 | /**
267 | * Adds Sequence/Track Name.
268 | * @param {string} text - Text of track name.
269 | * @return {Track}
270 | */
271 | addTrackName(text: string): Track {
272 | return this.addEvent(new TrackNameEvent({text}));
273 | }
274 |
275 | /**
276 | * Sets instrument name of track.
277 | * @param {string} text - Name of instrument.
278 | * @return {Track}
279 | */
280 | addInstrumentName(text: string): Track {
281 | return this.addEvent(new InstrumentNameEvent({text}));
282 | }
283 |
284 | /**
285 | * Adds marker to MIDI file.
286 | * @param {string} text - Marker text.
287 | * @return {Track}
288 | */
289 | addMarker(text: string): Track {
290 | return this.addEvent(new MarkerEvent({text}));
291 | }
292 |
293 | /**
294 | * Adds cue point to MIDI file.
295 | * @param {string} text - Text of cue point.
296 | * @return {Track}
297 | */
298 | addCuePoint(text: string): Track {
299 | return this.addEvent(new CuePointEvent({text}));
300 | }
301 |
302 | /**
303 | * Adds lyric to MIDI file.
304 | * @param {string} text - Lyric text to add.
305 | * @return {Track}
306 | */
307 | addLyric(text: string): Track {
308 | return this.addEvent(new LyricEvent({text}));
309 | }
310 |
311 | /**
312 | * Channel mode messages
313 | * @return {Track}
314 | */
315 | polyModeOn(): Track {
316 | const event = new NoteOnEvent({data: [0x00, 0xB0, 0x7E, 0x00]});
317 | return this.addEvent(event);
318 | }
319 |
320 |
321 | /**
322 | * Sets a pitch bend.
323 | * @param {float} bend - Bend value ranging [-1,1], zero meaning no bend.
324 | * @return {Track}
325 | */
326 | setPitchBend(bend: number): Track {
327 | return this.addEvent(new PitchBendEvent({bend}));
328 | }
329 |
330 |
331 | /**
332 | * Adds a controller change event
333 | * @param {number} number - Control number.
334 | * @param {number} value - Control value.
335 | * @param {number} channel - Channel to send controller change event on (1-based).
336 | * @param {number} delta - Track tick offset for cc event.
337 | * @return {Track}
338 | */
339 | controllerChange(number: number, value: number, channel?: number, delta?: number): Track {
340 | return this.addEvent(new ControllerChangeEvent({controllerNumber: number, controllerValue: value, channel: channel, delta: delta}));
341 | }
342 |
343 | }
344 |
345 | export {Track};
346 |
--------------------------------------------------------------------------------
/src/constants.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * MIDI file format constants.
3 | * @return {Constants}
4 | */
5 |
6 | const Constants = {
7 | VERSION : '3.1.1',
8 | HEADER_CHUNK_TYPE : [0x4d, 0x54, 0x68, 0x64], // Mthd
9 | HEADER_CHUNK_LENGTH : [0x00, 0x00, 0x00, 0x06], // Header size for SMF
10 | HEADER_CHUNK_FORMAT0 : [0x00, 0x00], // Midi Type 0 id
11 | HEADER_CHUNK_FORMAT1 : [0x00, 0x01], // Midi Type 1 id
12 | HEADER_CHUNK_DIVISION : [0x00, 0x80], // Defaults to 128 ticks per beat
13 | TRACK_CHUNK_TYPE : [0x4d, 0x54, 0x72, 0x6b], // MTrk,
14 | META_EVENT_ID : 0xFF,
15 | META_SMTPE_OFFSET : 0x54
16 | };
17 |
18 | export {Constants};
19 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 |
2 | import {Constants} from './constants';
3 | import {ControllerChangeEvent} from './midi-events/controller-change-event';
4 | import {CopyrightEvent} from './meta-events/copyright-event';
5 | import {CuePointEvent} from './meta-events/cue-point-event';
6 | import {EndTrackEvent} from './meta-events/end-track-event';
7 | import {InstrumentNameEvent} from './meta-events/instrument-name-event';
8 | import {KeySignatureEvent} from './meta-events/key-signature-event';
9 | import {LyricEvent} from './meta-events/lyric-event';
10 | import {MarkerEvent} from './meta-events/marker-event';
11 | import {NoteOnEvent} from './midi-events/note-on-event';
12 | import {NoteOffEvent} from './midi-events/note-off-event';
13 | import {NoteEvent} from './midi-events/note-event';
14 | import {PitchBendEvent} from './midi-events/pitch-bend-event';
15 | import {ProgramChangeEvent} from './midi-events/program-change-event';
16 | import {TempoEvent} from './meta-events/tempo-event';
17 | import {TextEvent} from './meta-events/text-event';
18 | import {TimeSignatureEvent} from './meta-events/time-signature-event';
19 | import {Track} from './chunks/track';
20 | import {TrackNameEvent} from './meta-events/track-name-event';
21 | import {Utils} from './utils';
22 | import {VexFlow} from './vexflow';
23 | import {Writer} from './writer';
24 |
25 | export default {
26 | Constants,
27 | ControllerChangeEvent,
28 | CopyrightEvent,
29 | CuePointEvent,
30 | EndTrackEvent,
31 | InstrumentNameEvent,
32 | KeySignatureEvent,
33 | LyricEvent,
34 | MarkerEvent,
35 | NoteOnEvent,
36 | NoteOffEvent,
37 | NoteEvent,
38 | PitchBendEvent,
39 | ProgramChangeEvent,
40 | TempoEvent,
41 | TextEvent,
42 | TimeSignatureEvent,
43 | Track,
44 | TrackNameEvent,
45 | Utils,
46 | VexFlow,
47 | Writer
48 | };
49 |
--------------------------------------------------------------------------------
/src/meta-events/copyright-event.ts:
--------------------------------------------------------------------------------
1 | import {Constants} from '../constants';
2 | import {MetaEvent} from './meta-event';
3 | import {Utils} from '../utils';
4 |
5 | /**
6 | * Object representation of a tempo meta event.
7 | * @param {object} fields {text: string, delta: integer}
8 | * @return {CopyrightEvent}
9 | */
10 | class CopyrightEvent implements MetaEvent {
11 | data: number[];
12 | delta: number;
13 | name: string;
14 | text: string;
15 | type: 0x02;
16 |
17 | constructor(fields: { text: string; delta?: number; }) {
18 | this.delta = fields.delta || 0x00;
19 | this.name = 'CopyrightEvent';
20 | this.text = fields.text;
21 | this.type = 0x02;
22 |
23 | const textBytes = Utils.stringToBytes(this.text);
24 |
25 | // Start with zero time delta
26 | this.data = Utils.numberToVariableLength(this.delta).concat(
27 | Constants.META_EVENT_ID,
28 | this.type,
29 | Utils.numberToVariableLength(textBytes.length), // Size
30 | textBytes, // Text
31 | );
32 | }
33 | }
34 |
35 | export {CopyrightEvent};
36 |
--------------------------------------------------------------------------------
/src/meta-events/cue-point-event.ts:
--------------------------------------------------------------------------------
1 | import {Constants} from '../constants';
2 | import {MetaEvent} from './meta-event';
3 | import {Utils} from '../utils';
4 |
5 | /**
6 | * Object representation of a cue point meta event.
7 | * @param {object} fields {text: string, delta: integer}
8 | * @return {CuePointEvent}
9 | */
10 | class CuePointEvent implements MetaEvent {
11 | data: number[];
12 | delta: number;
13 | name: string;
14 | text: string;
15 | type: 0x07;
16 |
17 | constructor(fields: { text: string; delta?: number; }) {
18 | this.delta = fields.delta || 0x00;
19 | this.name = 'CuePointEvent';
20 | this.text = fields.text;
21 | this.type = 0x07;
22 |
23 | const textBytes = Utils.stringToBytes(this.text);
24 |
25 | // Start with zero time delta
26 | this.data = Utils.numberToVariableLength(this.delta).concat(
27 | Constants.META_EVENT_ID,
28 | this.type,
29 | Utils.numberToVariableLength(textBytes.length), // Size
30 | textBytes, // Text
31 | );
32 | }
33 | }
34 |
35 | export {CuePointEvent};
36 |
--------------------------------------------------------------------------------
/src/meta-events/end-track-event.ts:
--------------------------------------------------------------------------------
1 | import {Constants} from '../constants';
2 | import {MetaEvent} from './meta-event';
3 | import {Utils} from '../utils';
4 |
5 | /**
6 | * Object representation of a end track meta event.
7 | * @param {object} fields {delta: integer}
8 | * @return {EndTrackEvent}
9 | */
10 | class EndTrackEvent implements MetaEvent {
11 | data: number[];
12 | delta: number;
13 | name: string;
14 | type: [0x2F, 0x00];
15 |
16 | constructor(fields?: { delta: number; }) {
17 | this.delta = fields?.delta || 0x00;
18 | this.name = 'EndTrackEvent';
19 | this.type = [0x2F, 0x00];
20 |
21 | // Start with zero time delta
22 | this.data = Utils.numberToVariableLength(this.delta).concat(
23 | Constants.META_EVENT_ID,
24 | this.type
25 | );
26 | }
27 | }
28 |
29 | export {EndTrackEvent};
30 |
--------------------------------------------------------------------------------
/src/meta-events/instrument-name-event.ts:
--------------------------------------------------------------------------------
1 | import {Constants} from '../constants';
2 | import {MetaEvent} from './meta-event';
3 | import {Utils} from '../utils';
4 |
5 | /**
6 | * Object representation of an instrument name meta event.
7 | * @param {object} fields {text: string, delta: integer}
8 | * @return {InstrumentNameEvent}
9 | */
10 | class InstrumentNameEvent implements MetaEvent {
11 | data: number[];
12 | delta: number;
13 | name: string;
14 | text: string;
15 | type: 0x04;
16 |
17 | constructor(fields: { text: string; delta?: number; }) {
18 | this.delta = fields.delta || 0x00;
19 | this.name = 'InstrumentNameEvent';
20 | this.text = fields.text;
21 | this.type = 0x04;
22 |
23 | const textBytes = Utils.stringToBytes(this.text);
24 |
25 | // Start with zero time delta
26 | this.data = Utils.numberToVariableLength(this.delta).concat(
27 | Constants.META_EVENT_ID,
28 | this.type,
29 | Utils.numberToVariableLength(textBytes.length), // Size
30 | textBytes, // Instrument name
31 | );
32 | }
33 | }
34 |
35 | export {InstrumentNameEvent};
36 |
--------------------------------------------------------------------------------
/src/meta-events/key-signature-event.ts:
--------------------------------------------------------------------------------
1 | import {Constants} from '../constants';
2 | import {MetaEvent} from './meta-event';
3 | import {Utils} from '../utils';
4 |
5 | /**
6 | * Object representation of a key signature meta event.
7 | * @return {KeySignatureEvent}
8 | */
9 | class KeySignatureEvent implements MetaEvent {
10 | data: number[];
11 | delta: number;
12 | name: string;
13 | type: 0x59;
14 |
15 | constructor(sf, mi) {
16 | this.name = 'KeySignatureEvent';
17 | this.type = 0x59;
18 |
19 | let mode = mi || 0;
20 | sf = sf || 0;
21 |
22 | // Function called with string notation
23 | if (typeof mi === 'undefined') {
24 | const fifths = [
25 | ['Cb', 'Gb', 'Db', 'Ab', 'Eb', 'Bb', 'F', 'C', 'G', 'D', 'A', 'E', 'B', 'F#', 'C#'],
26 | ['ab', 'eb', 'bb', 'f', 'c', 'g', 'd', 'a', 'e', 'b', 'f#', 'c#', 'g#', 'd#', 'a#']
27 | ];
28 | const _sflen = sf.length;
29 | let note = sf || 'C';
30 |
31 | if (sf[0] === sf[0].toLowerCase()) mode = 1
32 |
33 | if (_sflen > 1) {
34 | switch (sf.charAt(_sflen - 1)) {
35 | case 'm':
36 | mode = 1;
37 | note = sf.charAt(0).toLowerCase();
38 | note = note.concat(sf.substring(1, _sflen - 1));
39 | break;
40 | case '-':
41 | mode = 1;
42 | note = sf.charAt(0).toLowerCase();
43 | note = note.concat(sf.substring(1, _sflen - 1));
44 | break;
45 | case 'M':
46 | mode = 0;
47 | note = sf.charAt(0).toUpperCase();
48 | note = note.concat(sf.substring(1, _sflen - 1));
49 | break;
50 | case '+':
51 | mode = 0;
52 | note = sf.charAt(0).toUpperCase();
53 | note = note.concat(sf.substring(1, _sflen - 1));
54 | break;
55 | }
56 | }
57 |
58 | const fifthindex = fifths[mode].indexOf(note);
59 | sf = fifthindex === -1 ? 0 : fifthindex - 7;
60 | }
61 |
62 | // Start with zero time delta
63 | this.data = Utils.numberToVariableLength(0x00).concat(
64 | Constants.META_EVENT_ID,
65 | this.type,
66 | [0x02], // Size
67 | Utils.numberToBytes(sf, 1), // Number of sharp or flats ( < 0 flat; > 0 sharp)
68 | Utils.numberToBytes(mode, 1), // Mode: 0 major, 1 minor
69 | );
70 | }
71 | }
72 |
73 | export {KeySignatureEvent};
74 |
--------------------------------------------------------------------------------
/src/meta-events/lyric-event.ts:
--------------------------------------------------------------------------------
1 | import {Constants} from '../constants';
2 | import {MetaEvent} from './meta-event';
3 | import {Utils} from '../utils';
4 |
5 | /**
6 | * Object representation of a lyric meta event.
7 | * @param {object} fields {text: string, delta: integer}
8 | * @return {LyricEvent}
9 | */
10 | class LyricEvent implements MetaEvent {
11 | data: number[];
12 | delta: number;
13 | name: string;
14 | text: string;
15 | type: 0x05;
16 |
17 | constructor(fields: { text: string; delta?: number; }) {
18 | this.delta = fields.delta || 0x00;
19 | this.name = 'LyricEvent';
20 | this.text = fields.text;
21 | this.type = 0x05;
22 |
23 | const textBytes = Utils.stringToBytes(this.text);
24 |
25 | // Start with zero time delta
26 | this.data = Utils.numberToVariableLength(this.delta).concat(
27 | Constants.META_EVENT_ID,
28 | this.type,
29 | Utils.numberToVariableLength(textBytes.length), // Size
30 | textBytes, // Text
31 | );
32 | }
33 | }
34 |
35 | export {LyricEvent};
36 |
--------------------------------------------------------------------------------
/src/meta-events/marker-event.ts:
--------------------------------------------------------------------------------
1 | import {Constants} from '../constants';
2 | import {MetaEvent} from './meta-event';
3 | import {Utils} from '../utils';
4 |
5 | /**
6 | * Object representation of a marker meta event.
7 | * @param {object} fields {text: string, delta: integer}
8 | * @return {MarkerEvent}
9 | */
10 | class MarkerEvent implements MetaEvent {
11 | data: number[];
12 | delta: number;
13 | name: string;
14 | text: string;
15 | type: 0x06;
16 |
17 | constructor(fields: { text: string; delta?: number; }) {
18 | this.delta = fields.delta || 0x00;
19 | this.name = 'MarkerEvent';
20 | this.text = fields.text;
21 | this.type = 0x06;
22 |
23 | const textBytes = Utils.stringToBytes(this.text);
24 |
25 | // Start with zero time delta
26 | this.data = Utils.numberToVariableLength(this.delta).concat(
27 | Constants.META_EVENT_ID,
28 | this.type,
29 | Utils.numberToVariableLength(textBytes.length), // Size
30 | textBytes, // Text
31 | );
32 | }
33 | }
34 |
35 | export {MarkerEvent};
36 |
--------------------------------------------------------------------------------
/src/meta-events/meta-event.ts:
--------------------------------------------------------------------------------
1 | import { AbstractEvent } from "../abstract-event";
2 |
3 | interface MetaEvent extends AbstractEvent {
4 | type: number|number[];
5 | }
6 |
7 | export {MetaEvent};
8 |
--------------------------------------------------------------------------------
/src/meta-events/tempo-event.ts:
--------------------------------------------------------------------------------
1 | import {Constants} from '../constants';
2 | import {MetaEvent} from './meta-event';
3 | import {Utils} from '../utils';
4 |
5 | /**
6 | * Object representation of a tempo meta event.
7 | * @param {object} fields {bpm: integer, delta: integer}
8 | * @return {TempoEvent}
9 | */
10 | class TempoEvent implements MetaEvent {
11 | bpm: number;
12 | data: number[];
13 | delta: number;
14 | name: string;
15 | tick: number;
16 | type: 0x51;
17 |
18 | constructor(fields: { bpm: number; tick?: number; delta?: number; }) {
19 | this.bpm = fields.bpm;
20 | this.delta = fields.delta || 0x00;
21 | this.tick = fields.tick;
22 | this.name = 'TempoEvent';
23 | this.type = 0x51;
24 |
25 | const tempo = Math.round(60000000 / this.bpm);
26 |
27 | // Start with zero time delta
28 | this.data = Utils.numberToVariableLength(this.delta).concat(
29 | Constants.META_EVENT_ID,
30 | this.type,
31 | [0x03], // Size
32 | Utils.numberToBytes(tempo, 3), // Tempo, 3 bytes
33 | );
34 | }
35 | }
36 |
37 | export {TempoEvent};
38 |
--------------------------------------------------------------------------------
/src/meta-events/text-event.ts:
--------------------------------------------------------------------------------
1 | import {Constants} from '../constants';
2 | import {MetaEvent} from './meta-event';
3 | import {Utils} from '../utils';
4 |
5 | /**
6 | * Object representation of a tempo meta event.
7 | * @param {object} fields {text: string, delta: integer}
8 | * @return {TextEvent}
9 | */
10 | class TextEvent implements MetaEvent {
11 | data: number[];
12 | delta: number;
13 | name: string;
14 | text: string;
15 | type: 0x01;
16 |
17 | constructor(fields: { text: string; delta?: number; }) {
18 | this.delta = fields.delta || 0x00;
19 | this.text = fields.text;
20 | this.name = 'TextEvent';
21 | this.type = 0x01;
22 |
23 | const textBytes = Utils.stringToBytes(this.text);
24 |
25 | // Start with zero time delta
26 | this.data = Utils.numberToVariableLength(fields.delta).concat(
27 | Constants.META_EVENT_ID,
28 | this.type,
29 | Utils.numberToVariableLength(textBytes.length), // Size
30 | textBytes, // Text
31 | );
32 | }
33 | }
34 |
35 | export {TextEvent};
36 |
--------------------------------------------------------------------------------
/src/meta-events/time-signature-event.ts:
--------------------------------------------------------------------------------
1 | import {Constants} from '../constants';
2 | import {MetaEvent} from './meta-event';
3 | import {Utils} from '../utils';
4 |
5 | /**
6 | * Object representation of a time signature meta event.
7 | * @return {TimeSignatureEvent}
8 | */
9 | class TimeSignatureEvent implements MetaEvent {
10 | data: number[];
11 | delta: number;
12 | name: string;
13 | type: 0x58;
14 |
15 | constructor(numerator, denominator, midiclockspertick, notespermidiclock) {
16 | this.name = 'TimeSignatureEvent';
17 | this.type = 0x58;
18 |
19 | // Start with zero time delta
20 | this.data = Utils.numberToVariableLength(0x00).concat(
21 | Constants.META_EVENT_ID,
22 | this.type,
23 | [0x04], // Size
24 | Utils.numberToBytes(numerator, 1), // Numerator, 1 bytes
25 | Utils.numberToBytes(Math.log2(denominator), 1), // Denominator is expressed as pow of 2, 1 bytes
26 | Utils.numberToBytes(midiclockspertick || 24, 1), // MIDI Clocks per tick, 1 bytes
27 | Utils.numberToBytes(notespermidiclock || 8, 1), // Number of 1/32 notes per MIDI clocks, 1 bytes
28 | );
29 | }
30 | }
31 |
32 | export {TimeSignatureEvent};
33 |
--------------------------------------------------------------------------------
/src/meta-events/track-name-event.ts:
--------------------------------------------------------------------------------
1 | import {Constants} from '../constants';
2 | import {MetaEvent} from './meta-event';
3 | import {Utils} from '../utils';
4 |
5 | /**
6 | * Object representation of a tempo meta event.
7 | * @param {object} fields {text: string, delta: integer}
8 | * @return {TrackNameEvent}
9 | */
10 | class TrackNameEvent implements MetaEvent {
11 | data: number[];
12 | delta: number;
13 | name: string;
14 | text: string;
15 | type: 0x03;
16 |
17 | constructor(fields: { text: string; delta?: number; }) {
18 | this.delta = fields.delta || 0x00;
19 | this.name = 'TrackNameEvent';
20 | this.text = fields.text;
21 | this.type = 0x03;
22 |
23 | const textBytes = Utils.stringToBytes(this.text);
24 |
25 | // Start with zero time delta
26 | this.data = Utils.numberToVariableLength(this.delta).concat(
27 | Constants.META_EVENT_ID,
28 | this.type,
29 | Utils.numberToVariableLength(textBytes.length), // Size
30 | textBytes, // Text
31 | );
32 | }
33 | }
34 |
35 | export {TrackNameEvent};
36 |
--------------------------------------------------------------------------------
/src/midi-events/controller-change-event.ts:
--------------------------------------------------------------------------------
1 | import {MidiEvent} from './midi-event';
2 | import {Utils} from '../utils';
3 |
4 | /**
5 | * Holds all data for a "controller change" MIDI event
6 | * @param {object} fields {controllerNumber: integer, controllerValue: integer, delta: integer}
7 | * @return {ControllerChangeEvent}
8 | */
9 | class ControllerChangeEvent implements MidiEvent {
10 | channel: number;
11 | controllerNumber: number;
12 | controllerValue: number;
13 | data: number[];
14 | delta: number;
15 | name: string;
16 | status: 0xB0;
17 |
18 | constructor(fields: { controllerNumber: number; controllerValue: number; channel?: number; delta?: number; }) {
19 | this.channel = fields.channel - 1 || 0;
20 | this.controllerValue = fields.controllerValue;
21 | this.controllerNumber = fields.controllerNumber;
22 | this.delta = fields.delta || 0x00;
23 | this.name = 'ControllerChangeEvent';
24 | this.status = 0xB0;
25 |
26 | this.data = Utils.numberToVariableLength(fields.delta).concat(this.status | this.channel, this.controllerNumber, this.controllerValue);
27 | }
28 | }
29 |
30 | export {ControllerChangeEvent};
31 |
--------------------------------------------------------------------------------
/src/midi-events/midi-event.ts:
--------------------------------------------------------------------------------
1 | import {AbstractEvent} from '../abstract-event';
2 |
3 |
4 | interface MidiEvent extends AbstractEvent {
5 | channel: number;
6 | status: number;
7 | // eslint-disable-next-line @typescript-eslint/ban-types
8 | buildData?: Function;
9 | }
10 |
11 | export {MidiEvent};
12 |
--------------------------------------------------------------------------------
/src/midi-events/note-event.ts:
--------------------------------------------------------------------------------
1 | import {AbstractEvent} from '../abstract-event';
2 | import {MidiEvent} from './midi-event';
3 | import {NoteOnEvent} from './note-on-event';
4 | import {NoteOffEvent} from './note-off-event';
5 | import {Utils} from '../utils';
6 |
7 | /**
8 | * Wrapper for noteOnEvent/noteOffEvent objects that builds both events.
9 | * @param {object} fields - {pitch: '[C4]', duration: '4', wait: '4', velocity: 1-100}
10 | * @return {NoteEvent}
11 | */
12 | class NoteEvent implements AbstractEvent {
13 | data: number[];
14 | delta: number;
15 | events: MidiEvent[];
16 | name: string;
17 | pitch: string[];
18 | grace: string|string[];
19 | channel: number;
20 | repeat: number;
21 | tick: number;
22 | duration: string;
23 | sequential: boolean;
24 | wait: string;
25 | velocity: number;
26 | tickDuration: number;
27 | restDuration: number;
28 |
29 | constructor(fields) {
30 | this.data = [];
31 | this.name = 'NoteEvent';
32 | this.pitch = Utils.toArray(fields.pitch);
33 |
34 | this.channel = fields.channel || 1;
35 | this.duration = fields.duration || '4';
36 | this.grace = fields.grace;
37 | this.repeat = fields.repeat || 1;
38 | this.sequential = fields.sequential || false;
39 | this.tick = fields.startTick || fields.tick || null;
40 | this.velocity = fields.velocity || 50;
41 | this.wait = fields.wait || 0;
42 |
43 | this.tickDuration = Utils.getTickDuration(this.duration);
44 | this.restDuration = Utils.getTickDuration(this.wait);
45 |
46 | this.events = []; // Hold actual NoteOn/NoteOff events
47 | }
48 |
49 | /**
50 | * Builds int array for this event.
51 | * @return {NoteEvent}
52 | */
53 | buildData(): NoteEvent {
54 | // Reset data array
55 | this.data = [];
56 |
57 | // Apply grace note(s) and subtract ticks (currently 1 tick per grace note) from tickDuration so net value is the same
58 | if (this.grace) {
59 | const graceDuration = 1;
60 | this.grace = Utils.toArray(this.grace);
61 | this.grace.forEach(() => {
62 | const noteEvent = new NoteEvent({pitch: this.grace, duration:'T' + graceDuration});
63 | this.data = this.data.concat(noteEvent.data);
64 | });
65 | }
66 |
67 | // fields.pitch could be an array of pitches.
68 | // If so create note events for each and apply the same duration.
69 |
70 | // By default this is a chord if it's an array of notes that requires one NoteOnEvent.
71 | // If this.sequential === true then it's a sequential string of notes that requires separate NoteOnEvents.
72 | if ( ! this.sequential) {
73 | // Handle repeat
74 | for (let j = 0; j < this.repeat; j++) {
75 | // Note on
76 | this.pitch.forEach((p, i) => {
77 | let noteOnNew;
78 |
79 | if (i == 0) {
80 | noteOnNew = new NoteOnEvent({
81 | channel: this.channel,
82 | wait: this.wait,
83 | delta: Utils.getTickDuration(this.wait),
84 | velocity: this.velocity,
85 | pitch: p,
86 | tick: this.tick,
87 | });
88 |
89 | } else {
90 | // Running status (can ommit the note on status)
91 | //noteOn = new NoteOnEvent({data: [0, Utils.getPitch(p), Utils.convertVelocity(this.velocity)]});
92 | noteOnNew = new NoteOnEvent({
93 | channel: this.channel,
94 | wait: 0,
95 | delta: 0,
96 | velocity: this.velocity,
97 | pitch: p,
98 | tick: this.tick,
99 | });
100 | }
101 |
102 | this.events.push(noteOnNew);
103 | });
104 |
105 | // Note off
106 | this.pitch.forEach((p, i) => {
107 | let noteOffNew;
108 |
109 | if (i == 0) {
110 | //noteOff = new NoteOffEvent({data: Utils.numberToVariableLength(tickDuration).concat(this.getNoteOffStatus(), Utils.getPitch(p), Utils.convertVelocity(this.velocity))});
111 | noteOffNew = new NoteOffEvent({
112 | channel: this.channel,
113 | duration: this.duration,
114 | velocity: this.velocity,
115 | pitch: p,
116 | tick: this.tick !== null ? Utils.getTickDuration(this.duration) + this.tick : null,
117 | });
118 |
119 | } else {
120 | // Running status (can omit the note off status)
121 | //noteOff = new NoteOffEvent({data: [0, Utils.getPitch(p), Utils.convertVelocity(this.velocity)]});
122 | noteOffNew = new NoteOffEvent({
123 | channel: this.channel,
124 | duration: 0,
125 | velocity: this.velocity,
126 | pitch: p,
127 | tick: this.tick !== null ? Utils.getTickDuration(this.duration) + this.tick : null,
128 | });
129 | }
130 |
131 | this.events.push(noteOffNew);
132 | });
133 | }
134 |
135 | } else {
136 | // Handle repeat
137 | for (let j = 0; j < this.repeat; j++) {
138 | this.pitch.forEach((p, i) => {
139 | const noteOnNew = new NoteOnEvent({
140 | channel: this.channel,
141 | wait: (i > 0 ? 0 : this.wait), // wait only applies to first note in repetition
142 | delta: (i > 0 ? 0 : Utils.getTickDuration(this.wait)),
143 | velocity: this.velocity,
144 | pitch: p,
145 | tick: this.tick,
146 | });
147 |
148 | const noteOffNew = new NoteOffEvent({
149 | channel: this.channel,
150 | duration: this.duration,
151 | velocity: this.velocity,
152 | pitch: p,
153 | });
154 |
155 | this.events.push(noteOnNew, noteOffNew);
156 | });
157 | }
158 | }
159 |
160 | return this;
161 | }
162 | }
163 |
164 | export {NoteEvent};
165 |
--------------------------------------------------------------------------------
/src/midi-events/note-off-event.ts:
--------------------------------------------------------------------------------
1 | import {MidiEvent} from './midi-event';
2 | import {Utils} from '../utils';
3 |
4 | /**
5 | * Holds all data for a "note off" MIDI event
6 | * @param {object} fields {data: []}
7 | * @return {NoteOffEvent}
8 | */
9 | class NoteOffEvent implements MidiEvent {
10 | channel: number;
11 | data: number[];
12 | delta: number;
13 | deltaWithPrecisionCorrection: number;
14 | status: 0x80;
15 | name: string;
16 | velocity: number;
17 | pitch: string|number;
18 | duration: string|number;
19 | tick: number;
20 |
21 | constructor(fields: { channel: number; duration: string|number; velocity: number; pitch: string|number; tick?: number; data?: number[]; delta?: number }) {
22 | this.name = 'NoteOffEvent';
23 | this.channel = fields.channel || 1;
24 | this.pitch = fields.pitch;
25 | this.velocity = fields.velocity || 50;
26 | this.tick = fields.tick || null;
27 | this.data = fields.data;
28 | this.delta = fields.delta || Utils.getTickDuration(fields.duration);
29 | this.status = 0x80;
30 | }
31 |
32 | /**
33 | * Builds int array for this event.
34 | * @param {Track} track - parent track
35 | * @return {NoteOffEvent}
36 | */
37 | buildData(track, precisionDelta: number, options: {middleC?: string} = {}) {
38 | if (this.tick === null) {
39 | this.tick = Utils.getRoundedIfClose(this.delta + track.tickPointer);
40 | }
41 |
42 | this.deltaWithPrecisionCorrection = Utils.getRoundedIfClose(this.delta - precisionDelta);
43 |
44 | this.data = Utils.numberToVariableLength(this.deltaWithPrecisionCorrection)
45 | .concat(
46 | this.status | this.channel - 1,
47 | Utils.getPitch(this.pitch, options.middleC),
48 | Utils.convertVelocity(this.velocity)
49 | );
50 |
51 | return this;
52 | }
53 | }
54 |
55 | export {NoteOffEvent};
56 |
--------------------------------------------------------------------------------
/src/midi-events/note-on-event.ts:
--------------------------------------------------------------------------------
1 | import {MidiEvent} from './midi-event';
2 | import {Utils} from '../utils';
3 |
4 | /**
5 | * Holds all data for a "note on" MIDI event
6 | * @param {object} fields {data: []}
7 | * @return {NoteOnEvent}
8 | */
9 | class NoteOnEvent implements MidiEvent {
10 | channel: number;
11 | data: number[];
12 | delta: number;
13 | status: 0x90;
14 | name: string;
15 | pitch: string|string[]|number|number[];
16 | velocity: number;
17 | wait: string|number;
18 | tick: number;
19 | deltaWithPrecisionCorrection: number;
20 |
21 | constructor(fields: { channel?: number; wait?: string|number; velocity?: number; pitch?: string|string[]|number|number[]; tick?: number; data?: number[]; delta?: number }) {
22 | this.name = 'NoteOnEvent';
23 | this.channel = fields.channel || 1;
24 | this.pitch = fields.pitch;
25 | this.wait = fields.wait || 0;
26 | this.velocity = fields.velocity || 50;
27 |
28 | this.tick = fields.tick || null;
29 | this.delta = null;
30 | this.data = fields.data;
31 | this.status = 0x90;
32 | }
33 |
34 | /**
35 | * Builds int array for this event.
36 | * @param {Track} track - parent track
37 | * @return {NoteOnEvent}
38 | */
39 | buildData(track, precisionDelta, options: {middleC?: string} = {}) {
40 | this.data = [];
41 |
42 | // Explicitly defined startTick event
43 | if (this.tick) {
44 | this.tick = Utils.getRoundedIfClose(this.tick);
45 |
46 | // If this is the first event in the track then use event's starting tick as delta.
47 | if (track.tickPointer == 0) {
48 | this.delta = this.tick;
49 | }
50 |
51 | } else {
52 | this.delta = Utils.getTickDuration(this.wait);
53 | this.tick = Utils.getRoundedIfClose(track.tickPointer + this.delta);
54 | }
55 |
56 | this.deltaWithPrecisionCorrection = Utils.getRoundedIfClose(this.delta - precisionDelta);
57 |
58 | this.data = Utils.numberToVariableLength(this.deltaWithPrecisionCorrection)
59 | .concat(
60 | this.status | this.channel - 1,
61 | Utils.getPitch(this.pitch, options.middleC),
62 | Utils.convertVelocity(this.velocity)
63 | );
64 |
65 | return this;
66 | }
67 | }
68 |
69 | export {NoteOnEvent};
70 |
--------------------------------------------------------------------------------
/src/midi-events/pitch-bend-event.ts:
--------------------------------------------------------------------------------
1 | import {MidiEvent} from './midi-event';
2 | import {Utils} from '../utils';
3 |
4 |
5 | /**
6 | * Holds all data for a "Pitch Bend" MIDI event
7 | * [ -1.0, 0, 1.0 ] -> [ 0, 8192, 16383]
8 | * @param {object} fields { bend : float, channel : int, delta: int }
9 | * @return {PitchBendEvent}
10 | */
11 | class PitchBendEvent implements MidiEvent {
12 | channel: number;
13 | data: number[];
14 | delta: number;
15 | name: string;
16 | status: 0xE0;
17 |
18 | constructor(fields) {
19 | this.channel = fields.channel || 0;
20 | this.delta = fields.delta || 0x00;
21 | this.name = 'PitchBendEvent';
22 | this.status = 0xE0;
23 |
24 | const bend14 = this.scale14bits(fields.bend);
25 |
26 | const lsbValue = bend14 & 0x7f;
27 | const msbValue = ( bend14 >> 7 ) & 0x7f;
28 | this.data = Utils.numberToVariableLength(this.delta).concat(this.status | this.channel, lsbValue, msbValue);
29 | }
30 |
31 | scale14bits(zeroOne) {
32 | if ( zeroOne <= 0 ) {
33 | return Math.floor( 16384 * ( zeroOne + 1 ) / 2 );
34 | }
35 |
36 | return Math.floor( 16383 * ( zeroOne + 1 ) / 2 );
37 | }
38 | }
39 |
40 | export {PitchBendEvent};
41 |
--------------------------------------------------------------------------------
/src/midi-events/program-change-event.ts:
--------------------------------------------------------------------------------
1 | import {MidiEvent} from './midi-event';
2 | import {Utils} from '../utils';
3 |
4 | /**
5 | * Holds all data for a "program change" MIDI event
6 | * @param {object} fields {instrument: integer, delta: integer}
7 | * @return {ProgramChangeEvent}
8 | */
9 | class ProgramChangeEvent implements MidiEvent {
10 | channel: number;
11 | data: number[];
12 | delta: number;
13 | status: 0xC0;
14 | name: string;
15 | instrument: number;
16 |
17 | constructor(fields: { channel?: number; delta?: number; instrument: number; }) {
18 | this.channel = fields.channel || 0;
19 | this.delta = fields.delta || 0x00;
20 | this.instrument = fields.instrument;
21 | this.status = 0xC0;
22 | this.name = 'ProgramChangeEvent';
23 | // delta time defaults to 0.
24 | this.data = Utils.numberToVariableLength(this.delta).concat(this.status | this.channel, this.instrument);
25 | }
26 | }
27 |
28 | export {ProgramChangeEvent};
29 |
--------------------------------------------------------------------------------
/src/utils.ts:
--------------------------------------------------------------------------------
1 | import {Constants} from './constants';
2 | import {toMidi} from '@tonaljs/midi';
3 |
4 | /**
5 | * Static utility functions used throughout the library.
6 | */
7 | class Utils {
8 |
9 | /**
10 | * Gets MidiWriterJS version number.
11 | * @return {string}
12 | */
13 | static version(): string {
14 | return Constants.VERSION;
15 | }
16 |
17 | /**
18 | * Convert a string to an array of bytes
19 | * @param {string} string
20 | * @return {array}
21 | */
22 | static stringToBytes(string: string): number[] {
23 | return string.split('').map(char => char.charCodeAt(0))
24 | }
25 |
26 | /**
27 | * Checks if argument is a valid number.
28 | * @param {*} n - Value to check
29 | * @return {boolean}
30 | */
31 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
32 | static isNumeric(n: any): boolean {
33 | return !isNaN(parseFloat(n)) && isFinite(n)
34 | }
35 |
36 | /**
37 | * Returns the correct MIDI number for the specified pitch.
38 | * Uses Tonal Midi - https://github.com/danigb/tonal/tree/master/packages/midi
39 | * @param {(string|number)} pitch - 'C#4' or midi note code
40 | * @param {string} middleC
41 | * @return {number}
42 | */
43 | static getPitch(pitch: any, middleC = 'C4'): number {
44 | return 60 - toMidi(middleC) + toMidi(pitch);
45 | }
46 |
47 | /**
48 | * Translates number of ticks to MIDI timestamp format, returning an array of
49 | * hex strings with the time values. Midi has a very particular time to express time,
50 | * take a good look at the spec before ever touching this function.
51 | * Thanks to https://github.com/sergi/jsmidi
52 | *
53 | * @param {number} ticks - Number of ticks to be translated
54 | * @return {array} - Bytes that form the MIDI time value
55 | */
56 | static numberToVariableLength(ticks: number): number[] {
57 | ticks = Math.round(ticks);
58 | let buffer = ticks & 0x7F;
59 |
60 | // eslint-disable-next-line no-cond-assign
61 | while (ticks = ticks >> 7) {
62 | buffer <<= 8;
63 | buffer |= ((ticks & 0x7F) | 0x80);
64 | }
65 |
66 | const bList = [];
67 | // eslint-disable-next-line no-constant-condition
68 | while (true) {
69 | bList.push(buffer & 0xff);
70 |
71 | if (buffer & 0x80) buffer >>= 8
72 | else { break; }
73 | }
74 |
75 | return bList;
76 | }
77 |
78 | /**
79 | * Counts number of bytes in string
80 | * @param {string} s
81 | * @return {number}
82 | */
83 | static stringByteCount(s: string): number {
84 | return encodeURI(s).split(/%..|./).length - 1
85 | }
86 |
87 | /**
88 | * Get an int from an array of bytes.
89 | * @param {array} bytes
90 | * @return {number}
91 | */
92 | static numberFromBytes(bytes: number[]): number {
93 | let hex = '';
94 | let stringResult;
95 |
96 | bytes.forEach((byte) => {
97 | stringResult = byte.toString(16);
98 |
99 | // ensure string is 2 chars
100 | if (stringResult.length == 1) stringResult = "0" + stringResult
101 |
102 | hex += stringResult;
103 | });
104 |
105 | return parseInt(hex, 16);
106 | }
107 |
108 | /**
109 | * Takes a number and splits it up into an array of bytes. Can be padded by passing a number to bytesNeeded
110 | * @param {number} number
111 | * @param {number} bytesNeeded
112 | * @return {array} - Array of bytes
113 | */
114 | static numberToBytes(number: number, bytesNeeded: number): number[] {
115 | bytesNeeded = bytesNeeded || 1;
116 |
117 | let hexString = number.toString(16);
118 |
119 | if (hexString.length & 1) { // Make sure hex string is even number of chars
120 | hexString = '0' + hexString;
121 | }
122 |
123 | // Split hex string into an array of two char elements
124 | const hexArray = hexString.match(/.{2}/g);
125 |
126 | // Now parse them out as integers
127 | const intArray = hexArray.map(item => parseInt(item, 16))
128 |
129 | // Prepend empty bytes if we don't have enough
130 | if (intArray.length < bytesNeeded) {
131 | while (bytesNeeded - intArray.length > 0) {
132 | intArray.unshift(0);
133 | }
134 | }
135 |
136 | return intArray;
137 | }
138 |
139 | /**
140 | * Converts value to array if needed.
141 | * @param {any} value
142 | * @return {array}
143 | */
144 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
145 | static toArray(value: any): any[] {
146 | if (Array.isArray(value)) return value;
147 | return [value];
148 | }
149 |
150 | /**
151 | * Converts velocity to value 0-127
152 | * @param {number} velocity - Velocity value 1-100
153 | * @return {number}
154 | */
155 | static convertVelocity(velocity: number): number {
156 | // Max passed value limited to 100
157 | velocity = velocity > 100 ? 100 : velocity;
158 | return Math.round(velocity / 100 * 127);
159 | }
160 |
161 | /**
162 | * Gets the total number of ticks of a specified duration.
163 | * Note: type=='note' defaults to quarter note, type==='rest' defaults to 0
164 | * @param {(string|array)} duration
165 | * @return {number}
166 | */
167 | static getTickDuration(duration: (string | string[] | number)): number {
168 | if (Array.isArray(duration)) {
169 | // Recursively execute this method for each item in the array and return the sum of tick durations.
170 | return duration.map((value) => {
171 | return Utils.getTickDuration(value);
172 | }).reduce((a, b) => {
173 | return a + b;
174 | }, 0);
175 | }
176 |
177 | duration = duration.toString();
178 |
179 | if (duration.toLowerCase().charAt(0) === 't') {
180 | // If duration starts with 't' then the number that follows is an explicit tick count
181 | const ticks = parseInt(duration.substring(1));
182 |
183 | if (isNaN(ticks) || ticks < 0) {
184 | throw new Error(duration + ' is not a valid duration.');
185 | }
186 |
187 | return ticks;
188 | }
189 |
190 | // Need to apply duration here. Quarter note == Constants.HEADER_CHUNK_DIVISION
191 | const quarterTicks = Utils.numberFromBytes(Constants.HEADER_CHUNK_DIVISION);
192 | const tickDuration = quarterTicks * Utils.getDurationMultiplier(duration);
193 | return Utils.getRoundedIfClose(tickDuration)
194 | }
195 |
196 | /**
197 | * Due to rounding errors in JavaScript engines,
198 | * it's safe to round when we're very close to the actual tick number
199 | *
200 | * @static
201 | * @param {number} tick
202 | * @return {number}
203 | */
204 | static getRoundedIfClose(tick: number): number {
205 | const roundedTick = Math.round(tick);
206 | return Math.abs(roundedTick - tick) < 0.000001 ? roundedTick : tick;
207 | }
208 |
209 | /**
210 | * Due to low precision of MIDI,
211 | * we need to keep track of rounding errors in deltas.
212 | * This function will calculate the rounding error for a given duration.
213 | *
214 | * @static
215 | * @param {number} tick
216 | * @return {number}
217 | */
218 | static getPrecisionLoss(tick: number): number {
219 | const roundedTick = Math.round(tick);
220 | return roundedTick - tick;
221 | }
222 |
223 | /**
224 | * Gets what to multiple ticks/quarter note by to get the specified duration.
225 | * Note: type=='note' defaults to quarter note, type==='rest' defaults to 0
226 | * @param {string} duration
227 | * @return {number}
228 | */
229 | static getDurationMultiplier(duration: string): number {
230 | // Need to apply duration here.
231 | // Quarter note == Constants.HEADER_CHUNK_DIVISION ticks.
232 |
233 | if (duration === '0') return 0;
234 |
235 | const match = duration.match(/^(?d+)?(?\d+)(?:t(?\d*))?/);
236 | if (match) {
237 | const base = Number(match.groups.base);
238 | // 1 or any power of two:
239 | const isValidBase = base === 1 || ((base & (base - 1)) === 0);
240 | if (isValidBase) {
241 | // how much faster or slower is this note compared to a quarter?
242 | const ratio = base / 4;
243 | let durationInQuarters = 1 / ratio;
244 | const {dotted, tuplet} = match.groups;
245 | if (dotted) {
246 | const thisManyDots = dotted.length;
247 | const divisor = Math.pow(2, thisManyDots);
248 | durationInQuarters = durationInQuarters + (durationInQuarters * ((divisor - 1) / divisor));
249 | }
250 | if (typeof tuplet === 'string') {
251 | const fitInto = durationInQuarters * 2;
252 | // default to triplet:
253 | const thisManyNotes = Number(tuplet || '3');
254 | durationInQuarters = fitInto / thisManyNotes;
255 | }
256 | return durationInQuarters
257 | }
258 | }
259 | throw new Error(duration + ' is not a valid duration.');
260 | }
261 | }
262 |
263 | export {Utils};
264 |
--------------------------------------------------------------------------------
/src/vexflow.ts:
--------------------------------------------------------------------------------
1 | import {NoteEvent} from './midi-events/note-event';
2 | import {Track} from './chunks/track';
3 |
4 | class VexFlow {
5 |
6 | /**
7 | * Support for converting VexFlow voice into MidiWriterJS track
8 | * @return MidiWriter.Track object
9 | */
10 | trackFromVoice(voice, options = {addRenderedAccidentals: false}): Track {
11 | const track = new Track;
12 | let wait = [];
13 |
14 | voice.tickables.forEach(tickable => {
15 | if (tickable.noteType === 'n') {
16 | track.addEvent(new NoteEvent({
17 | pitch: tickable.keys.map((pitch, index) => this.convertPitch(pitch, index, tickable, options.addRenderedAccidentals)),
18 | duration: this.convertDuration(tickable),
19 | wait
20 | }));
21 | // reset wait
22 | wait = [];
23 | } else if (tickable.noteType === 'r') {
24 | // move on to the next tickable and add this to the stack
25 | // of the `wait` property for the next note event
26 | wait.push(this.convertDuration(tickable));
27 | }
28 | });
29 |
30 | // There may be outstanding rests at the end of the track,
31 | // pad with a ghost note (zero duration and velocity), just to capture the wait.
32 | if(wait.length > 0) {
33 | track.addEvent(new NoteEvent({pitch: '[c4]', duration: '0', wait, velocity: '0'}));
34 | }
35 |
36 | return track;
37 | }
38 |
39 | /**
40 | * Converts VexFlow pitch syntax to MidiWriterJS syntax
41 | * @param pitch string
42 | * @param index pitch index
43 | * @param note struct from Vexflow
44 | * @param addRenderedAccidentals adds Vexflow rendered accidentals
45 | */
46 | convertPitch(pitch, index, note, addRenderedAccidentals = false) {
47 | // Splits note name from octave
48 | const pitchParts = pitch.split('/');
49 |
50 | // Retrieves accidentals from pitch
51 | // Removes natural accidentals since they are not accepted in Tonal Midi
52 | let accidentals = pitchParts[0].substring(1).replace('n', '');
53 |
54 | if (addRenderedAccidentals) {
55 | note.getAccidentals()?.forEach(accidental => {
56 | if (accidental.index === index) {
57 | if (accidental.type === 'n') {
58 | accidentals = '';
59 | } else {
60 | accidentals += accidental.type;
61 | }
62 | }
63 | });
64 | }
65 |
66 | return pitchParts[0][0] + accidentals + pitchParts[1];
67 | }
68 |
69 | /**
70 | * Converts VexFlow duration syntax to MidiWriterJS syntax
71 | * @param note struct from VexFlow
72 | */
73 | convertDuration(note) {
74 | return 'd'.repeat(note.dots) + this.convertBaseDuration(note.duration) + (note.tuplet ? 't' + note.tuplet.num_notes : '');
75 | }
76 |
77 | /**
78 | * Converts VexFlow base duration syntax to MidiWriterJS syntax
79 | * @param duration Vexflow duration
80 | * @returns MidiWriterJS duration
81 | */
82 | convertBaseDuration(duration: string): string {
83 | switch (duration) {
84 | case 'w':
85 | return '1';
86 | case 'h':
87 | return '2';
88 | case 'q':
89 | return '4';
90 | default:
91 | return duration;
92 | }
93 | }
94 | }
95 |
96 | export {VexFlow};
97 |
--------------------------------------------------------------------------------
/src/writer.ts:
--------------------------------------------------------------------------------
1 | import {Header} from './chunks/header';
2 | import {Track} from './chunks/track';
3 | import {Utils} from './utils';
4 |
5 | /**
6 | * Object that puts together tracks and provides methods for file output.
7 | * @param {array|Track} tracks - A single {Track} object or an array of {Track} objects.
8 | * @param {object} options - {middleC: 'C4'}
9 | * @return {Writer}
10 | */
11 | class Writer {
12 | tracks: Track[];
13 | options: object;
14 |
15 | constructor(tracks, options = {}) {
16 | // Ensure tracks is an array
17 | this.tracks = Utils.toArray(tracks);
18 | this.options = options;
19 | }
20 |
21 | /**
22 | * Builds array of data from chunkschunks.
23 | * @return {array}
24 | */
25 | buildData() {
26 | const data = [];
27 | data.push(new Header(this.tracks.length))
28 |
29 | // For each track add final end of track event and build data
30 | this.tracks.forEach((track) => {
31 | data.push(track.buildData(this.options));
32 | });
33 |
34 | return data;
35 | }
36 |
37 | /**
38 | * Builds the file into a Uint8Array
39 | * @return {Uint8Array}
40 | */
41 | buildFile(): Uint8Array {
42 | let build = [];
43 |
44 | // Data consists of chunks which consists of data
45 | this.buildData().forEach((d) => build = build.concat(d.type, d.size, d.data));
46 |
47 | return new Uint8Array(build);
48 | }
49 |
50 | /**
51 | * Convert file buffer to a base64 string. Different methods depending on if browser or node.
52 | * @return {string}
53 | */
54 | base64(): string {
55 | if (typeof btoa === 'function') {
56 | let binary = '';
57 | const bytes = this.buildFile();
58 | const len = bytes.byteLength;
59 |
60 | for (let i = 0; i < len; i++) {
61 | binary += String.fromCharCode(bytes[i]);
62 | }
63 |
64 | return btoa(binary);
65 | }
66 |
67 | return Buffer.from(this.buildFile()).toString('base64');
68 | }
69 |
70 | /**
71 | * Get the data URI.
72 | * @return {string}
73 | */
74 | dataUri(): string {
75 | return 'data:audio/midi;base64,' + this.base64();
76 | }
77 |
78 |
79 | /**
80 | * Set option on instantiated Writer.
81 | * @param {string} key
82 | * @param {any} value
83 | * @return {Writer}
84 | */
85 | setOption(key: string, value: number|string): Writer {
86 | this.options[key] = value;
87 | return this;
88 | }
89 |
90 | /**
91 | * Output to stdout
92 | * @return {string}
93 | */
94 | stdout() {
95 | return process.stdout.write(Buffer.from(this.buildFile()));
96 | }
97 | }
98 |
99 | export {Writer};
100 |
--------------------------------------------------------------------------------
/test/main.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert');
2 | const MidiWriter = require('..');
3 |
4 | describe('MidiWriterJS', function() {
5 | describe('#NoteEvent()', function () {
6 | describe('#getTickDuration()', function () {
7 | it('should create a dotted half note if passed three quarter notes.', function () {
8 | const track = new MidiWriter.Track(); // Need to instantiate to build note object
9 | const note = new MidiWriter.NoteEvent({pitch: 'C4', duration: ['4', '4', '4']});
10 | track.addEvent(note);
11 | const write = new MidiWriter.Writer(track);
12 | assert.equal('TVRoZAAAAAYAAAABAIBNVHJrAAAADQCQPECDAIA8QAD/LwA=', write.base64());
13 | });
14 |
15 | it('should create a note with duration of 50 ticks if passed T50', function () {
16 | const track = new MidiWriter.Track(); // Need to instantiate to build note object
17 | const note = new MidiWriter.NoteEvent({pitch: 'C4', duration: 'T50'});
18 | track.addEvent(note);
19 | const write = new MidiWriter.Writer(track);
20 | assert.equal('TVRoZAAAAAYAAAABAIBNVHJrAAAADACQPEAygDxAAP8vAA==', write.base64());
21 | });
22 | });
23 | });
24 |
25 | describe('#Track()', function() {
26 | describe('#Time Signature', function() {
27 | it('should return specific base64 string when time signature is 4/4', function() {
28 | const track = new MidiWriter.Track();
29 | track.setTimeSignature(4, 4);
30 | const write = new MidiWriter.Writer(track);
31 | assert.equal('TVRoZAAAAAYAAAABAIBNVHJrAAAADAD/WAQEAhgIAP8vAA==', write.base64());
32 | });
33 |
34 | it('should return specific base64 string when time signature is 2/2', function() {
35 | const track = new MidiWriter.Track();
36 | track.setTimeSignature(2, 2);
37 | const write = new MidiWriter.Writer(track);
38 | assert.equal('TVRoZAAAAAYAAAABAIBNVHJrAAAADAD/WAQCARgIAP8vAA==', write.base64());
39 | });
40 |
41 | it('should return specific base64 string when time signature is 2/8', function() {
42 | const track = new MidiWriter.Track();
43 | track.setTimeSignature(2, 8);
44 | const write = new MidiWriter.Writer(track);
45 | assert.equal('TVRoZAAAAAYAAAABAIBNVHJrAAAADAD/WAQCAxgIAP8vAA==', write.base64());
46 | });
47 | });
48 |
49 | it('should return specific base64 string when setting C major key signature', function() {
50 | const track = new MidiWriter.Track();
51 | track.setKeySignature('C');
52 | const write = new MidiWriter.Writer(track);
53 | assert.equal('TVRoZAAAAAYAAAABAIBNVHJrAAAACgD/WQIAAAD/LwA=', write.base64());
54 | });
55 |
56 | it('should return specific base64 string when adding copyright', function() {
57 | const track = new MidiWriter.Track();
58 | track.addCopyright('2018 Garrett Grimm');
59 | const write = new MidiWriter.Writer(track);
60 | assert.equal('TVRoZAAAAAYAAAABAIBNVHJrAAAAGgD/AhIyMDE4IEdhcnJldHQgR3JpbW0A/y8A', write.base64());
61 | });
62 |
63 | it('should return specific base64 string when adding text', function() {
64 | const track = new MidiWriter.Track();
65 | track.addText('MidiWriterJS is the bomb!');
66 | const write = new MidiWriter.Writer(track);
67 | assert.equal('TVRoZAAAAAYAAAABAIBNVHJrAAAAIQD/ARlNaWRpV3JpdGVySlMgaXMgdGhlIGJvbWIhAP8vAA==', write.base64());
68 | });
69 |
70 | it('should return specific base64 string when adding a track name', function() {
71 | const track = new MidiWriter.Track();
72 | track.addTrackName('Name of a cool track');
73 | const write = new MidiWriter.Writer(track);
74 | assert.equal('TVRoZAAAAAYAAAABAIBNVHJrAAAAHAD/AxROYW1lIG9mIGEgY29vbCB0cmFjawD/LwA=', write.base64());
75 | });
76 |
77 | it('should return specific base64 string when adding an instrument name', function() {
78 | const track = new MidiWriter.Track();
79 | track.addInstrumentName('Alto Saxophone');
80 | const write = new MidiWriter.Writer(track);
81 | assert.equal('TVRoZAAAAAYAAAABAIBNVHJrAAAAFgD/BA5BbHRvIFNheG9waG9uZQD/LwA=', write.base64());
82 | });
83 |
84 | it('should return specific base64 string when adding a marker', function() {
85 | const track = new MidiWriter.Track();
86 | track.addMarker('This is my favorite part of the song.');
87 | const write = new MidiWriter.Writer(track);
88 | assert.equal('TVRoZAAAAAYAAAABAIBNVHJrAAAALQD/BiVUaGlzIGlzIG15IGZhdm9yaXRlIHBhcnQgb2YgdGhlIHNvbmcuAP8vAA==', write.base64());
89 | });
90 |
91 | it('should return specific base64 string when adding a cue point', function() {
92 | const track = new MidiWriter.Track();
93 | track.addCuePoint('Here is a cue point.');
94 | const write = new MidiWriter.Writer(track);
95 | assert.equal('TVRoZAAAAAYAAAABAIBNVHJrAAAAHAD/BxRIZXJlIGlzIGEgY3VlIHBvaW50LgD/LwA=', write.base64());
96 | });
97 |
98 | it('should return specific base64 string when adding a lyric', function() {
99 | const track = new MidiWriter.Track();
100 | track.addLyric('Oh say can you see.');
101 | const write = new MidiWriter.Writer(track);
102 | assert.equal('TVRoZAAAAAYAAAABAIBNVHJrAAAAGwD/BRNPaCBzYXkgY2FuIHlvdSBzZWUuAP8vAA==', write.base64());
103 | });
104 |
105 | it('should return specific base64 string when adding a controller change event', function() {
106 | const track = new MidiWriter.Track();
107 | track.controllerChange(1, 127);
108 | const write = new MidiWriter.Writer(track);
109 | assert.equal('TVRoZAAAAAYAAAABAIBNVHJrAAAACACwAX8A/y8A', write.base64());
110 | });
111 |
112 | it('should return specific base64 string when adding a controller change event with channel and delta specified', function() {
113 | const track = new MidiWriter.Track();
114 | track.controllerChange(1, 127, 16, 256);
115 | const write = new MidiWriter.Writer(track);
116 | assert.equal('TVRoZAAAAAYAAAABAIBNVHJrAAAACYIAvwF/AP8vAA==', write.base64());
117 | });
118 |
119 | it('should create 3 triplet eights followed by a quarter - on correct ticks', function() {
120 | const track = new MidiWriter.Track();
121 | track.addEvent([
122 | new MidiWriter.NoteEvent({pitch: ['D5', 'C5', 'B4'], duration: '8t', sequential: true}),
123 | new MidiWriter.NoteEvent({pitch: ['B4'], duration: 'd2'}),
124 | ])
125 | const builtTrack = track.buildData();
126 | assert.equal(builtTrack.events[6].tick, 128)
127 | assert.equal(builtTrack.events[7].tick, 512)
128 | })
129 |
130 | /*
131 | it('should create 3 triplet eights followed by a whole, after normal 6 eights - on correct ticks', function() {
132 | const track = new MidiWriter.Track();
133 | track.addEvent([
134 | new MidiWriter.NoteEvent({pitch: ['G4', 'Gb4', 'C4', 'B3', 'Eb4', 'Gb4'], duration: '8', sequential:true}),
135 | new MidiWriter.NoteEvent({pitch: ['D5', 'C5', 'B4'], duration: '8t', sequential: true}),
136 | new MidiWriter.NoteEvent({pitch: ['B#4'], duration: '1'}),
137 | ])
138 | const builtTrack = track.buildData();
139 |
140 | // Reversing these events breaks this test since EndOfTrackEvent gets misplaced.
141 | const lastEvents = [...builtTrack.events].reverse();
142 | // 2nd bar:
143 | assert.equal(lastEvents[2].tick, 128 * 4);
144 | // 3rd bar:
145 | assert.equal(lastEvents[1].tick, 128 * 8);
146 | })
147 | */
148 |
149 | it('should write 14 bars where certain notes should start exactly at the beginning of each bar', function() {
150 | const track = new MidiWriter.Track();
151 | track
152 | .setTempo(60)
153 | .addEvent([
154 | new MidiWriter.ProgramChangeEvent({instrument : 1}),
155 | new MidiWriter.NoteEvent({pitch: ['B4'], duration: 'd2'}),
156 | new MidiWriter.NoteEvent({pitch: ['C5'], duration: '4'}),
157 | new MidiWriter.NoteEvent({pitch: ['B4'], duration: 'd2'}),
158 | new MidiWriter.NoteEvent({pitch: ['C5'], duration: '4'}),
159 | new MidiWriter.NoteEvent({pitch: ['B4'], duration: 'd2'}),
160 | new MidiWriter.NoteEvent({pitch: ['C5'], duration: '4'}),
161 | new MidiWriter.NoteEvent({pitch: ['B4'], duration: 'd2'}),
162 | new MidiWriter.NoteEvent({pitch: ['Bb4'], duration: '4'}),
163 | new MidiWriter.NoteEvent({pitch: ['A4'], duration: 'd2'}),
164 | new MidiWriter.NoteEvent({pitch: ['B4'], duration: '4'}),
165 | new MidiWriter.NoteEvent({pitch: ['A4'], duration: 'd2'}),
166 | new MidiWriter.NoteEvent({pitch: ['B4'], duration: '4'}),
167 | new MidiWriter.NoteEvent({pitch: ['A4'], duration: 'd2'}),
168 | new MidiWriter.NoteEvent({pitch: ['B4'], duration: 'd8'}),
169 | new MidiWriter.NoteEvent({pitch: ['A4'], duration: '16'}),
170 | new MidiWriter.NoteEvent({pitch: ['A4'], duration: 'd2'}),
171 | new MidiWriter.NoteEvent({pitch: ['Ab4'], duration: '2'}),
172 | new MidiWriter.NoteEvent({pitch: ['A4', 'B4', 'D5', 'C5', 'E4', 'A4'], duration: '8', sequential:true}),
173 | new MidiWriter.NoteEvent({pitch: ['Gb4'], duration: 'd2'}),
174 | new MidiWriter.NoteEvent({pitch: ['A4'], duration: '4'}),
175 | new MidiWriter.NoteEvent({pitch: ['Gb4'], duration: 'd2'}),
176 | new MidiWriter.NoteEvent({pitch: ['A4'], duration: '4'}),
177 | new MidiWriter.NoteEvent({pitch: ['G1', 'Gb4', 'C4', 'B3', 'Eb4', 'Gb4'], duration: '8', sequential: true}),
178 | new MidiWriter.NoteEvent({pitch: ['D5', 'C5', 'B5'], duration: '8t', sequential: true}),
179 | // 4x quintuplets
180 | new MidiWriter.NoteEvent({pitch: ['B1', 'A4', 'A4', 'A4', 'A4'], duration: '8t5', sequential: true}),
181 | new MidiWriter.NoteEvent({pitch: ['A4', 'A4', 'A4', 'A4', 'A4'], duration: '8t5', sequential: true}),
182 | new MidiWriter.NoteEvent({pitch: ['A4', 'A4', 'A4', 'A4', 'A4'], duration: '8t5', sequential: true}),
183 | new MidiWriter.NoteEvent({pitch: ['A4', 'A4', 'A4', 'A4', 'A4'], duration: '8t5', sequential: true}),
184 | new MidiWriter.NoteEvent({pitch: ['F1'], duration: '1'}),
185 | ]);
186 |
187 | const builtTrack = track.buildData();
188 | const twelvethBarEvent = builtTrack.events.find((event) => event.pitch === 'G1')
189 | const thirteenthBarEvent = builtTrack.events.find((event) => event.pitch === 'B1');
190 | const fourteenthBarEvent = builtTrack.events.find((event) => event.pitch === 'F1');
191 | // 12th bar:
192 | assert.equal(twelvethBarEvent.tick, 512 * 11);
193 | // 13th bar:
194 | assert.equal(thirteenthBarEvent.tick, 512 * 12);
195 | // 14th bar:
196 | assert.equal(fourteenthBarEvent.tick, 512 * 13);
197 | // console.log(builtTrack.events)
198 | })
199 |
200 | it('3 triplets and 1 dotted is the duration as one whole - next bar starts in the correct place', function() {
201 | const track = new MidiWriter.Track();
202 |
203 | track
204 | .addEvent([
205 | new MidiWriter.ProgramChangeEvent({instrument : 1}),
206 | new MidiWriter.NoteEvent({pitch: ['D5', 'G5', 'B4'], duration: '8t', sequential: true}),
207 | new MidiWriter.NoteEvent({pitch: ['B4'], duration: 'd2'}),
208 | new MidiWriter.NoteEvent({pitch: ['C5'], duration: '4'}),
209 | ]);
210 |
211 | const builtTrack = track.buildData();
212 | const bar2note = builtTrack.events.find((event) => event.pitch === 'C5');
213 |
214 | const track2 = new MidiWriter.Track();
215 |
216 | track2
217 | .addEvent([
218 | new MidiWriter.ProgramChangeEvent({instrument : 1}),
219 | new MidiWriter.NoteEvent({pitch: ['D5'], duration: '1'}),
220 | new MidiWriter.NoteEvent({pitch: ['C5'], duration: '4'}),
221 | ]);
222 |
223 | const builtTrack2 = track2.buildData();
224 | const bar2note2 = builtTrack2.events.find((event) => event.pitch === 'C5');
225 |
226 | assert.equal(JSON.stringify(bar2note, null, 2), JSON.stringify(bar2note2, null, 2));
227 | })
228 |
229 | it('should return specific base64 string when manually adding EndTrackEvent', function() {
230 | const track = new MidiWriter.Track();
231 |
232 | track.addEvent([
233 | new MidiWriter.NoteEvent({pitch: ['C5', 'E5', 'G4'], duration: '4'}),
234 | new MidiWriter.EndTrackEvent({delta: 5000}),
235 | ]);
236 |
237 | const write = new MidiWriter.Writer(track);
238 | assert.equal('TVRoZAAAAAYAAAABAIBNVHJrAAAAHgCQSEAAkExAAJBDQIEAgEhAAIBMQACAQ0CnCP8vAA==', write.base64());
239 | })
240 |
241 | it('should return specific base64 string when removing events by name', function() {
242 | const track = new MidiWriter.Track();
243 |
244 | track.addEvent(new MidiWriter.CopyrightEvent({text: "Garrett Grimm"}));
245 | track.removeEventsByName('CopyrightEvent');
246 | track.addEvent(new MidiWriter.NoteEvent({pitch: ["C4", "D4", "E4"], sequential: true}));
247 |
248 | const write = new MidiWriter.Writer([track]);
249 |
250 | assert.equal('TVRoZAAAAAYAAAABAIBNVHJrAAAAHwCQPECBAIA8QACQPkCBAIA+QACQQECBAIBAQAD/LwA=', write.base64());
251 | });
252 |
253 | it('should return specific base64 string when merging two tracks', function() {
254 | const track = new MidiWriter.Track();
255 |
256 | track.addEvent([
257 | new MidiWriter.NoteEvent({pitch: ['E4','D4'], duration: '4', sequential: true}),
258 | new MidiWriter.NoteEvent({pitch: 'C4', duration: '2'})
259 | ]);
260 |
261 | const track2 = new MidiWriter.Track();
262 |
263 | track2.addEvent([
264 | new MidiWriter.NoteEvent({pitch: ['G4','F4'], duration: '4', sequential: true}),
265 | new MidiWriter.NoteEvent({pitch: 'E4', duration: '2'})
266 | ]);
267 |
268 | track.mergeTrack(track2);
269 | const write = new MidiWriter.Writer([track]);
270 |
271 | assert.equal('TVRoZAAAAAYAAAABAIBNVHJrAAAANwCQQEAAkENAgQCAQEAAkD5AAIBDQACQQUCBAIA+QACQPEAAgEFAAJBAQIIAgDxAAIBAQAD/LwA=', write.base64());
272 | });
273 | });
274 |
275 | describe('#Utils()', function() {
276 | describe('#stringToBytes()', function () {
277 | it('should return [116, 101, 115, 116] when "test" is passed.', function () {
278 | assert.equal([116, 101, 115, 116].toString(), MidiWriter.Utils.stringToBytes('test').toString());
279 | });
280 | });
281 |
282 | describe('#isNumeric()', function () {
283 | it('should return false when "t" is passed.', function () {
284 | assert.equal(false, MidiWriter.Utils.isNumeric('t'));
285 | });
286 | });
287 |
288 | describe('#getPitch()', function () {
289 | it('should return 101 when "F7" is passed.', function () {
290 | assert.equal(101, MidiWriter.Utils.getPitch('F7'));
291 | });
292 |
293 | it('should return 98 when "D6" is passed and middle C is set to "C3".', function () {
294 | assert.equal(98, MidiWriter.Utils.getPitch('D6', 'C3'));
295 | });
296 |
297 | it('should return 72 (C5) when "B#4" is passed.', function () {
298 | assert.equal(72, MidiWriter.Utils.getPitch('B#4'));
299 | });
300 | });
301 |
302 | describe('#stringByteCount()', function () {
303 | it('should return 7 when "Garrett" is passed.', function () {
304 | assert.equal(7, MidiWriter.Utils.stringByteCount('Garrett'));
305 | });
306 | });
307 |
308 | describe('#numberFromBytes()', function () {
309 | it('should return 8463 when [0x21, 0x0f] is passed.', function () {
310 | assert.equal(8463, MidiWriter.Utils.numberFromBytes([0x21, 0x0f]));
311 | });
312 | });
313 |
314 | describe('#numberToBytes()', function () {
315 | it('should return [0, 5] when converting the number 5 into two bytes.', function() {
316 | assert.equal([0, 5].toString(), MidiWriter.Utils.numberToBytes(5, 2));
317 | });
318 | });
319 |
320 | describe('#getDurationMultiplier()', function () {
321 | it('should return 1 for a quarter note.', function () {
322 | const note = new MidiWriter.NoteEvent({pitch: 'C4', duration: '4'});
323 | assert.equal(MidiWriter.Utils.getDurationMultiplier(note.duration), 1);
324 | });
325 | });
326 |
327 | describe('#getTickDuration()', function () {
328 | it('should return 128 for a quarter note.', function () {
329 | const note = new MidiWriter.NoteEvent({pitch: 'C4', duration: '4'});
330 | assert.equal(MidiWriter.Utils.getTickDuration(note.duration), 128);
331 | });
332 | it('should return 128 for 3 triplet eights.', function () {
333 | const note = new MidiWriter.NoteEvent({pitch: 'C4', duration: ['8t', '8t', '8t']});
334 | assert.equal(MidiWriter.Utils.getTickDuration(note.duration), 128);
335 | });
336 | });
337 |
338 | });
339 | });
340 |
--------------------------------------------------------------------------------
/test/vexflow.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert');
2 | const MidiWriter = require('..');
3 |
4 | /**
5 | * Mock a VexFlow StaveNote
6 | * @param {String} noteType
7 | * @param {String} duration
8 | * @param {[String]} keys
9 | * @param {boolean} isDotted
10 | */
11 | function mockNote(noteType='n', duration='8', keys=['c/4'], dots=0, tuplet=null, accidentals=null) {
12 | const result = {
13 | noteType,
14 | duration,
15 | keys,
16 | dots,
17 | tuplet,
18 | getAccidentals() {
19 | return accidentals;
20 | }
21 | };
22 | return result;
23 | }
24 |
25 | describe('MidiWriterJS', function() {
26 | describe('#VexFlow()', function() {
27 | it('instantiates', function() {
28 | const v = new MidiWriter.VexFlow();
29 | assert.notStrictEqual(typeof v, 'undefined');
30 | assert.strictEqual(v instanceof MidiWriter.VexFlow, true);
31 | });
32 | describe('#trackFromVoice', function() {
33 | it('converts a VexFlow voice into a track', function() {
34 | const mockVoice = {
35 | tickables: [
36 | mockNote('n', '8', ['g#/3']),
37 | mockNote('n', '8', ['b/3']),
38 | mockNote('n', '8', ['c#/3']),
39 | mockNote('r'),
40 | mockNote('n', '8', ['b/3']),
41 | mockNote('r'),
42 | mockNote('n', '8', ['c#/3']),
43 | mockNote('n', '8', ['b/3']),
44 | mockNote('n', '8', ['a#/3']),
45 | mockNote('r'),
46 | mockNote('n', '8', ['b/3']),
47 | mockNote('r')
48 | ]
49 | };
50 | const vexFlow = new MidiWriter.VexFlow();
51 | const track = vexFlow.trackFromVoice(mockVoice);
52 | const write = new MidiWriter.Writer(track);
53 | assert.strictEqual(write.base64(), 'TVRoZAAAAAYAAAABAIBNVHJrAAAATACQOEBAgDhAAJA7QECAO0AAkDFAQIAxQECQO0BAgDtAQJAxQECAMUAAkDtAQIA7QACQOkBAgDpAQJA7QECAO0BAkAAAAIAAAAD/LwA=');
54 | });
55 |
56 | it('preserves multiple rests', function() {
57 | const mockVoice = {
58 | tickables: [
59 | mockNote(),
60 | mockNote('r'),
61 | mockNote('r'),
62 | mockNote('')
63 | ]
64 | };
65 | const vexFlow = new MidiWriter.VexFlow();
66 | const track = vexFlow.trackFromVoice(mockVoice);
67 | const write = new MidiWriter.Writer(track);
68 | assert.strictEqual(write.base64(), 'TVRoZAAAAAYAAAABAIBNVHJrAAAAFQCQPEBAgDxAgQCQAAAAgAAAAP8vAA==');
69 | });
70 |
71 | it('appends trailing rests with a silent note', function() {
72 | const mockVoice = {
73 | tickables: [
74 | mockNote(),
75 | mockNote(),
76 | mockNote('r'),
77 | mockNote('r')
78 | ]
79 | };
80 | const vexFlow = new MidiWriter.VexFlow();
81 | const track = vexFlow.trackFromVoice(mockVoice);
82 | const write = new MidiWriter.Writer(track);
83 | assert.strictEqual(write.base64(), 'TVRoZAAAAAYAAAABAIBNVHJrAAAAHQCQPEBAgDxAAJA8QECAPECBAJAAAACAAAAA/y8A');
84 | });
85 | });
86 |
87 | describe('#convertPitch()', function() {
88 | it('converts pitch', function () {
89 | const vexFlow = new MidiWriter.VexFlow();
90 | const tickable = mockNote('n', 'h', ['c/4']);
91 | assert.deepStrictEqual(tickable.keys.map((pitch, index) => vexFlow.convertPitch(pitch, index, tickable)), ['c4']);
92 | });
93 | it('converts multiple pitch', function() {
94 | const vexFlow = new MidiWriter.VexFlow();
95 | const tickable = mockNote('n', 'h', ['b/4', 'c/4']);
96 | assert.deepStrictEqual(tickable.keys.map((pitch, index) => vexFlow.convertPitch(pitch, index, tickable)), ['b4', 'c4']);
97 | });
98 | it('converts accidentals pitch', function() {
99 | const vexFlow = new MidiWriter.VexFlow();
100 | const tickable = mockNote('n', 'h', ['b#/4', 'cb/4', 'dn/4']);
101 | assert.deepStrictEqual(tickable.keys.map((pitch, index) => vexFlow.convertPitch(pitch, index, tickable)), ['b#4', 'cb4', 'd4']);
102 | });
103 | it('converts rendered accidentals pitch', function() {
104 | const vexFlow = new MidiWriter.VexFlow();
105 | const tickable = mockNote('n', 'h', ['b/4', 'c/4'], 0, null, [{index: 0, type: '#'}, {index: 1, type: 'b'}]);
106 | assert.deepStrictEqual(tickable.keys.map((pitch, index) => vexFlow.convertPitch(pitch, index, tickable, true)), ['b#4', 'cb4']);
107 | });
108 | });
109 |
110 | describe('#convertDuration()', function() {
111 | it('converts whole, half, quarter and eighth durations', function () {
112 | const vexFlow = new MidiWriter.VexFlow();
113 | const tickable = mockNote('n', 'w');
114 | assert.strictEqual(vexFlow.convertDuration(tickable), '1');
115 | tickable.duration = 'h'
116 | assert.strictEqual(vexFlow.convertDuration(tickable), '2');
117 | tickable.duration = 'q'
118 | assert.strictEqual(vexFlow.convertDuration(tickable), '4');
119 | tickable.duration = '8'
120 | assert.strictEqual(vexFlow.convertDuration(tickable), '8');
121 | });
122 | it('converts dotted half, quarter and eighth durations', function () {
123 | const vexFlow = new MidiWriter.VexFlow();
124 | const tickable = mockNote('n', 'h', ['c/4'], 1);
125 | assert.strictEqual(vexFlow.convertDuration(tickable), 'd2');
126 | tickable.duration = 'q'
127 | assert.strictEqual(vexFlow.convertDuration(tickable), 'd4');
128 | tickable.duration = '8'
129 | assert.strictEqual(vexFlow.convertDuration(tickable), 'd8');
130 | });
131 | it('converts multiple dotted durations', function() {
132 | const vexFlow = new MidiWriter.VexFlow();
133 | const tickable = mockNote('n', 'h', ['c/4'], 2);
134 | assert.strictEqual(vexFlow.convertDuration(tickable), 'dd2');
135 | tickable.dots = 3
136 | assert.strictEqual(vexFlow.convertDuration(tickable), 'ddd2');
137 | tickable.dots = 4
138 | assert.strictEqual(vexFlow.convertDuration(tickable), 'dddd2');
139 | });
140 | it('converts tuplet durations', function() {
141 | const vexFlow = new MidiWriter.VexFlow();
142 | const tickable = mockNote('n', 'h', ['c/4'], 0, {num_notes: 3});
143 | assert.strictEqual(vexFlow.convertDuration(tickable), '2t3');
144 | tickable.tuplet.num_notes = 5
145 | assert.strictEqual(vexFlow.convertDuration(tickable), '2t5');
146 | tickable.tuplet.num_notes = 7
147 | assert.strictEqual(vexFlow.convertDuration(tickable), '2t7');
148 | });
149 | it('converts dotted tuplet durations', function() {
150 | const vexFlow = new MidiWriter.VexFlow();
151 | const tickable = mockNote('n', 'h', ['c/4'], 1, {num_notes: 3});
152 | assert.strictEqual(vexFlow.convertDuration(tickable), 'd2t3');
153 | tickable.tuplet.num_notes = 5
154 | assert.strictEqual(vexFlow.convertDuration(tickable), 'd2t5');
155 | tickable.dots = 2
156 | assert.strictEqual(vexFlow.convertDuration(tickable), 'dd2t5');
157 | });
158 | it('returns other durations', function () {
159 | const vexFlow = new MidiWriter.VexFlow();
160 | const tickable = mockNote('n', '64', ['c/4']);
161 | assert.strictEqual(vexFlow.convertDuration(tickable), '64');
162 | });
163 | });
164 | });
165 | });
166 |
--------------------------------------------------------------------------------
/test/writer.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert');
2 | const MidiWriter = require('..');
3 |
4 | describe('MidiWriterJS', function() {
5 | describe('#Writer()', function() {
6 | describe('#base64()', function() {
7 | it('should return specific base64 string when a single C4 quarter note is created.', function () {
8 | const track = new MidiWriter.Track();
9 | const note = new MidiWriter.NoteEvent({pitch: 'C4', duration: '4'});
10 | track.addEvent(note);
11 | const write = new MidiWriter.Writer(track);
12 | assert.equal('TVRoZAAAAAYAAAABAIBNVHJrAAAADQCQPECBAIA8QAD/LwA=', write.base64());
13 | });
14 | });
15 |
16 | describe('#Hot Cross Buns', function() {
17 | it('should return specific base64 string when hot cross buns is created.', function () {
18 | const midi = require('../examples/hot-cross-buns.js');
19 | assert.equal('data:audio/midi;base64,TVRoZAAAAAYAAAABAIBNVHJrAAAAlQCQQECBAIBAQACQPkCBAIA+QACQPECCAIA8QACQQECBAIBAQACQPkCBAIA+QACQPECCAIA8QACQPEBAgDxAAJA8QECAPEAAkDxAQIA8QACQPEBAgDxAAJA+QECAPkAAkD5AQIA+QACQPkBAgD5AAJA+QECAPkAAkEBAgQCAQEAAkD5AgQCAPkAAkDxAggCAPEAA/y8A', midi.dataUri());
20 | });
21 | });
22 |
23 | describe('#setMiddleC()', function() {
24 | it('should return specific base64 when setting middle C to C3 in Hot Cross Buns example.', function() {
25 | const midi = require('../examples/hot-cross-buns.js');
26 | assert.equal('data:audio/midi;base64,TVRoZAAAAAYAAAABAIBNVHJrAAAAlQCQTECBAIBMQACQSkCBAIBKQACQSECCAIBIQACQTECBAIBMQACQSkCBAIBKQACQSECCAIBIQACQSEBAgEhAAJBIQECASEAAkEhAQIBIQACQSEBAgEhAAJBKQECASkAAkEpAQIBKQACQSkBAgEpAAJBKQECASkAAkExAgQCATEAAkEpAgQCASkAAkEhAggCASEAA/y8A', midi.setOption('middleC', 'C3').dataUri());
27 | });
28 | });
29 |
30 | describe('#Maruo Giuliani', function() {
31 | it('should return specific base64 string when Maruo Giuliani example is created.', function () {
32 | const midi = require('../examples/mauro.giuliani-op.47-main-theme.js');
33 | assert.equal('data:audio/midi;base64,TVRoZAAAAAYAAQADAIBNVHJrAAAAEwD/WAQDAhgIAP9RAwknwAD/LwBNVHJrAAACFwCQSUAAkExAggCASUAAgExAAJBJQACQTECBAIBJQACATEAAkElAAJBMQIIAgElAAIBMQACQRUAAkElAgQCARUAAgElAAJBHQACQSkCCAIBHQACASkAAkERAAJBMQIEAgERAAIBMQACQRUAAkElAggCARUAAgElAAJBFQIEAgEVAAJBJQACQTECCAIBJQACATEAAkElAAJBMQIEAgElAAIBMQACQSUAAkExAggCASUAAgExAAJBFQACQSUCBAIBFQACASUAAkEdAAJBKQIIAgEdAAIBKQACQREAAkExAgQCAREAAgExAAJBFQACQSUCCAIBFQACASUCBAJBMQACQTECBAIBMQACATEAAkEtAAJBOQIEAgEtAAIBOQACQSkAAkFBAgQCASkAAgFBAAJBJQACQUUCCAIBJQACAUUAAkExAgQCATEAAkExAAJBMQIEAgExAAIBMQACQS0AAkE5AgQCAS0AAgE5AAJBKQACQUECBAIBKQACAUEAAkElAAJBRQIIAgElAAIBRQIEAkElAAJBMQIIAgElAAIBMQACQSUAAkExAgQCASUAAgExAAJBJQACQTECBAIBJQACATEAAkFFAgQCAUUAAkEVAAJBJQIEAgEVAAIBJQACQSUAAkExAgQCASUAAgExAAJBHQACQSkCBAIBHQACASkAAkERAAJBHQIEAgERAAIBHQACQRUCCAIBFQAD/LwBNVHJrAAAAowCQOUCCAIA5QIEAkDlAggCAOUCBAJA0QIIAgDRAgQCQOUCCAIA5QIEAkDlAggCAOUCBAJA5QIIAgDlAgQCQNECCAIA0QIEAkDlAggCAOUCBAJA0QIIAgDRAgQCQOUCCAIA5QIEAkDRAggCANECBAJA5QIIAgDlAgQCQOUCCAIA5QIEAkDlAggCAOUCBAJA0QIIAgDRAgQCQOUCCAIA5QAD/LwA=', midi.dataUri());
34 | });
35 | });
36 |
37 | describe('#Chopin Prelude', function() {
38 | it('should return specific base64 string when chopin prelude is created.', function () {
39 | const midi = require('../examples/chopin-prelude-e-minor.js');
40 | assert.equal('data:audio/midi;base64,TVRoZAAAAAYAAQACAIBNVHJrAAAC3gD/UQMPQkAAwAEAkEd/gwCAR38AkEh/gQCASH8AkEd/gwCAR38AkEh/gQCASH8AkEd/gwCAR38AkEh/gQCASH8AkEd/gwCAR38AkEZ/gQCARn8AkEV/gwCARX8AkEd/gQCAR38AkEV/gwCARX8AkEd/gQCAR38AkEV/gwCARX8AkEd/YIBHfwCQRX8ggEV/AJBFf4MAgEV/AJBEf4IAgER/AJBFf0CARX8AkEd/QIBHfwCQSn9AgEp/AJBIf0CASH8AkEB/QIBAfwCQRX9AgEV/AJBCf4MAgEJ/AJBFf4EAgEV/AJBCf4MAgEJ/AJBFf4EAgEV/AJBDf0CAQ38AkEJ/QIBCfwCQPH9AgDx/AJA7f0CAO38AkD9/QIA/fwCQQn9AgEJ/AJBKfyuASn8AkEh/KoBIfwCQR38rgEd/AJBHf4MAgEd/AJBIf4EAgEh/AJBHf4MAgEd/AJBIf4EAgEh/AJBHf4MAgEd/AJBIf4EAgEh/AJBHf2CAR38AkEZ/IIBGfwCQRn+BAIBGfwCQT3+BAIBPfwCQTn9ggE5/AJBMfyCATH8AkEx/QIBMfwCQS39AgEt/AJBUf0CAVH8AkEt/QIBLfwCQS39AgEt/AJBMf0CATH8AkE9/QIBPfwCQR39AgEd/AJBKf0CASn8AkEh/QIBIfwCQTH8rgEx/AJBAfyqAQH8AkEV/K4BFfwCQQn+BQIBCfwCQRX9AgEV/AJBCf4MAgEJ/AJBFf4EAgEV/AJBCf4MAgEJ/AJBCf2CAQn8AkEB/IIBAfwCQQH+DAIBAfwCQQn+BAIBCfwCQQH+DAIBAfwCQQn+BAIBCfwCQQH+CAIBAf4IAkFR/AJBPfwCQSn8AkEh/ggCAVH8AgE9/AIBKfwCASH8AkFR/AJBPfwCQSn8AkEh/ggCAVH8AgE9/AIBKfwCASH8AkFR/AJBPfwCQTH8AkEh/gQCAVH8AgE9/AIBMfwCASH8A/y8ATVRyawAAED4AwAEAkEBAAJA7QACQN0BAgEBAAIA7QACAN0AAkEBAAJA7QACQN0BAgEBAAIA7QACAN0AAkEBAAJA7QACQN0BAgEBAAIA7QACAN0AAkEBAAJA7QACQN0BAgEBAAIA7QACAN0AAkEBAAJA7QACQN0BAgEBAAIA7QACAN0AAkEBAAJA7QACQN0BAgEBAAIA7QACAN0AAkEBAAJA7QACQN0BAgEBAAIA7QACAN0AAkEBAAJA7QACQN0BAgEBAAIA7QACAN0AAkEBAAJA5QACQNkBAgEBAAIA5QACANkAAkEBAAJA5QACQNkBAgEBAAIA5QACANkAAkEBAAJA5QACQNkBAgEBAAIA5QACANkAAkEBAAJA5QACQNkBAgEBAAIA5QACANkAAkD9AAJA5QACQNkBAgD9AAIA5QACANkAAkD9AAJA5QACQNkBAgD9AAIA5QACANkAAkD9AAJA5QACQNkBAgD9AAIA5QACANkAAkD9AAJA5QACQNkBAgD9AAIA5QACANkAAkD9AAJA5QACQNUBAgD9AAIA5QACANUAAkD9AAJA5QACQNUBAgD9AAIA5QACANUAAkD9AAJA5QACQNUBAgD9AAIA5QACANUAAkD9AAJA5QACQNUBAgD9AAIA5QACANUAAkD5AAJA5QACQNUBAgD5AAIA5QACANUAAkD5AAJA5QACQNUBAgD5AAIA5QACANUAAkD5AAJA4QACQNUBAgD5AAIA4QACANUAAkD5AAJA4QACQNUBAgD5AAIA4QACANUAAkD5AAJA4QACQNEBAgD5AAIA4QACANEAAkD5AAJA4QACQNEBAgD5AAIA4QACANEAAkD5AAJA4QACQNEBAgD5AAIA4QACANEAAkD5AAJA4QACQNEBAgD5AAIA4QACANEAAkD5AAJA3QACQNEBAgD5AAIA3QACANEAAkD5AAJA3QACQNEBAgD5AAIA3QACANEAAkD1AAJA3QACQNEBAgD1AAIA3QACANEAAkD1AAJA3QACQNEBAgD1AAIA3QACANEAAkDxAAJA3QACQNEBAgDxAAIA3QACANEAAkDxAAJA3QACQNEBAgDxAAIA3QACANEAAkDxAAJA3QACQNEBAgDxAAIA3QACANEAAkDxAAJA3QACQNEBAgDxAAIA3QACANEAAkDxAAJA2QACQNEBAgDxAAIA2QACANEAAkDxAAJA2QACQNEBAgDxAAIA2QACANEAAkDxAAJA2QACQNEBAgDxAAIA2QACANEAAkDxAAJA2QACQNEBAgDxAAIA2QACANEAAkDxAAJA2QACQNEBAgDxAAIA2QACANEAAkDxAAJA2QACQNEBAgDxAAIA2QACANEAAkDxAAJA2QACQNEBAgDxAAIA2QACANEAAkDxAAJA2QACQNEBAgDxAAIA2QACANEAAkDxAAJA2QACQM0BAgDxAAIA2QACAM0AAkDxAAJA2QACQM0BAgDxAAIA2QACAM0AAkDxAAJA2QACQM0BAgDxAAIA2QACAM0AAkDxAAJA2QACQM0BAgDxAAIA2QACAM0AAkDxAAJA2QACQMkBAgDxAAIA2QACAMkAAkDxAAJA2QACQMkBAgDxAAIA2QACAMkAAkDxAAJA2QACQMkBAgDxAAIA2QACAMkAAkDxAAJA2QACQMkBAgDxAAIA2QACAMkAAkDxAAJA2QACQMkBAgDxAAIA2QACAMkAAkDxAAJA2QACQMkBAgDxAAIA2QACAMkAAkDxAAJA2QACQMkBAgDxAAIA2QACAMkAAkDxAAJA2QACQMkBAgDxAAIA2QACAMkAAkDxAAJA1QACQMkBAgDxAAIA1QACAMkAAkDxAAJA1QACQMkBAgDxAAIA1QACAMkAAkDxAAJA1QACQMkBAgDxAAIA1QACAMkAAkDxAAJA1QACQMkBAgDxAAIA1QACAMkAAkEdAAJA1QACQMkBAgEdAAIA1QACAMkAAkEdAAJA1QACQMkBAgEdAAIA1QACAMkAAkEdAAJA1QACQMkBAgEdAAIA1QACAMkAAkEdAAJA1QACQMkBAgEdAAIA1QACAMkAAkEdAAJA0QACQMEBAgEdAAIA0QACAMEAAkEdAAJA0QACQMEBAgEdAAIA0QACAMEAAkEVAAJA0QACQMEBAgEVAAIA0QACAMEAAkEVAAJA0QACQMEBAgEVAAIA0QACAMEAAkEVAAJA0QACQMEBAgEVAAIA0QACAMEAAkEVAAJA0QACQMEBAgEVAAIA0QACAMEAAkEVAAJA0QACQMEBAgEVAAIA0QACAMEAAkEVAAJA0QACQMEBAgEVAAIA0QACAMEAAkEVAAJA0QACQO0BAgEVAAIA0QACAO0AAkEVAAJA0QACQO0BAgEVAAIA0QACAO0AAkEVAAJAzQACQO0BAgEVAAIAzQACAO0AAkEVAAJAzQACQO0BAgEVAAIAzQACAO0AAkEVAAJA0QACQMEBAgEVAAIA0QACAMEAAkEVAAJA0QACQMEBAgEVAAIA0QACAMEAAkEVAAJA0QACQMEBAgEVAAIA0QACAMEAAkEVAAJA0QACQMEBAgEVAAIA0QACAMEAAkEVAAJAzQACQO0BAgEVAAIAzQACAO0AAkEVAAJAzQACQO0BAgEVAAIAzQACAO0AAkEVAAJAzQACQO0BAgEVAAIAzQACAO0AAkEVAAJAzQACQO0BAgEVAAIAzQACAO0AAkEVAAJA0QACQMEBAgEVAAIA0QACAMEAAkEVAAJA0QACQMEBAgEVAAIA0QACAMEAAkEVAAJA0QACQMEBAgEVAAIA0QACAMEAAkEVAAJA0QACQMEBAgEVAAIA0QACAMEAAkEVAAJAzQACQO0CBAIBFQACAM0AAgDtAgwCQQEAAkDtAAJA3QECAQEAAgDtAAIA3QACQQEAAkDtAAJA3QECAQEAAgDtAAIA3QACQQEAAkDtAAJA3QECAQEAAgDtAAIA3QACQQEAAkDtAAJA3QECAQEAAgDtAAIA3QACQQEAAkDtAAJA3QECAQEAAgDtAAIA3QACQQEAAkDtAAJA3QECAQEAAgDtAAIA3QACQQEAAkDtAAJA3QECAQEAAgDtAAIA3QACQQEAAkDtAAJA3QECAQEAAgDtAAIA3QACQQEAAkDlAAJA2QECAQEAAgDlAAIA2QACQQEAAkDlAAJA2QECAQEAAgDlAAIA2QACQQEAAkDlAAJA2QECAQEAAgDlAAIA2QACQQEAAkDlAAJA2QECAQEAAgDlAAIA2QACQP0AAkDlAAJA1QECAP0AAgDlAAIA1QACQP0AAkDlAAJA1QECAP0AAgDlAAIA1QACQP0AAkDlAAJA1QECAP0AAgDlAAIA1QACQP0AAkDlAAJA1QECAP0AAgDlAAIA1QACQP0AAkDhAAJA1QECAP0AAgDhAAIA1QACQP0AAkDhAAJA1QECAP0AAgDhAAIA1QACQPkAAkDhAAJA1QECAPkAAgDhAAIA1QACQPkAAkDhAAJA1QECAPkAAgDhAAIA1QACQPkAAkDdAAJA0QECAPkAAgDdAAIA0QACQPkAAkDdAAJA0QECAPkAAgDdAAIA0QACQPkAAkDdAAJA0QECAPkAAgDdAAIA0QACQPkAAkDdAAJA0QECAPkAAgDdAAIA0QACQPkAAkDdAAJA0QECAPkAAgDdAAIA0QACQPkAAkDdAAJA0QECAPkAAgDdAAIA0QACQPUAAkDdAAJA0QECAPUAAgDdAAIA0QACQPUAAkDdAAJA0QECAPUAAgDdAAIA0QACQRkAAkDRAAJAxQECARkAAgDRAAIAxQACQRkAAkDRAAJAxQECARkAAgDRAAIAxQACQRUAAkDRAAJAwQECARUAAgDRAAIAwQACQRUAAkDRAAJAwQECARUAAgDRAAIAwQACQO0AAkC9AQIA7QACAL0AAkFFAAJBOQACQPEAAkEVAQIBRQACATkAAgDxAAIBFQACQUUAAkE5AAJA8QACQRUBAgFFAAIBOQACAPEAAgEVAAJBRQACQTkAAkDxAAJBFQECAUUAAgE5AAIA8QACARUAAkE5AAJBLQACQR0AAkENAQIBOQACAS0AAgEdAAIBDQACQTEAAkEdAAJBDQECATEAAgEdAAIBDQACQTEAAkEdAAJBDQECATEAAgEdAAIBDQACQTEAAkEdAAJBDQECATEAAgEdAAIBDQACQTEAAkDxAAJBFQECATEAAgDxAAIBFQACQTEAAkDxAAJBFQECATEAAgDxAAIBFQACQOUBAgDlAAJA8QACQNkAAkDRAQIA8QACANkAAgDRAAJBHQACQNEAAkDtAQIBHQACANEAAgDtAAJBHQACQNEAAkDtAQIBHQACANEAAgDtAAJBFQACQNEAAkDBAQIBFQACANEAAgDBAAJBFQACQNEAAkDBAQIBFQACANEAAgDBAAJA7QACQNEAAkC9AQIA7QACANEAAgC9AAJA7QACQNEAAkC9AQIA7QACANEAAgC9AAJA7QACQNEAAkC9AQIA7QACANEAAgC9AAJA7QACQNEAAkC9AQIA7QACANEAAgC9AAJA5QACQNEAAkDBAQIA5QACANEAAgDBAAJA5QACQNEAAkDBAQIA5QACANEAAgDBAAJA5QACQNEAAkDBAQIA5QACANEAAgDBAAJA5QACQNEAAkDBAQIA5QACANEAAgDBAAJA7QACQNEAAkC9AQIA7QACANEAAgC9AAJA7QACQNEAAkC9AQIA7QACANEAAgC9AAJA7QACQNEAAkC9AQIA7QACANEAAgC9AAJA7QACQNEAAkC9AQIA7QACANEAAgC9AAJA7QACQM0AAkDtAQIA7QACAM0AAgDtAAJA7QACQM0AAkDtAQIA7QACAM0AAgDtAAJA5QACQM0AAkDtAQIA5QACAM0AAgDtAAJA5QACQM0AAkDtAQIA5QACAM0AAgDtAAJA3QACQNEAAkDBAQIA3QACANEAAgDBAAJA3QACQNEAAkDBAQIA3QACANEAAgDBAAJA3QACQNEAAkDBAQIA3QACANEAAgDBAAJA3QACQNEAAkDBAQIA3QACANEAAgDBAAJA6QACQNEAAkDBAQIA6QACANEAAgDBAAJA6QACQNEAAkDBAQIA6QACANEAAgDBAAJA5QACQNEAAkDBAQIA5QACANEAAgDBAAJA5QACQNEAAkDBAQIA5QACANEAAgDBAAJA5QACQNEAAkDtAQIA5QACANEAAgDtAAJA5QACQNEAAkDtAQIA5QACANEAAgDtAAJA4QACQNEAAkDtAQIA4QACANEAAgDtAAJA4QACQNEAAkDtAQIA4QACANEAAgDtAAJA3QACQNEAAkDtAQIA3QACANEAAgDtAAJA3QACQNEAAkDtAQIA3QACANEAAgDtAAJA3QACQNEAAkDtAQIA3QACANEAAgDtAAJA3QACQNEAAkDtAQIA3QACANEAAgDtAAJA3QACQMEAAkDpAggCAN0AAgDBAAIA6QIIAkDtAAJAvQIIAgDtAAIAvQACQO0AAkDZAAJAvQIIAgDtAAIA2QACAL0AAkChAAJAcQIQAgChAAIAcQAD/LwA=', midi.dataUri());
41 | });
42 | });
43 |
44 | describe('#Zelda Theme', function() {
45 | it('should return specific base64 string when zelda theme is created.', function () {
46 | const midi = require('../examples/zelda-main-theme.js');
47 | assert.equal('data:audio/midi;base64,TVRoZAAAAAYAAQACAIBNVHJrAAADoQDAAQCQRn+CAIBGfwCQRn9AgEZ/AJBBf0CAQX8AkEF/QIBBfwCQRn9AgEZ/AJBEfyCARH8AkEJ/IIBCfwCQRH+DAIBEf0CQRn+CAIBGfwCQRn9AgEZ/AJBBf0CAQX8AkEF/QIBBfwCQRn9AgEZ/AJBFfyCARX8AkEN/IIBDfwCQRX+BQIBFf4IAkEUBhACARQGEAJBGf4EAgEZ/AJBBf4FAgEF/AJBGf0CARn8AkEZ/IIBGfwCQSH8ggEh/AJBKfyCASn8AkEt/IIBLfwCQTX+CAIBNf0CQTX9AgE1/AJBNf0CATX8AkE5/IIBOfwCQUH8ggFB/AJBSf4IAgFJ/QJBSf0CAUn8AkFJ/QIBSfwCQUH8ggFB/AJBOfyCATn8AkFB/YIBQfwCQTn8ggE5/AJBNf4IAgE1/AJBNf4EAgE1/AJBLf2CAS38AkE1/IIBNfwCQTn+CAIBOfwCQTX9AgE1/AJBLf0CAS38AkEl/YIBJfwCQS38ggEt/AJBNf4IAgE1/AJBLf0CAS38AkEl/QIBJfwCQSH9ggEh/AJBKfyCASn8AkEx/ggCATH8AkE9/gQCAT38AkE1/QIBNfwCQQX8ggEF/AJBBfyCAQX8AkEF/QIBBfwCQQX8ggEF/AJBBfyCAQX8AkEF/QIBBfwCQQX8ggEF/AJBBfyCAQX8AkEF/QIBBfwCQQX9AgEF/AJBGf4EAgEZ/AJBBf4FAgEF/AJBGf0CARn8AkEZ/IIBGfwCQSH8ggEh/AJBKfyCASn8AkEt/IIBLfwCQTX+CAIBNf0CQTX9AgE1/AJBNf0CATX8AkE5/IIBOfwCQUH8ggFB/AJBSf4MAgFJ/AJBVf4EAgFV/AJBUf4EAgFR/AJBRf4IAgFF/AJBNf4EAgE1/AJBOf4MAgE5/AJBSf4EAgFJ/AJBRf4EAgFF/AJBNf4IAgE1/AJBNf4EAgE1/AJBOf4MAgE5/AJBSf4EAgFJ/AJBRf4EAgFF/AJBNf4IAgE1/AJBKf4EAgEp/AJBLf4MAgEt/AJBOf4EAgE5/AJBNf4EAgE1/AJBJf4IAgEl/AJBGf4EAgEZ/AJBIf2CASH8AkEp/IIBKfwCQTH+CAIBMfwCQT3+BAIBPfwCQTX9AgE1/AJBBfyCAQX8AkEF/IIBBfwCQQX9AgEF/AJBBfyCAQX8AkEF/IIBBfwCQQX9AgEF/AJBBfyCAQX8AkEF/IIBBfwCQQX9AgEF/AJBBf0CAQX8A/y8ATVRyawAACUMAwAEAkCpAgQCAKkAAkDVAgQCANUAAkDpAggCAOkAAkClAgQCAKUAAkDNAgQCAM0AAkDhAggCAOEAAkCdAgQCAJ0AAkDFAgQCAMUAAkDpAggCAOkAAkCpAgQCAKkAAkDVAgQCANUAAkDpAggCAOkAAkC5AQIAuQACQLkAggC5AAJApQCCAKUAAkC5AQIAuQACQLkAggC5AAJApQCCAKUAAkC5AQIAuQACQLkAggC5AAJApQCCAKUAAkC5AIIAuQACQKUAggClAAJAuQCCALkAAkClAIIApQACQLkBAgC5AAJAuQCCALkAAkClAIIApQACQLkBAgC5AAJAuQCCALkAAkClAIIApQACQLkBAgC5AAJAuQCCALkAAkClAIIApQACQLkAggC5AAJApQCCAKUAAkC5AIIAuQACQKUAggClAAJAuQECALkAAkC5AIIAuQACQKUAggClAAJAuQECALkAAkC5AIIAuQACQKUAggClAAJAuQECALkAAkC5AIIAuQACQKUAggClAAJAuQCCALkAAkClAIIApQACQLkAggC5AAJApQCCAKUAAkCxAQIAsQACQLEAggCxAAJAnQCCAJ0AAkCxAQIAsQACQLEAggCxAAJAnQCCAJ0AAkCxAQIAsQACQLEAggCxAAJAnQCCAJ0AAkCxAIIAsQACQJ0AggCdAAJAsQCCALEAAkCdAIIAnQACQKkBAgCpAAJAqQCCAKkAAkCVAIIAlQACQKkBAgCpAAJAqQCCAKkAAkCVAIIAlQACQKkBAgCpAAJAqQCCAKkAAkCVAIIAlQACQKkAggCpAAJAlQCCAJUAAkCpAIIAqQACQJUAggCVAAJAxQECAMUAAkDFAIIAxQACQLEAggCxAAJAxQECAMUAAkDFAIIAxQACQLEAggCxAAJAxQECAMUAAkDFAIIAxQACQLEAggCxAAJAxQCCAMUAAkCxAIIAsQACQMUAggDFAAJAsQCCALEAAkC9AQIAvQACQL0AggC9AAJAqQCCAKkAAkC9AQIAvQACQL0AggC9AAJAqQCCAKkAAkC9AQIAvQACQL0AggC9AAJAqQCCAKkAAkC9AIIAvQACQKkAggCpAAJAvQCCAL0AAkCpAIIAqQACQLkBAgC5AAJAuQCCALkAAkClAIIApQACQLkBAgC5AAJAuQCCALkAAkClAIIApQACQLkBAgC5AAJAuQCCALkAAkClAIIApQACQLkAggC5AAJApQCCAKUAAkC5AIIAuQACQKUAggClAAJAwQECAMEAAkDBAIIAwQACQK0AggCtAAJAwQECAMEAAkDBAIIAwQACQK0AggCtAAJAwQECAMEAAkDBAIIAwQACQK0AggCtAAJAwQCCAMEAAkCtAIIArQACQMEAggDBAAJArQCCAK0AAkClAQIApQACQOUAggDlAAJA5QCCAOUAAkDhAQIA4QACQOEAggDhAAJA4QCCAOEAAkDdAQIA3QACQN0AggDdAAJA3QCCAN0AAkDZAQIA2QACQKUBAgClAAJAuQECALkAAkC5AIIAuQACQKUAggClAAJAuQECALkAAkC5AIIAuQACQKUAggClAAJAuQECALkAAkC5AIIAuQACQKUAggClAAJAuQCCALkAAkClAIIApQACQLkAggC5AAJApQCCAKUAAkCxAQIAsQACQLEAggCxAAJAnQCCAJ0AAkCxAQIAsQACQLEAggCxAAJAnQCCAJ0AAkCxAQIAsQACQLEAggCxAAJAnQCCAJ0AAkCxAIIAsQACQJ0AggCdAAJAsQCCALEAAkCdAIIAnQACQKkBAgCpAAJAqQCCAKkAAkCpAIIAqQACQKkBAgCpAAJAqQCCAKkAAkCpAIIAqQACQKkBAgCpAAJAqQCCAKkAAkCpAIIAqQACQKkAggCpAAJAqQCCAKkAAkCpAIIAqQACQKkAggCpAAJApQECAKUAAkClAIIApQACQKUAggClAAJApQECAKUAAkClAIIApQACQKUAggClAAJApQECAKUAAkClAIIApQACQKUAggClAAJApQCCAKUAAkClAIIApQACQKUAggClAAJApQCCAKUAAkChAQIAoQACQKEAggChAAJAoQCCAKEAAkChAQIAoQACQKEAggChAAJAoQCCAKEAAkChAQIAoQACQKEAggChAAJAoQCCAKEAAkChAIIAoQACQKEAggChAAJAoQCCAKEAAkChAIIAoQACQKUBAgClAAJApQCCAKUAAkClAIIApQACQKUBAgClAAJApQCCAKUAAkClAIIApQACQKUBAgClAAJApQCCAKUAAkClAIIApQACQKUAggClAAJApQCCAKUAAkClAIIApQACQKUAggClAAJAoQECAKEAAkChAIIAoQACQKEAggChAAJAoQECAKEAAkChAIIAoQACQKEAggChAAJAoQECAKEAAkChAIIAoQACQKEAggChAAJAoQCCAKEAAkChAIIAoQACQKEAggChAAJAoQCCAKEAAkClAQIApQACQKUAggClAAJApQCCAKUAAkClAQIApQACQKUAggClAAJApQCCAKUAAkClAQIApQACQKUAggClAAJApQCCAKUAAkClAIIApQACQKUAggClAAJApQCCAKUAAkClAIIApQACQL0BAgC9AAJAvQCCAL0AAkC9AIIAvQACQL0BAgC9AAJAvQCCAL0AAkC9AIIAvQACQL0BAgC9AAJAvQCCAL0AAkC9AIIAvQACQL0AggC9AAJAvQCCAL0AAkC9AIIAvQACQL0AggC9AAJAuQECALkAAkC5AIIAuQACQLkAggC5AAJAuQECALkAAkC5AIIAuQACQLkAggC5AAJAuQECALkAAkC5AIIAuQACQLkAggC5AAJAuQCCALkAAkC5AIIAuQACQLkAggC5AAJAuQCCALkAAkDBAQIAwQACQMEAggDBAAJAwQCCAMEAAkDBAQIAwQACQMEAggDBAAJAwQCCAMEAAkDBAQIAwQACQMEAggDBAAJAwQCCAMEAAkDBAIIAwQACQMEAggDBAAJAwQCCAMEAAkDBAIIAwQACQKUBAgClAAJA5QCCAOUAAkDlAIIA5QACQOEBAgDhAAJA4QCCAOEAAkDhAIIA4QACQN0BAgDdAAJA3QCCAN0AAkDdAIIA3QACQNkBAgDZAAJApQECAKUAA/y8A', midi.dataUri());
48 | });
49 | });
50 |
51 | describe('#Notes by Start Tick', function() {
52 | it('should return specific base64 string when notes by start tick example is created.', function () {
53 | const midi = require('../examples/notes-by-start-tick.js');
54 | assert.equal('data:audio/midi;base64,TVRoZAAAAAYAAAABAIBNVHJrAAAARACQPEAygDxAAJBAQDKAQEAAkENAAJBHQDKAQ0AAgEdAAJBIQDKASEAAkEpAMoBKQACQTUAygE1AAJBRQDKAUUAA/y8A', midi.dataUri());
55 | });
56 | });
57 | });
58 |
59 | });
60 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "lib": ["esnext", "dom"],
4 | "declaration": true,
5 | "declarationDir": "./types"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/typedoc.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://typedoc.org/schema.json",
3 | "entryPoints": ["./src/main.ts"],
4 | "out": "docs"
5 | }
6 |
--------------------------------------------------------------------------------