spessasynth_lib 3.21.14 → 3.22.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/@types/soundfont/basic_soundfont/basic_soundfont.d.ts +4 -0
- package/@types/synthetizer/key_modifier_manager.d.ts +42 -0
- package/@types/synthetizer/synthetizer.d.ts +6 -0
- package/@types/synthetizer/worklet_system/message_protocol/worklet_message.d.ts +4 -0
- package/@types/synthetizer/worklet_system/worklet_methods/worklet_key_modifier.d.ts +69 -0
- package/package.json +1 -1
- package/sequencer/sequencer.js +1 -1
- package/sequencer/worklet_sequencer/song_control.js +1 -1
- package/soundfont/basic_soundfont/basic_soundfont.js +8 -0
- package/soundfont/basic_soundfont/write_sf2/write.js +1 -1
- package/soundfont/dls/articulator_converter.js +1 -1
- package/soundfont/dls/dls_sample.js +1 -1
- package/soundfont/dls/dls_soundfont.js +18 -5
- package/soundfont/dls/read_instrument.js +1 -1
- package/soundfont/dls/read_samples.js +1 -1
- package/soundfont/read_sf2/samples.js +12 -5
- package/soundfont/read_sf2/soundfont.js +3 -3
- package/synthetizer/key_modifier_manager.js +73 -0
- package/synthetizer/synthetizer.js +7 -0
- package/synthetizer/worklet_processor.min.js +10 -10
- package/synthetizer/worklet_system/main_processor.js +8 -1
- package/synthetizer/worklet_system/message_protocol/handle_message.js +21 -2
- package/synthetizer/worklet_system/message_protocol/worklet_message.js +4 -1
- package/synthetizer/worklet_system/worklet_methods/note_on.js +7 -0
- package/synthetizer/worklet_system/worklet_methods/worklet_key_modifier.js +141 -0
- package/synthetizer/worklet_system/worklet_utilities/lowpass_filter.js +0 -1
- package/synthetizer/worklet_system/worklet_utilities/worklet_voice.js +114 -100
|
@@ -81,6 +81,10 @@ export class BasicSoundFont {
|
|
|
81
81
|
* @returns {BasicPreset}
|
|
82
82
|
*/
|
|
83
83
|
getPresetByName(presetName: string): BasicPreset;
|
|
84
|
+
/**
|
|
85
|
+
* @param error {string}
|
|
86
|
+
*/
|
|
87
|
+
parsingError(error: string): void;
|
|
84
88
|
write: typeof write;
|
|
85
89
|
}
|
|
86
90
|
import { Modulator } from "./modulator.js";
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export class KeyModifierManager {
|
|
2
|
+
/**
|
|
3
|
+
* @param synth {Synthetizer}
|
|
4
|
+
*/
|
|
5
|
+
constructor(synth: Synthetizer);
|
|
6
|
+
synth: Synthetizer;
|
|
7
|
+
/**
|
|
8
|
+
* @private
|
|
9
|
+
* @param type {workletKeyModifierMessageType}
|
|
10
|
+
* @param data {any}
|
|
11
|
+
*/
|
|
12
|
+
private _sendToWorklet;
|
|
13
|
+
/**
|
|
14
|
+
* Modifies a single key
|
|
15
|
+
* @param channel {number} the channel affected. Usually 0-15
|
|
16
|
+
* @param midiNote {number} the MIDI note to change. 0-127
|
|
17
|
+
* @param options {{
|
|
18
|
+
* velocity: number|undefined,
|
|
19
|
+
* patch: {
|
|
20
|
+
* bank: number,
|
|
21
|
+
* program: number
|
|
22
|
+
* }|undefined
|
|
23
|
+
* }} the key's modifiers
|
|
24
|
+
*/
|
|
25
|
+
addModifier(channel: number, midiNote: number, options: {
|
|
26
|
+
velocity: number | undefined;
|
|
27
|
+
patch: {
|
|
28
|
+
bank: number;
|
|
29
|
+
program: number;
|
|
30
|
+
} | undefined;
|
|
31
|
+
}): void;
|
|
32
|
+
/**
|
|
33
|
+
* Deletes a key modifier
|
|
34
|
+
* @param channel {number} the channel affected. Usually 0-15
|
|
35
|
+
* @param midiNote {number} the MIDI note to change. 0-127
|
|
36
|
+
*/
|
|
37
|
+
deleteModifier(channel: number, midiNote: number): void;
|
|
38
|
+
/**
|
|
39
|
+
* Clears ALL Modifiers
|
|
40
|
+
*/
|
|
41
|
+
clearModifiers(): void;
|
|
42
|
+
}
|
|
@@ -82,6 +82,11 @@ export class Synthetizer {
|
|
|
82
82
|
* @type {SoundfontManager}
|
|
83
83
|
*/
|
|
84
84
|
soundfontManager: SoundfontManager;
|
|
85
|
+
/**
|
|
86
|
+
* The synth's key modifier manager
|
|
87
|
+
* @type {KeyModifierManager}
|
|
88
|
+
*/
|
|
89
|
+
keyModifierManager: KeyModifierManager;
|
|
85
90
|
/**
|
|
86
91
|
* @type {function(SynthesizerSnapshot)}
|
|
87
92
|
* @private
|
|
@@ -343,4 +348,5 @@ export type StartRenderingDataConfig = {
|
|
|
343
348
|
};
|
|
344
349
|
import { EventHandler } from "./synth_event_handler.js";
|
|
345
350
|
import { SoundfontManager } from "./synth_soundfont_manager.js";
|
|
351
|
+
import { KeyModifierManager } from "./key_modifier_manager.js";
|
|
346
352
|
import { FancyChorus } from "./audio_effects/fancy_chorus.js";
|
|
@@ -29,6 +29,7 @@ export namespace workletMessageType {
|
|
|
29
29
|
let sequencerSpecific: number;
|
|
30
30
|
let requestSynthesizerSnapshot: number;
|
|
31
31
|
let setLogLevel: number;
|
|
32
|
+
let keyModifierManager: number;
|
|
32
33
|
}
|
|
33
34
|
export type masterParameterType = number;
|
|
34
35
|
export namespace masterParameterType {
|
|
@@ -63,6 +64,9 @@ export type WorkletMessage = {
|
|
|
63
64
|
} | boolean | ArrayBuffer | {
|
|
64
65
|
messageType: WorkletSequencerMessageType;
|
|
65
66
|
messageData: any;
|
|
67
|
+
} | {
|
|
68
|
+
messageType: workletKeyModifierMessageType;
|
|
69
|
+
messageData: any;
|
|
66
70
|
});
|
|
67
71
|
};
|
|
68
72
|
export type WorkletReturnMessage = {
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
export class KeyModifier {
|
|
2
|
+
/**
|
|
3
|
+
* @param velocity {number}
|
|
4
|
+
* @param bank {number}
|
|
5
|
+
* @param program {number}
|
|
6
|
+
*/
|
|
7
|
+
constructor(velocity?: number, bank?: number, program?: number);
|
|
8
|
+
/**
|
|
9
|
+
* The new override velocity. -1 means unchanged
|
|
10
|
+
* @type {number}
|
|
11
|
+
*/
|
|
12
|
+
velocity: number;
|
|
13
|
+
/**
|
|
14
|
+
* The patch this key uses. -1 on either means default
|
|
15
|
+
* @type {{bank: number, program: number}}
|
|
16
|
+
*/
|
|
17
|
+
patch: {
|
|
18
|
+
bank: number;
|
|
19
|
+
program: number;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export type workletKeyModifierMessageType = number;
|
|
23
|
+
export namespace workletKeyModifierMessageType {
|
|
24
|
+
let addMapping: number;
|
|
25
|
+
let deleteMapping: number;
|
|
26
|
+
let clearMappings: number;
|
|
27
|
+
}
|
|
28
|
+
export class WorkletKeyModifierManager {
|
|
29
|
+
/**
|
|
30
|
+
* The velocity override mappings for MIDI keys
|
|
31
|
+
* @type {KeyModifier[][]}
|
|
32
|
+
* @private
|
|
33
|
+
*/
|
|
34
|
+
private _keyMappings;
|
|
35
|
+
/**
|
|
36
|
+
* @param type {workletKeyModifierMessageType}
|
|
37
|
+
* @param data {any}
|
|
38
|
+
*/
|
|
39
|
+
handleMessage(type: workletKeyModifierMessageType, data: any): void;
|
|
40
|
+
/**
|
|
41
|
+
* @param channel {number}
|
|
42
|
+
* @param midiNote {number}
|
|
43
|
+
* @param mapping {KeyModifier}
|
|
44
|
+
*/
|
|
45
|
+
addMapping(channel: number, midiNote: number, mapping: KeyModifier): void;
|
|
46
|
+
deleteMapping(channel: any, midiNote: any): void;
|
|
47
|
+
clearMappings(): void;
|
|
48
|
+
/**
|
|
49
|
+
* @param channel {number}
|
|
50
|
+
* @param midiNote {number}
|
|
51
|
+
* @returns {number} velocity, -1 if unchanged
|
|
52
|
+
*/
|
|
53
|
+
getVelocity(channel: number, midiNote: number): number;
|
|
54
|
+
/**
|
|
55
|
+
* @param channel {number}
|
|
56
|
+
* @param midiNote {number}
|
|
57
|
+
* @returns {boolean}
|
|
58
|
+
*/
|
|
59
|
+
hasOverridePatch(channel: number, midiNote: number): boolean;
|
|
60
|
+
/**
|
|
61
|
+
* @param channel {number}
|
|
62
|
+
* @param midiNote {number}
|
|
63
|
+
* @returns {{bank: number, program: number}} -1 if unchanged
|
|
64
|
+
*/
|
|
65
|
+
getPatch(channel: number, midiNote: number): {
|
|
66
|
+
bank: number;
|
|
67
|
+
program: number;
|
|
68
|
+
};
|
|
69
|
+
}
|
package/package.json
CHANGED
package/sequencer/sequencer.js
CHANGED
|
@@ -49,7 +49,7 @@ export function loadNewSequence(parsedMidi, autoPlay = true)
|
|
|
49
49
|
this.stop();
|
|
50
50
|
if (!parsedMidi.tracks)
|
|
51
51
|
{
|
|
52
|
-
throw "
|
|
52
|
+
throw new Error("This MIDI has no tracks!");
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
this.oneTickToSeconds = 60 / (120 * parsedMidi.timeDivision);
|
|
@@ -225,6 +225,14 @@ class BasicSoundFont
|
|
|
225
225
|
}
|
|
226
226
|
return preset;
|
|
227
227
|
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* @param error {string}
|
|
231
|
+
*/
|
|
232
|
+
parsingError(error)
|
|
233
|
+
{
|
|
234
|
+
throw new Error(`SF parsing error: ${error} The file may be corrupted.`);
|
|
235
|
+
}
|
|
228
236
|
}
|
|
229
237
|
|
|
230
238
|
BasicSoundFont.prototype.write = write;
|
|
@@ -25,7 +25,7 @@ class DLSSoundFont extends BasicSoundFont
|
|
|
25
25
|
if (!this.dataArray)
|
|
26
26
|
{
|
|
27
27
|
SpessaSynthGroupEnd();
|
|
28
|
-
|
|
28
|
+
this.parsingError("No data provided!");
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
// read the main chunk
|
|
@@ -85,7 +85,7 @@ class DLSSoundFont extends BasicSoundFont
|
|
|
85
85
|
if (!colhChunk)
|
|
86
86
|
{
|
|
87
87
|
SpessaSynthGroupEnd();
|
|
88
|
-
|
|
88
|
+
this.parsingError("No colh chunk!");
|
|
89
89
|
}
|
|
90
90
|
this.instrumentAmount = readLittleEndian(colhChunk.chunkData, 4);
|
|
91
91
|
SpessaSynthInfo(
|
|
@@ -96,6 +96,11 @@ class DLSSoundFont extends BasicSoundFont
|
|
|
96
96
|
|
|
97
97
|
// read wave list
|
|
98
98
|
let waveListChunk = findRIFFListType(chunks, "wvpl");
|
|
99
|
+
if (!waveListChunk)
|
|
100
|
+
{
|
|
101
|
+
SpessaSynthGroupEnd();
|
|
102
|
+
this.parsingError("No wvpl chunk!");
|
|
103
|
+
}
|
|
99
104
|
this.readDLSSamples(waveListChunk);
|
|
100
105
|
|
|
101
106
|
// read instrument list
|
|
@@ -103,7 +108,7 @@ class DLSSoundFont extends BasicSoundFont
|
|
|
103
108
|
if (!instrumentListChunk)
|
|
104
109
|
{
|
|
105
110
|
SpessaSynthGroupEnd();
|
|
106
|
-
|
|
111
|
+
this.parsingError("No lins chunk!");
|
|
107
112
|
}
|
|
108
113
|
this.readDLSInstrumentList(instrumentListChunk);
|
|
109
114
|
|
|
@@ -135,7 +140,7 @@ class DLSSoundFont extends BasicSoundFont
|
|
|
135
140
|
if (chunk.header.toLowerCase() !== expected.toLowerCase())
|
|
136
141
|
{
|
|
137
142
|
SpessaSynthGroupEnd();
|
|
138
|
-
|
|
143
|
+
this.parsingError(`Invalid DLS chunk header! Expected "${expected.toLowerCase()}" got "${chunk.header.toLowerCase()}"`);
|
|
139
144
|
}
|
|
140
145
|
}
|
|
141
146
|
|
|
@@ -148,9 +153,17 @@ class DLSSoundFont extends BasicSoundFont
|
|
|
148
153
|
if (text.toLowerCase() !== expected.toLowerCase())
|
|
149
154
|
{
|
|
150
155
|
SpessaSynthGroupEnd();
|
|
151
|
-
|
|
156
|
+
this.parsingError(`FourCC error: Expected "${expected.toLowerCase()}" got "${text.toLowerCase()}"`);
|
|
152
157
|
}
|
|
153
158
|
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* @param error {string}
|
|
162
|
+
*/
|
|
163
|
+
parsingError(error)
|
|
164
|
+
{
|
|
165
|
+
throw new Error(`DLS parse error: ${error} The file may be corrupted.`);
|
|
166
|
+
}
|
|
154
167
|
}
|
|
155
168
|
|
|
156
169
|
DLSSoundFont.prototype.readDLSInstrumentList = readDLSInstrumentList;
|
|
@@ -85,7 +85,7 @@ export function readDLSInstrument(chunk)
|
|
|
85
85
|
if (type !== "rgn " && type !== "rgn2")
|
|
86
86
|
{
|
|
87
87
|
SpessaSynthGroupEnd();
|
|
88
|
-
|
|
88
|
+
this.parsingError(`Invalid DLS region! Expected "rgn " or "rgn2" got "${type}"`);
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
|
|
@@ -80,7 +80,7 @@ export function readDLSSamples(waveListChunk)
|
|
|
80
80
|
const dataChunk = waveChunks.find(c => c.header === "data");
|
|
81
81
|
if (!dataChunk)
|
|
82
82
|
{
|
|
83
|
-
|
|
83
|
+
this.parsingError("No data chunk in the WAVE chunk!");
|
|
84
84
|
}
|
|
85
85
|
const sampleLength = dataChunk.size / bytesPerSample;
|
|
86
86
|
const sampleData = new Float32Array(sampleLength);
|
|
@@ -113,11 +113,18 @@ export class LoadedSample extends BasicSample
|
|
|
113
113
|
const buff = smplArr.slice(this.sampleStartIndex / 2 + smplStart, this.sampleEndIndex / 2 + smplStart);
|
|
114
114
|
// reset array and being decoding
|
|
115
115
|
this.sampleData = new Float32Array(0);
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
116
|
+
try
|
|
117
|
+
{
|
|
118
|
+
/**
|
|
119
|
+
* @type {{data: Float32Array[], error: (string|null), sampleRate: number, eof: boolean}}
|
|
120
|
+
*/
|
|
121
|
+
const vorbis = stbvorbis.decode(buff.buffer);
|
|
122
|
+
this.sampleData = vorbis.data[0];
|
|
123
|
+
}
|
|
124
|
+
catch (e)
|
|
125
|
+
{
|
|
126
|
+
throw new Error(`Ogg Vorbis decode error: ${e}`);
|
|
127
|
+
}
|
|
121
128
|
}
|
|
122
129
|
|
|
123
130
|
/**
|
|
@@ -39,7 +39,7 @@ export class SoundFont2 extends BasicSoundFont
|
|
|
39
39
|
if (!this.dataArray)
|
|
40
40
|
{
|
|
41
41
|
SpessaSynthGroupEnd();
|
|
42
|
-
|
|
42
|
+
this.parsingError("No data provided!");
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
// read the main read
|
|
@@ -280,7 +280,7 @@ export class SoundFont2 extends BasicSoundFont
|
|
|
280
280
|
if (chunk.header.toLowerCase() !== expected.toLowerCase())
|
|
281
281
|
{
|
|
282
282
|
SpessaSynthGroupEnd();
|
|
283
|
-
|
|
283
|
+
this.parsingError(`Invalid chunk header! Expected "${expected.toLowerCase()}" got "${chunk.header.toLowerCase()}"`);
|
|
284
284
|
}
|
|
285
285
|
}
|
|
286
286
|
|
|
@@ -293,7 +293,7 @@ export class SoundFont2 extends BasicSoundFont
|
|
|
293
293
|
if (text.toLowerCase() !== expected.toLowerCase())
|
|
294
294
|
{
|
|
295
295
|
SpessaSynthGroupEnd();
|
|
296
|
-
|
|
296
|
+
this.parsingError(`Invalid FourCC: Expected "${expected.toLowerCase()}" got "${text.toLowerCase()}"\``);
|
|
297
297
|
}
|
|
298
298
|
}
|
|
299
299
|
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { workletMessageType } from "./worklet_system/message_protocol/worklet_message.js";
|
|
2
|
+
import { KeyModifier, workletKeyModifierMessageType } from "./worklet_system/worklet_methods/worklet_key_modifier.js";
|
|
3
|
+
|
|
4
|
+
export class KeyModifierManager
|
|
5
|
+
{
|
|
6
|
+
/**
|
|
7
|
+
* @param synth {Synthetizer}
|
|
8
|
+
*/
|
|
9
|
+
constructor(synth)
|
|
10
|
+
{
|
|
11
|
+
this.synth = synth;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @private
|
|
16
|
+
* @param type {workletKeyModifierMessageType}
|
|
17
|
+
* @param data {any}
|
|
18
|
+
*/
|
|
19
|
+
_sendToWorklet(type, data)
|
|
20
|
+
{
|
|
21
|
+
this.synth.post({
|
|
22
|
+
messageType: workletMessageType.keyModifierManager,
|
|
23
|
+
messageData: [
|
|
24
|
+
type,
|
|
25
|
+
data
|
|
26
|
+
]
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Modifies a single key
|
|
32
|
+
* @param channel {number} the channel affected. Usually 0-15
|
|
33
|
+
* @param midiNote {number} the MIDI note to change. 0-127
|
|
34
|
+
* @param options {{
|
|
35
|
+
* velocity: number|undefined,
|
|
36
|
+
* patch: {
|
|
37
|
+
* bank: number,
|
|
38
|
+
* program: number
|
|
39
|
+
* }|undefined
|
|
40
|
+
* }} the key's modifiers
|
|
41
|
+
*/
|
|
42
|
+
addModifier(channel, midiNote, options)
|
|
43
|
+
{
|
|
44
|
+
const velocity = options?.velocity || -1;
|
|
45
|
+
const program = options?.patch?.program ?? -1;
|
|
46
|
+
const bank = options?.patch?.bank ?? -1;
|
|
47
|
+
this._sendToWorklet(
|
|
48
|
+
workletKeyModifierMessageType.addMapping,
|
|
49
|
+
[channel, midiNote, new KeyModifier(velocity, bank, program)]
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Deletes a key modifier
|
|
55
|
+
* @param channel {number} the channel affected. Usually 0-15
|
|
56
|
+
* @param midiNote {number} the MIDI note to change. 0-127
|
|
57
|
+
*/
|
|
58
|
+
deleteModifier(channel, midiNote)
|
|
59
|
+
{
|
|
60
|
+
this._sendToWorklet(
|
|
61
|
+
workletKeyModifierMessageType.deleteMapping,
|
|
62
|
+
[channel, midiNote]
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Clears ALL Modifiers
|
|
68
|
+
*/
|
|
69
|
+
clearModifiers()
|
|
70
|
+
{
|
|
71
|
+
this._sendToWorklet(workletKeyModifierMessageType.clearMappings, undefined);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -14,6 +14,7 @@ import { SpessaSynthInfo, SpessaSynthWarn } from "../utils/loggin.js";
|
|
|
14
14
|
import { DEFAULT_EFFECTS_CONFIG } from "./audio_effects/effects_config.js";
|
|
15
15
|
import { SoundfontManager } from "./synth_soundfont_manager.js";
|
|
16
16
|
import { channelConfiguration } from "./worklet_system/worklet_utilities/worklet_processor_channel.js";
|
|
17
|
+
import { KeyModifierManager } from "./key_modifier_manager.js";
|
|
17
18
|
|
|
18
19
|
|
|
19
20
|
/**
|
|
@@ -183,6 +184,12 @@ export class Synthetizer
|
|
|
183
184
|
*/
|
|
184
185
|
this.soundfontManager = new SoundfontManager(this);
|
|
185
186
|
|
|
187
|
+
/**
|
|
188
|
+
* The synth's key modifier manager
|
|
189
|
+
* @type {KeyModifierManager}
|
|
190
|
+
*/
|
|
191
|
+
this.keyModifierManager = new KeyModifierManager(this);
|
|
192
|
+
|
|
186
193
|
/**
|
|
187
194
|
* @type {function(SynthesizerSnapshot)}
|
|
188
195
|
* @private
|