@huffduff/midi-writer-ts 3.2.0 → 3.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -28
- package/build/index.cjs +24 -26
- package/build/index.mjs +1 -26
- package/build/types/main.d.ts +2 -35
- package/package.json +9 -2
- package/src/abstract-event.ts +8 -0
- package/src/chunks/chunk.ts +6 -0
- package/src/chunks/header.ts +29 -0
- package/src/chunks/track.ts +345 -0
- package/src/constants.ts +18 -0
- package/src/main.ts +48 -0
- package/src/meta-events/copyright-event.ts +35 -0
- package/src/meta-events/cue-point-event.ts +35 -0
- package/src/meta-events/end-track-event.ts +29 -0
- package/src/meta-events/instrument-name-event.ts +35 -0
- package/src/meta-events/key-signature-event.ts +73 -0
- package/src/meta-events/lyric-event.ts +35 -0
- package/src/meta-events/marker-event.ts +35 -0
- package/src/meta-events/meta-event.ts +7 -0
- package/src/meta-events/tempo-event.ts +37 -0
- package/src/meta-events/text-event.ts +35 -0
- package/src/meta-events/time-signature-event.ts +32 -0
- package/src/meta-events/track-name-event.ts +35 -0
- package/src/midi-events/controller-change-event.ts +30 -0
- package/src/midi-events/midi-event.ts +11 -0
- package/src/midi-events/note-event.ts +164 -0
- package/src/midi-events/note-off-event.ts +55 -0
- package/src/midi-events/note-on-event.ts +69 -0
- package/src/midi-events/pitch-bend-event.ts +40 -0
- package/src/midi-events/program-change-event.ts +28 -0
- package/src/utils.ts +263 -0
- package/src/vexflow.ts +96 -0
- package/src/writer.ts +99 -0
- package/.editorconfig +0 -24
- package/.eslintignore +0 -3
- package/.eslintrc.js +0 -18
- package/.nvmrc +0 -1
- package/.travis.yml +0 -3
- package/browser/midiwriter.js +0 -1367
- package/jsdoc.json +0 -5
- package/postinstall.js +0 -1
- package/rollup.config.js +0 -22
- package/runkit.js +0 -18
- package/tsconfig.json +0 -13
- package/typedoc.json +0 -5
package/src/vexflow.ts
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
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};
|
package/src/writer.ts
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
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};
|
package/.editorconfig
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
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
|
package/.eslintignore
DELETED
package/.eslintrc.js
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
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
|
-
};
|
package/.nvmrc
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
v18.14.2
|
package/.travis.yml
DELETED