spessasynth_lib 3.24.28 → 3.24.35
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 +1 -1
- package/external_midi/web_midi_link.js +1 -1
- package/index.js +8 -52
- package/midi_parser/basic_midi.js +15 -30
- package/midi_parser/midi_builder.js +6 -6
- package/midi_parser/midi_data.js +2 -2
- package/midi_parser/midi_editor.js +15 -15
- package/midi_parser/midi_loader.js +3 -3
- package/midi_parser/midi_message.js +2 -2
- package/midi_parser/midi_sequence.js +32 -4
- package/midi_parser/midi_writer.js +8 -8
- package/midi_parser/rmidi_writer.js +20 -21
- package/midi_parser/used_keys_loaded.js +8 -6
- package/package.json +1 -1
- package/sequencer/sequencer.js +6 -6
- package/sequencer/worklet_sequencer/events.js +2 -1
- package/sequencer/worklet_sequencer/play.js +2 -3
- package/sequencer/worklet_sequencer/process_event.js +1 -1
- package/sequencer/worklet_sequencer/process_tick.js +1 -1
- package/sequencer/worklet_sequencer/sequencer_message.js +1 -1
- package/sequencer/worklet_sequencer/song_control.js +6 -7
- package/sequencer/worklet_sequencer/worklet_sequencer.js +2 -1
- package/soundfont/basic_soundfont/basic_soundfont.js +197 -13
- package/soundfont/basic_soundfont/generator.js +18 -16
- package/soundfont/basic_soundfont/modulator.js +3 -0
- package/soundfont/basic_soundfont/riff_chunk.js +2 -2
- package/soundfont/basic_soundfont/write_dls/art2.js +1 -1
- package/soundfont/basic_soundfont/write_dls/combine_zones.js +12 -10
- package/soundfont/basic_soundfont/write_dls/ins.js +2 -2
- package/soundfont/basic_soundfont/write_dls/lins.js +1 -1
- package/soundfont/basic_soundfont/write_dls/modulator_converter.js +9 -9
- package/soundfont/basic_soundfont/write_dls/rgn2.js +5 -5
- package/soundfont/basic_soundfont/write_dls/wave.js +2 -1
- package/soundfont/basic_soundfont/write_dls/write_dls.js +1 -1
- package/soundfont/basic_soundfont/write_dls/wsmp.js +2 -2
- package/soundfont/basic_soundfont/write_dls/wvpl.js +1 -1
- package/soundfont/basic_soundfont/write_sf2/ibag.js +2 -2
- package/soundfont/basic_soundfont/write_sf2/igen.js +20 -17
- package/soundfont/basic_soundfont/write_sf2/imod.js +3 -3
- package/soundfont/basic_soundfont/write_sf2/inst.js +2 -2
- package/soundfont/basic_soundfont/write_sf2/pbag.js +2 -2
- package/soundfont/basic_soundfont/write_sf2/pgen.js +21 -18
- package/soundfont/basic_soundfont/write_sf2/phdr.js +2 -2
- package/soundfont/basic_soundfont/write_sf2/pmod.js +2 -2
- package/soundfont/basic_soundfont/write_sf2/sdta.js +1 -1
- package/soundfont/basic_soundfont/write_sf2/shdr.js +1 -1
- package/soundfont/basic_soundfont/write_sf2/write.js +7 -6
- package/soundfont/dls/articulator_converter.js +1 -0
- package/soundfont/dls/dls_destinations.js +3 -3
- package/soundfont/dls/dls_preset.js +1 -1
- package/soundfont/dls/dls_soundfont.js +7 -7
- package/soundfont/dls/read_instrument.js +1 -1
- package/soundfont/dls/read_region.js +1 -0
- package/soundfont/dls/read_samples.js +4 -5
- package/soundfont/load_soundfont.js +1 -1
- package/soundfont/read_sf2/presets.js +1 -1
- package/soundfont/read_sf2/soundfont.js +5 -5
- package/synthetizer/audio_effects/effects_config.js +3 -3
- package/synthetizer/audio_effects/fancy_chorus.js +7 -7
- package/synthetizer/audio_effects/reverb.js +5 -5
- package/synthetizer/audio_effects/reverb_as_binary.js +15 -0
- package/synthetizer/audio_effects/reverb_buffer.min.js +1 -0
- package/synthetizer/synth_constants.js +14 -0
- package/synthetizer/synthetizer.js +1 -17
- package/synthetizer/worklet_processor.min.js +14 -12
- package/synthetizer/worklet_system/main_processor.js +2 -2
- package/synthetizer/worklet_system/worklet_methods/controller_control/controller_change.js +1 -1
- package/synthetizer/worklet_system/worklet_methods/controller_control/reset_controllers.js +1 -1
- package/synthetizer/worklet_system/worklet_methods/create_worklet_channel.js +2 -1
- package/synthetizer/worklet_system/worklet_methods/worklet_soundfont_manager/worklet_soundfont_manager.js +2 -2
- package/synthetizer/worklet_system/worklet_processor.js +1 -1
- package/soundfont/basic_soundfont/write_sf2/soundfont_trimmer.js +0 -185
- package/synthetizer/audio_effects/impulse_response_2.flac +0 -0
- package/synthetizer/worklet_system/minify_processor.sh +0 -4
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
import { SpessaSynthGroupCollapsed, SpessaSynthGroupEnd, SpessaSynthInfo } from "../utils/loggin.js";
|
|
2
2
|
import { consoleColors } from "../utils/other.js";
|
|
3
|
-
import { DEFAULT_PERCUSSION } from "../synthetizer/synthetizer.js";
|
|
4
3
|
import { messageTypes, midiControllers } from "./midi_message.js";
|
|
4
|
+
import { DEFAULT_PERCUSSION } from "../synthetizer/synth_constants.js";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
8
|
-
* @
|
|
7
|
+
* Gets the used programs and keys for this MIDI file with a given sound bank
|
|
8
|
+
* @this {BasicMIDI}
|
|
9
|
+
* @param soundfont {{getPreset: function(number, number): BasicPreset}} - the sound bank
|
|
9
10
|
* @returns {Object<string, Set<string>>}
|
|
10
11
|
*/
|
|
11
|
-
export function getUsedProgramsAndKeys(
|
|
12
|
+
export function getUsedProgramsAndKeys(soundfont)
|
|
12
13
|
{
|
|
14
|
+
const mid = this;
|
|
13
15
|
SpessaSynthGroupCollapsed(
|
|
14
16
|
"%cSearching for all used programs and keys...",
|
|
15
17
|
consoleColors.info
|
|
16
18
|
);
|
|
17
|
-
//
|
|
19
|
+
// Find every bank:program combo and every key:velocity for each. Make sure to care about ports and drums
|
|
18
20
|
const channelsAmount = 16 + mid.midiPortChannelOffsets.reduce((max, cur) => cur > max ? cur : max);
|
|
19
21
|
/**
|
|
20
22
|
* @type {{program: number, bank: number, drums: boolean, string: string}[]}
|
|
@@ -152,7 +154,7 @@ export function getUsedProgramsAndKeys(mid, soundfont)
|
|
|
152
154
|
continue;
|
|
153
155
|
}
|
|
154
156
|
channelPresets[channel].bank = realBank;
|
|
155
|
-
// do not update the data, bank change
|
|
157
|
+
// do not update the data, bank change doesn't change the preset
|
|
156
158
|
break;
|
|
157
159
|
|
|
158
160
|
case messageTypes.noteOn:
|
package/package.json
CHANGED
package/sequencer/sequencer.js
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
WorkletSequencerReturnMessageType
|
|
9
9
|
} from "./worklet_sequencer/sequencer_message.js";
|
|
10
10
|
import { SpessaSynthWarn } from "../utils/loggin.js";
|
|
11
|
-
import { DUMMY_MIDI_DATA,
|
|
11
|
+
import { DUMMY_MIDI_DATA, MIDIData } from "../midi_parser/midi_data.js";
|
|
12
12
|
import { BasicMIDI } from "../midi_parser/basic_midi.js";
|
|
13
13
|
import { readBytesAsUintBigEndian } from "../utils/byte_functions/big_endian.js";
|
|
14
14
|
import { IndexedByteArray } from "../utils/indexed_array.js";
|
|
@@ -68,12 +68,12 @@ export class Sequencer
|
|
|
68
68
|
|
|
69
69
|
/**
|
|
70
70
|
* The sequence's data, except for the track data.
|
|
71
|
-
* @type {
|
|
71
|
+
* @type {MIDIData}
|
|
72
72
|
*/
|
|
73
73
|
midiData;
|
|
74
74
|
|
|
75
75
|
/**
|
|
76
|
-
* @type {Object<string, function(
|
|
76
|
+
* @type {Object<string, function(MIDIData)>}
|
|
77
77
|
* @private
|
|
78
78
|
*/
|
|
79
79
|
onSongChange = {};
|
|
@@ -398,7 +398,7 @@ export class Sequencer
|
|
|
398
398
|
|
|
399
399
|
/**
|
|
400
400
|
* Adds a new event that gets called when the song changes
|
|
401
|
-
* @param callback {function(
|
|
401
|
+
* @param callback {function(MIDIData)}
|
|
402
402
|
* @param id {string} must be unique
|
|
403
403
|
*/
|
|
404
404
|
addOnSongChangeEvent(callback, id)
|
|
@@ -538,8 +538,8 @@ export class Sequencer
|
|
|
538
538
|
|
|
539
539
|
case WorkletSequencerReturnMessageType.songChange:
|
|
540
540
|
/**
|
|
541
|
-
* messageData is expected to be {
|
|
542
|
-
* @type {
|
|
541
|
+
* messageData is expected to be {MIDIData}
|
|
542
|
+
* @type {MIDIData}
|
|
543
543
|
*/
|
|
544
544
|
let songChangeData = messageData[0];
|
|
545
545
|
this.songIndex = messageData[1];
|
|
@@ -4,7 +4,8 @@ import {
|
|
|
4
4
|
} from "../../synthetizer/worklet_system/message_protocol/worklet_message.js";
|
|
5
5
|
import { SongChangeType, WorkletSequencerMessageType, WorkletSequencerReturnMessageType } from "./sequencer_message.js";
|
|
6
6
|
import { messageTypes, midiControllers } from "../../midi_parser/midi_message.js";
|
|
7
|
-
|
|
7
|
+
|
|
8
|
+
import { MIDI_CHANNEL_COUNT } from "../../synthetizer/synth_constants.js";
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* @param messageType {WorkletSequencerMessageType}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { getEvent, messageTypes, midiControllers } from "../../midi_parser/midi_message.js";
|
|
2
2
|
import { WorkletSequencerReturnMessageType } from "./sequencer_message.js";
|
|
3
|
-
import { MIDIticksToSeconds } from "../../midi_parser/basic_midi.js";
|
|
4
3
|
import { resetArray } from "../../synthetizer/worklet_system/worklet_utilities/controller_tables.js";
|
|
5
4
|
import {
|
|
6
5
|
nonResetableCCs
|
|
@@ -158,7 +157,7 @@ export function _playTo(time, ticks = undefined)
|
|
|
158
157
|
}
|
|
159
158
|
else if (controllerNumber === midiControllers.resetAllControllers)
|
|
160
159
|
{
|
|
161
|
-
resetAllControlllers(
|
|
160
|
+
resetAllControlllers(channel);
|
|
162
161
|
}
|
|
163
162
|
if (this.sendMIDIMessages)
|
|
164
163
|
{
|
|
@@ -324,7 +323,7 @@ export function setTimeTicks(ticks)
|
|
|
324
323
|
this.pausedTime = undefined;
|
|
325
324
|
this.post(
|
|
326
325
|
WorkletSequencerReturnMessageType.timeChange,
|
|
327
|
-
currentTime - MIDIticksToSeconds(ticks
|
|
326
|
+
currentTime - this.midiData.MIDIticksToSeconds(ticks)
|
|
328
327
|
);
|
|
329
328
|
const isNotFinished = this._playTo(0, ticks);
|
|
330
329
|
this._recalculateStartTime(this.playedTime);
|
|
@@ -41,7 +41,7 @@ export const WorkletSequencerMessageType = {
|
|
|
41
41
|
*/
|
|
42
42
|
export const WorkletSequencerReturnMessageType = {
|
|
43
43
|
midiEvent: 0, // [...midiEventBytes<number>]
|
|
44
|
-
songChange: 1, // [midiData<
|
|
44
|
+
songChange: 1, // [midiData<MIDIData>, songIndex<number>, isAutoPlayed<boolean>]
|
|
45
45
|
textEvent: 2, // [messageData<number[]>, statusByte<number>, lyricsIndex<number>]
|
|
46
46
|
timeChange: 3, // newAbsoluteTime<number>
|
|
47
47
|
pause: 4, // no data
|
|
@@ -6,10 +6,9 @@ import {
|
|
|
6
6
|
SpessaSynthInfo,
|
|
7
7
|
SpessaSynthWarn
|
|
8
8
|
} from "../../utils/loggin.js";
|
|
9
|
-
import {
|
|
9
|
+
import { MIDIData } from "../../midi_parser/midi_data.js";
|
|
10
10
|
import { MIDI } from "../../midi_parser/midi_loader.js";
|
|
11
|
-
|
|
12
|
-
import { MIDIticksToSeconds } from "../../midi_parser/basic_midi.js";
|
|
11
|
+
|
|
13
12
|
|
|
14
13
|
/**
|
|
15
14
|
* @param trackNum {number}
|
|
@@ -80,7 +79,7 @@ export function loadNewSequence(parsedMidi, autoPlay = true)
|
|
|
80
79
|
}
|
|
81
80
|
SpessaSynthGroupCollapsed("%cPreloading samples...", consoleColors.info);
|
|
82
81
|
// smart preloading: load only samples used in the midi!
|
|
83
|
-
const used =
|
|
82
|
+
const used = this.midiData.getUsedProgramsAndKeys(this.synth.soundfontManager);
|
|
84
83
|
for (const [programBank, combos] of Object.entries(used))
|
|
85
84
|
{
|
|
86
85
|
const bank = parseInt(programBank.split(":")[0]);
|
|
@@ -103,7 +102,7 @@ export function loadNewSequence(parsedMidi, autoPlay = true)
|
|
|
103
102
|
|
|
104
103
|
/**
|
|
105
104
|
* the midi track data
|
|
106
|
-
* @type {
|
|
105
|
+
* @type {MIDIMessage[][]}
|
|
107
106
|
*/
|
|
108
107
|
this.tracks = this.midiData.tracks;
|
|
109
108
|
|
|
@@ -124,10 +123,10 @@ export function loadNewSequence(parsedMidi, autoPlay = true)
|
|
|
124
123
|
* @type {number}
|
|
125
124
|
*/
|
|
126
125
|
this.duration = this.midiData.duration;
|
|
127
|
-
this.firstNoteTime = MIDIticksToSeconds(this.midiData.firstNoteOn
|
|
126
|
+
this.firstNoteTime = this.midiData.MIDIticksToSeconds(this.midiData.firstNoteOn);
|
|
128
127
|
SpessaSynthInfo(`%cTotal song time: ${formatTime(Math.ceil(this.duration)).time}`, consoleColors.recognized);
|
|
129
128
|
|
|
130
|
-
this.post(WorkletSequencerReturnMessageType.songChange, [new
|
|
129
|
+
this.post(WorkletSequencerReturnMessageType.songChange, [new MIDIData(this.midiData), this.songIndex, autoPlay]);
|
|
131
130
|
|
|
132
131
|
if (this.duration <= 1)
|
|
133
132
|
{
|
|
@@ -14,7 +14,8 @@ import {
|
|
|
14
14
|
sendMIDIReset
|
|
15
15
|
} from "./events.js";
|
|
16
16
|
import { SpessaSynthWarn } from "../../utils/loggin.js";
|
|
17
|
-
|
|
17
|
+
|
|
18
|
+
import { MIDI_CHANNEL_COUNT } from "../../synthetizer/synth_constants.js";
|
|
18
19
|
|
|
19
20
|
class WorkletSequencer
|
|
20
21
|
{
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
SpessaSynthGroup,
|
|
3
|
+
SpessaSynthGroupCollapsed,
|
|
4
|
+
SpessaSynthGroupEnd,
|
|
5
|
+
SpessaSynthInfo,
|
|
6
|
+
SpessaSynthWarn
|
|
7
|
+
} from "../../utils/loggin.js";
|
|
2
8
|
import { consoleColors } from "../../utils/other.js";
|
|
3
9
|
import { write } from "./write_sf2/write.js";
|
|
4
10
|
import { defaultModulators, Modulator } from "./modulator.js";
|
|
@@ -9,7 +15,7 @@ import { Generator, generatorTypes } from "./generator.js";
|
|
|
9
15
|
import { BasicInstrument } from "./basic_instrument.js";
|
|
10
16
|
import { BasicPreset } from "./basic_preset.js";
|
|
11
17
|
|
|
12
|
-
class
|
|
18
|
+
class BasicSoundBank
|
|
13
19
|
{
|
|
14
20
|
/**
|
|
15
21
|
* Creates a new basic soundfont template
|
|
@@ -18,7 +24,7 @@ class BasicSoundFont
|
|
|
18
24
|
constructor(data = undefined)
|
|
19
25
|
{
|
|
20
26
|
/**
|
|
21
|
-
* Soundfont's info stored as name: value. ifil and iver are stored as string representation of float (e.g
|
|
27
|
+
* Soundfont's info stored as name: value. ifil and iver are stored as string representation of float (e.g., 2.1)
|
|
22
28
|
* @type {Object<string, string|IndexedByteArray>}
|
|
23
29
|
*/
|
|
24
30
|
this.soundFontInfo = {};
|
|
@@ -56,10 +62,10 @@ class BasicSoundFont
|
|
|
56
62
|
|
|
57
63
|
/**
|
|
58
64
|
* Merges soundfonts with the given order. Keep in mind that the info read is copied from the first one
|
|
59
|
-
* @param soundfonts {...
|
|
60
|
-
* @returns {
|
|
65
|
+
* @param soundfonts {...BasicSoundBank} the soundfonts to merge, the first overwrites the last
|
|
66
|
+
* @returns {BasicSoundBank}
|
|
61
67
|
*/
|
|
62
|
-
static
|
|
68
|
+
static mergeSoundBanks(...soundfonts)
|
|
63
69
|
{
|
|
64
70
|
const mainSf = soundfonts.shift();
|
|
65
71
|
const presets = mainSf.presets;
|
|
@@ -77,7 +83,7 @@ class BasicSoundFont
|
|
|
77
83
|
});
|
|
78
84
|
}
|
|
79
85
|
|
|
80
|
-
return new
|
|
86
|
+
return new BasicSoundBank({ presets: presets, info: mainSf.soundFontInfo });
|
|
81
87
|
}
|
|
82
88
|
|
|
83
89
|
/**
|
|
@@ -86,7 +92,7 @@ class BasicSoundFont
|
|
|
86
92
|
*/
|
|
87
93
|
static getDummySoundfontFile()
|
|
88
94
|
{
|
|
89
|
-
const font = new
|
|
95
|
+
const font = new BasicSoundBank();
|
|
90
96
|
const sample = new BasicSample(
|
|
91
97
|
"Saw",
|
|
92
98
|
44100,
|
|
@@ -139,6 +145,184 @@ class BasicSoundFont
|
|
|
139
145
|
return font.write().buffer;
|
|
140
146
|
}
|
|
141
147
|
|
|
148
|
+
/**
|
|
149
|
+
* Trims a sound bank to only contain samples in a given MIDI file
|
|
150
|
+
* @param mid {BasicMIDI} - the MIDI file
|
|
151
|
+
*/
|
|
152
|
+
trimSoundBank(mid)
|
|
153
|
+
{
|
|
154
|
+
const soundfont = this;
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* @param instrument {Instrument}
|
|
158
|
+
* @param combos {{key: number, velocity: number}[]}
|
|
159
|
+
* @returns {number}
|
|
160
|
+
*/
|
|
161
|
+
function trimInstrumentZones(instrument, combos)
|
|
162
|
+
{
|
|
163
|
+
let trimmedIZones = 0;
|
|
164
|
+
for (let iZoneIndex = 0; iZoneIndex < instrument.instrumentZones.length; iZoneIndex++)
|
|
165
|
+
{
|
|
166
|
+
const iZone = instrument.instrumentZones[iZoneIndex];
|
|
167
|
+
if (iZone.isGlobal)
|
|
168
|
+
{
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
const iKeyRange = iZone.keyRange;
|
|
172
|
+
const iVelRange = iZone.velRange;
|
|
173
|
+
let isIZoneUsed = false;
|
|
174
|
+
for (const iCombo of combos)
|
|
175
|
+
{
|
|
176
|
+
if (
|
|
177
|
+
(iCombo.key >= iKeyRange.min && iCombo.key <= iKeyRange.max) &&
|
|
178
|
+
(iCombo.velocity >= iVelRange.min && iCombo.velocity <= iVelRange.max)
|
|
179
|
+
)
|
|
180
|
+
{
|
|
181
|
+
isIZoneUsed = true;
|
|
182
|
+
break;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
if (!isIZoneUsed)
|
|
186
|
+
{
|
|
187
|
+
SpessaSynthInfo(
|
|
188
|
+
`%c${iZone.sample.sampleName} %cremoved from %c${instrument.instrumentName}%c. Use count: %c${iZone.useCount - 1}`,
|
|
189
|
+
consoleColors.recognized,
|
|
190
|
+
consoleColors.info,
|
|
191
|
+
consoleColors.recognized,
|
|
192
|
+
consoleColors.info,
|
|
193
|
+
consoleColors.recognized
|
|
194
|
+
);
|
|
195
|
+
if (instrument.safeDeleteZone(iZoneIndex))
|
|
196
|
+
{
|
|
197
|
+
trimmedIZones++;
|
|
198
|
+
iZoneIndex--;
|
|
199
|
+
SpessaSynthInfo(
|
|
200
|
+
`%c${iZone.sample.sampleName} %cdeleted`,
|
|
201
|
+
consoleColors.recognized,
|
|
202
|
+
consoleColors.info
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
if (iZone.sample.useCount < 1)
|
|
206
|
+
{
|
|
207
|
+
soundfont.deleteSample(iZone.sample);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
}
|
|
212
|
+
return trimmedIZones;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
SpessaSynthGroup(
|
|
216
|
+
"%cTrimming soundfont...",
|
|
217
|
+
consoleColors.info
|
|
218
|
+
);
|
|
219
|
+
const usedProgramsAndKeys = mid.getUsedProgramsAndKeys(soundfont);
|
|
220
|
+
|
|
221
|
+
SpessaSynthGroupCollapsed(
|
|
222
|
+
"%cModifying soundfont...",
|
|
223
|
+
consoleColors.info
|
|
224
|
+
);
|
|
225
|
+
SpessaSynthInfo("Detected keys for midi:", usedProgramsAndKeys);
|
|
226
|
+
// modify the soundfont to only include programs and samples that are used
|
|
227
|
+
for (let presetIndex = 0; presetIndex < soundfont.presets.length; presetIndex++)
|
|
228
|
+
{
|
|
229
|
+
const p = soundfont.presets[presetIndex];
|
|
230
|
+
const string = p.bank + ":" + p.program;
|
|
231
|
+
const used = usedProgramsAndKeys[string];
|
|
232
|
+
if (used === undefined)
|
|
233
|
+
{
|
|
234
|
+
SpessaSynthInfo(
|
|
235
|
+
`%cDeleting preset %c${p.presetName}%c and its zones`,
|
|
236
|
+
consoleColors.info,
|
|
237
|
+
consoleColors.recognized,
|
|
238
|
+
consoleColors.info
|
|
239
|
+
);
|
|
240
|
+
soundfont.deletePreset(p);
|
|
241
|
+
presetIndex--;
|
|
242
|
+
}
|
|
243
|
+
else
|
|
244
|
+
{
|
|
245
|
+
const combos = [...used].map(s =>
|
|
246
|
+
{
|
|
247
|
+
const split = s.split("-");
|
|
248
|
+
return {
|
|
249
|
+
key: parseInt(split[0]),
|
|
250
|
+
velocity: parseInt(split[1])
|
|
251
|
+
};
|
|
252
|
+
});
|
|
253
|
+
SpessaSynthGroupCollapsed(
|
|
254
|
+
`%cTrimming %c${p.presetName}`,
|
|
255
|
+
consoleColors.info,
|
|
256
|
+
consoleColors.recognized
|
|
257
|
+
);
|
|
258
|
+
SpessaSynthInfo(`Keys for ${p.presetName}:`, combos);
|
|
259
|
+
let trimmedZones = 0;
|
|
260
|
+
// clean the preset to only use zones that are used
|
|
261
|
+
for (let zoneIndex = 0; zoneIndex < p.presetZones.length; zoneIndex++)
|
|
262
|
+
{
|
|
263
|
+
const zone = p.presetZones[zoneIndex];
|
|
264
|
+
if (zone.isGlobal)
|
|
265
|
+
{
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
const keyRange = zone.keyRange;
|
|
269
|
+
const velRange = zone.velRange;
|
|
270
|
+
// check if any of the combos matches the zone
|
|
271
|
+
let isZoneUsed = false;
|
|
272
|
+
for (const combo of combos)
|
|
273
|
+
{
|
|
274
|
+
if (
|
|
275
|
+
(combo.key >= keyRange.min && combo.key <= keyRange.max) &&
|
|
276
|
+
(combo.velocity >= velRange.min && combo.velocity <= velRange.max)
|
|
277
|
+
)
|
|
278
|
+
{
|
|
279
|
+
// zone is used, trim the instrument zones
|
|
280
|
+
isZoneUsed = true;
|
|
281
|
+
const trimmedIZones = trimInstrumentZones(zone.instrument, combos);
|
|
282
|
+
SpessaSynthInfo(
|
|
283
|
+
`%cTrimmed off %c${trimmedIZones}%c zones from %c${zone.instrument.instrumentName}`,
|
|
284
|
+
consoleColors.info,
|
|
285
|
+
consoleColors.recognized,
|
|
286
|
+
consoleColors.info,
|
|
287
|
+
consoleColors.recognized
|
|
288
|
+
);
|
|
289
|
+
break;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
if (!isZoneUsed)
|
|
293
|
+
{
|
|
294
|
+
trimmedZones++;
|
|
295
|
+
p.deleteZone(zoneIndex);
|
|
296
|
+
if (zone.instrument.useCount < 1)
|
|
297
|
+
{
|
|
298
|
+
soundfont.deleteInstrument(zone.instrument);
|
|
299
|
+
}
|
|
300
|
+
zoneIndex--;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
SpessaSynthInfo(
|
|
304
|
+
`%cTrimmed off %c${trimmedZones}%c zones from %c${p.presetName}`,
|
|
305
|
+
consoleColors.info,
|
|
306
|
+
consoleColors.recognized,
|
|
307
|
+
consoleColors.info,
|
|
308
|
+
consoleColors.recognized
|
|
309
|
+
);
|
|
310
|
+
SpessaSynthGroupEnd();
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
soundfont.removeUnusedElements();
|
|
314
|
+
|
|
315
|
+
soundfont.soundFontInfo["ICMT"] = `NOTE: This soundfont was trimmed by SpessaSynth to only contain presets used in "${mid.midiName}"\n\n`
|
|
316
|
+
+ soundfont.soundFontInfo["ICMT"];
|
|
317
|
+
|
|
318
|
+
SpessaSynthInfo(
|
|
319
|
+
"%cSoundfont modified!",
|
|
320
|
+
consoleColors.recognized
|
|
321
|
+
);
|
|
322
|
+
SpessaSynthGroupEnd();
|
|
323
|
+
SpessaSynthGroupEnd();
|
|
324
|
+
}
|
|
325
|
+
|
|
142
326
|
removeUnusedElements()
|
|
143
327
|
{
|
|
144
328
|
this.instruments.forEach(i =>
|
|
@@ -254,7 +438,7 @@ class BasicSoundFont
|
|
|
254
438
|
}
|
|
255
439
|
else
|
|
256
440
|
{
|
|
257
|
-
// non
|
|
441
|
+
// non-drum preset: find any preset with the given program that is not a drum preset
|
|
258
442
|
preset = this.presets.find(p => p.program === programNr && p.bank !== 128);
|
|
259
443
|
}
|
|
260
444
|
if (preset)
|
|
@@ -299,7 +483,7 @@ class BasicSoundFont
|
|
|
299
483
|
throw new Error(`SF parsing error: ${error} The file may be corrupted.`);
|
|
300
484
|
}
|
|
301
485
|
|
|
302
|
-
|
|
486
|
+
destroySoundBank()
|
|
303
487
|
{
|
|
304
488
|
delete this.presets;
|
|
305
489
|
delete this.instruments;
|
|
@@ -307,7 +491,7 @@ class BasicSoundFont
|
|
|
307
491
|
}
|
|
308
492
|
}
|
|
309
493
|
|
|
310
|
-
|
|
311
|
-
|
|
494
|
+
BasicSoundBank.prototype.write = write;
|
|
495
|
+
BasicSoundBank.prototype.writeDLS = writeDLS;
|
|
312
496
|
|
|
313
|
-
export {
|
|
497
|
+
export { BasicSoundBank };
|
|
@@ -7,7 +7,7 @@ export const generatorTypes = {
|
|
|
7
7
|
endAddrOffset: 1, // sample control - moves sample end point
|
|
8
8
|
startloopAddrsOffset: 2, // loop control - moves loop start point
|
|
9
9
|
endloopAddrsOffset: 3, // loop control - moves loop end point
|
|
10
|
-
startAddrsCoarseOffset: 4, // sample control - moves sample start point in
|
|
10
|
+
startAddrsCoarseOffset: 4, // sample control - moves sample start point in 32,768 increments
|
|
11
11
|
modLfoToPitch: 5, // pitch modulation - modulation lfo pitch modulation in cents
|
|
12
12
|
vibLfoToPitch: 6, // pitch modulation - vibrato lfo pitch modulation in cents
|
|
13
13
|
modEnvToPitch: 7, // pitch modulation - modulation envelope pitch modulation in cents
|
|
@@ -15,7 +15,7 @@ export const generatorTypes = {
|
|
|
15
15
|
initialFilterQ: 9, // filter - lowpass filter resonance
|
|
16
16
|
modLfoToFilterFc: 10, // filter modulation - modulation lfo lowpass filter cutoff in cents
|
|
17
17
|
modEnvToFilterFc: 11, // filter modulation - modulation envelope lowpass filter cutoff in cents
|
|
18
|
-
endAddrsCoarseOffset: 12, // ample control -
|
|
18
|
+
endAddrsCoarseOffset: 12, // ample control - move sample end point in 32,768 increments
|
|
19
19
|
modLfoToVolume: 13, // modulation lfo - volume (tremolo), where 100 = 10dB
|
|
20
20
|
unused1: 14, // unused
|
|
21
21
|
chorusEffectsSend: 15, // effect send - how much is sent to chorus 0 - 1000
|
|
@@ -24,11 +24,11 @@ export const generatorTypes = {
|
|
|
24
24
|
unused2: 18, // unused
|
|
25
25
|
unused3: 19, // unused
|
|
26
26
|
unused4: 20, // unused
|
|
27
|
-
delayModLFO: 21, // mod lfo - delay for mod lfo to start from zero
|
|
28
|
-
freqModLFO: 22, // mod lfo - frequency of mod lfo, 0 = 8.
|
|
29
|
-
delayVibLFO: 23, // vib lfo - delay for vibrato lfo to start from zero
|
|
27
|
+
delayModLFO: 21, // mod lfo - delay for mod lfo to start from zero
|
|
28
|
+
freqModLFO: 22, // mod lfo - frequency of mod lfo, 0 = 8.176 Hz, units: f => 1200log2(f/8.176)
|
|
29
|
+
delayVibLFO: 23, // vib lfo - delay for vibrato lfo to start from zero
|
|
30
30
|
freqVibLFO: 24, // vib lfo - frequency of vibrato lfo, 0 = 8.176Hz, unit: f => 1200log2(f/8.176)
|
|
31
|
-
delayModEnv: 25, // mod env - 0 =
|
|
31
|
+
delayModEnv: 25, // mod env - 0 = 1 s decay till mod env starts
|
|
32
32
|
attackModEnv: 26, // mod env - attack of mod env
|
|
33
33
|
holdModEnv: 27, // mod env - hold of mod env
|
|
34
34
|
decayModEnv: 28, // mod env - decay of mod env
|
|
@@ -48,16 +48,16 @@ export const generatorTypes = {
|
|
|
48
48
|
reserved1: 42, // reserved
|
|
49
49
|
keyRange: 43, // zone - key range for which preset / instrument zone is active
|
|
50
50
|
velRange: 44, // zone - velocity range for which preset / instrument zone is active
|
|
51
|
-
startloopAddrsCoarseOffset: 45, // sample control - moves sample loop start point in
|
|
51
|
+
startloopAddrsCoarseOffset: 45, // sample control - moves sample loop start point in 32,768 increments
|
|
52
52
|
keyNum: 46, // zone - instrument only: always use this midi number (ignore what's pressed)
|
|
53
53
|
velocity: 47, // zone - instrument only: always use this velocity (ignore what's pressed)
|
|
54
54
|
initialAttenuation: 48, // zone - allows turning down the volume, 10 = -1dB
|
|
55
55
|
reserved2: 49, // reserved
|
|
56
|
-
endloopAddrsCoarseOffset: 50, // sample control - moves sample loop end point in
|
|
56
|
+
endloopAddrsCoarseOffset: 50, // sample control - moves sample loop end point in 32,768 increments
|
|
57
57
|
coarseTune: 51, // tune - pitch offset in semitones
|
|
58
58
|
fineTune: 52, // tune - pitch offset in cents
|
|
59
59
|
sampleID: 53, // sample - instrument zone only: which sample to use
|
|
60
|
-
sampleModes: 54, // sample - 0 = no loop, 1 = loop, 2 = reserved, 3 = loop and play till end in release phase
|
|
60
|
+
sampleModes: 54, // sample - 0 = no loop, 1 = loop, 2 = reserved, 3 = loop and play till the end in release phase
|
|
61
61
|
reserved3: 55, // reserved
|
|
62
62
|
scaleTuning: 56, // sample - the degree to which MIDI key number influences pitch, 100 = default
|
|
63
63
|
exclusiveClass: 57, // sample - = cut = choke group
|
|
@@ -103,13 +103,14 @@ generatorLimits[generatorTypes.delayVibLFO] = { min: -12000, max: 5000, def: -12
|
|
|
103
103
|
generatorLimits[generatorTypes.freqVibLFO] = { min: -16000, max: 4500, def: 0 };
|
|
104
104
|
|
|
105
105
|
// mod env
|
|
106
|
-
generatorLimits[generatorTypes.delayModEnv] = { min: -32768, max: 5000, def: -32768 }; // -
|
|
106
|
+
generatorLimits[generatorTypes.delayModEnv] = { min: -32768, max: 5000, def: -32768 }; // -32,768 indicates instant phase,
|
|
107
|
+
// this is done to prevent click at the start of filter modenv
|
|
107
108
|
generatorLimits[generatorTypes.attackModEnv] = { min: -32768, max: 8000, def: -32768 };
|
|
108
109
|
generatorLimits[generatorTypes.holdModEnv] = { min: -12000, max: 5000, def: -12000 };
|
|
109
110
|
generatorLimits[generatorTypes.decayModEnv] = { min: -12000, max: 8000, def: -12000 };
|
|
110
111
|
generatorLimits[generatorTypes.sustainModEnv] = { min: 0, max: 1000, def: 0 };
|
|
111
112
|
generatorLimits[generatorTypes.releaseModEnv] = { min: -7200, max: 8000, def: -12000 }; // min is set to -7200 to prevent lowpass clicks
|
|
112
|
-
//
|
|
113
|
+
// key num to mod env
|
|
113
114
|
generatorLimits[generatorTypes.keyNumToModEnvHold] = { min: -1200, max: 1200, def: 0 };
|
|
114
115
|
generatorLimits[generatorTypes.keyNumToModEnvDecay] = { min: -1200, max: 1200, def: 0 };
|
|
115
116
|
|
|
@@ -119,8 +120,8 @@ generatorLimits[generatorTypes.attackVolEnv] = { min: -12000, max: 8000, def: -1
|
|
|
119
120
|
generatorLimits[generatorTypes.holdVolEnv] = { min: -12000, max: 5000, def: -12000 };
|
|
120
121
|
generatorLimits[generatorTypes.decayVolEnv] = { min: -12000, max: 8000, def: -12000 };
|
|
121
122
|
generatorLimits[generatorTypes.sustainVolEnv] = { min: 0, max: 1440, def: 0 };
|
|
122
|
-
generatorLimits[generatorTypes.releaseVolEnv] = { min: -7200, max: 8000, def: -12000 }; // min is set to -7200
|
|
123
|
-
//
|
|
123
|
+
generatorLimits[generatorTypes.releaseVolEnv] = { min: -7200, max: 8000, def: -12000 }; // min is set to -7200 prevent clicks
|
|
124
|
+
// key num to vol env
|
|
124
125
|
generatorLimits[generatorTypes.keyNumToVolEnvHold] = { min: -1200, max: 1200, def: 0 };
|
|
125
126
|
generatorLimits[generatorTypes.keyNumToVolEnvDecay] = { min: -1200, max: 1200, def: 0 };
|
|
126
127
|
|
|
@@ -128,7 +129,7 @@ generatorLimits[generatorTypes.startloopAddrsCoarseOffset] = { min: -32768, max:
|
|
|
128
129
|
generatorLimits[generatorTypes.keyNum] = { min: -1, max: 127, def: -1 };
|
|
129
130
|
generatorLimits[generatorTypes.velocity] = { min: -1, max: 127, def: -1 };
|
|
130
131
|
|
|
131
|
-
generatorLimits[generatorTypes.initialAttenuation] = { min: -250, max: 1440, def: 0 }; //
|
|
132
|
+
generatorLimits[generatorTypes.initialAttenuation] = { min: -250, max: 1440, def: 0 }; // sound blaster allows 10dB of gain (divide by 0.4)
|
|
132
133
|
|
|
133
134
|
generatorLimits[generatorTypes.endloopAddrsCoarseOffset] = { min: -32768, max: 32768, def: 0 };
|
|
134
135
|
|
|
@@ -180,7 +181,8 @@ export class Generator
|
|
|
180
181
|
|
|
181
182
|
/**
|
|
182
183
|
* generator.js
|
|
183
|
-
* purpose: contains enums for generators
|
|
184
|
+
* purpose: contains enums for generators,
|
|
185
|
+
* and their limis parses reads soundfont generators, sums them and applies limits
|
|
184
186
|
*/
|
|
185
187
|
/**
|
|
186
188
|
* @param generatorType {number}
|
|
@@ -206,7 +208,7 @@ export function addAndClampGenerator(generatorType, presetGens, instrumentGens)
|
|
|
206
208
|
|
|
207
209
|
let value = instruValue + presetValue;
|
|
208
210
|
|
|
209
|
-
//
|
|
211
|
+
// Special case, initial attenuation.
|
|
210
212
|
// Shall get clamped in the volume envelope,
|
|
211
213
|
// so the modulators can be affected by negative generators (the "Brass" patch was problematic...)
|
|
212
214
|
if (generatorType === generatorTypes.initialAttenuation)
|
|
@@ -74,7 +74,7 @@ export function writeRIFFChunk(chunk, prepend = undefined)
|
|
|
74
74
|
size += prepend.length;
|
|
75
75
|
}
|
|
76
76
|
const array = new IndexedByteArray(size);
|
|
77
|
-
// prepend data (for example type before the read)
|
|
77
|
+
// prepend data (for example, type before the read)
|
|
78
78
|
if (prepend)
|
|
79
79
|
{
|
|
80
80
|
array.set(prepend, array.currentIndex);
|
|
@@ -82,7 +82,7 @@ export function writeRIFFChunk(chunk, prepend = undefined)
|
|
|
82
82
|
}
|
|
83
83
|
// write header
|
|
84
84
|
writeStringAsBytes(array, chunk.header);
|
|
85
|
-
// write size (excluding header and the size itself) and
|
|
85
|
+
// write size (excluding header and the size itself) and then prepend if specified
|
|
86
86
|
writeDword(array, size - 8 - (prepend?.length || 0));
|
|
87
87
|
// write data
|
|
88
88
|
array.set(chunk.chunkData, array.currentIndex);
|
|
@@ -66,7 +66,7 @@ export function writeArticulator(zone)
|
|
|
66
66
|
|
|
67
67
|
// read_articulation.js:
|
|
68
68
|
// according to viena and another strange (with modulators) rendition of gm.dls in sf2,
|
|
69
|
-
// it shall be divided by -128
|
|
69
|
+
// it shall be divided by -128,
|
|
70
70
|
// and a strange correction needs to be applied to the real value:
|
|
71
71
|
// real + (60 / 128) * scale
|
|
72
72
|
// we invert this here
|