spessasynth_core 1.1.3 → 1.1.5
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/LICENSE +3 -26
- package/README.md +156 -474
- package/index.js +74 -8
- package/package.json +21 -8
- package/src/externals/fflate/LICENSE +21 -0
- package/src/externals/fflate/fflate.min.js +1 -0
- package/src/externals/stbvorbis_sync/@types/stbvorbis_sync.d.ts +12 -0
- package/src/externals/stbvorbis_sync/LICENSE +202 -0
- package/src/externals/stbvorbis_sync/NOTICE +6 -0
- package/src/externals/stbvorbis_sync/stbvorbis_sync.min.js +1 -0
- package/src/midi/README.md +32 -0
- package/src/midi/basic_midi.js +567 -0
- package/src/midi/midi_builder.js +202 -0
- package/src/midi/midi_loader.js +324 -0
- package/{spessasynth_core/midi_parser → src/midi}/midi_message.js +58 -35
- package/src/midi/midi_sequence.js +224 -0
- package/src/midi/midi_tools/get_note_times.js +154 -0
- package/src/midi/midi_tools/midi_editor.js +611 -0
- package/src/midi/midi_tools/midi_writer.js +99 -0
- package/src/midi/midi_tools/rmidi_writer.js +567 -0
- package/src/midi/midi_tools/used_keys_loaded.js +238 -0
- package/src/midi/xmf_loader.js +454 -0
- package/src/sequencer/README.md +5 -0
- package/src/sequencer/events.js +81 -0
- package/src/sequencer/play.js +349 -0
- package/src/sequencer/process_event.js +165 -0
- package/{spessasynth_core/sequencer/worklet_sequencer → src/sequencer}/process_tick.js +103 -84
- package/src/sequencer/sequencer_engine.js +367 -0
- package/src/sequencer/song_control.js +201 -0
- package/src/soundfont/README.md +13 -0
- package/src/soundfont/basic_soundfont/basic_instrument.js +77 -0
- package/src/soundfont/basic_soundfont/basic_preset.js +336 -0
- package/src/soundfont/basic_soundfont/basic_sample.js +206 -0
- package/src/soundfont/basic_soundfont/basic_soundfont.js +565 -0
- package/src/soundfont/basic_soundfont/basic_zone.js +64 -0
- package/src/soundfont/basic_soundfont/basic_zones.js +43 -0
- package/src/soundfont/basic_soundfont/generator.js +220 -0
- package/src/soundfont/basic_soundfont/modulator.js +378 -0
- package/src/soundfont/basic_soundfont/riff_chunk.js +149 -0
- package/src/soundfont/basic_soundfont/write_dls/art2.js +173 -0
- package/src/soundfont/basic_soundfont/write_dls/articulator.js +49 -0
- package/src/soundfont/basic_soundfont/write_dls/combine_zones.js +400 -0
- package/src/soundfont/basic_soundfont/write_dls/ins.js +103 -0
- package/src/soundfont/basic_soundfont/write_dls/lins.js +18 -0
- package/src/soundfont/basic_soundfont/write_dls/modulator_converter.js +330 -0
- package/src/soundfont/basic_soundfont/write_dls/rgn2.js +121 -0
- package/src/soundfont/basic_soundfont/write_dls/wave.js +94 -0
- package/src/soundfont/basic_soundfont/write_dls/write_dls.js +119 -0
- package/src/soundfont/basic_soundfont/write_dls/wsmp.js +78 -0
- package/src/soundfont/basic_soundfont/write_dls/wvpl.js +32 -0
- package/src/soundfont/basic_soundfont/write_sf2/ibag.js +39 -0
- package/src/soundfont/basic_soundfont/write_sf2/igen.js +80 -0
- package/src/soundfont/basic_soundfont/write_sf2/imod.js +46 -0
- package/src/soundfont/basic_soundfont/write_sf2/inst.js +34 -0
- package/src/soundfont/basic_soundfont/write_sf2/pbag.js +39 -0
- package/src/soundfont/basic_soundfont/write_sf2/pgen.js +82 -0
- package/src/soundfont/basic_soundfont/write_sf2/phdr.js +42 -0
- package/src/soundfont/basic_soundfont/write_sf2/pmod.js +46 -0
- package/src/soundfont/basic_soundfont/write_sf2/sdta.js +80 -0
- package/src/soundfont/basic_soundfont/write_sf2/shdr.js +55 -0
- package/src/soundfont/basic_soundfont/write_sf2/write.js +222 -0
- package/src/soundfont/dls/articulator_converter.js +396 -0
- package/src/soundfont/dls/dls_destinations.js +38 -0
- package/src/soundfont/dls/dls_preset.js +44 -0
- package/src/soundfont/dls/dls_sample.js +75 -0
- package/src/soundfont/dls/dls_soundfont.js +186 -0
- package/src/soundfont/dls/dls_sources.js +62 -0
- package/src/soundfont/dls/dls_zone.js +95 -0
- package/src/soundfont/dls/read_articulation.js +299 -0
- package/src/soundfont/dls/read_instrument.js +121 -0
- package/src/soundfont/dls/read_instrument_list.js +17 -0
- package/src/soundfont/dls/read_lart.js +35 -0
- package/src/soundfont/dls/read_region.js +152 -0
- package/src/soundfont/dls/read_samples.js +270 -0
- package/src/soundfont/load_soundfont.js +21 -0
- package/src/soundfont/read_sf2/generators.js +46 -0
- package/{spessasynth_core/soundfont/chunk → src/soundfont/read_sf2}/instruments.js +20 -14
- package/src/soundfont/read_sf2/modulators.js +36 -0
- package/src/soundfont/read_sf2/presets.js +80 -0
- package/src/soundfont/read_sf2/samples.js +304 -0
- package/src/soundfont/read_sf2/soundfont.js +305 -0
- package/{spessasynth_core/soundfont/chunk → src/soundfont/read_sf2}/zones.js +68 -69
- package/src/synthetizer/README.md +7 -0
- package/src/synthetizer/audio_engine/README.md +9 -0
- package/src/synthetizer/audio_engine/engine_components/compute_modulator.js +266 -0
- package/src/synthetizer/audio_engine/engine_components/controller_tables.js +88 -0
- package/src/synthetizer/audio_engine/engine_components/key_modifier_manager.js +150 -0
- package/{spessasynth_core/synthetizer/worklet_system/worklet_utilities → src/synthetizer/audio_engine/engine_components}/lfo.js +9 -6
- package/src/synthetizer/audio_engine/engine_components/lowpass_filter.js +282 -0
- package/src/synthetizer/audio_engine/engine_components/midi_audio_channel.js +467 -0
- package/src/synthetizer/audio_engine/engine_components/modulation_envelope.js +181 -0
- package/{spessasynth_core/synthetizer/worklet_system/worklet_utilities → src/synthetizer/audio_engine/engine_components}/modulator_curves.js +33 -30
- package/src/synthetizer/audio_engine/engine_components/soundfont_manager.js +221 -0
- package/src/synthetizer/audio_engine/engine_components/stereo_panner.js +120 -0
- package/{spessasynth_core/synthetizer/worklet_system/worklet_utilities → src/synthetizer/audio_engine/engine_components}/unit_converter.js +11 -4
- package/src/synthetizer/audio_engine/engine_components/voice.js +519 -0
- package/src/synthetizer/audio_engine/engine_components/volume_envelope.js +401 -0
- package/src/synthetizer/audio_engine/engine_components/wavetable_oscillator.js +263 -0
- package/src/synthetizer/audio_engine/engine_methods/controller_control/controller_change.js +132 -0
- package/src/synthetizer/audio_engine/engine_methods/controller_control/master_parameters.js +48 -0
- package/src/synthetizer/audio_engine/engine_methods/controller_control/reset_controllers.js +241 -0
- package/src/synthetizer/audio_engine/engine_methods/create_midi_channel.js +27 -0
- package/src/synthetizer/audio_engine/engine_methods/data_entry/data_entry_coarse.js +253 -0
- package/src/synthetizer/audio_engine/engine_methods/data_entry/data_entry_fine.js +66 -0
- package/src/synthetizer/audio_engine/engine_methods/mute_channel.js +17 -0
- package/src/synthetizer/audio_engine/engine_methods/note_on.js +175 -0
- package/src/synthetizer/audio_engine/engine_methods/portamento_time.js +92 -0
- package/src/synthetizer/audio_engine/engine_methods/program_change.js +61 -0
- package/src/synthetizer/audio_engine/engine_methods/render_voice.js +196 -0
- package/src/synthetizer/audio_engine/engine_methods/soundfont_management/clear_sound_font.js +30 -0
- package/src/synthetizer/audio_engine/engine_methods/soundfont_management/get_preset.js +22 -0
- package/src/synthetizer/audio_engine/engine_methods/soundfont_management/reload_sound_font.js +28 -0
- package/src/synthetizer/audio_engine/engine_methods/soundfont_management/send_preset_list.js +31 -0
- package/src/synthetizer/audio_engine/engine_methods/soundfont_management/set_embedded_sound_font.js +21 -0
- package/src/synthetizer/audio_engine/engine_methods/stopping_notes/kill_note.js +20 -0
- package/src/synthetizer/audio_engine/engine_methods/stopping_notes/note_off.js +55 -0
- package/src/synthetizer/audio_engine/engine_methods/stopping_notes/stop_all_channels.js +16 -0
- package/src/synthetizer/audio_engine/engine_methods/stopping_notes/stop_all_notes.js +30 -0
- package/src/synthetizer/audio_engine/engine_methods/stopping_notes/voice_killing.js +63 -0
- package/src/synthetizer/audio_engine/engine_methods/system_exclusive.js +776 -0
- package/src/synthetizer/audio_engine/engine_methods/tuning_control/channel_pressure.js +24 -0
- package/src/synthetizer/audio_engine/engine_methods/tuning_control/pitch_wheel.js +33 -0
- package/src/synthetizer/audio_engine/engine_methods/tuning_control/poly_pressure.js +31 -0
- package/src/synthetizer/audio_engine/engine_methods/tuning_control/set_master_tuning.js +15 -0
- package/src/synthetizer/audio_engine/engine_methods/tuning_control/set_modulation_depth.js +27 -0
- package/src/synthetizer/audio_engine/engine_methods/tuning_control/set_octave_tuning.js +19 -0
- package/src/synthetizer/audio_engine/engine_methods/tuning_control/set_tuning.js +27 -0
- package/src/synthetizer/audio_engine/engine_methods/tuning_control/transpose_all_channels.js +15 -0
- package/src/synthetizer/audio_engine/engine_methods/tuning_control/transpose_channel.js +34 -0
- package/src/synthetizer/audio_engine/main_processor.js +804 -0
- package/src/synthetizer/audio_engine/snapshot/apply_synthesizer_snapshot.js +15 -0
- package/src/synthetizer/audio_engine/snapshot/channel_snapshot.js +175 -0
- package/src/synthetizer/audio_engine/snapshot/synthesizer_snapshot.js +116 -0
- package/src/synthetizer/synth_constants.js +22 -0
- package/{spessasynth_core → src}/utils/README.md +1 -0
- package/src/utils/buffer_to_wav.js +185 -0
- package/src/utils/byte_functions/big_endian.js +32 -0
- package/src/utils/byte_functions/little_endian.js +77 -0
- package/src/utils/byte_functions/string.js +107 -0
- package/src/utils/byte_functions/variable_length_quantity.js +42 -0
- package/src/utils/fill_with_defaults.js +21 -0
- package/src/utils/indexed_array.js +52 -0
- package/{spessasynth_core → src}/utils/loggin.js +70 -78
- package/src/utils/other.js +92 -0
- package/src/utils/sysex_detector.js +58 -0
- package/src/utils/xg_hacks.js +193 -0
- package/.idea/inspectionProfiles/Project_Default.xml +0 -10
- package/.idea/jsLibraryMappings.xml +0 -6
- package/.idea/modules.xml +0 -8
- package/.idea/spessasynth_core.iml +0 -12
- package/.idea/vcs.xml +0 -6
- package/spessasynth_core/midi_parser/README.md +0 -3
- package/spessasynth_core/midi_parser/midi_loader.js +0 -386
- package/spessasynth_core/sequencer/sequencer.js +0 -202
- package/spessasynth_core/sequencer/worklet_sequencer/play.js +0 -209
- package/spessasynth_core/sequencer/worklet_sequencer/process_event.js +0 -120
- package/spessasynth_core/sequencer/worklet_sequencer/song_control.js +0 -112
- package/spessasynth_core/soundfont/README.md +0 -4
- package/spessasynth_core/soundfont/chunk/generators.js +0 -205
- package/spessasynth_core/soundfont/chunk/modulators.js +0 -232
- package/spessasynth_core/soundfont/chunk/presets.js +0 -264
- package/spessasynth_core/soundfont/chunk/riff_chunk.js +0 -46
- package/spessasynth_core/soundfont/chunk/samples.js +0 -250
- package/spessasynth_core/soundfont/soundfont_parser.js +0 -301
- package/spessasynth_core/synthetizer/README.md +0 -6
- package/spessasynth_core/synthetizer/synthesizer.js +0 -313
- package/spessasynth_core/synthetizer/worklet_system/README.md +0 -3
- package/spessasynth_core/synthetizer/worklet_system/worklet_methods/controller_control.js +0 -290
- package/spessasynth_core/synthetizer/worklet_system/worklet_methods/data_entry.js +0 -280
- package/spessasynth_core/synthetizer/worklet_system/worklet_methods/note_off.js +0 -102
- package/spessasynth_core/synthetizer/worklet_system/worklet_methods/note_on.js +0 -77
- package/spessasynth_core/synthetizer/worklet_system/worklet_methods/program_control.js +0 -140
- package/spessasynth_core/synthetizer/worklet_system/worklet_methods/system_exclusive.js +0 -266
- package/spessasynth_core/synthetizer/worklet_system/worklet_methods/tuning_control.js +0 -104
- package/spessasynth_core/synthetizer/worklet_system/worklet_methods/vibrato_control.js +0 -29
- package/spessasynth_core/synthetizer/worklet_system/worklet_methods/voice_control.js +0 -223
- package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/lowpass_filter.js +0 -133
- package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/modulation_envelope.js +0 -73
- package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/stereo_panner.js +0 -76
- package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/volume_envelope.js +0 -272
- package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/wavetable_oscillator.js +0 -83
- package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/worklet_modulator.js +0 -175
- package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/worklet_processor_channel.js +0 -106
- package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/worklet_voice.js +0 -285
- package/spessasynth_core/utils/buffer_to_wav.js +0 -70
- package/spessasynth_core/utils/byte_functions.js +0 -141
- package/spessasynth_core/utils/other.js +0 -49
- package/spessasynth_core/utils/shiftable_array.js +0 -26
- package/spessasynth_core/utils/stbvorbis_sync.js +0 -1877
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import { RiffChunk } from "../basic_soundfont/riff_chunk.js";
|
|
2
|
+
import { IndexedByteArray } from "../../utils/indexed_array.js";
|
|
3
|
+
import { readLittleEndian, signedInt8 } from "../../utils/byte_functions/little_endian.js";
|
|
4
|
+
import { stbvorbis } from "../../externals/stbvorbis_sync/stbvorbis_sync.min.js";
|
|
5
|
+
import { SpessaSynthWarn } from "../../utils/loggin.js";
|
|
6
|
+
import { readBytesAsString } from "../../utils/byte_functions/string.js";
|
|
7
|
+
import { BasicSample } from "../basic_soundfont/basic_sample.js";
|
|
8
|
+
|
|
9
|
+
export class SoundFontSample extends BasicSample
|
|
10
|
+
{
|
|
11
|
+
/**
|
|
12
|
+
* Creates a sample
|
|
13
|
+
* @param sampleName {string}
|
|
14
|
+
* @param sampleStartIndex {number}
|
|
15
|
+
* @param sampleEndIndex {number}
|
|
16
|
+
* @param sampleLoopStartIndex {number}
|
|
17
|
+
* @param sampleLoopEndIndex {number}
|
|
18
|
+
* @param sampleRate {number}
|
|
19
|
+
* @param samplePitch {number}
|
|
20
|
+
* @param samplePitchCorrection {number}
|
|
21
|
+
* @param sampleLink {number}
|
|
22
|
+
* @param sampleType {number}
|
|
23
|
+
* @param smplArr {IndexedByteArray|Float32Array}
|
|
24
|
+
* @param sampleIndex {number} initial sample index when loading the sfont
|
|
25
|
+
* @param isDataRaw {boolean} if false, the data is decoded as float32.
|
|
26
|
+
* Used for SF2Pack support
|
|
27
|
+
*/
|
|
28
|
+
constructor(
|
|
29
|
+
sampleName,
|
|
30
|
+
sampleStartIndex,
|
|
31
|
+
sampleEndIndex,
|
|
32
|
+
sampleLoopStartIndex,
|
|
33
|
+
sampleLoopEndIndex,
|
|
34
|
+
sampleRate,
|
|
35
|
+
samplePitch,
|
|
36
|
+
samplePitchCorrection,
|
|
37
|
+
sampleLink,
|
|
38
|
+
sampleType,
|
|
39
|
+
smplArr,
|
|
40
|
+
sampleIndex,
|
|
41
|
+
isDataRaw
|
|
42
|
+
)
|
|
43
|
+
{
|
|
44
|
+
super(
|
|
45
|
+
sampleName,
|
|
46
|
+
sampleRate,
|
|
47
|
+
samplePitch,
|
|
48
|
+
samplePitchCorrection,
|
|
49
|
+
sampleLink,
|
|
50
|
+
sampleType,
|
|
51
|
+
sampleLoopStartIndex - (sampleStartIndex / 2),
|
|
52
|
+
sampleLoopEndIndex - (sampleStartIndex / 2)
|
|
53
|
+
);
|
|
54
|
+
this.sampleName = sampleName;
|
|
55
|
+
// in bytes
|
|
56
|
+
this.sampleStartIndex = sampleStartIndex;
|
|
57
|
+
this.sampleEndIndex = sampleEndIndex;
|
|
58
|
+
this.isSampleLoaded = false;
|
|
59
|
+
this.sampleID = sampleIndex;
|
|
60
|
+
// in bytes
|
|
61
|
+
this.sampleLength = this.sampleEndIndex - this.sampleStartIndex;
|
|
62
|
+
this.sampleDataArray = smplArr;
|
|
63
|
+
this.sampleData = new Float32Array(0);
|
|
64
|
+
if (this.isCompressed)
|
|
65
|
+
{
|
|
66
|
+
// correct loop points
|
|
67
|
+
this.sampleLoopStartIndex += this.sampleStartIndex / 2;
|
|
68
|
+
this.sampleLoopEndIndex += this.sampleStartIndex / 2;
|
|
69
|
+
this.sampleLength = 99999999; // set to 999,999 before we decode it
|
|
70
|
+
}
|
|
71
|
+
this.isDataRaw = isDataRaw;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Get raw data, whether it's compressed or not as we simply write it to the file
|
|
76
|
+
* @return {Uint8Array} either s16 or vorbis data
|
|
77
|
+
*/
|
|
78
|
+
getRawData()
|
|
79
|
+
{
|
|
80
|
+
const smplArr = this.sampleDataArray;
|
|
81
|
+
if (this.isCompressed)
|
|
82
|
+
{
|
|
83
|
+
if (this.compressedData)
|
|
84
|
+
{
|
|
85
|
+
return this.compressedData;
|
|
86
|
+
}
|
|
87
|
+
const smplStart = smplArr.currentIndex;
|
|
88
|
+
return smplArr.slice(this.sampleStartIndex / 2 + smplStart, this.sampleEndIndex / 2 + smplStart);
|
|
89
|
+
}
|
|
90
|
+
else
|
|
91
|
+
{
|
|
92
|
+
if (!this.isDataRaw)
|
|
93
|
+
{
|
|
94
|
+
// encode the f32 into s16 manually
|
|
95
|
+
super.getRawData();
|
|
96
|
+
}
|
|
97
|
+
const dataStartIndex = smplArr.currentIndex;
|
|
98
|
+
return smplArr.slice(dataStartIndex + this.sampleStartIndex, dataStartIndex + this.sampleEndIndex);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Decode binary vorbis into a float32 pcm
|
|
104
|
+
*/
|
|
105
|
+
decodeVorbis()
|
|
106
|
+
{
|
|
107
|
+
if (this.sampleLength < 1)
|
|
108
|
+
{
|
|
109
|
+
// eos, do not do anything
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
// get the compressed byte stream
|
|
113
|
+
const smplArr = this.sampleDataArray;
|
|
114
|
+
const smplStart = smplArr.currentIndex;
|
|
115
|
+
const buff = smplArr.slice(this.sampleStartIndex / 2 + smplStart, this.sampleEndIndex / 2 + smplStart);
|
|
116
|
+
// reset array and being decoding
|
|
117
|
+
this.sampleData = new Float32Array(0);
|
|
118
|
+
try
|
|
119
|
+
{
|
|
120
|
+
/**
|
|
121
|
+
* @type {{data: Float32Array[], error: (string|null), sampleRate: number, eof: boolean}}
|
|
122
|
+
*/
|
|
123
|
+
const vorbis = stbvorbis.decode(buff.buffer);
|
|
124
|
+
this.sampleData = vorbis.data[0];
|
|
125
|
+
if (this.sampleData === undefined)
|
|
126
|
+
{
|
|
127
|
+
SpessaSynthWarn(`Error decoding sample ${this.sampleName}: Vorbis decode returned undefined.`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
catch (e)
|
|
131
|
+
{
|
|
132
|
+
// do not error out, fill with silence
|
|
133
|
+
SpessaSynthWarn(`Error decoding sample ${this.sampleName}: ${e}`);
|
|
134
|
+
this.sampleData = new Float32Array(this.sampleLoopEndIndex + 1);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Loads the audio data and stores it for reuse
|
|
140
|
+
* @returns {Float32Array} The audioData
|
|
141
|
+
*/
|
|
142
|
+
getAudioData()
|
|
143
|
+
{
|
|
144
|
+
if (!this.isSampleLoaded)
|
|
145
|
+
{
|
|
146
|
+
// start loading data if it is not loaded
|
|
147
|
+
if (this.sampleLength < 1)
|
|
148
|
+
{
|
|
149
|
+
SpessaSynthWarn(`Invalid sample ${this.sampleName}! Invalid length: ${this.sampleLength}`);
|
|
150
|
+
return new Float32Array(1);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (this.isCompressed)
|
|
154
|
+
{
|
|
155
|
+
// if compressed, decode
|
|
156
|
+
this.decodeVorbis();
|
|
157
|
+
this.isSampleLoaded = true;
|
|
158
|
+
return this.sampleData;
|
|
159
|
+
}
|
|
160
|
+
else if (!this.isDataRaw)
|
|
161
|
+
{
|
|
162
|
+
return this.getUncompressedReadyData();
|
|
163
|
+
}
|
|
164
|
+
return this.loadUncompressedData();
|
|
165
|
+
}
|
|
166
|
+
return this.sampleData;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* @returns {Float32Array}
|
|
171
|
+
*/
|
|
172
|
+
loadUncompressedData()
|
|
173
|
+
{
|
|
174
|
+
if (this.isCompressed)
|
|
175
|
+
{
|
|
176
|
+
SpessaSynthWarn("Trying to load a compressed sample via loadUncompressedData()... aborting!");
|
|
177
|
+
return new Float32Array(0);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// read the sample data
|
|
181
|
+
let audioData = new Float32Array(this.sampleLength / 2);
|
|
182
|
+
const dataStartIndex = this.sampleDataArray.currentIndex;
|
|
183
|
+
let convertedSigned16 = new Int16Array(
|
|
184
|
+
this.sampleDataArray.slice(dataStartIndex + this.sampleStartIndex, dataStartIndex + this.sampleEndIndex)
|
|
185
|
+
.buffer
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
// convert to float
|
|
189
|
+
for (let i = 0; i < convertedSigned16.length; i++)
|
|
190
|
+
{
|
|
191
|
+
audioData[i] = convertedSigned16[i] / 32768;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
this.sampleData = audioData;
|
|
195
|
+
this.isSampleLoaded = true;
|
|
196
|
+
return audioData;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* @returns {Float32Array}
|
|
201
|
+
*/
|
|
202
|
+
getUncompressedReadyData()
|
|
203
|
+
{
|
|
204
|
+
/**
|
|
205
|
+
* read the sample data
|
|
206
|
+
* @type {Float32Array}
|
|
207
|
+
*/
|
|
208
|
+
let audioData = this.sampleDataArray.slice(this.sampleStartIndex / 2, this.sampleEndIndex / 2);
|
|
209
|
+
this.sampleData = audioData;
|
|
210
|
+
this.isSampleLoaded = true;
|
|
211
|
+
return audioData;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Reads the generatorTranslator from the shdr read
|
|
217
|
+
* @param sampleHeadersChunk {RiffChunk}
|
|
218
|
+
* @param smplChunkData {IndexedByteArray|Float32Array}
|
|
219
|
+
* @param isSmplDataRaw {boolean}
|
|
220
|
+
* @returns {SoundFontSample[]}
|
|
221
|
+
*/
|
|
222
|
+
export function readSamples(sampleHeadersChunk, smplChunkData, isSmplDataRaw = true)
|
|
223
|
+
{
|
|
224
|
+
/**
|
|
225
|
+
* @type {SoundFontSample[]}
|
|
226
|
+
*/
|
|
227
|
+
let samples = [];
|
|
228
|
+
let index = 0;
|
|
229
|
+
while (sampleHeadersChunk.chunkData.length > sampleHeadersChunk.chunkData.currentIndex)
|
|
230
|
+
{
|
|
231
|
+
const sample = readSample(index, sampleHeadersChunk.chunkData, smplChunkData, isSmplDataRaw);
|
|
232
|
+
samples.push(sample);
|
|
233
|
+
index++;
|
|
234
|
+
}
|
|
235
|
+
// remove EOS
|
|
236
|
+
if (samples.length > 1)
|
|
237
|
+
{
|
|
238
|
+
samples.pop();
|
|
239
|
+
}
|
|
240
|
+
return samples;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Reads it into a sample
|
|
245
|
+
* @param index {number}
|
|
246
|
+
* @param sampleHeaderData {IndexedByteArray}
|
|
247
|
+
* @param smplArrayData {IndexedByteArray|Float32Array}
|
|
248
|
+
* @param isDataRaw {boolean} true means binary 16-bit data, false means float32
|
|
249
|
+
* @returns {SoundFontSample}
|
|
250
|
+
*/
|
|
251
|
+
function readSample(index, sampleHeaderData, smplArrayData, isDataRaw)
|
|
252
|
+
{
|
|
253
|
+
|
|
254
|
+
// read the sample name
|
|
255
|
+
let sampleName = readBytesAsString(sampleHeaderData, 20);
|
|
256
|
+
|
|
257
|
+
// read the sample start index
|
|
258
|
+
let sampleStartIndex = readLittleEndian(sampleHeaderData, 4) * 2;
|
|
259
|
+
|
|
260
|
+
// read the sample end index
|
|
261
|
+
let sampleEndIndex = readLittleEndian(sampleHeaderData, 4) * 2;
|
|
262
|
+
|
|
263
|
+
// read the sample looping start index
|
|
264
|
+
let sampleLoopStartIndex = readLittleEndian(sampleHeaderData, 4);
|
|
265
|
+
|
|
266
|
+
// read the sample looping end index
|
|
267
|
+
let sampleLoopEndIndex = readLittleEndian(sampleHeaderData, 4);
|
|
268
|
+
|
|
269
|
+
// read the sample rate
|
|
270
|
+
let sampleRate = readLittleEndian(sampleHeaderData, 4);
|
|
271
|
+
|
|
272
|
+
// read the original sample pitch
|
|
273
|
+
let samplePitch = sampleHeaderData[sampleHeaderData.currentIndex++];
|
|
274
|
+
if (samplePitch === 255)
|
|
275
|
+
{
|
|
276
|
+
// if it's 255, then default to 60
|
|
277
|
+
samplePitch = 60;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// read the sample pitch correction
|
|
281
|
+
let samplePitchCorrection = signedInt8(sampleHeaderData[sampleHeaderData.currentIndex++]);
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
// read the link to the other channel
|
|
285
|
+
let sampleLink = readLittleEndian(sampleHeaderData, 2);
|
|
286
|
+
let sampleType = readLittleEndian(sampleHeaderData, 2);
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
return new SoundFontSample(
|
|
290
|
+
sampleName,
|
|
291
|
+
sampleStartIndex,
|
|
292
|
+
sampleEndIndex,
|
|
293
|
+
sampleLoopStartIndex,
|
|
294
|
+
sampleLoopEndIndex,
|
|
295
|
+
sampleRate,
|
|
296
|
+
samplePitch,
|
|
297
|
+
samplePitchCorrection,
|
|
298
|
+
sampleLink,
|
|
299
|
+
sampleType,
|
|
300
|
+
smplArrayData,
|
|
301
|
+
index,
|
|
302
|
+
isDataRaw
|
|
303
|
+
);
|
|
304
|
+
}
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
import { IndexedByteArray } from "../../utils/indexed_array.js";
|
|
2
|
+
import { readSamples } from "./samples.js";
|
|
3
|
+
import { readLittleEndian } from "../../utils/byte_functions/little_endian.js";
|
|
4
|
+
import { readGenerators } from "./generators.js";
|
|
5
|
+
import { InstrumentZone, readInstrumentZones, readPresetZones } from "./zones.js";
|
|
6
|
+
import { readPresets } from "./presets.js";
|
|
7
|
+
import { readInstruments } from "./instruments.js";
|
|
8
|
+
import { readModulators } from "./modulators.js";
|
|
9
|
+
import { readRIFFChunk, RiffChunk } from "../basic_soundfont/riff_chunk.js";
|
|
10
|
+
import { consoleColors } from "../../utils/other.js";
|
|
11
|
+
import { SpessaSynthGroup, SpessaSynthGroupEnd, SpessaSynthInfo } from "../../utils/loggin.js";
|
|
12
|
+
import { readBytesAsString } from "../../utils/byte_functions/string.js";
|
|
13
|
+
import { stbvorbis } from "../../externals/stbvorbis_sync/stbvorbis_sync.min.js";
|
|
14
|
+
import { BasicSoundBank } from "../basic_soundfont/basic_soundfont.js";
|
|
15
|
+
import { Generator } from "../basic_soundfont/generator.js";
|
|
16
|
+
import { Modulator } from "../basic_soundfont/modulator.js";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* soundfont.js
|
|
20
|
+
* purpose: parses a soundfont2 file
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
export class SoundFont2 extends BasicSoundBank
|
|
24
|
+
{
|
|
25
|
+
/**
|
|
26
|
+
* Initializes a new SoundFont2 Parser and parses the given data array
|
|
27
|
+
* @param arrayBuffer {ArrayBuffer}
|
|
28
|
+
* @param warnDeprecated {boolean}
|
|
29
|
+
*/
|
|
30
|
+
constructor(arrayBuffer, warnDeprecated = true)
|
|
31
|
+
{
|
|
32
|
+
super();
|
|
33
|
+
if (warnDeprecated)
|
|
34
|
+
{
|
|
35
|
+
console.warn("Using the constructor directly is deprecated. Use loadSoundFont instead.");
|
|
36
|
+
}
|
|
37
|
+
this.dataArray = new IndexedByteArray(arrayBuffer);
|
|
38
|
+
SpessaSynthGroup("%cParsing SoundFont...", consoleColors.info);
|
|
39
|
+
if (!this.dataArray)
|
|
40
|
+
{
|
|
41
|
+
SpessaSynthGroupEnd();
|
|
42
|
+
this.parsingError("No data provided!");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// read the main read
|
|
46
|
+
let firstChunk = readRIFFChunk(this.dataArray, false);
|
|
47
|
+
this.verifyHeader(firstChunk, "riff");
|
|
48
|
+
|
|
49
|
+
const type = readBytesAsString(this.dataArray, 4).toLowerCase();
|
|
50
|
+
if (type !== "sfbk" && type !== "sfpk")
|
|
51
|
+
{
|
|
52
|
+
SpessaSynthGroupEnd();
|
|
53
|
+
throw new SyntaxError(`Invalid soundFont! Expected "sfbk" or "sfpk" got "${type}"`);
|
|
54
|
+
}
|
|
55
|
+
/*
|
|
56
|
+
Some SF2Pack description:
|
|
57
|
+
this is essentially sf2, but the entire smpl chunk is compressed (we only support Ogg Vorbis here)
|
|
58
|
+
and the only other difference is that the main chunk isn't "sfbk" but rather "sfpk"
|
|
59
|
+
*/
|
|
60
|
+
const isSF2Pack = type === "sfpk";
|
|
61
|
+
|
|
62
|
+
// INFO
|
|
63
|
+
let infoChunk = readRIFFChunk(this.dataArray);
|
|
64
|
+
this.verifyHeader(infoChunk, "list");
|
|
65
|
+
readBytesAsString(infoChunk.chunkData, 4);
|
|
66
|
+
|
|
67
|
+
while (infoChunk.chunkData.length > infoChunk.chunkData.currentIndex)
|
|
68
|
+
{
|
|
69
|
+
let chunk = readRIFFChunk(infoChunk.chunkData);
|
|
70
|
+
let text;
|
|
71
|
+
// special cases
|
|
72
|
+
switch (chunk.header.toLowerCase())
|
|
73
|
+
{
|
|
74
|
+
case "ifil":
|
|
75
|
+
case "iver":
|
|
76
|
+
text = `${readLittleEndian(chunk.chunkData, 2)}.${readLittleEndian(chunk.chunkData, 2)}`;
|
|
77
|
+
this.soundFontInfo[chunk.header] = text;
|
|
78
|
+
break;
|
|
79
|
+
|
|
80
|
+
case "icmt":
|
|
81
|
+
text = readBytesAsString(chunk.chunkData, chunk.chunkData.length, undefined, false);
|
|
82
|
+
this.soundFontInfo[chunk.header] = text;
|
|
83
|
+
break;
|
|
84
|
+
|
|
85
|
+
// dmod: default modulators
|
|
86
|
+
case "dmod":
|
|
87
|
+
const newModulators = readModulators(chunk);
|
|
88
|
+
newModulators.pop(); // remove the terminal record
|
|
89
|
+
text = `Modulators: ${newModulators.length}`;
|
|
90
|
+
// override default modulators
|
|
91
|
+
const oldDefaults = this.defaultModulators;
|
|
92
|
+
|
|
93
|
+
this.defaultModulators = newModulators;
|
|
94
|
+
this.defaultModulators.push(...oldDefaults.filter(m => !this.defaultModulators.find(mm => Modulator.isIdentical(
|
|
95
|
+
m,
|
|
96
|
+
mm
|
|
97
|
+
))));
|
|
98
|
+
this.soundFontInfo[chunk.header] = chunk.chunkData;
|
|
99
|
+
break;
|
|
100
|
+
|
|
101
|
+
default:
|
|
102
|
+
text = readBytesAsString(chunk.chunkData, chunk.chunkData.length);
|
|
103
|
+
this.soundFontInfo[chunk.header] = text;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
SpessaSynthInfo(
|
|
107
|
+
`%c"${chunk.header}": %c"${text}"`,
|
|
108
|
+
consoleColors.info,
|
|
109
|
+
consoleColors.recognized
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// SDTA
|
|
114
|
+
const sdtaChunk = readRIFFChunk(this.dataArray, false);
|
|
115
|
+
this.verifyHeader(sdtaChunk, "list");
|
|
116
|
+
this.verifyText(readBytesAsString(this.dataArray, 4), "sdta");
|
|
117
|
+
|
|
118
|
+
// smpl
|
|
119
|
+
SpessaSynthInfo("%cVerifying smpl chunk...", consoleColors.warn);
|
|
120
|
+
let sampleDataChunk = readRIFFChunk(this.dataArray, false);
|
|
121
|
+
this.verifyHeader(sampleDataChunk, "smpl");
|
|
122
|
+
/**
|
|
123
|
+
* @type {IndexedByteArray|Float32Array}
|
|
124
|
+
*/
|
|
125
|
+
let sampleData;
|
|
126
|
+
// SF2Pack: the entire data is compressed
|
|
127
|
+
if (isSF2Pack)
|
|
128
|
+
{
|
|
129
|
+
SpessaSynthInfo(
|
|
130
|
+
"%cSF2Pack detected, attempting to decode the smpl chunk...",
|
|
131
|
+
consoleColors.info
|
|
132
|
+
);
|
|
133
|
+
try
|
|
134
|
+
{
|
|
135
|
+
/**
|
|
136
|
+
* @type {Float32Array}
|
|
137
|
+
*/
|
|
138
|
+
sampleData = stbvorbis.decode(this.dataArray.buffer.slice(
|
|
139
|
+
this.dataArray.currentIndex,
|
|
140
|
+
this.dataArray.currentIndex + sdtaChunk.size - 12
|
|
141
|
+
)).data[0];
|
|
142
|
+
}
|
|
143
|
+
catch (e)
|
|
144
|
+
{
|
|
145
|
+
SpessaSynthGroupEnd();
|
|
146
|
+
throw new Error(`SF2Pack Ogg Vorbis decode error: ${e}`);
|
|
147
|
+
}
|
|
148
|
+
SpessaSynthInfo(
|
|
149
|
+
`%cDecoded the smpl chunk! Length: %c${sampleData.length}`,
|
|
150
|
+
consoleColors.info,
|
|
151
|
+
consoleColors.value
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
else
|
|
155
|
+
{
|
|
156
|
+
/**
|
|
157
|
+
* @type {IndexedByteArray}
|
|
158
|
+
*/
|
|
159
|
+
sampleData = this.dataArray;
|
|
160
|
+
this.sampleDataStartIndex = this.dataArray.currentIndex;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
SpessaSynthInfo(
|
|
164
|
+
`%cSkipping sample chunk, length: %c${sdtaChunk.size - 12}`,
|
|
165
|
+
consoleColors.info,
|
|
166
|
+
consoleColors.value
|
|
167
|
+
);
|
|
168
|
+
this.dataArray.currentIndex += sdtaChunk.size - 12;
|
|
169
|
+
|
|
170
|
+
// PDTA
|
|
171
|
+
SpessaSynthInfo("%cLoading preset data chunk...", consoleColors.warn);
|
|
172
|
+
let presetChunk = readRIFFChunk(this.dataArray);
|
|
173
|
+
this.verifyHeader(presetChunk, "list");
|
|
174
|
+
readBytesAsString(presetChunk.chunkData, 4);
|
|
175
|
+
|
|
176
|
+
// read the hydra chunks
|
|
177
|
+
const presetHeadersChunk = readRIFFChunk(presetChunk.chunkData);
|
|
178
|
+
this.verifyHeader(presetHeadersChunk, "phdr");
|
|
179
|
+
|
|
180
|
+
const presetZonesChunk = readRIFFChunk(presetChunk.chunkData);
|
|
181
|
+
this.verifyHeader(presetZonesChunk, "pbag");
|
|
182
|
+
|
|
183
|
+
const presetModulatorsChunk = readRIFFChunk(presetChunk.chunkData);
|
|
184
|
+
this.verifyHeader(presetModulatorsChunk, "pmod");
|
|
185
|
+
|
|
186
|
+
const presetGeneratorsChunk = readRIFFChunk(presetChunk.chunkData);
|
|
187
|
+
this.verifyHeader(presetGeneratorsChunk, "pgen");
|
|
188
|
+
|
|
189
|
+
const presetInstrumentsChunk = readRIFFChunk(presetChunk.chunkData);
|
|
190
|
+
this.verifyHeader(presetInstrumentsChunk, "inst");
|
|
191
|
+
|
|
192
|
+
const presetInstrumentZonesChunk = readRIFFChunk(presetChunk.chunkData);
|
|
193
|
+
this.verifyHeader(presetInstrumentZonesChunk, "ibag");
|
|
194
|
+
|
|
195
|
+
const presetInstrumentModulatorsChunk = readRIFFChunk(presetChunk.chunkData);
|
|
196
|
+
this.verifyHeader(presetInstrumentModulatorsChunk, "imod");
|
|
197
|
+
|
|
198
|
+
const presetInstrumentGeneratorsChunk = readRIFFChunk(presetChunk.chunkData);
|
|
199
|
+
this.verifyHeader(presetInstrumentGeneratorsChunk, "igen");
|
|
200
|
+
|
|
201
|
+
const presetSamplesChunk = readRIFFChunk(presetChunk.chunkData);
|
|
202
|
+
this.verifyHeader(presetSamplesChunk, "shdr");
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* read all the samples
|
|
206
|
+
* (the current index points to start of the smpl read)
|
|
207
|
+
*/
|
|
208
|
+
this.dataArray.currentIndex = this.sampleDataStartIndex;
|
|
209
|
+
this.samples.push(...readSamples(presetSamplesChunk, sampleData, !isSF2Pack));
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* read all the instrument generators
|
|
213
|
+
* @type {Generator[]}
|
|
214
|
+
*/
|
|
215
|
+
let instrumentGenerators = readGenerators(presetInstrumentGeneratorsChunk);
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* read all the instrument modulators
|
|
219
|
+
* @type {Modulator[]}
|
|
220
|
+
*/
|
|
221
|
+
let instrumentModulators = readModulators(presetInstrumentModulatorsChunk);
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* read all the instrument zones
|
|
225
|
+
* @type {InstrumentZone[]}
|
|
226
|
+
*/
|
|
227
|
+
let instrumentZones = readInstrumentZones(
|
|
228
|
+
presetInstrumentZonesChunk,
|
|
229
|
+
instrumentGenerators,
|
|
230
|
+
instrumentModulators,
|
|
231
|
+
this.samples
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
this.instruments = readInstruments(presetInstrumentsChunk, instrumentZones);
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* read all the preset generators
|
|
238
|
+
* @type {Generator[]}
|
|
239
|
+
*/
|
|
240
|
+
let presetGenerators = readGenerators(presetGeneratorsChunk);
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Read all the preset modulatorrs
|
|
244
|
+
* @type {Modulator[]}
|
|
245
|
+
*/
|
|
246
|
+
let presetModulators = readModulators(presetModulatorsChunk);
|
|
247
|
+
|
|
248
|
+
let presetZones = readPresetZones(presetZonesChunk, presetGenerators, presetModulators, this.instruments);
|
|
249
|
+
|
|
250
|
+
this.presets.push(...readPresets(presetHeadersChunk, presetZones, this));
|
|
251
|
+
this.presets.sort((a, b) => (a.program - b.program) + (a.bank - b.bank));
|
|
252
|
+
this._parseInternal();
|
|
253
|
+
SpessaSynthInfo(
|
|
254
|
+
`%cParsing finished! %c"${this.soundFontInfo["INAM"]}"%c has %c${this.presets.length} %cpresets,
|
|
255
|
+
%c${this.instruments.length}%c instruments and %c${this.samples.length}%c samples.`,
|
|
256
|
+
consoleColors.info,
|
|
257
|
+
consoleColors.recognized,
|
|
258
|
+
consoleColors.info,
|
|
259
|
+
consoleColors.recognized,
|
|
260
|
+
consoleColors.info,
|
|
261
|
+
consoleColors.recognized,
|
|
262
|
+
consoleColors.info,
|
|
263
|
+
consoleColors.recognized,
|
|
264
|
+
consoleColors.info
|
|
265
|
+
);
|
|
266
|
+
SpessaSynthGroupEnd();
|
|
267
|
+
|
|
268
|
+
if (isSF2Pack)
|
|
269
|
+
{
|
|
270
|
+
delete this.dataArray;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* @param chunk {RiffChunk}
|
|
276
|
+
* @param expected {string}
|
|
277
|
+
*/
|
|
278
|
+
verifyHeader(chunk, expected)
|
|
279
|
+
{
|
|
280
|
+
if (chunk.header.toLowerCase() !== expected.toLowerCase())
|
|
281
|
+
{
|
|
282
|
+
SpessaSynthGroupEnd();
|
|
283
|
+
this.parsingError(`Invalid chunk header! Expected "${expected.toLowerCase()}" got "${chunk.header.toLowerCase()}"`);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* @param text {string}
|
|
289
|
+
* @param expected {string}
|
|
290
|
+
*/
|
|
291
|
+
verifyText(text, expected)
|
|
292
|
+
{
|
|
293
|
+
if (text.toLowerCase() !== expected.toLowerCase())
|
|
294
|
+
{
|
|
295
|
+
SpessaSynthGroupEnd();
|
|
296
|
+
this.parsingError(`Invalid FourCC: Expected "${expected.toLowerCase()}" got "${text.toLowerCase()}"\``);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
destroySoundBank()
|
|
301
|
+
{
|
|
302
|
+
super.destroySoundBank();
|
|
303
|
+
delete this.dataArray;
|
|
304
|
+
}
|
|
305
|
+
}
|