spessasynth_core 3.26.1 → 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 +63 -62
- package/package.json +1 -1
- package/src/synthetizer/audio_engine/engine_components/midi_audio_channel.js +6 -1
- package/src/synthetizer/audio_engine/engine_components/soundfont_manager.js +31 -18
- package/src/synthetizer/audio_engine/engine_methods/create_midi_channel.js +1 -8
- 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/main_processor.js +68 -87
- package/src/synthetizer/audio_engine/snapshot/apply_synthesizer_snapshot.js +1 -0
- 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
|
@@ -21,68 +21,6 @@ npm install --save spessasynth_core
|
|
|
21
21
|
|
|
22
22
|
> Note: This is the new heart of the SpessaSynth library, after the repository has been split.
|
|
23
23
|
|
|
24
|
-
###
|
|
25
|
-
```js
|
|
26
|
-
import * as fs from "node:fs";
|
|
27
|
-
import { MIDI } from "../../src/midi/midi_loader.js";
|
|
28
|
-
import { SpessaSynthProcessor } from "../../src/synthetizer/audio_engine/main_processor.js";
|
|
29
|
-
import { SpessaSynthSequencer } from "../../src/sequencer/sequencer_engine.js";
|
|
30
|
-
import { audioToWav } from "../../src/utils/buffer_to_wav.js";
|
|
31
|
-
|
|
32
|
-
// process arguments
|
|
33
|
-
const args = process.argv.slice(2);
|
|
34
|
-
if (args.length !== 3)
|
|
35
|
-
{
|
|
36
|
-
console.log("Usage: node index.js <soundfont path> <midi path> <wav output path>");
|
|
37
|
-
process.exit();
|
|
38
|
-
}
|
|
39
|
-
// load the files
|
|
40
|
-
const sf = fs.readFileSync(args[0]);
|
|
41
|
-
const mid = fs.readFileSync(args[1]);
|
|
42
|
-
|
|
43
|
-
const midi = new MIDI(mid);
|
|
44
|
-
const sampleRate = 44100;
|
|
45
|
-
const sampleCount = 44100 * (midi.duration + 2);
|
|
46
|
-
|
|
47
|
-
const synth = new SpessaSynthProcessor(
|
|
48
|
-
sf,
|
|
49
|
-
sampleRate,
|
|
50
|
-
{},
|
|
51
|
-
false,
|
|
52
|
-
false
|
|
53
|
-
);
|
|
54
|
-
await synth.processorInitialized;
|
|
55
|
-
|
|
56
|
-
const seq = new SpessaSynthSequencer(synth);
|
|
57
|
-
seq.loadNewSongList([midi]);
|
|
58
|
-
seq.loop = false;
|
|
59
|
-
|
|
60
|
-
const outLeft = new Float32Array(sampleCount);
|
|
61
|
-
const outRight = new Float32Array(sampleCount);
|
|
62
|
-
const start = performance.now();
|
|
63
|
-
let filledSamples = 0;
|
|
64
|
-
|
|
65
|
-
const bufSize = 128;
|
|
66
|
-
while (filledSamples + bufSize < sampleCount)
|
|
67
|
-
{
|
|
68
|
-
const bufLeft = new Float32Array(bufSize);
|
|
69
|
-
const bufRight = new Float32Array(bufSize);
|
|
70
|
-
seq.processTick();
|
|
71
|
-
const arr = [bufLeft, bufRight];
|
|
72
|
-
synth.renderAudio(arr, arr, arr);
|
|
73
|
-
outLeft.set(bufLeft, filledSamples);
|
|
74
|
-
outRight.set(bufRight, filledSamples);
|
|
75
|
-
filledSamples += bufSize;
|
|
76
|
-
}
|
|
77
|
-
const wave = audioToWav({
|
|
78
|
-
leftChannel: outLeft,
|
|
79
|
-
rightChannel: outRight,
|
|
80
|
-
sampleRate: sampleRate
|
|
81
|
-
});
|
|
82
|
-
fs.writeFileSync(args[2], new Buffer(wave));
|
|
83
|
-
process.exit();
|
|
84
|
-
```
|
|
85
|
-
|
|
86
24
|
## Current Features
|
|
87
25
|
|
|
88
26
|
### Easy Integration
|
|
@@ -208,6 +146,69 @@ process.exit();
|
|
|
208
146
|
|
|
209
147
|
**If you like this project, consider giving it a star. It really helps out!**
|
|
210
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
|
+
|
|
211
212
|
# License
|
|
212
213
|
Copyright © 2025 Spessasus
|
|
213
214
|
Licensed under the Apache-2.0 License.
|
package/package.json
CHANGED
|
@@ -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
|
/**
|
|
@@ -12,12 +11,21 @@ import { isXGDrums } from "../../../utils/xg_hacks.js";
|
|
|
12
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 SoundFontManager
|
|
|
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 SoundFontManager
|
|
|
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 SoundFontManager
|
|
|
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 SoundFontManager
|
|
|
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 SoundFontManager
|
|
|
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 SoundFontManager
|
|
|
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 SoundFontManager
|
|
|
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
|
{
|
|
@@ -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
|
}
|
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,12 +1,7 @@
|
|
|
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";
|
|
@@ -18,9 +13,8 @@ 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
|
/**
|
|
@@ -332,61 +328,73 @@ class SpessaSynthProcessor
|
|
|
332
328
|
sampleRate;
|
|
333
329
|
|
|
334
330
|
/**
|
|
335
|
-
*
|
|
336
|
-
* @
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
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.
|
|
340
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
|
+
|
|
341
373
|
|
|
342
374
|
/**
|
|
343
375
|
* Creates a new synthesizer engine.
|
|
344
|
-
* @param
|
|
345
|
-
* @param
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
* @param initialTime {number}
|
|
350
|
-
* @param effectsEnabled {boolean}
|
|
351
|
-
* @param snapshot {SynthesizerSnapshot}
|
|
352
|
-
*/
|
|
353
|
-
constructor(
|
|
354
|
-
soundfont,
|
|
355
|
-
sampleRate,
|
|
356
|
-
callbacks,
|
|
357
|
-
effectsEnabled = true,
|
|
358
|
-
enableEventSystem = true,
|
|
359
|
-
initialTime = 0,
|
|
360
|
-
midiChannels = 16,
|
|
361
|
-
snapshot = undefined)
|
|
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
|
-
/**
|
|
384
|
-
* Sample time in seconds
|
|
385
|
-
* @type {number}
|
|
386
|
-
*/
|
|
387
|
+
this.midiOutputsCount = options.midiChannels;
|
|
388
|
+
this.effectsEnabled = options.effectsEnabled;
|
|
389
|
+
this.enableEventSystem = options.enableEventSystem;
|
|
390
|
+
this.currentSynthTime = options.midiChannels;
|
|
387
391
|
this.sampleTime = 1 / sampleRate;
|
|
392
|
+
this.sampleRate = sampleRate;
|
|
388
393
|
|
|
389
|
-
|
|
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);
|
|
390
398
|
|
|
391
399
|
|
|
392
400
|
for (let i = 0; i < 128; i++)
|
|
@@ -397,32 +405,16 @@ class SpessaSynthProcessor
|
|
|
397
405
|
/**
|
|
398
406
|
* @type {SoundFontManager}
|
|
399
407
|
*/
|
|
400
|
-
this.soundfontManager = new SoundFontManager(
|
|
401
|
-
soundfont
|
|
402
|
-
);
|
|
403
|
-
this.sendPresetList();
|
|
404
|
-
this.getDefaultPresets();
|
|
405
|
-
|
|
408
|
+
this.soundfontManager = new SoundFontManager(this.updatePresetList.bind(this));
|
|
406
409
|
|
|
407
|
-
for (let i = 0; i <
|
|
410
|
+
for (let i = 0; i < this.midiOutputsCount; i++)
|
|
408
411
|
{
|
|
409
412
|
this.createMidiChannel(false);
|
|
410
413
|
}
|
|
411
|
-
|
|
412
|
-
this.midiAudioChannels[DEFAULT_PERCUSSION].preset = this.drumPreset;
|
|
413
|
-
this.midiAudioChannels[DEFAULT_PERCUSSION].drumChannel = true;
|
|
414
|
-
|
|
415
|
-
// these smoothing factors were tested on 44,100 Hz, adjust them to target sample rate here
|
|
416
|
-
this.volumeEnvelopeSmoothingFactor = VOLUME_ENVELOPE_SMOOTHING_FACTOR * (44100 / sampleRate);
|
|
417
|
-
this.panSmoothingFactor = PAN_SMOOTHING_FACTOR * (44100 / sampleRate);
|
|
418
|
-
this.filterSmoothingFactor = FILTER_SMOOTHING_FACTOR * (44100 / sampleRate);
|
|
419
|
-
|
|
420
|
-
this._snapshot = snapshot;
|
|
421
|
-
if (this?._snapshot)
|
|
414
|
+
this.processorInitialized.then(() =>
|
|
422
415
|
{
|
|
423
|
-
|
|
424
|
-
}
|
|
425
|
-
this.postReady();
|
|
416
|
+
SpessaSynthInfo("%cSpessaSynth is ready!", consoleColors.recognized);
|
|
417
|
+
});
|
|
426
418
|
}
|
|
427
419
|
|
|
428
420
|
/**
|
|
@@ -451,7 +443,7 @@ class SpessaSynthProcessor
|
|
|
451
443
|
setSystem(value)
|
|
452
444
|
{
|
|
453
445
|
this.system = value;
|
|
454
|
-
this?.
|
|
446
|
+
this?.onMasterParameterChange?.(masterParameterType.midiSystem, this.system);
|
|
455
447
|
}
|
|
456
448
|
|
|
457
449
|
/**
|
|
@@ -497,16 +489,6 @@ class SpessaSynthProcessor
|
|
|
497
489
|
this.cachedVoices[bank][program][midiNote][velocity] = voices;
|
|
498
490
|
}
|
|
499
491
|
|
|
500
|
-
postReady()
|
|
501
|
-
{
|
|
502
|
-
// ensure stbvorbis is fully initialized
|
|
503
|
-
this.processorInitialized.then(() =>
|
|
504
|
-
{
|
|
505
|
-
SpessaSynthInfo("%cSpessaSynth is ready!", consoleColors.recognized);
|
|
506
|
-
this?.callbacks?.ready?.();
|
|
507
|
-
});
|
|
508
|
-
}
|
|
509
|
-
|
|
510
492
|
// noinspection JSUnusedGlobalSymbols
|
|
511
493
|
/**
|
|
512
494
|
* Renders float32 audio data to stereo outputs; buffer size of 128 is recommended
|
|
@@ -765,7 +747,7 @@ class SpessaSynthProcessor
|
|
|
765
747
|
*/
|
|
766
748
|
callEvent(eventName, eventData)
|
|
767
749
|
{
|
|
768
|
-
this?.
|
|
750
|
+
this?.onEventCall?.(eventName, eventData);
|
|
769
751
|
}
|
|
770
752
|
}
|
|
771
753
|
|
|
@@ -791,10 +773,9 @@ SpessaSynthProcessor.prototype.setMasterTuning = setMasterTuning;
|
|
|
791
773
|
|
|
792
774
|
// program related
|
|
793
775
|
SpessaSynthProcessor.prototype.getPreset = getPreset;
|
|
794
|
-
SpessaSynthProcessor.prototype.reloadSoundFont = reloadSoundFont;
|
|
795
776
|
SpessaSynthProcessor.prototype.clearSoundFont = clearSoundFont;
|
|
796
777
|
SpessaSynthProcessor.prototype.setEmbeddedSoundFont = setEmbeddedSoundFont;
|
|
797
|
-
SpessaSynthProcessor.prototype.
|
|
778
|
+
SpessaSynthProcessor.prototype.updatePresetList = updatePresetList;
|
|
798
779
|
|
|
799
780
|
// snapshot related
|
|
800
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();
|
|
@@ -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
|
-
}
|