spessasynth_core 3.26.0 → 3.26.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 +71 -67
- package/package.json +1 -1
- package/src/midi/midi_tools/used_keys_loaded.js +1 -1
- package/src/sequencer/process_event.js +1 -1
- package/src/synthetizer/audio_engine/engine_components/key_modifier_manager.js +1 -0
- package/src/synthetizer/audio_engine/engine_components/lowpass_filter.js +9 -9
- package/src/synthetizer/audio_engine/engine_components/midi_audio_channel.js +6 -1
- package/src/synthetizer/audio_engine/engine_components/soundfont_manager.js +32 -19
- package/src/synthetizer/audio_engine/engine_components/stereo_panner.js +4 -4
- package/src/synthetizer/audio_engine/engine_components/voice.js +11 -12
- package/src/synthetizer/audio_engine/engine_methods/create_midi_channel.js +1 -8
- package/src/synthetizer/audio_engine/engine_methods/render_voice.js +2 -2
- package/src/synthetizer/audio_engine/engine_methods/soundfont_management/clear_sound_font.js +6 -4
- package/src/synthetizer/audio_engine/engine_methods/soundfont_management/set_embedded_sound_font.js +13 -1
- package/src/synthetizer/audio_engine/engine_methods/soundfont_management/{send_preset_list.js → update_preset_list.js} +3 -1
- package/src/synthetizer/audio_engine/engine_methods/tuning_control/set_master_tuning.js +1 -1
- package/src/synthetizer/audio_engine/main_processor.js +78 -103
- package/src/synthetizer/audio_engine/snapshot/apply_synthesizer_snapshot.js +1 -0
- package/src/synthetizer/audio_engine/snapshot/channel_snapshot.js +6 -6
- package/src/synthetizer/audio_engine/snapshot/synthesizer_snapshot.js +21 -21
- package/src/synthetizer/audio_engine/synth_processor_options.js +18 -0
- package/src/synthetizer/audio_engine/engine_methods/soundfont_management/reload_sound_font.js +0 -28
package/README.md
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
<!--suppress HtmlDeprecatedAttribute, HtmlRequiredAltAttribute, HtmlExtraClosingTag -->
|
|
2
|
+
<p align='center'>
|
|
3
|
+
<img src='https://raw.githubusercontent.com/spessasus/SpessaSynth/refs/heads/master/src/website/spessasynth_logo_rounded.png' width='300' alt='SpessaSynth logo'>
|
|
4
|
+
</p>
|
|
2
5
|
|
|
3
6
|
**A powerful SF2/DLS/MIDI JavaScript library. It works with any modern JS environment that supports WebAssembly.**
|
|
4
7
|
|
|
@@ -11,82 +14,20 @@ npm install --save spessasynth_core
|
|
|
11
14
|
|
|
12
15
|
### [Project site (consider giving it a star!)](https://github.com/spessasus/SpessaSynth)
|
|
13
16
|
|
|
14
|
-
### [Demo (using the spessasynth_lib wrapper)](https://spessasus.github.io/
|
|
17
|
+
### [Demo (using the spessasynth_lib wrapper)](https://spessasus.github.io/SpessaSynth)
|
|
15
18
|
|
|
16
19
|
### [Documentation (in progress!)](https://github.com/spessasus/spessasynth_core/wiki/Home)
|
|
17
20
|
|
|
18
21
|
|
|
19
|
-
> Note: This is the new heart of the
|
|
20
|
-
|
|
21
|
-
###
|
|
22
|
-
```js
|
|
23
|
-
import * as fs from "node:fs";
|
|
24
|
-
import { MIDI } from "../../src/midi/midi_loader.js";
|
|
25
|
-
import { SpessaSynthProcessor } from "../../src/synthetizer/audio_engine/main_processor.js";
|
|
26
|
-
import { SpessaSynthSequencer } from "../../src/sequencer/sequencer_engine.js";
|
|
27
|
-
import { audioToWav } from "../../src/utils/buffer_to_wav.js";
|
|
28
|
-
|
|
29
|
-
// process arguments
|
|
30
|
-
const args = process.argv.slice(2);
|
|
31
|
-
if (args.length !== 3)
|
|
32
|
-
{
|
|
33
|
-
console.log("Usage: node index.js <soundfont path> <midi path> <wav output path>");
|
|
34
|
-
process.exit();
|
|
35
|
-
}
|
|
36
|
-
// load the files
|
|
37
|
-
const sf = fs.readFileSync(args[0]);
|
|
38
|
-
const mid = fs.readFileSync(args[1]);
|
|
39
|
-
|
|
40
|
-
const midi = new MIDI(mid);
|
|
41
|
-
const sampleRate = 44100;
|
|
42
|
-
const sampleCount = 44100 * (midi.duration + 2);
|
|
43
|
-
|
|
44
|
-
const synth = new SpessaSynthProcessor(
|
|
45
|
-
sf,
|
|
46
|
-
sampleRate,
|
|
47
|
-
{},
|
|
48
|
-
false,
|
|
49
|
-
false
|
|
50
|
-
);
|
|
51
|
-
await synth.processorInitialized;
|
|
52
|
-
|
|
53
|
-
const seq = new SpessaSynthSequencer(synth);
|
|
54
|
-
seq.loadNewSongList([midi]);
|
|
55
|
-
seq.loop = false;
|
|
56
|
-
|
|
57
|
-
const outLeft = new Float32Array(sampleCount);
|
|
58
|
-
const outRight = new Float32Array(sampleCount);
|
|
59
|
-
const start = performance.now();
|
|
60
|
-
let filledSamples = 0;
|
|
61
|
-
|
|
62
|
-
const bufSize = 128;
|
|
63
|
-
while (filledSamples + bufSize < sampleCount)
|
|
64
|
-
{
|
|
65
|
-
const bufLeft = new Float32Array(bufSize);
|
|
66
|
-
const bufRight = new Float32Array(bufSize);
|
|
67
|
-
seq.processTick();
|
|
68
|
-
const arr = [bufLeft, bufRight];
|
|
69
|
-
synth.renderAudio(arr, arr, arr);
|
|
70
|
-
outLeft.set(bufLeft, filledSamples);
|
|
71
|
-
outRight.set(bufRight, filledSamples);
|
|
72
|
-
filledSamples += bufSize;
|
|
73
|
-
}
|
|
74
|
-
const wave = audioToWav({
|
|
75
|
-
leftChannel: outLeft,
|
|
76
|
-
rightChannel: outRight,
|
|
77
|
-
sampleRate: sampleRate
|
|
78
|
-
});
|
|
79
|
-
fs.writeFileSync(args[2], new Buffer(wave));
|
|
80
|
-
process.exit();
|
|
81
|
-
```
|
|
22
|
+
> Note: This is the new heart of the SpessaSynth library, after the repository has been split.
|
|
82
23
|
|
|
83
24
|
## Current Features
|
|
84
25
|
|
|
85
26
|
### Easy Integration
|
|
86
27
|
- **Modular design:** *Easy integration into other projects (load what you need)*
|
|
87
|
-
- **[Detailed documentation:](https://github.com/spessasus/spessasynth_core/wiki/Home)** *With [examples!](https://github.com/spessasus/spessasynth_core/wiki/
|
|
28
|
+
- **[Detailed documentation:](https://github.com/spessasus/spessasynth_core/wiki/Home)** *With [examples!](https://github.com/spessasus/spessasynth_core/wiki/Getting-Started#examples)*
|
|
88
29
|
- **Flexible:** *It's not just a MIDI player!*
|
|
89
|
-
- **Easy to Use:** *Basic setup is just [two lines of code!](https://github.com/spessasus/spessasynth_core/wiki/
|
|
30
|
+
- **Easy to Use:** *Basic setup is just [two lines of code!](https://github.com/spessasus/spessasynth_core/wiki/Getting-Started#minimal-setup)*
|
|
90
31
|
- **No dependencies:** *Batteries included!*
|
|
91
32
|
|
|
92
33
|
### Powerful MIDI Synthesizer
|
|
@@ -205,6 +146,69 @@ process.exit();
|
|
|
205
146
|
|
|
206
147
|
**If you like this project, consider giving it a star. It really helps out!**
|
|
207
148
|
|
|
149
|
+
### Short example: MIDI to wav converter
|
|
150
|
+
```js
|
|
151
|
+
import * as fs from "node:fs";
|
|
152
|
+
import { MIDI, SpessaSynthProcessor, SpessaSynthSequencer, audioToWav, loadSoundFont } from "spessasynth_core";
|
|
153
|
+
|
|
154
|
+
// process arguments
|
|
155
|
+
const args = process.argv.slice(2);
|
|
156
|
+
if (args.length !== 3)
|
|
157
|
+
{
|
|
158
|
+
console.log("Usage: node index.js <soundfont path> <midi path> <wav output path>");
|
|
159
|
+
process.exit();
|
|
160
|
+
}
|
|
161
|
+
const sf = fs.readFileSync(args[0]);
|
|
162
|
+
const mid = fs.readFileSync(args[1]);
|
|
163
|
+
const midi = new MIDI(mid);
|
|
164
|
+
const sampleRate = 44100;
|
|
165
|
+
const sampleCount = 44100 * (midi.duration + 2);
|
|
166
|
+
const synth = new SpessaSynthProcessor(sampleRate, {
|
|
167
|
+
enableEventSystem: false,
|
|
168
|
+
effectsEnabled: false
|
|
169
|
+
});
|
|
170
|
+
synth.soundfontManager.reloadManager(loadSoundFont(sf));
|
|
171
|
+
await synth.processorInitialized;
|
|
172
|
+
const seq = new SpessaSynthSequencer(synth);
|
|
173
|
+
seq.loadNewSongList([midi]);
|
|
174
|
+
seq.loop = false;
|
|
175
|
+
const outLeft = new Float32Array(sampleCount);
|
|
176
|
+
const outRight = new Float32Array(sampleCount);
|
|
177
|
+
const start = performance.now();
|
|
178
|
+
let filledSamples = 0;
|
|
179
|
+
// note: buffer size is recommended to be very small, as this is the interval between modulator updates and LFO updates
|
|
180
|
+
const bufSize = 128;
|
|
181
|
+
let i = 0;
|
|
182
|
+
while (filledSamples + bufSize < sampleCount)
|
|
183
|
+
{
|
|
184
|
+
const bufLeft = new Float32Array(bufSize);
|
|
185
|
+
const bufRight = new Float32Array(bufSize);
|
|
186
|
+
// process sequencer
|
|
187
|
+
seq.processTick();
|
|
188
|
+
const arr = [bufLeft, bufRight];
|
|
189
|
+
// render
|
|
190
|
+
synth.renderAudio(arr, arr, arr);
|
|
191
|
+
// write out
|
|
192
|
+
outLeft.set(bufLeft, filledSamples);
|
|
193
|
+
outRight.set(bufRight, filledSamples);
|
|
194
|
+
filledSamples += bufSize;
|
|
195
|
+
i++;
|
|
196
|
+
// log progress
|
|
197
|
+
if (i % 100 === 0)
|
|
198
|
+
{
|
|
199
|
+
console.log("Rendered", seq.currentTime, "/", midi.duration);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
console.log("Rendered in", Math.floor(performance.now() - start), "ms");
|
|
203
|
+
const wave = audioToWav({
|
|
204
|
+
leftChannel: outLeft,
|
|
205
|
+
rightChannel: outRight,
|
|
206
|
+
sampleRate: sampleRate
|
|
207
|
+
});
|
|
208
|
+
fs.writeFileSync(args[2], new Buffer(wave));
|
|
209
|
+
process.exit();
|
|
210
|
+
```
|
|
211
|
+
|
|
208
212
|
# License
|
|
209
213
|
Copyright © 2025 Spessasus
|
|
210
214
|
Licensed under the Apache-2.0 License.
|
package/package.json
CHANGED
|
@@ -8,7 +8,7 @@ import { isGSDrumsOn, isXGOn } from "../../utils/sysex_detector.js";
|
|
|
8
8
|
/**
|
|
9
9
|
* Gets the used programs and keys for this MIDI file with a given sound bank
|
|
10
10
|
* @this {BasicMIDI}
|
|
11
|
-
* @param soundfont {BasicSoundBank|
|
|
11
|
+
* @param soundfont {BasicSoundBank|SoundFontManager} - the sound bank
|
|
12
12
|
* @returns {Object<string, Set<string>>} Object<bank:program, Set<key-velocity>>
|
|
13
13
|
*/
|
|
14
14
|
export function getUsedProgramsAndKeys(soundfont)
|
|
@@ -22,7 +22,7 @@ export const FILTER_SMOOTHING_FACTOR = 0.1;
|
|
|
22
22
|
* @property {number} a4 - Filter coefficient 5
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
|
-
export class
|
|
25
|
+
export class LowpassFilter
|
|
26
26
|
{
|
|
27
27
|
/**
|
|
28
28
|
* Cached coefficient calculations
|
|
@@ -175,7 +175,7 @@ export class WorkletLowpassFilter
|
|
|
175
175
|
{
|
|
176
176
|
filter.lastTargetCutoff = targetCutoff;
|
|
177
177
|
filter.resonanceCb = modulatedResonance;
|
|
178
|
-
|
|
178
|
+
LowpassFilter.calculateCoefficients(filter, targetCutoff);
|
|
179
179
|
}
|
|
180
180
|
|
|
181
181
|
// filter the input
|
|
@@ -200,7 +200,7 @@ export class WorkletLowpassFilter
|
|
|
200
200
|
}
|
|
201
201
|
|
|
202
202
|
/**
|
|
203
|
-
* @param filter {
|
|
203
|
+
* @param filter {LowpassFilter}
|
|
204
204
|
* @param cutoffCents {number}
|
|
205
205
|
*/
|
|
206
206
|
static calculateCoefficients(filter, cutoffCents)
|
|
@@ -208,7 +208,7 @@ export class WorkletLowpassFilter
|
|
|
208
208
|
cutoffCents = ~~cutoffCents; // Math.floor
|
|
209
209
|
const qCb = filter.resonanceCb;
|
|
210
210
|
// check if these coefficients were already cached
|
|
211
|
-
const cached =
|
|
211
|
+
const cached = LowpassFilter.cachedCoefficients?.[qCb]?.[cutoffCents];
|
|
212
212
|
if (cached !== undefined)
|
|
213
213
|
{
|
|
214
214
|
filter.a0 = cached.a0;
|
|
@@ -263,20 +263,20 @@ export class WorkletLowpassFilter
|
|
|
263
263
|
filter.a3 = toCache.a3;
|
|
264
264
|
filter.a4 = toCache.a4;
|
|
265
265
|
|
|
266
|
-
if (
|
|
266
|
+
if (LowpassFilter.cachedCoefficients[qCb] === undefined)
|
|
267
267
|
{
|
|
268
|
-
|
|
268
|
+
LowpassFilter.cachedCoefficients[qCb] = [];
|
|
269
269
|
}
|
|
270
|
-
|
|
270
|
+
LowpassFilter.cachedCoefficients[qCb][cutoffCents] = toCache;
|
|
271
271
|
}
|
|
272
272
|
}
|
|
273
273
|
|
|
274
274
|
// precompute all the cutoffs for 0q (most common)
|
|
275
|
-
const dummy = new
|
|
275
|
+
const dummy = new LowpassFilter(44100);
|
|
276
276
|
dummy.resonanceCb = 0;
|
|
277
277
|
// sfspec section 8.1.3: initialFilterFc ranges from 1500 to 13,500 cents
|
|
278
278
|
for (let i = 1500; i < 13500; i++)
|
|
279
279
|
{
|
|
280
280
|
dummy.currentInitialFc = i;
|
|
281
|
-
|
|
281
|
+
LowpassFilter.calculateCoefficients(dummy, i);
|
|
282
282
|
}
|
|
@@ -370,6 +370,7 @@ class MidiAudioChannel
|
|
|
370
370
|
this.sendChannelProperty();
|
|
371
371
|
}
|
|
372
372
|
|
|
373
|
+
// noinspection JSUnusedGlobalSymbols
|
|
373
374
|
/**
|
|
374
375
|
* Sets a custom vibrato
|
|
375
376
|
* @param depth {number} cents
|
|
@@ -387,6 +388,10 @@ class MidiAudioChannel
|
|
|
387
388
|
this.channelVibrato.depth = depth;
|
|
388
389
|
}
|
|
389
390
|
|
|
391
|
+
// noinspection JSUnusedGlobalSymbols
|
|
392
|
+
/**
|
|
393
|
+
* Yes
|
|
394
|
+
*/
|
|
390
395
|
disableAndLockGSNRPN()
|
|
391
396
|
{
|
|
392
397
|
this.lockGSNRPNParams = true;
|
|
@@ -431,7 +436,7 @@ class MidiAudioChannel
|
|
|
431
436
|
bank: this.sentBank,
|
|
432
437
|
program: this.preset.program
|
|
433
438
|
};
|
|
434
|
-
this.synth?.
|
|
439
|
+
this.synth?.onChannelPropertyChange?.(data, this.channelNumber);
|
|
435
440
|
}
|
|
436
441
|
}
|
|
437
442
|
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { SpessaSynthWarn } from "../../../utils/loggin.js";
|
|
2
|
-
import { loadSoundFont } from "../../../soundfont/load_soundfont.js";
|
|
3
2
|
import { isXGDrums } from "../../../utils/xg_hacks.js";
|
|
4
3
|
|
|
5
4
|
/**
|
|
@@ -9,15 +8,24 @@ import { isXGDrums } from "../../../utils/xg_hacks.js";
|
|
|
9
8
|
* @property {number} bankOffset - the soundfont's bank offset
|
|
10
9
|
*/
|
|
11
10
|
|
|
12
|
-
export class
|
|
11
|
+
export class SoundFontManager
|
|
13
12
|
{
|
|
14
13
|
/**
|
|
15
|
-
*
|
|
16
|
-
* @
|
|
14
|
+
* All the soundfonts, ordered from the most important to the least.
|
|
15
|
+
* @type {SoundFontType[]}
|
|
17
16
|
*/
|
|
18
|
-
|
|
17
|
+
soundfontList = [];
|
|
18
|
+
/**
|
|
19
|
+
* @type {{bank: number, presetName: string, program: number}[]}
|
|
20
|
+
*/
|
|
21
|
+
presetList = [];
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @param presetListChangeCallback {function} to call when stuff changes
|
|
25
|
+
*/
|
|
26
|
+
constructor(presetListChangeCallback)
|
|
19
27
|
{
|
|
20
|
-
this.
|
|
28
|
+
this.presetListChangeCallback = presetListChangeCallback;
|
|
21
29
|
}
|
|
22
30
|
|
|
23
31
|
generatePresetList()
|
|
@@ -49,9 +57,6 @@ export class WorkletSoundfontManager
|
|
|
49
57
|
}
|
|
50
58
|
}
|
|
51
59
|
|
|
52
|
-
/**
|
|
53
|
-
* @type {{bank: number, presetName: string, program: number}[]}
|
|
54
|
-
*/
|
|
55
60
|
this.presetList = [];
|
|
56
61
|
for (const [string, name] of Object.entries(presetList))
|
|
57
62
|
{
|
|
@@ -62,6 +67,7 @@ export class WorkletSoundfontManager
|
|
|
62
67
|
bank: parseInt(pb[0])
|
|
63
68
|
});
|
|
64
69
|
}
|
|
70
|
+
this.presetListChangeCallback();
|
|
65
71
|
}
|
|
66
72
|
|
|
67
73
|
/**
|
|
@@ -73,13 +79,13 @@ export class WorkletSoundfontManager
|
|
|
73
79
|
return this.presetList.slice();
|
|
74
80
|
}
|
|
75
81
|
|
|
82
|
+
// noinspection JSUnusedGlobalSymbols
|
|
76
83
|
/**
|
|
77
|
-
* Clears all soundfonts and adds a new one
|
|
78
|
-
* @param
|
|
84
|
+
* Clears all soundfonts and adds a new one with an ID "main"
|
|
85
|
+
* @param soundFont {BasicSoundBank}
|
|
79
86
|
*/
|
|
80
|
-
reloadManager(
|
|
87
|
+
reloadManager(soundFont)
|
|
81
88
|
{
|
|
82
|
-
const font = loadSoundFont(soundFontArrayBuffer);
|
|
83
89
|
/**
|
|
84
90
|
* All the soundfonts, ordered from the most important to the least.
|
|
85
91
|
* @type {SoundFontType[]}
|
|
@@ -88,11 +94,16 @@ export class WorkletSoundfontManager
|
|
|
88
94
|
this.soundfontList.push({
|
|
89
95
|
id: "main",
|
|
90
96
|
bankOffset: 0,
|
|
91
|
-
soundfont:
|
|
97
|
+
soundfont: soundFont
|
|
92
98
|
});
|
|
93
99
|
this.generatePresetList();
|
|
94
100
|
}
|
|
95
101
|
|
|
102
|
+
// noinspection JSUnusedGlobalSymbols
|
|
103
|
+
/**
|
|
104
|
+
* Deletes a given soundfont.
|
|
105
|
+
* @param id {string}
|
|
106
|
+
*/
|
|
96
107
|
deleteSoundFont(id)
|
|
97
108
|
{
|
|
98
109
|
if (this.soundfontList.length === 0)
|
|
@@ -113,13 +124,14 @@ export class WorkletSoundfontManager
|
|
|
113
124
|
this.generatePresetList();
|
|
114
125
|
}
|
|
115
126
|
|
|
127
|
+
// noinspection JSUnusedGlobalSymbols
|
|
116
128
|
/**
|
|
117
|
-
* Adds a new soundfont
|
|
118
|
-
* @param
|
|
129
|
+
* Adds a new soundfont with a given ID
|
|
130
|
+
* @param font {BasicSoundBank}
|
|
119
131
|
* @param id {string}
|
|
120
132
|
* @param bankOffset {number}
|
|
121
133
|
*/
|
|
122
|
-
addNewSoundFont(
|
|
134
|
+
addNewSoundFont(font, id, bankOffset)
|
|
123
135
|
{
|
|
124
136
|
if (this.soundfontList.find(s => s.id === id) !== undefined)
|
|
125
137
|
{
|
|
@@ -127,12 +139,13 @@ export class WorkletSoundfontManager
|
|
|
127
139
|
}
|
|
128
140
|
this.soundfontList.push({
|
|
129
141
|
id: id,
|
|
130
|
-
soundfont:
|
|
142
|
+
soundfont: font,
|
|
131
143
|
bankOffset: bankOffset
|
|
132
144
|
});
|
|
133
145
|
this.generatePresetList();
|
|
134
146
|
}
|
|
135
147
|
|
|
148
|
+
// noinspection JSUnusedGlobalSymbols
|
|
136
149
|
/**
|
|
137
150
|
* Rearranges the soundfonts
|
|
138
151
|
* @param newList {string[]} the order of soundfonts, a list of strings, first overwrites second
|
|
@@ -156,7 +169,7 @@ export class WorkletSoundfontManager
|
|
|
156
169
|
{
|
|
157
170
|
if (this.soundfontList.length < 1)
|
|
158
171
|
{
|
|
159
|
-
throw new Error("No soundfonts!
|
|
172
|
+
throw new Error("No soundfonts! Did you forget to add one?");
|
|
160
173
|
}
|
|
161
174
|
for (const sf of this.soundfontList)
|
|
162
175
|
{
|
|
@@ -7,8 +7,8 @@ import { generatorTypes } from "../../../soundfont/basic_soundfont/generator.js"
|
|
|
7
7
|
|
|
8
8
|
export const PAN_SMOOTHING_FACTOR = 0.05;
|
|
9
9
|
|
|
10
|
-
export const
|
|
11
|
-
export const
|
|
10
|
+
export const REVERB_DIVIDER = 4600;
|
|
11
|
+
export const CHORUS_DIVIDER = 2000;
|
|
12
12
|
const HALF_PI = Math.PI / 2;
|
|
13
13
|
|
|
14
14
|
const MIN_PAN = -500;
|
|
@@ -78,7 +78,7 @@ export function panVoice(voice,
|
|
|
78
78
|
if (reverbSend > 0)
|
|
79
79
|
{
|
|
80
80
|
// reverb is mono so we need to multiply by gain
|
|
81
|
-
const reverbGain = this.synth.reverbGain * gain * (reverbSend /
|
|
81
|
+
const reverbGain = this.synth.reverbGain * gain * (reverbSend / REVERB_DIVIDER);
|
|
82
82
|
for (let i = 0; i < inputBuffer.length; i++)
|
|
83
83
|
{
|
|
84
84
|
reverbLeft[i] += reverbGain * inputBuffer[i];
|
|
@@ -91,7 +91,7 @@ export function panVoice(voice,
|
|
|
91
91
|
if (chorusSend > 0)
|
|
92
92
|
{
|
|
93
93
|
// chorus is stereo so we do not need to
|
|
94
|
-
const chorusGain = this.synth.chorusGain * chorusSend /
|
|
94
|
+
const chorusGain = this.synth.chorusGain * chorusSend / CHORUS_DIVIDER;
|
|
95
95
|
const chorusLeftGain = gainLeft * chorusGain;
|
|
96
96
|
const chorusRightGain = gainRight * chorusGain;
|
|
97
97
|
for (let i = 0; i < inputBuffer.length; i++)
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* voice.js
|
|
3
|
-
* purpose: prepares Voices from sample and generator data
|
|
4
|
-
* note: sample dumping means sending it over to the AudioWorkletGlobalScope
|
|
3
|
+
* purpose: prepares Voices from sample and generator data
|
|
5
4
|
*/
|
|
6
5
|
import { MIN_EXCLUSIVE_LENGTH, MIN_NOTE_LENGTH } from "../main_processor.js";
|
|
7
6
|
import { SpessaSynthWarn } from "../../../utils/loggin.js";
|
|
8
|
-
import {
|
|
7
|
+
import { LowpassFilter } from "./lowpass_filter.js";
|
|
9
8
|
import { VolumeEnvelope } from "./volume_envelope.js";
|
|
10
9
|
import { ModulationEnvelope } from "./modulation_envelope.js";
|
|
11
10
|
import { addAndClampGenerator, generatorTypes } from "../../../soundfont/basic_soundfont/generator.js";
|
|
@@ -122,7 +121,7 @@ class Voice
|
|
|
122
121
|
|
|
123
122
|
/**
|
|
124
123
|
* Lowpass filter applied to the voice.
|
|
125
|
-
* @type {
|
|
124
|
+
* @type {LowpassFilter}
|
|
126
125
|
*/
|
|
127
126
|
filter;
|
|
128
127
|
|
|
@@ -270,7 +269,7 @@ class Voice
|
|
|
270
269
|
/**
|
|
271
270
|
* Creates a Voice
|
|
272
271
|
* @param sampleRate {number}
|
|
273
|
-
* @param
|
|
272
|
+
* @param audioSample {AudioSample}
|
|
274
273
|
* @param midiNote {number}
|
|
275
274
|
* @param velocity {number}
|
|
276
275
|
* @param channel {number}
|
|
@@ -282,7 +281,7 @@ class Voice
|
|
|
282
281
|
*/
|
|
283
282
|
constructor(
|
|
284
283
|
sampleRate,
|
|
285
|
-
|
|
284
|
+
audioSample,
|
|
286
285
|
midiNote,
|
|
287
286
|
velocity,
|
|
288
287
|
channel,
|
|
@@ -293,12 +292,12 @@ class Voice
|
|
|
293
292
|
modulators
|
|
294
293
|
)
|
|
295
294
|
{
|
|
296
|
-
this.sample =
|
|
295
|
+
this.sample = audioSample;
|
|
297
296
|
this.generators = generators;
|
|
298
297
|
this.exclusiveClass = this.generators[generatorTypes.exclusiveClass];
|
|
299
298
|
this.modulatedGenerators = new Int16Array(generators);
|
|
300
299
|
this.modulators = modulators;
|
|
301
|
-
this.filter = new
|
|
300
|
+
this.filter = new LowpassFilter(sampleRate);
|
|
302
301
|
|
|
303
302
|
this.velocity = velocity;
|
|
304
303
|
this.midiNote = midiNote;
|
|
@@ -461,11 +460,11 @@ export function getVoices(channel,
|
|
|
461
460
|
let loopEnd = sampleAndGenerators.sample.sampleLoopEndIndex;
|
|
462
461
|
let loopingMode = generators[generatorTypes.sampleModes];
|
|
463
462
|
/**
|
|
464
|
-
* create the
|
|
463
|
+
* create the sample
|
|
465
464
|
* offsets are calculated at note on time (to allow for modulation of them)
|
|
466
465
|
* @type {AudioSample}
|
|
467
466
|
*/
|
|
468
|
-
const
|
|
467
|
+
const audioSample = new AudioSample(
|
|
469
468
|
sampleAndGenerators.sample.sampleData,
|
|
470
469
|
(sampleAndGenerators.sample.sampleRate / this.sampleRate) * Math.pow(
|
|
471
470
|
2,
|
|
@@ -492,14 +491,14 @@ export function getVoices(channel,
|
|
|
492
491
|
// Velocity: velocity,
|
|
493
492
|
// TargetKey: targetKey,
|
|
494
493
|
// MidiNote: midiNote,
|
|
495
|
-
// AudioSample:
|
|
494
|
+
// AudioSample: audioSample
|
|
496
495
|
// }]);
|
|
497
496
|
|
|
498
497
|
|
|
499
498
|
voices.push(
|
|
500
499
|
new Voice(
|
|
501
500
|
this.sampleRate,
|
|
502
|
-
|
|
501
|
+
audioSample,
|
|
503
502
|
midiNote,
|
|
504
503
|
velocity,
|
|
505
504
|
channel,
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { MidiAudioChannel } from "../engine_components/midi_audio_channel.js";
|
|
2
2
|
|
|
3
|
-
import { DEFAULT_PERCUSSION } from "../../synth_constants.js";
|
|
4
|
-
|
|
5
3
|
/**
|
|
6
4
|
* @param sendEvent {boolean}
|
|
7
5
|
* @this {SpessaSynthProcessor}
|
|
@@ -13,15 +11,10 @@ export function createMidiChannel(sendEvent = false)
|
|
|
13
11
|
*/
|
|
14
12
|
const channel = new MidiAudioChannel(this, this.defaultPreset, this.midiAudioChannels.length);
|
|
15
13
|
this.midiAudioChannels.push(channel);
|
|
16
|
-
channel.resetControllers();
|
|
17
|
-
channel.sendChannelProperty();
|
|
18
14
|
if (sendEvent)
|
|
19
15
|
{
|
|
20
16
|
this.callEvent("newchannel", undefined);
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
if (channel.channelNumber % 16 === DEFAULT_PERCUSSION)
|
|
24
|
-
{
|
|
17
|
+
channel.sendChannelProperty();
|
|
25
18
|
this.midiAudioChannels[this.midiAudioChannels.length - 1].setDrums(true);
|
|
26
19
|
}
|
|
27
20
|
}
|
|
@@ -5,7 +5,7 @@ import { customControllers } from "../engine_components/controller_tables.js";
|
|
|
5
5
|
import { absCentsToHz, timecentsToSeconds } from "../engine_components/unit_converter.js";
|
|
6
6
|
import { getLFOValue } from "../engine_components/lfo.js";
|
|
7
7
|
import { WavetableOscillator } from "../engine_components/wavetable_oscillator.js";
|
|
8
|
-
import {
|
|
8
|
+
import { LowpassFilter } from "../engine_components/lowpass_filter.js";
|
|
9
9
|
import { interpolationTypes } from "../engine_components/enums.js";
|
|
10
10
|
|
|
11
11
|
/**
|
|
@@ -181,7 +181,7 @@ export function renderVoice(
|
|
|
181
181
|
}
|
|
182
182
|
|
|
183
183
|
// low pass filter
|
|
184
|
-
|
|
184
|
+
LowpassFilter.apply(voice, bufferOut, lowpassExcursion, this.synth.filterSmoothingFactor);
|
|
185
185
|
|
|
186
186
|
// vol env
|
|
187
187
|
VolumeEnvelope.apply(voice, bufferOut, modLfoCentibels, this.synth.volumeEnvelopeSmoothingFactor);
|
package/src/synthetizer/audio_engine/engine_methods/soundfont_management/clear_sound_font.js
CHANGED
|
@@ -14,6 +14,11 @@ export function clearSoundFont(sendPresets = true, clearOverride = true)
|
|
|
14
14
|
this.getDefaultPresets();
|
|
15
15
|
this.cachedVoices = [];
|
|
16
16
|
|
|
17
|
+
if (sendPresets)
|
|
18
|
+
{
|
|
19
|
+
this.updatePresetList();
|
|
20
|
+
}
|
|
21
|
+
|
|
17
22
|
for (let i = 0; i < this.midiAudioChannels.length; i++)
|
|
18
23
|
{
|
|
19
24
|
const channelObject = this.midiAudioChannels[i];
|
|
@@ -23,8 +28,5 @@ export function clearSoundFont(sendPresets = true, clearOverride = true)
|
|
|
23
28
|
}
|
|
24
29
|
channelObject.programChange(channelObject.preset.program);
|
|
25
30
|
}
|
|
26
|
-
|
|
27
|
-
{
|
|
28
|
-
this.sendPresetList();
|
|
29
|
-
}
|
|
31
|
+
|
|
30
32
|
}
|
package/src/synthetizer/audio_engine/engine_methods/soundfont_management/set_embedded_sound_font.js
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { loadSoundFont } from "../../../../soundfont/load_soundfont.js";
|
|
2
|
+
import { SpessaSynthInfo } from "../../../../utils/loggin.js";
|
|
3
|
+
import { consoleColors } from "../../../../utils/other.js";
|
|
4
|
+
|
|
1
5
|
/**
|
|
2
6
|
* Sets the embedded (RMI soundfont)
|
|
3
7
|
* @param font {ArrayBuffer}
|
|
@@ -8,14 +12,22 @@ export function setEmbeddedSoundFont(font, offset)
|
|
|
8
12
|
{
|
|
9
13
|
// set offset
|
|
10
14
|
this.soundfontBankOffset = offset;
|
|
11
|
-
this.
|
|
15
|
+
this.clearSoundFont(false, true);
|
|
16
|
+
this.overrideSoundfont = loadSoundFont(font);
|
|
17
|
+
this.updatePresetList();
|
|
18
|
+
this.getDefaultPresets();
|
|
19
|
+
this.midiAudioChannels.forEach(c =>
|
|
20
|
+
c.programChange(c.preset.program)
|
|
21
|
+
);
|
|
12
22
|
// preload all samples
|
|
13
23
|
this.overrideSoundfont.samples.forEach(s => s.getAudioData());
|
|
14
24
|
|
|
25
|
+
|
|
15
26
|
// apply snapshot again if applicable
|
|
16
27
|
if (this._snapshot !== undefined)
|
|
17
28
|
{
|
|
18
29
|
this.applySynthesizerSnapshot(this._snapshot);
|
|
19
30
|
this.resetAllControllers();
|
|
20
31
|
}
|
|
32
|
+
SpessaSynthInfo("%cSpessaSynth is ready!", consoleColors.recognized);
|
|
21
33
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @this {SpessaSynthProcessor}
|
|
3
3
|
*/
|
|
4
|
-
export function
|
|
4
|
+
export function updatePresetList()
|
|
5
5
|
{
|
|
6
6
|
/**
|
|
7
7
|
* @type {{bank: number, presetName: string, program: number}[]}
|
|
@@ -28,4 +28,6 @@ export function sendPresetList()
|
|
|
28
28
|
});
|
|
29
29
|
}
|
|
30
30
|
this.callEvent("presetlistchange", mainFont);
|
|
31
|
+
this.getDefaultPresets();
|
|
32
|
+
this.resetAllControllers(false);
|
|
31
33
|
}
|
|
@@ -1,26 +1,20 @@
|
|
|
1
1
|
import { SpessaSynthInfo } from "../../utils/loggin.js";
|
|
2
2
|
import { consoleColors } from "../../utils/other.js";
|
|
3
3
|
import { voiceKilling } from "./engine_methods/stopping_notes/voice_killing.js";
|
|
4
|
-
import {
|
|
5
|
-
ALL_CHANNELS_OR_DIFFERENT_ACTION,
|
|
6
|
-
DEFAULT_PERCUSSION,
|
|
7
|
-
DEFAULT_SYNTH_MODE,
|
|
8
|
-
VOICE_CAP
|
|
9
|
-
} from "../synth_constants.js";
|
|
4
|
+
import { ALL_CHANNELS_OR_DIFFERENT_ACTION, DEFAULT_SYNTH_MODE, VOICE_CAP } from "../synth_constants.js";
|
|
10
5
|
import { stbvorbis } from "../../externals/stbvorbis_sync/stbvorbis_sync.min.js";
|
|
11
6
|
import { VOLUME_ENVELOPE_SMOOTHING_FACTOR } from "./engine_components/volume_envelope.js";
|
|
12
7
|
import { systemExclusive } from "./engine_methods/system_exclusive.js";
|
|
13
8
|
import { masterParameterType, setMasterParameter } from "./engine_methods/controller_control/master_parameters.js";
|
|
14
9
|
import { resetAllControllers } from "./engine_methods/controller_control/reset_controllers.js";
|
|
15
|
-
import {
|
|
10
|
+
import { SoundFontManager } from "./engine_components/soundfont_manager.js";
|
|
16
11
|
import { KeyModifierManager } from "./engine_components/key_modifier_manager.js";
|
|
17
12
|
import { getVoices } from "./engine_components/voice.js";
|
|
18
13
|
import { PAN_SMOOTHING_FACTOR } from "./engine_components/stereo_panner.js";
|
|
19
14
|
import { stopAllChannels } from "./engine_methods/stopping_notes/stop_all_channels.js";
|
|
20
15
|
import { setEmbeddedSoundFont } from "./engine_methods/soundfont_management/set_embedded_sound_font.js";
|
|
21
|
-
import { reloadSoundFont } from "./engine_methods/soundfont_management/reload_sound_font.js";
|
|
22
16
|
import { clearSoundFont } from "./engine_methods/soundfont_management/clear_sound_font.js";
|
|
23
|
-
import {
|
|
17
|
+
import { updatePresetList } from "./engine_methods/soundfont_management/update_preset_list.js";
|
|
24
18
|
import { getPreset } from "./engine_methods/soundfont_management/get_preset.js";
|
|
25
19
|
import { transposeAllChannels } from "./engine_methods/tuning_control/transpose_all_channels.js";
|
|
26
20
|
import { setMasterTuning } from "./engine_methods/tuning_control/set_master_tuning.js";
|
|
@@ -30,6 +24,8 @@ import { FILTER_SMOOTHING_FACTOR } from "./engine_components/lowpass_filter.js";
|
|
|
30
24
|
import { getEvent, messageTypes } from "../../midi/midi_message.js";
|
|
31
25
|
import { IndexedByteArray } from "../../utils/indexed_array.js";
|
|
32
26
|
import { interpolationTypes } from "./engine_components/enums.js";
|
|
27
|
+
import { DEFAULT_SYNTH_OPTIONS } from "./synth_processor_options.js";
|
|
28
|
+
import { fillWithDefaults } from "../../utils/fill_with_defaults.js";
|
|
33
29
|
|
|
34
30
|
|
|
35
31
|
/**
|
|
@@ -314,7 +310,7 @@ class SpessaSynthProcessor
|
|
|
314
310
|
defaultDrumsUsesOverride = false;
|
|
315
311
|
|
|
316
312
|
/**
|
|
317
|
-
* Controls if the
|
|
313
|
+
* Controls if the processor is fully initialized
|
|
318
314
|
* @type {Promise<boolean>}
|
|
319
315
|
*/
|
|
320
316
|
processorInitialized = stbvorbis.isInitialized;
|
|
@@ -332,67 +328,73 @@ class SpessaSynthProcessor
|
|
|
332
328
|
sampleRate;
|
|
333
329
|
|
|
334
330
|
/**
|
|
335
|
-
*
|
|
336
|
-
* @
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
*
|
|
348
|
-
* @
|
|
349
|
-
* @
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
331
|
+
* Sample time in seconds
|
|
332
|
+
* @type {number}
|
|
333
|
+
*/
|
|
334
|
+
sampleTime;
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* are the chorus and reverb effects enabled?
|
|
338
|
+
* @type {boolean}
|
|
339
|
+
*/
|
|
340
|
+
effectsEnabled;
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* for applying the snapshot after an override sound bank too
|
|
344
|
+
* @type {SynthesizerSnapshot}
|
|
345
|
+
* @private
|
|
346
|
+
*/
|
|
347
|
+
_snapshot;
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Calls when an event occurs.
|
|
351
|
+
* @type {function}
|
|
352
|
+
* @param {EventTypes} eventType - the event type.
|
|
353
|
+
* @param {EventCallbackData} eventData - the event data.
|
|
354
|
+
*/
|
|
355
|
+
onEventCall;
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Calls when a channel property is changed.
|
|
359
|
+
* @type {function}
|
|
360
|
+
* @param {ChannelProperty} property - the updated property.
|
|
361
|
+
* @param {number} channelNumber - the channel number of the said property.
|
|
362
|
+
*/
|
|
363
|
+
onChannelPropertyChange;
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Calls when a master parameter is changed.
|
|
367
|
+
* @type {function}
|
|
368
|
+
* @param {masterParameterType} parameter - the parameter type
|
|
369
|
+
* @param {number|string} value - the new value.
|
|
370
|
+
*/
|
|
371
|
+
onMasterParameterChange;
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Creates a new synthesizer engine.
|
|
376
|
+
* @param sampleRate {number} - sample rate, in Hertz.
|
|
377
|
+
* @param options {SynthProcessorOptions} - the processor's options.
|
|
378
|
+
*/
|
|
379
|
+
constructor(sampleRate,
|
|
380
|
+
options = DEFAULT_SYNTH_OPTIONS)
|
|
362
381
|
{
|
|
382
|
+
options = fillWithDefaults(options, DEFAULT_SYNTH_OPTIONS);
|
|
363
383
|
/**
|
|
364
384
|
* Midi output count
|
|
365
385
|
* @type {number}
|
|
366
386
|
*/
|
|
367
|
-
this.midiOutputsCount = midiChannels;
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
*/
|
|
372
|
-
this.effectsEnabled = effectsEnabled;
|
|
373
|
-
let initialChannelCount = this.midiOutputsCount;
|
|
374
|
-
|
|
375
|
-
/**
|
|
376
|
-
* @type {CallbacksTypedef}
|
|
377
|
-
*/
|
|
378
|
-
this.callbacks = callbacks;
|
|
379
|
-
|
|
380
|
-
this.currentSynthTime = initialTime;
|
|
381
|
-
this.sampleRate = sampleRate;
|
|
382
|
-
|
|
383
|
-
this.isFullyReady = false;
|
|
384
|
-
this.processorInitialized.then(() =>
|
|
385
|
-
{
|
|
386
|
-
this.isFullyReady = true;
|
|
387
|
-
});
|
|
388
|
-
|
|
389
|
-
/**
|
|
390
|
-
* Sample time in seconds
|
|
391
|
-
* @type {number}
|
|
392
|
-
*/
|
|
387
|
+
this.midiOutputsCount = options.midiChannels;
|
|
388
|
+
this.effectsEnabled = options.effectsEnabled;
|
|
389
|
+
this.enableEventSystem = options.enableEventSystem;
|
|
390
|
+
this.currentSynthTime = options.midiChannels;
|
|
393
391
|
this.sampleTime = 1 / sampleRate;
|
|
392
|
+
this.sampleRate = sampleRate;
|
|
394
393
|
|
|
395
|
-
|
|
394
|
+
// these smoothing factors were tested on 44,100 Hz, adjust them to target sample rate here
|
|
395
|
+
this.volumeEnvelopeSmoothingFactor = VOLUME_ENVELOPE_SMOOTHING_FACTOR * (44100 / sampleRate);
|
|
396
|
+
this.panSmoothingFactor = PAN_SMOOTHING_FACTOR * (44100 / sampleRate);
|
|
397
|
+
this.filterSmoothingFactor = FILTER_SMOOTHING_FACTOR * (44100 / sampleRate);
|
|
396
398
|
|
|
397
399
|
|
|
398
400
|
for (let i = 0; i < 128; i++)
|
|
@@ -401,34 +403,18 @@ class SpessaSynthProcessor
|
|
|
401
403
|
}
|
|
402
404
|
|
|
403
405
|
/**
|
|
404
|
-
* @type {
|
|
406
|
+
* @type {SoundFontManager}
|
|
405
407
|
*/
|
|
406
|
-
this.soundfontManager = new
|
|
407
|
-
soundfont
|
|
408
|
-
);
|
|
409
|
-
this.sendPresetList();
|
|
410
|
-
this.getDefaultPresets();
|
|
411
|
-
|
|
408
|
+
this.soundfontManager = new SoundFontManager(this.updatePresetList.bind(this));
|
|
412
409
|
|
|
413
|
-
for (let i = 0; i <
|
|
410
|
+
for (let i = 0; i < this.midiOutputsCount; i++)
|
|
414
411
|
{
|
|
415
|
-
this.
|
|
412
|
+
this.createMidiChannel(false);
|
|
416
413
|
}
|
|
417
|
-
|
|
418
|
-
this.midiAudioChannels[DEFAULT_PERCUSSION].preset = this.drumPreset;
|
|
419
|
-
this.midiAudioChannels[DEFAULT_PERCUSSION].drumChannel = true;
|
|
420
|
-
|
|
421
|
-
// these smoothing factors were tested on 44,100 Hz, adjust them to target sample rate here
|
|
422
|
-
this.volumeEnvelopeSmoothingFactor = VOLUME_ENVELOPE_SMOOTHING_FACTOR * (44100 / sampleRate);
|
|
423
|
-
this.panSmoothingFactor = PAN_SMOOTHING_FACTOR * (44100 / sampleRate);
|
|
424
|
-
this.filterSmoothingFactor = FILTER_SMOOTHING_FACTOR * (44100 / sampleRate);
|
|
425
|
-
|
|
426
|
-
this._snapshot = snapshot;
|
|
427
|
-
if (this?._snapshot)
|
|
414
|
+
this.processorInitialized.then(() =>
|
|
428
415
|
{
|
|
429
|
-
|
|
430
|
-
}
|
|
431
|
-
this.postReady();
|
|
416
|
+
SpessaSynthInfo("%cSpessaSynth is ready!", consoleColors.recognized);
|
|
417
|
+
});
|
|
432
418
|
}
|
|
433
419
|
|
|
434
420
|
/**
|
|
@@ -457,7 +443,7 @@ class SpessaSynthProcessor
|
|
|
457
443
|
setSystem(value)
|
|
458
444
|
{
|
|
459
445
|
this.system = value;
|
|
460
|
-
this?.
|
|
446
|
+
this?.onMasterParameterChange?.(masterParameterType.midiSystem, this.system);
|
|
461
447
|
}
|
|
462
448
|
|
|
463
449
|
/**
|
|
@@ -503,16 +489,6 @@ class SpessaSynthProcessor
|
|
|
503
489
|
this.cachedVoices[bank][program][midiNote][velocity] = voices;
|
|
504
490
|
}
|
|
505
491
|
|
|
506
|
-
postReady()
|
|
507
|
-
{
|
|
508
|
-
// ensure stbvorbis is fully initialized
|
|
509
|
-
this.processorInitialized.then(() =>
|
|
510
|
-
{
|
|
511
|
-
SpessaSynthInfo("%cSpessaSynth is ready!", consoleColors.recognized);
|
|
512
|
-
this?.callbacks?.ready?.();
|
|
513
|
-
});
|
|
514
|
-
}
|
|
515
|
-
|
|
516
492
|
// noinspection JSUnusedGlobalSymbols
|
|
517
493
|
/**
|
|
518
494
|
* Renders float32 audio data to stereo outputs; buffer size of 128 is recommended
|
|
@@ -764,14 +740,14 @@ class SpessaSynthProcessor
|
|
|
764
740
|
}
|
|
765
741
|
|
|
766
742
|
/**
|
|
767
|
-
* Calls synth event
|
|
743
|
+
* Calls synth event
|
|
768
744
|
* @param eventName {EventTypes} the event name
|
|
769
745
|
* @param eventData {EventCallbackData}
|
|
770
746
|
* @this {SpessaSynthProcessor}
|
|
771
747
|
*/
|
|
772
748
|
callEvent(eventName, eventData)
|
|
773
749
|
{
|
|
774
|
-
this?.
|
|
750
|
+
this?.onEventCall?.(eventName, eventData);
|
|
775
751
|
}
|
|
776
752
|
}
|
|
777
753
|
|
|
@@ -785,7 +761,7 @@ SpessaSynthProcessor.prototype.systemExclusive = systemExclusive;
|
|
|
785
761
|
|
|
786
762
|
// channel related
|
|
787
763
|
SpessaSynthProcessor.prototype.stopAllChannels = stopAllChannels;
|
|
788
|
-
SpessaSynthProcessor.prototype.
|
|
764
|
+
SpessaSynthProcessor.prototype.createMidiChannel = createMidiChannel;
|
|
789
765
|
SpessaSynthProcessor.prototype.resetAllControllers = resetAllControllers;
|
|
790
766
|
|
|
791
767
|
// master parameter related
|
|
@@ -797,10 +773,9 @@ SpessaSynthProcessor.prototype.setMasterTuning = setMasterTuning;
|
|
|
797
773
|
|
|
798
774
|
// program related
|
|
799
775
|
SpessaSynthProcessor.prototype.getPreset = getPreset;
|
|
800
|
-
SpessaSynthProcessor.prototype.reloadSoundFont = reloadSoundFont;
|
|
801
776
|
SpessaSynthProcessor.prototype.clearSoundFont = clearSoundFont;
|
|
802
777
|
SpessaSynthProcessor.prototype.setEmbeddedSoundFont = setEmbeddedSoundFont;
|
|
803
|
-
SpessaSynthProcessor.prototype.
|
|
778
|
+
SpessaSynthProcessor.prototype.updatePresetList = updatePresetList;
|
|
804
779
|
|
|
805
780
|
// snapshot related
|
|
806
781
|
SpessaSynthProcessor.prototype.applySynthesizerSnapshot = applySynthesizerSnapshot;
|
|
@@ -9,6 +9,7 @@ import { SynthesizerSnapshot } from "./synthesizer_snapshot.js";
|
|
|
9
9
|
*/
|
|
10
10
|
export function applySynthesizerSnapshot(snapshot)
|
|
11
11
|
{
|
|
12
|
+
this._snapshot = snapshot;
|
|
12
13
|
SynthesizerSnapshot.applySnapshot(this, snapshot);
|
|
13
14
|
SpessaSynthInfo("%cFinished applying snapshot!", consoleColors.info);
|
|
14
15
|
this.resetAllControllers();
|
|
@@ -104,13 +104,13 @@ export class ChannelSnapshot
|
|
|
104
104
|
|
|
105
105
|
/**
|
|
106
106
|
* Creates a snapshot of a single channel's state in the synthesizer.
|
|
107
|
-
* @param
|
|
107
|
+
* @param spessaSynthProcessor {SpessaSynthProcessor}
|
|
108
108
|
* @param channelNumber {number}
|
|
109
109
|
* @returns {ChannelSnapshot}
|
|
110
110
|
*/
|
|
111
|
-
static getChannelSnapshot(
|
|
111
|
+
static getChannelSnapshot(spessaSynthProcessor, channelNumber)
|
|
112
112
|
{
|
|
113
|
-
const channelObject =
|
|
113
|
+
const channelObject = spessaSynthProcessor.midiAudioChannels[channelNumber];
|
|
114
114
|
const channelSnapshot = new ChannelSnapshot();
|
|
115
115
|
// program data
|
|
116
116
|
channelSnapshot.program = channelObject.preset.program;
|
|
@@ -142,13 +142,13 @@ export class ChannelSnapshot
|
|
|
142
142
|
|
|
143
143
|
/**
|
|
144
144
|
* Applies the snapshot to the specified channel.
|
|
145
|
-
* @param
|
|
145
|
+
* @param spessaSynthProcessor {SpessaSynthProcessor}
|
|
146
146
|
* @param channelNumber {number}
|
|
147
147
|
* @param channelSnapshot {ChannelSnapshot}
|
|
148
148
|
*/
|
|
149
|
-
static applyChannelSnapshot(
|
|
149
|
+
static applyChannelSnapshot(spessaSynthProcessor, channelNumber, channelSnapshot)
|
|
150
150
|
{
|
|
151
|
-
const channelObject =
|
|
151
|
+
const channelObject = spessaSynthProcessor.midiAudioChannels[channelNumber];
|
|
152
152
|
channelObject.muteChannel(channelSnapshot.isMuted);
|
|
153
153
|
channelObject.setDrums(channelSnapshot.drumChannel);
|
|
154
154
|
|
|
@@ -53,27 +53,27 @@ export class SynthesizerSnapshot
|
|
|
53
53
|
|
|
54
54
|
/**
|
|
55
55
|
* Creates a snapshot of the synthesizer's state.
|
|
56
|
-
* @param
|
|
56
|
+
* @param spessaSynthProcessor {SpessaSynthProcessor}
|
|
57
57
|
* @returns {SynthesizerSnapshot}
|
|
58
58
|
*/
|
|
59
|
-
static createSynthesizerSnapshot(
|
|
59
|
+
static createSynthesizerSnapshot(spessaSynthProcessor)
|
|
60
60
|
{
|
|
61
61
|
const snapshot = new SynthesizerSnapshot();
|
|
62
62
|
// channel snapshots
|
|
63
63
|
snapshot.channelSnapshots =
|
|
64
|
-
|
|
65
|
-
ChannelSnapshot.getChannelSnapshot(
|
|
64
|
+
spessaSynthProcessor.midiAudioChannels.map((_, i) =>
|
|
65
|
+
ChannelSnapshot.getChannelSnapshot(spessaSynthProcessor, i));
|
|
66
66
|
|
|
67
67
|
// key mappings
|
|
68
|
-
snapshot.keyMappings =
|
|
68
|
+
snapshot.keyMappings = spessaSynthProcessor.keyModifierManager.getMappings();
|
|
69
69
|
// pan and volume
|
|
70
|
-
snapshot.mainVolume =
|
|
71
|
-
snapshot.pan =
|
|
70
|
+
snapshot.mainVolume = spessaSynthProcessor.midiVolume;
|
|
71
|
+
snapshot.pan = spessaSynthProcessor.pan;
|
|
72
72
|
|
|
73
73
|
// others
|
|
74
|
-
snapshot.system =
|
|
75
|
-
snapshot.interpolation =
|
|
76
|
-
snapshot.transposition =
|
|
74
|
+
snapshot.system = spessaSynthProcessor.system;
|
|
75
|
+
snapshot.interpolation = spessaSynthProcessor.interpolationType;
|
|
76
|
+
snapshot.transposition = spessaSynthProcessor.transposition;
|
|
77
77
|
|
|
78
78
|
// effect config is stored on the main thread, leave it empty
|
|
79
79
|
snapshot.effectsConfig = {};
|
|
@@ -83,31 +83,31 @@ export class SynthesizerSnapshot
|
|
|
83
83
|
|
|
84
84
|
/**
|
|
85
85
|
* Applies the snapshot to the synthesizer.
|
|
86
|
-
* @param
|
|
86
|
+
* @param spessaSynthProcessor {SpessaSynthProcessor}
|
|
87
87
|
* @param snapshot {SynthesizerSnapshot}
|
|
88
88
|
*/
|
|
89
|
-
static applySnapshot(
|
|
89
|
+
static applySnapshot(spessaSynthProcessor, snapshot)
|
|
90
90
|
{
|
|
91
91
|
// restore system
|
|
92
|
-
|
|
92
|
+
spessaSynthProcessor.setSystem(snapshot.system);
|
|
93
93
|
|
|
94
94
|
// restore pan and volume
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
95
|
+
spessaSynthProcessor.setMasterParameter(masterParameterType.mainVolume, snapshot.mainVolume);
|
|
96
|
+
spessaSynthProcessor.setMasterParameter(masterParameterType.masterPan, snapshot.pan);
|
|
97
|
+
spessaSynthProcessor.transposeAllChannels(snapshot.transposition);
|
|
98
|
+
spessaSynthProcessor.interpolationType = snapshot.interpolation;
|
|
99
|
+
spessaSynthProcessor.keyModifierManager.setMappings(snapshot.keyMappings);
|
|
100
100
|
|
|
101
101
|
// add channels if more needed
|
|
102
|
-
while (
|
|
102
|
+
while (spessaSynthProcessor.midiAudioChannels.length < snapshot.channelSnapshots.length)
|
|
103
103
|
{
|
|
104
|
-
|
|
104
|
+
spessaSynthProcessor.createMidiChannel();
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
// restore channels
|
|
108
108
|
snapshot.channelSnapshots.forEach((channelSnapshot, index) =>
|
|
109
109
|
{
|
|
110
|
-
ChannelSnapshot.applyChannelSnapshot(
|
|
110
|
+
ChannelSnapshot.applyChannelSnapshot(spessaSynthProcessor, index, channelSnapshot);
|
|
111
111
|
});
|
|
112
112
|
|
|
113
113
|
SpessaSynthInfo("%cFinished restoring controllers!", consoleColors.info);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {Object} SynthProcessorOptions
|
|
3
|
+
* @property {boolean?} enableEventSystem - if the event system is enabled.
|
|
4
|
+
* @property {number?} initialTime - initial synth time, in seconds.
|
|
5
|
+
* @property {boolean?} effectsEnabled - if the processor should route audio to the effect channels.
|
|
6
|
+
* @property {number?} midiChannels - the default MIDI channel count.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @type {SynthProcessorOptions}
|
|
12
|
+
*/
|
|
13
|
+
export const DEFAULT_SYNTH_OPTIONS = {
|
|
14
|
+
enableEventSystem: true,
|
|
15
|
+
initialTime: 0,
|
|
16
|
+
effectsEnabled: true,
|
|
17
|
+
midiChannels: 16
|
|
18
|
+
};
|
package/src/synthetizer/audio_engine/engine_methods/soundfont_management/reload_sound_font.js
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { loadSoundFont } from "../../../../soundfont/load_soundfont.js";
|
|
2
|
-
import { SpessaSynthInfo } from "../../../../utils/loggin.js";
|
|
3
|
-
import { consoleColors } from "../../../../utils/other.js";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* @param buffer {ArrayBuffer}
|
|
7
|
-
* @param isOverride {Boolean}
|
|
8
|
-
* @this {SpessaSynthProcessor}
|
|
9
|
-
*/
|
|
10
|
-
export function reloadSoundFont(buffer, isOverride = false)
|
|
11
|
-
{
|
|
12
|
-
this.clearSoundFont(false, isOverride);
|
|
13
|
-
if (isOverride)
|
|
14
|
-
{
|
|
15
|
-
this.overrideSoundfont = loadSoundFont(buffer);
|
|
16
|
-
}
|
|
17
|
-
else
|
|
18
|
-
{
|
|
19
|
-
this.soundfontManager.reloadManager(buffer);
|
|
20
|
-
}
|
|
21
|
-
this.getDefaultPresets();
|
|
22
|
-
this.midiAudioChannels.forEach(c =>
|
|
23
|
-
c.programChange(c.preset.program)
|
|
24
|
-
);
|
|
25
|
-
this.postReady();
|
|
26
|
-
this.sendPresetList();
|
|
27
|
-
SpessaSynthInfo("%cSpessaSynth is ready!", consoleColors.recognized);
|
|
28
|
-
}
|