spessasynth_lib 3.24.38 → 3.24.40
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 +2 -0
- package/externals/fflate/LICENSE +21 -0
- package/externals/fflate/fflate.min.js +1 -0
- package/midi_parser/basic_midi.js +2 -0
- package/midi_parser/midi_data.js +1 -0
- package/midi_parser/midi_loader.js +7 -1
- package/midi_parser/midi_sequence.js +6 -0
- package/midi_parser/midi_writer.js +1 -1
- package/midi_parser/xmf_loader.js +454 -0
- package/package.json +1 -1
- package/sequencer/sequencer.js +1 -1
- package/sequencer/worklet_sequencer/play.js +10 -0
- package/sequencer/worklet_sequencer/process_event.js +10 -0
- package/sequencer/worklet_sequencer/song_control.js +2 -1
- package/synthetizer/audio_effects/reverb.js +1 -1
- package/synthetizer/worklet_processor.min.js +11 -11
- package/utils/indexed_array.js +13 -2
- package/utils/other.js +3 -1
package/README.md
CHANGED
|
@@ -43,6 +43,8 @@ Supported formats list:
|
|
|
43
43
|
- `.rmi` - RIFF MIDI File
|
|
44
44
|
- `.rmi` - RIFF MIDI File With Embedded DLS
|
|
45
45
|
- `.rmi` - [RIFF MIDI File With Embedded SF2](https://github.com/spessasus/sf2-rmidi-specification)
|
|
46
|
+
- `.xmf` - eXtensible Music Format
|
|
47
|
+
- `.mxmf` - Mobile eXtensible Music format
|
|
46
48
|
|
|
47
49
|
*With [an easy way of converting between them!](https://github.com/spessasus/SpessaSynth/wiki/Converting-Between-Formats)*
|
|
48
50
|
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 Arjun Barrett
|
|
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.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
let tr;(()=>{var l=Uint8Array,T=Uint16Array,ur=Int32Array,W=new l([0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0,0]),X=new l([0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13,0,0]),wr=new l([16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15]),Y=function(r,a){for(var e=new T(31),f=0;f<31;++f)e[f]=a+=1<<r[f-1];for(var v=new ur(e[30]),f=1;f<30;++f)for(var g=e[f];g<e[f+1];++g)v[g]=g-e[f]<<5|f;return{b:e,r:v}},Z=Y(W,2),$=Z.b,cr=Z.r;$[28]=258,cr[258]=28;var j=Y(X,0),hr=j.b,Fr=j.r,_=new T(32768);for(i=0;i<32768;++i)c=(i&43690)>>1|(i&21845)<<1,c=(c&52428)>>2|(c&13107)<<2,c=(c&61680)>>4|(c&3855)<<4,_[i]=((c&65280)>>8|(c&255)<<8)>>1;var c,i,A=function(r,a,e){for(var f=r.length,v=0,g=new T(a);v<f;++v)r[v]&&++g[r[v]-1];var k=new T(a);for(v=1;v<a;++v)k[v]=k[v-1]+g[v-1]<<1;var b;if(e){b=new T(1<<a);var m=15-a;for(v=0;v<f;++v)if(r[v])for(var U=v<<4|r[v],x=a-r[v],n=k[r[v]-1]++<<x,o=n|(1<<x)-1;n<=o;++n)b[_[n]>>m]=U}else for(b=new T(f),v=0;v<f;++v)r[v]&&(b[v]=_[k[r[v]-1]++]>>15-r[v]);return b},M=new l(288);for(i=0;i<144;++i)M[i]=8;var i;for(i=144;i<256;++i)M[i]=9;var i;for(i=256;i<280;++i)M[i]=7;var i;for(i=280;i<288;++i)M[i]=8;var i,L=new l(32);for(i=0;i<32;++i)L[i]=5;var i,gr=A(M,9,1),br=A(L,5,1),q=function(r){for(var a=r[0],e=1;e<r.length;++e)r[e]>a&&(a=r[e]);return a},u=function(r,a,e){var f=a/8|0;return(r[f]|r[f+1]<<8)>>(a&7)&e},C=function(r,a){var e=a/8|0;return(r[e]|r[e+1]<<8|r[e+2]<<16)>>(a&7)},kr=function(r){return(r+7)/8|0},xr=function(r,a,e){return(a==null||a<0)&&(a=0),(e==null||e>r.length)&&(e=r.length),new l(r.subarray(a,e))},yr=["unexpected EOF","invalid block type","invalid length/literal","invalid distance","stream finished","no stream handler",,"no callback","invalid UTF-8 data","extra field too long","date not in range 1980-2099","filename too long","stream finishing","invalid zip data"],h=function(r,a,e){var f=new Error(a||yr[r]);if(f.code=r,Error.captureStackTrace&&Error.captureStackTrace(f,h),!e)throw f;return f},Sr=function(r,a,e,f){var v=r.length,g=f?f.length:0;if(!v||a.f&&!a.l)return e||new l(0);var k=!e,b=k||a.i!=2,m=a.i;k&&(e=new l(v*3));var U=function(fr){var or=e.length;if(fr>or){var lr=new l(Math.max(or*2,fr));lr.set(e),e=lr}},x=a.f||0,n=a.p||0,o=a.b||0,S=a.l,I=a.d,z=a.m,D=a.n,G=v*8;do{if(!S){x=u(r,n,1);var H=u(r,n+1,3);if(n+=3,H)if(H==1)S=gr,I=br,z=9,D=5;else if(H==2){var N=u(r,n,31)+257,s=u(r,n+10,15)+4,d=N+u(r,n+5,31)+1;n+=14;for(var F=new l(d),P=new l(19),t=0;t<s;++t)P[wr[t]]=u(r,n+t*3,7);n+=s*3;for(var rr=q(P),Ar=(1<<rr)-1,Mr=A(P,rr,1),t=0;t<d;){var ar=Mr[u(r,n,Ar)];n+=ar&15;var w=ar>>4;if(w<16)F[t++]=w;else{var E=0,O=0;for(w==16?(O=3+u(r,n,3),n+=2,E=F[t-1]):w==17?(O=3+u(r,n,7),n+=3):w==18&&(O=11+u(r,n,127),n+=7);O--;)F[t++]=E}}var er=F.subarray(0,N),y=F.subarray(N);z=q(er),D=q(y),S=A(er,z,1),I=A(y,D,1)}else h(1);else{var w=kr(n)+4,J=r[w-4]|r[w-3]<<8,K=w+J;if(K>v){m&&h(0);break}b&&U(o+J),e.set(r.subarray(w,K),o),a.b=o+=J,a.p=n=K*8,a.f=x;continue}if(n>G){m&&h(0);break}}b&&U(o+131072);for(var Ur=(1<<z)-1,zr=(1<<D)-1,Q=n;;Q=n){var E=S[C(r,n)&Ur],p=E>>4;if(n+=E&15,n>G){m&&h(0);break}if(E||h(2),p<256)e[o++]=p;else if(p==256){Q=n,S=null;break}else{var nr=p-254;if(p>264){var t=p-257,B=W[t];nr=u(r,n,(1<<B)-1)+$[t],n+=B}var R=I[C(r,n)&zr],V=R>>4;R||h(3),n+=R&15;var y=hr[V];if(V>3){var B=X[V];y+=C(r,n)&(1<<B)-1,n+=B}if(n>G){m&&h(0);break}b&&U(o+131072);var vr=o+nr;if(o<y){var ir=g-y,Dr=Math.min(y,vr);for(ir+o<0&&h(3);o<Dr;++o)e[o]=f[ir+o]}for(;o<vr;++o)e[o]=e[o-y]}}a.l=S,a.p=Q,a.b=o,a.f=x,S&&(x=1,a.m=z,a.d=I,a.n=D)}while(!x);return o!=e.length&&k?xr(e,0,o):e.subarray(0,o)},Tr=new l(0);function mr(r,a){return Sr(r,{i:2},a&&a.out,a&&a.dictionary)}var Er=typeof TextDecoder<"u"&&new TextDecoder,pr=0;try{Er.decode(Tr,{stream:!0}),pr=1}catch{}tr=mr})();export{tr as inflateSync};
|
|
@@ -59,6 +59,7 @@ class BasicMIDI extends MIDISequenceData
|
|
|
59
59
|
m.format = mid.format;
|
|
60
60
|
m.bankOffset = mid.bankOffset;
|
|
61
61
|
m.isKaraokeFile = mid.isKaraokeFile;
|
|
62
|
+
m.isMultiPort = mid.isMultiPort;
|
|
62
63
|
m.isDLSRMIDI = mid.isDLSRMIDI;
|
|
63
64
|
|
|
64
65
|
// Copying arrays
|
|
@@ -452,6 +453,7 @@ class BasicMIDI extends MIDISequenceData
|
|
|
452
453
|
}
|
|
453
454
|
else
|
|
454
455
|
{
|
|
456
|
+
this.isMultiPort = true;
|
|
455
457
|
SpessaSynthInfo(`%cMIDI Ports detected!`, consoleColors.recognized);
|
|
456
458
|
}
|
|
457
459
|
|
package/midi_parser/midi_data.js
CHANGED
|
@@ -44,6 +44,7 @@ export class MIDIData extends MIDISequenceData
|
|
|
44
44
|
this.RMIDInfo = midi.RMIDInfo;
|
|
45
45
|
this.bankOffset = midi.bankOffset;
|
|
46
46
|
this.isKaraokeFile = midi.isKaraokeFile;
|
|
47
|
+
this.isMultiPort = midi.isMultiPort;
|
|
47
48
|
|
|
48
49
|
// Set isEmbedded based on the presence of an embeddedSoundFont
|
|
49
50
|
this.isEmbedded = midi.embeddedSoundFont !== undefined;
|
|
@@ -9,6 +9,7 @@ import { readBytesAsString } from "../utils/byte_functions/string.js";
|
|
|
9
9
|
import { readLittleEndian } from "../utils/byte_functions/little_endian.js";
|
|
10
10
|
import { RMIDINFOChunks } from "./rmidi_writer.js";
|
|
11
11
|
import { BasicMIDI } from "./basic_midi.js";
|
|
12
|
+
import { loadXMF } from "./xmf_loader.js";
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* midi_loader.js
|
|
@@ -145,6 +146,11 @@ class MIDI extends BasicMIDI
|
|
|
145
146
|
this.bankOffset = 0;
|
|
146
147
|
}
|
|
147
148
|
}
|
|
149
|
+
else if (initialString === "XMF_")
|
|
150
|
+
{
|
|
151
|
+
// XMF file
|
|
152
|
+
fileByteArray = loadXMF(this, binaryData);
|
|
153
|
+
}
|
|
148
154
|
else
|
|
149
155
|
{
|
|
150
156
|
fileByteArray = binaryData;
|
|
@@ -212,7 +218,7 @@ class MIDI extends BasicMIDI
|
|
|
212
218
|
}
|
|
213
219
|
else
|
|
214
220
|
{ // noinspection PointlessBooleanExpressionJS
|
|
215
|
-
if (
|
|
221
|
+
if (runningByte === undefined && statusByteCheck < 0x80)
|
|
216
222
|
{
|
|
217
223
|
// if we don't have a running byte and the status byte isn't valid, it's an error.
|
|
218
224
|
SpessaSynthGroupEnd();
|
|
@@ -144,6 +144,12 @@ class MIDISequenceData
|
|
|
144
144
|
*/
|
|
145
145
|
isKaraokeFile = false;
|
|
146
146
|
|
|
147
|
+
/**
|
|
148
|
+
* Indicates if this file is a Multi-Port MIDI file.
|
|
149
|
+
* @type {boolean}
|
|
150
|
+
*/
|
|
151
|
+
isMultiPort = false;
|
|
152
|
+
|
|
147
153
|
/**
|
|
148
154
|
* Converts ticks to time in seconds
|
|
149
155
|
* @param ticks {number} time in MIDI ticks
|
|
@@ -31,7 +31,7 @@ export function writeMIDI()
|
|
|
31
31
|
*/
|
|
32
32
|
let messageData;
|
|
33
33
|
// determine the message
|
|
34
|
-
if (event.messageStatusByte <= messageTypes.
|
|
34
|
+
if (event.messageStatusByte <= messageTypes.sequenceSpecific)
|
|
35
35
|
{
|
|
36
36
|
// this is a meta-message
|
|
37
37
|
// syntax is FF<type><length><data>
|
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
import { readBytesAsString } from "../utils/byte_functions/string.js";
|
|
2
|
+
import { SpessaSynthGroup, SpessaSynthGroupEnd, SpessaSynthInfo, SpessaSynthWarn } from "../utils/loggin.js";
|
|
3
|
+
import { consoleColors } from "../utils/other.js";
|
|
4
|
+
import { readBytesAsUintBigEndian } from "../utils/byte_functions/big_endian.js";
|
|
5
|
+
import { readVariableLengthQuantity } from "../utils/byte_functions/variable_length_quantity.js";
|
|
6
|
+
import { RMIDINFOChunks } from "./rmidi_writer.js";
|
|
7
|
+
import { inflateSync } from "../externals/fflate/fflate.min.js";
|
|
8
|
+
import { IndexedByteArray } from "../utils/indexed_array.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @enum {number}
|
|
12
|
+
*/
|
|
13
|
+
const metadataTypes = {
|
|
14
|
+
XMFFileType: 0,
|
|
15
|
+
nodeName: 1,
|
|
16
|
+
nodeIDNumber: 2,
|
|
17
|
+
resourceFormat: 3,
|
|
18
|
+
filenameOnDisk: 4,
|
|
19
|
+
filenameExtensionOnDisk: 5,
|
|
20
|
+
macOSFileTypeAndCreator: 6,
|
|
21
|
+
mimeType: 7,
|
|
22
|
+
title: 8,
|
|
23
|
+
copyrightNotice: 9,
|
|
24
|
+
comment: 10,
|
|
25
|
+
autoStart: 11, // Node Name of the FileNode containing the SMF image to autostart when the XMF file loads
|
|
26
|
+
preload: 12, // Used to preload specific SMF and DLS file images.
|
|
27
|
+
contentDescription: 13, // RP-42a (https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/rp42.pdf)
|
|
28
|
+
ID3Metadata: 14 // RP-47 (https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/rp47.pdf)
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @enum {number}
|
|
33
|
+
*/
|
|
34
|
+
const referenceTypeIds = {
|
|
35
|
+
inLineResource: 1,
|
|
36
|
+
inFileResource: 2,
|
|
37
|
+
inFileNode: 3,
|
|
38
|
+
externalFile: 4,
|
|
39
|
+
externalXMF: 5,
|
|
40
|
+
XMFFileURIandNodeID: 6
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* @enum {number}
|
|
45
|
+
*/
|
|
46
|
+
const resourceFormatIDs = {
|
|
47
|
+
StandardMIDIFile: 0,
|
|
48
|
+
StandardMIDIFileType1: 1,
|
|
49
|
+
DLS1: 2,
|
|
50
|
+
DLS2: 3,
|
|
51
|
+
DLS22: 4,
|
|
52
|
+
mobileDLS: 5
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* @enum {number}
|
|
57
|
+
*/
|
|
58
|
+
const formatTypeIDs = {
|
|
59
|
+
standard: 0,
|
|
60
|
+
MMA: 1,
|
|
61
|
+
registered: 2,
|
|
62
|
+
nonRegistered: 3
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* @enum {number}
|
|
68
|
+
*/
|
|
69
|
+
const unpackerIDs = {
|
|
70
|
+
none: 0,
|
|
71
|
+
MMAUnpacker: 1,
|
|
72
|
+
registered: 2,
|
|
73
|
+
nonRegistered: 3
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
class XMFNode
|
|
77
|
+
{
|
|
78
|
+
/**
|
|
79
|
+
* @type {number}
|
|
80
|
+
*/
|
|
81
|
+
length;
|
|
82
|
+
/**
|
|
83
|
+
* 0 means it's a file node
|
|
84
|
+
* @type {number}
|
|
85
|
+
*/
|
|
86
|
+
itemCount;
|
|
87
|
+
/**
|
|
88
|
+
* @type {number}
|
|
89
|
+
*/
|
|
90
|
+
metadataLength;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* @type {Object<string, any>}
|
|
94
|
+
*/
|
|
95
|
+
metadata = {};
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* @type {IndexedByteArray}
|
|
99
|
+
*/
|
|
100
|
+
nodeData;
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* @type {XMFNode[]}
|
|
104
|
+
*/
|
|
105
|
+
innerNodes = [];
|
|
106
|
+
|
|
107
|
+
packedContent = false;
|
|
108
|
+
|
|
109
|
+
nodeUnpackers = [];
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* @type {"StandardMIDIFile"|
|
|
114
|
+
* "StandardMIDIFileType1"|
|
|
115
|
+
* "DLS1"|
|
|
116
|
+
* "DLS2"|
|
|
117
|
+
* "DLS22"|
|
|
118
|
+
* "mobileDLS"|
|
|
119
|
+
* "unknown"|"folder"}
|
|
120
|
+
*/
|
|
121
|
+
resourceFormat = "unknown";
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* @param binaryData {IndexedByteArray}
|
|
125
|
+
*/
|
|
126
|
+
constructor(binaryData)
|
|
127
|
+
{
|
|
128
|
+
let nodeStartIndex = binaryData.currentIndex;
|
|
129
|
+
this.length = readVariableLengthQuantity(binaryData);
|
|
130
|
+
this.itemCount = readVariableLengthQuantity(binaryData);
|
|
131
|
+
// header length
|
|
132
|
+
const headerLength = readVariableLengthQuantity(binaryData);
|
|
133
|
+
const readBytes = binaryData.currentIndex - nodeStartIndex;
|
|
134
|
+
|
|
135
|
+
const remainingHeader = headerLength - readBytes;
|
|
136
|
+
const headerData = binaryData.slice(
|
|
137
|
+
binaryData.currentIndex,
|
|
138
|
+
binaryData.currentIndex + remainingHeader
|
|
139
|
+
);
|
|
140
|
+
binaryData.currentIndex += remainingHeader;
|
|
141
|
+
|
|
142
|
+
this.metadataLength = readVariableLengthQuantity(headerData);
|
|
143
|
+
|
|
144
|
+
const metadataChunk = headerData.slice(
|
|
145
|
+
headerData.currentIndex,
|
|
146
|
+
headerData.currentIndex + this.metadataLength
|
|
147
|
+
);
|
|
148
|
+
headerData.currentIndex += this.metadataLength;
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* @type {metadataTypes|string|number}
|
|
152
|
+
*/
|
|
153
|
+
let fieldSpecifier;
|
|
154
|
+
let key;
|
|
155
|
+
while (metadataChunk.currentIndex < metadataChunk.length)
|
|
156
|
+
{
|
|
157
|
+
const firstSpecifierByte = metadataChunk[metadataChunk.currentIndex];
|
|
158
|
+
if (firstSpecifierByte === 0)
|
|
159
|
+
{
|
|
160
|
+
metadataChunk.currentIndex++;
|
|
161
|
+
fieldSpecifier = readVariableLengthQuantity(metadataChunk);
|
|
162
|
+
if (Object.values(metadataTypes).indexOf(fieldSpecifier) === -1)
|
|
163
|
+
{
|
|
164
|
+
SpessaSynthWarn(`Unknown field specifier: ${fieldSpecifier}`);
|
|
165
|
+
key = `unknown_${fieldSpecifier}`;
|
|
166
|
+
}
|
|
167
|
+
else
|
|
168
|
+
{
|
|
169
|
+
key = Object.keys(metadataTypes).find(k => metadataTypes[k] === fieldSpecifier);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
else
|
|
173
|
+
{
|
|
174
|
+
// this is the length of string
|
|
175
|
+
const stringLength = readVariableLengthQuantity(metadataChunk);
|
|
176
|
+
fieldSpecifier = readBytesAsString(metadataChunk, stringLength);
|
|
177
|
+
key = fieldSpecifier;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const numberOfVersions = readVariableLengthQuantity(metadataChunk);
|
|
181
|
+
if (numberOfVersions === 0)
|
|
182
|
+
{
|
|
183
|
+
const dataLength = readVariableLengthQuantity(metadataChunk);
|
|
184
|
+
const contentsChunk = metadataChunk.slice(
|
|
185
|
+
metadataChunk.currentIndex,
|
|
186
|
+
metadataChunk.currentIndex + dataLength
|
|
187
|
+
);
|
|
188
|
+
metadataChunk.currentIndex += dataLength;
|
|
189
|
+
const formatID = readVariableLengthQuantity(contentsChunk);
|
|
190
|
+
// text only
|
|
191
|
+
if (formatID < 4)
|
|
192
|
+
{
|
|
193
|
+
this.metadata[key] = readBytesAsString(contentsChunk, dataLength - 1);
|
|
194
|
+
}
|
|
195
|
+
else
|
|
196
|
+
{
|
|
197
|
+
this.metadata[key] = contentsChunk.slice(contentsChunk.currentIndex);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
else
|
|
201
|
+
{
|
|
202
|
+
// throw new Error ("International content is not supported.");
|
|
203
|
+
// Skip the number of versions
|
|
204
|
+
SpessaSynthWarn(`International content: ${numberOfVersions}`);
|
|
205
|
+
// Length in bytes
|
|
206
|
+
// Skip the whole thing!
|
|
207
|
+
metadataChunk.currentIndex += readVariableLengthQuantity(metadataChunk);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const unpackersStart = headerData.currentIndex;
|
|
212
|
+
const unpackersLength = readVariableLengthQuantity(headerData);
|
|
213
|
+
const unpackersData = headerData.slice(headerData.currentIndex, unpackersStart + unpackersLength);
|
|
214
|
+
headerData.currentIndex = unpackersStart + unpackersLength;
|
|
215
|
+
if (unpackersLength > 0)
|
|
216
|
+
{
|
|
217
|
+
this.packedContent = true;
|
|
218
|
+
while (unpackersData.currentIndex < unpackersLength)
|
|
219
|
+
{
|
|
220
|
+
const unpacker = {};
|
|
221
|
+
unpacker.id = readVariableLengthQuantity(unpackersData);
|
|
222
|
+
switch (unpacker.id)
|
|
223
|
+
{
|
|
224
|
+
case unpackerIDs.nonRegistered:
|
|
225
|
+
case unpackerIDs.registered:
|
|
226
|
+
SpessaSynthGroupEnd();
|
|
227
|
+
throw new Error(`Unsupported unpacker ID: ${unpacker.id}`);
|
|
228
|
+
|
|
229
|
+
default:
|
|
230
|
+
SpessaSynthGroupEnd();
|
|
231
|
+
throw new Error(`Unknown unpacker ID: ${unpacker.id}`);
|
|
232
|
+
|
|
233
|
+
case unpackerIDs.none:
|
|
234
|
+
unpacker.standardID = readVariableLengthQuantity(unpackersData);
|
|
235
|
+
break;
|
|
236
|
+
|
|
237
|
+
case unpackerIDs.MMAUnpacker:
|
|
238
|
+
let manufacturerID = unpackersData[unpackersData.currentIndex++];
|
|
239
|
+
// one or three byte form, depending on if the first byte is zero
|
|
240
|
+
if (manufacturerID === 0)
|
|
241
|
+
{
|
|
242
|
+
manufacturerID <<= 8;
|
|
243
|
+
manufacturerID |= unpackersData[unpackersData.currentIndex++];
|
|
244
|
+
manufacturerID <<= 8;
|
|
245
|
+
manufacturerID |= unpackersData[unpackersData.currentIndex++];
|
|
246
|
+
}
|
|
247
|
+
const manufacturerInternalID = readVariableLengthQuantity(unpackersData);
|
|
248
|
+
unpacker.manufacturerID = manufacturerID;
|
|
249
|
+
unpacker.manufacturerInternalID = manufacturerInternalID;
|
|
250
|
+
break;
|
|
251
|
+
}
|
|
252
|
+
unpacker.decodedSize = readVariableLengthQuantity(unpackersData);
|
|
253
|
+
this.nodeUnpackers.push(unpacker);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
binaryData.currentIndex = nodeStartIndex + headerLength;
|
|
257
|
+
/**
|
|
258
|
+
* @type {referenceTypeIds|number}
|
|
259
|
+
*/
|
|
260
|
+
this.referenceTypeID = readVariableLengthQuantity(binaryData);
|
|
261
|
+
this.nodeData = binaryData.slice(binaryData.currentIndex, nodeStartIndex + this.length);
|
|
262
|
+
binaryData.currentIndex = nodeStartIndex + this.length;
|
|
263
|
+
switch (this.referenceTypeID)
|
|
264
|
+
{
|
|
265
|
+
case referenceTypeIds.inLineResource:
|
|
266
|
+
break;
|
|
267
|
+
|
|
268
|
+
case referenceTypeIds.externalXMF:
|
|
269
|
+
case referenceTypeIds.inFileNode:
|
|
270
|
+
case referenceTypeIds.XMFFileURIandNodeID:
|
|
271
|
+
case referenceTypeIds.externalFile:
|
|
272
|
+
case referenceTypeIds.inFileResource:
|
|
273
|
+
SpessaSynthGroupEnd();
|
|
274
|
+
throw new Error(`Unsupported reference type: ${this.referenceTypeID}`);
|
|
275
|
+
|
|
276
|
+
default:
|
|
277
|
+
SpessaSynthGroupEnd();
|
|
278
|
+
throw new Error(`Unknown reference type: ${this.referenceTypeID}`);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// read the data
|
|
282
|
+
if (this.isFile)
|
|
283
|
+
{
|
|
284
|
+
if (this.packedContent)
|
|
285
|
+
{
|
|
286
|
+
const compressed = this.nodeData.slice(2, this.nodeData.length);
|
|
287
|
+
SpessaSynthInfo(
|
|
288
|
+
`%cPacked content. Attemting to deflate. Target size: %c${this.nodeUnpackers[0].decodedSize}`,
|
|
289
|
+
consoleColors.warn,
|
|
290
|
+
consoleColors.value
|
|
291
|
+
);
|
|
292
|
+
try
|
|
293
|
+
{
|
|
294
|
+
this.nodeData = new IndexedByteArray(inflateSync(compressed).buffer);
|
|
295
|
+
}
|
|
296
|
+
catch (e)
|
|
297
|
+
{
|
|
298
|
+
SpessaSynthGroupEnd();
|
|
299
|
+
throw new Error(`Error unpacking XMF file contents: ${e.message}.`);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* interpret the content
|
|
304
|
+
* @type {number[]}
|
|
305
|
+
*/
|
|
306
|
+
const resourceFormat = this.metadata["resourceFormat"];
|
|
307
|
+
if (resourceFormat === undefined)
|
|
308
|
+
{
|
|
309
|
+
SpessaSynthWarn("No resource format for this file node!");
|
|
310
|
+
}
|
|
311
|
+
else
|
|
312
|
+
{
|
|
313
|
+
const formatTypeID = resourceFormat[0];
|
|
314
|
+
if (formatTypeID !== formatTypeIDs.standard)
|
|
315
|
+
{
|
|
316
|
+
SpessaSynthWarn(`Non-standard formatTypeID: ${resourceFormat}`);
|
|
317
|
+
this.resourceFormat = resourceFormat.toString();
|
|
318
|
+
}
|
|
319
|
+
const resourceFormatID = resourceFormat[1];
|
|
320
|
+
if (Object.values(resourceFormatIDs).indexOf(resourceFormatID) === -1)
|
|
321
|
+
{
|
|
322
|
+
SpessaSynthWarn(`Unrecognized resource format: ${resourceFormatID}`);
|
|
323
|
+
}
|
|
324
|
+
else
|
|
325
|
+
{
|
|
326
|
+
this.resourceFormat = Object.keys(resourceFormatIDs)
|
|
327
|
+
.find(k => resourceFormatIDs[k] === resourceFormatID);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
else
|
|
332
|
+
{
|
|
333
|
+
// folder node
|
|
334
|
+
this.resourceFormat = "folder";
|
|
335
|
+
while (this.nodeData.currentIndex < this.nodeData.length)
|
|
336
|
+
{
|
|
337
|
+
const nodeStartIndex = this.nodeData.currentIndex;
|
|
338
|
+
const nodeLength = readVariableLengthQuantity(this.nodeData);
|
|
339
|
+
const nodeData = this.nodeData.slice(nodeStartIndex, nodeStartIndex + nodeLength);
|
|
340
|
+
this.nodeData.currentIndex = nodeStartIndex + nodeLength;
|
|
341
|
+
this.innerNodes.push(new XMFNode(nodeData));
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
get isFile()
|
|
347
|
+
{
|
|
348
|
+
return this.itemCount === 0;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* @param midi {MIDI}
|
|
354
|
+
* @param binaryData {IndexedByteArray}
|
|
355
|
+
* @returns {IndexedByteArray} the file byte array
|
|
356
|
+
*/
|
|
357
|
+
export function loadXMF(midi, binaryData)
|
|
358
|
+
{
|
|
359
|
+
midi.bankOffset = 0;
|
|
360
|
+
// https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/xmf-v1a.pdf
|
|
361
|
+
// https://wiki.multimedia.cx/index.php?title=Extensible_Music_Format_(XMF)
|
|
362
|
+
const sanityCheck = readBytesAsString(binaryData, 4);
|
|
363
|
+
if (sanityCheck !== "XMF_")
|
|
364
|
+
{
|
|
365
|
+
SpessaSynthGroupEnd();
|
|
366
|
+
throw new SyntaxError(`Invalid XMF Header! Expected "_XMF", got "${sanityCheck}"`);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
SpessaSynthGroup("%cParsing XMF file...", consoleColors.info);
|
|
370
|
+
const version = readBytesAsString(binaryData, 4);
|
|
371
|
+
SpessaSynthInfo(
|
|
372
|
+
`%cXMF version: %c${version}`,
|
|
373
|
+
consoleColors.info, consoleColors.recognized
|
|
374
|
+
);
|
|
375
|
+
// https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/rp43.pdf
|
|
376
|
+
// version 2.00 has additional bytes
|
|
377
|
+
if (version === "2.00")
|
|
378
|
+
{
|
|
379
|
+
const fileTypeId = readBytesAsUintBigEndian(binaryData, 4);
|
|
380
|
+
const fileTypeRevisionId = readBytesAsUintBigEndian(binaryData, 4);
|
|
381
|
+
SpessaSynthInfo(
|
|
382
|
+
`%cFile Type ID: %c${fileTypeId}%c, File Type Revision ID: %c${fileTypeRevisionId}`,
|
|
383
|
+
consoleColors.info,
|
|
384
|
+
consoleColors.recognized,
|
|
385
|
+
consoleColors.info,
|
|
386
|
+
consoleColors.recognized
|
|
387
|
+
);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// file length
|
|
391
|
+
readVariableLengthQuantity(binaryData);
|
|
392
|
+
|
|
393
|
+
const metadataTableLength = readVariableLengthQuantity(binaryData);
|
|
394
|
+
// skip metadata
|
|
395
|
+
binaryData.currentIndex += metadataTableLength;
|
|
396
|
+
|
|
397
|
+
// skip to tree root
|
|
398
|
+
binaryData.currentIndex = readVariableLengthQuantity(binaryData);
|
|
399
|
+
const rootNode = new XMFNode(binaryData);
|
|
400
|
+
/**
|
|
401
|
+
* @type {IndexedByteArray}
|
|
402
|
+
*/
|
|
403
|
+
let midiArray;
|
|
404
|
+
/**
|
|
405
|
+
* find the stuff we care about
|
|
406
|
+
* @param node {XMFNode}
|
|
407
|
+
*/
|
|
408
|
+
const searchNode = node =>
|
|
409
|
+
{
|
|
410
|
+
const checkMeta = (xmf, rmid) =>
|
|
411
|
+
{
|
|
412
|
+
if (node.metadata[xmf] !== undefined && typeof node.metadata[xmf] === "string")
|
|
413
|
+
{
|
|
414
|
+
midi.RMIDInfo[rmid] = node.metadata[xmf];
|
|
415
|
+
}
|
|
416
|
+
};
|
|
417
|
+
// meta
|
|
418
|
+
checkMeta("nodeName", RMIDINFOChunks.name);
|
|
419
|
+
checkMeta("title", RMIDINFOChunks.name);
|
|
420
|
+
checkMeta("copyrightNotice", RMIDINFOChunks.copyright);
|
|
421
|
+
checkMeta("comment", RMIDINFOChunks.comment);
|
|
422
|
+
if (node.isFile)
|
|
423
|
+
{
|
|
424
|
+
switch (node.resourceFormat)
|
|
425
|
+
{
|
|
426
|
+
default:
|
|
427
|
+
return;
|
|
428
|
+
case "DLS1":
|
|
429
|
+
case "DLS2":
|
|
430
|
+
case "DLS22":
|
|
431
|
+
case "mobileDLS":
|
|
432
|
+
SpessaSynthInfo("%cFound embedded DLS!", consoleColors.recognized);
|
|
433
|
+
midi.embeddedSoundFont = node.nodeData.buffer;
|
|
434
|
+
break;
|
|
435
|
+
|
|
436
|
+
case "StandardMIDIFile":
|
|
437
|
+
case "StandardMIDIFileType1":
|
|
438
|
+
SpessaSynthInfo("%cFound embedded MIDI!", consoleColors.recognized);
|
|
439
|
+
midiArray = node.nodeData;
|
|
440
|
+
break;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
else
|
|
444
|
+
{
|
|
445
|
+
for (const n of node.innerNodes)
|
|
446
|
+
{
|
|
447
|
+
searchNode(n);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
};
|
|
451
|
+
searchNode(rootNode);
|
|
452
|
+
SpessaSynthGroupEnd();
|
|
453
|
+
return midiArray;
|
|
454
|
+
}
|
package/package.json
CHANGED
package/sequencer/sequencer.js
CHANGED
|
@@ -138,12 +138,22 @@ export function _playTo(time, ticks = undefined)
|
|
|
138
138
|
break;
|
|
139
139
|
|
|
140
140
|
case messageTypes.programChange:
|
|
141
|
+
// empty tracks cannot program change
|
|
142
|
+
if (this.midiData.isMultiPort && this.midiData.usedChannelsOnTrack[trackIndex].size === 0)
|
|
143
|
+
{
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
141
146
|
const p = programs[channel];
|
|
142
147
|
p.program = event.messageData[0];
|
|
143
148
|
p.actualBank = p.bank;
|
|
144
149
|
break;
|
|
145
150
|
|
|
146
151
|
case messageTypes.controllerChange:
|
|
152
|
+
// empty tracks cannot controller change
|
|
153
|
+
if (this.midiData.isMultiPort && this.midiData.usedChannelsOnTrack[trackIndex].size === 0)
|
|
154
|
+
{
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
147
157
|
// do not skip data entries
|
|
148
158
|
const controllerNumber = event.messageData[0];
|
|
149
159
|
if (isCCNonSkippable(controllerNumber))
|
|
@@ -65,10 +65,20 @@ export function _processEvent(event, trackIndex)
|
|
|
65
65
|
break;
|
|
66
66
|
|
|
67
67
|
case messageTypes.controllerChange:
|
|
68
|
+
// empty tracks cannot cc change
|
|
69
|
+
if (this.midiData.isMultiPort && this.midiData.usedChannelsOnTrack[trackIndex].size === 0)
|
|
70
|
+
{
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
68
73
|
this.synth.controllerChange(statusByteData.channel, event.messageData[0], event.messageData[1]);
|
|
69
74
|
break;
|
|
70
75
|
|
|
71
76
|
case messageTypes.programChange:
|
|
77
|
+
// empty tracks cannot program change
|
|
78
|
+
if (this.midiData.isMultiPort && this.midiData.usedChannelsOnTrack[trackIndex].size === 0)
|
|
79
|
+
{
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
72
82
|
this.synth.programChange(statusByteData.channel, event.messageData[0]);
|
|
73
83
|
break;
|
|
74
84
|
|
|
@@ -174,7 +174,8 @@ export function loadNewSongList(midiBuffers, autoPlay = true)
|
|
|
174
174
|
}
|
|
175
175
|
catch (e)
|
|
176
176
|
{
|
|
177
|
-
|
|
177
|
+
console.error(e);
|
|
178
|
+
this.post(WorkletSequencerReturnMessageType.midiError, e);
|
|
178
179
|
return mids;
|
|
179
180
|
}
|
|
180
181
|
return mids;
|
|
@@ -16,7 +16,7 @@ export function getReverbProcessor(context, reverbBuffer = undefined)
|
|
|
16
16
|
else
|
|
17
17
|
{
|
|
18
18
|
// decode
|
|
19
|
-
context.decodeAudioData(reverbBufferBinary).then(b =>
|
|
19
|
+
context.decodeAudioData(reverbBufferBinary.slice(0, reverbBufferBinary.byteLength)).then(b =>
|
|
20
20
|
{
|
|
21
21
|
convolver.buffer = b;
|
|
22
22
|
});
|