spessasynth_core 1.1.2 → 1.1.4
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 +73 -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 -303
- 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 -222
- package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/lowpass_filter.js +0 -95
- 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 -194
- 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 -173
- 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 -313
- 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,270 @@
|
|
|
1
|
+
import { findRIFFListType, readRIFFChunk } from "../basic_soundfont/riff_chunk.js";
|
|
2
|
+
import { readBytesAsString } from "../../utils/byte_functions/string.js";
|
|
3
|
+
import {
|
|
4
|
+
SpessaSynthGroupCollapsed,
|
|
5
|
+
SpessaSynthGroupEnd,
|
|
6
|
+
SpessaSynthInfo,
|
|
7
|
+
SpessaSynthWarn
|
|
8
|
+
} from "../../utils/loggin.js";
|
|
9
|
+
import { consoleColors } from "../../utils/other.js";
|
|
10
|
+
import { readLittleEndian, signedInt16 } from "../../utils/byte_functions/little_endian.js";
|
|
11
|
+
import { DLSSample } from "./dls_sample.js";
|
|
12
|
+
|
|
13
|
+
const W_FORMAT_TAG = {
|
|
14
|
+
PCM: 0x01,
|
|
15
|
+
ALAW: 0x6
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @param dataChunk {RiffChunk}
|
|
20
|
+
* @param bytesPerSample {number}
|
|
21
|
+
* @returns {Float32Array}
|
|
22
|
+
*/
|
|
23
|
+
function readPCM(dataChunk, bytesPerSample)
|
|
24
|
+
{
|
|
25
|
+
const maxSampleValue = Math.pow(2, bytesPerSample * 8 - 1); // Max value for the sample
|
|
26
|
+
const maxUnsigned = Math.pow(2, bytesPerSample * 8);
|
|
27
|
+
|
|
28
|
+
let normalizationFactor;
|
|
29
|
+
let isUnsigned = false;
|
|
30
|
+
|
|
31
|
+
if (bytesPerSample === 1)
|
|
32
|
+
{
|
|
33
|
+
normalizationFactor = 255; // For 8-bit normalize from 0-255
|
|
34
|
+
isUnsigned = true;
|
|
35
|
+
}
|
|
36
|
+
else
|
|
37
|
+
{
|
|
38
|
+
normalizationFactor = maxSampleValue; // For 16-bit normalize from -32,768 to 32,767
|
|
39
|
+
}
|
|
40
|
+
const sampleLength = dataChunk.size / bytesPerSample;
|
|
41
|
+
const sampleData = new Float32Array(sampleLength);
|
|
42
|
+
for (let i = 0; i < sampleData.length; i++)
|
|
43
|
+
{
|
|
44
|
+
// read
|
|
45
|
+
let sample = readLittleEndian(dataChunk.chunkData, bytesPerSample);
|
|
46
|
+
// turn into signed
|
|
47
|
+
if (isUnsigned)
|
|
48
|
+
{
|
|
49
|
+
// normalize unsigned 8-bit sample
|
|
50
|
+
sampleData[i] = (sample / normalizationFactor) - 0.5;
|
|
51
|
+
}
|
|
52
|
+
else
|
|
53
|
+
{
|
|
54
|
+
// normalize signed 16-bit sample
|
|
55
|
+
if (sample >= maxSampleValue)
|
|
56
|
+
{
|
|
57
|
+
sample -= maxUnsigned;
|
|
58
|
+
}
|
|
59
|
+
sampleData[i] = sample / normalizationFactor;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return sampleData;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* @param dataChunk {RiffChunk}
|
|
67
|
+
* @param bytesPerSample {number}
|
|
68
|
+
* @returns {Float32Array}
|
|
69
|
+
*/
|
|
70
|
+
function readALAW(dataChunk, bytesPerSample)
|
|
71
|
+
{
|
|
72
|
+
const sampleLength = dataChunk.size / bytesPerSample;
|
|
73
|
+
const sampleData = new Float32Array(sampleLength);
|
|
74
|
+
for (let i = 0; i < sampleData.length; i++)
|
|
75
|
+
{
|
|
76
|
+
// read
|
|
77
|
+
const input = readLittleEndian(dataChunk.chunkData, bytesPerSample);
|
|
78
|
+
|
|
79
|
+
// https://en.wikipedia.org/wiki/G.711#A-law
|
|
80
|
+
// re-toggle toggled bits
|
|
81
|
+
let sample = input ^ 0x55;
|
|
82
|
+
|
|
83
|
+
// remove sign bit
|
|
84
|
+
sample &= 0x7F;
|
|
85
|
+
|
|
86
|
+
// extract exponent
|
|
87
|
+
let exponent = sample >> 4;
|
|
88
|
+
// extract mantissa
|
|
89
|
+
let mantissa = sample & 0xF;
|
|
90
|
+
if (exponent > 0)
|
|
91
|
+
{
|
|
92
|
+
mantissa += 16; // add leading '1', if exponent > 0
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
mantissa = (mantissa << 4) + 0x8;
|
|
96
|
+
if (exponent > 1)
|
|
97
|
+
{
|
|
98
|
+
mantissa = mantissa << (exponent - 1);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const s16sample = input > 127 ? mantissa : -mantissa;
|
|
102
|
+
|
|
103
|
+
// convert to float
|
|
104
|
+
sampleData[i] = s16sample / 32678;
|
|
105
|
+
}
|
|
106
|
+
return sampleData;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* @this {DLSSoundFont}
|
|
111
|
+
* @param waveListChunk {RiffChunk}
|
|
112
|
+
*/
|
|
113
|
+
export function readDLSSamples(waveListChunk)
|
|
114
|
+
{
|
|
115
|
+
SpessaSynthGroupCollapsed(
|
|
116
|
+
"%cLoading Wave samples...",
|
|
117
|
+
consoleColors.recognized
|
|
118
|
+
);
|
|
119
|
+
let sampleID = 0;
|
|
120
|
+
while (waveListChunk.chunkData.currentIndex < waveListChunk.chunkData.length)
|
|
121
|
+
{
|
|
122
|
+
const waveChunk = readRIFFChunk(waveListChunk.chunkData);
|
|
123
|
+
this.verifyHeader(waveChunk, "LIST");
|
|
124
|
+
this.verifyText(readBytesAsString(waveChunk.chunkData, 4), "wave");
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* @type {RiffChunk[]}
|
|
128
|
+
*/
|
|
129
|
+
const waveChunks = [];
|
|
130
|
+
while (waveChunk.chunkData.currentIndex < waveChunk.chunkData.length)
|
|
131
|
+
{
|
|
132
|
+
waveChunks.push(readRIFFChunk(waveChunk.chunkData));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const fmtChunk = waveChunks.find(c => c.header === "fmt ");
|
|
136
|
+
if (!fmtChunk)
|
|
137
|
+
{
|
|
138
|
+
throw new Error("No fmt chunk in the wave file!");
|
|
139
|
+
}
|
|
140
|
+
// https://github.com/tpn/winsdk-10/blob/9b69fd26ac0c7d0b83d378dba01080e93349c2ed/Include/10.0.14393.0/shared/mmreg.h#L2108
|
|
141
|
+
const waveFormat = readLittleEndian(fmtChunk.chunkData, 2);
|
|
142
|
+
const channelsAmount = readLittleEndian(fmtChunk.chunkData, 2);
|
|
143
|
+
if (channelsAmount !== 1)
|
|
144
|
+
{
|
|
145
|
+
throw new Error(`Only mono samples are supported. Fmt reports ${channelsAmount} channels`);
|
|
146
|
+
}
|
|
147
|
+
const sampleRate = readLittleEndian(fmtChunk.chunkData, 4);
|
|
148
|
+
// skip avg bytes
|
|
149
|
+
readLittleEndian(fmtChunk.chunkData, 4);
|
|
150
|
+
// blockAlign
|
|
151
|
+
readLittleEndian(fmtChunk.chunkData, 2);
|
|
152
|
+
// it's bits per sample because one channel
|
|
153
|
+
const wBitsPerSample = readLittleEndian(fmtChunk.chunkData, 2);
|
|
154
|
+
const bytesPerSample = wBitsPerSample / 8;
|
|
155
|
+
|
|
156
|
+
// read the data
|
|
157
|
+
let failed = false;
|
|
158
|
+
const dataChunk = waveChunks.find(c => c.header === "data");
|
|
159
|
+
if (!dataChunk)
|
|
160
|
+
{
|
|
161
|
+
this.parsingError("No data chunk in the WAVE chunk!");
|
|
162
|
+
}
|
|
163
|
+
let sampleData;
|
|
164
|
+
switch (waveFormat)
|
|
165
|
+
{
|
|
166
|
+
default:
|
|
167
|
+
failed = true;
|
|
168
|
+
sampleData = new Float32Array(dataChunk.size / bytesPerSample);
|
|
169
|
+
break;
|
|
170
|
+
|
|
171
|
+
case W_FORMAT_TAG.PCM:
|
|
172
|
+
sampleData = readPCM(dataChunk, bytesPerSample);
|
|
173
|
+
break;
|
|
174
|
+
|
|
175
|
+
case W_FORMAT_TAG.ALAW:
|
|
176
|
+
sampleData = readALAW(dataChunk, bytesPerSample);
|
|
177
|
+
break;
|
|
178
|
+
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// read sample name
|
|
182
|
+
const waveInfo = findRIFFListType(waveChunks, "INFO");
|
|
183
|
+
let sampleName = `Unnamed ${sampleID}`;
|
|
184
|
+
if (waveInfo)
|
|
185
|
+
{
|
|
186
|
+
let infoChunk = readRIFFChunk(waveInfo.chunkData);
|
|
187
|
+
while (infoChunk.header !== "INAM" && waveInfo.chunkData.currentIndex < waveInfo.chunkData.length)
|
|
188
|
+
{
|
|
189
|
+
infoChunk = readRIFFChunk(waveInfo.chunkData);
|
|
190
|
+
}
|
|
191
|
+
if (infoChunk.header === "INAM")
|
|
192
|
+
{
|
|
193
|
+
sampleName = readBytesAsString(infoChunk.chunkData, infoChunk.size).trim();
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// correct defaults
|
|
198
|
+
let sampleKey = 60;
|
|
199
|
+
let samplePitch = 0;
|
|
200
|
+
let sampleLoopStart = 0;
|
|
201
|
+
let sampleLoopEnd = sampleData.length - 1;
|
|
202
|
+
let sampleDbAttenuation = 0;
|
|
203
|
+
|
|
204
|
+
// read wsmp
|
|
205
|
+
const wsmpChunk = waveChunks.find(c => c.header === "wsmp");
|
|
206
|
+
if (wsmpChunk)
|
|
207
|
+
{
|
|
208
|
+
// skip cbsize
|
|
209
|
+
readLittleEndian(wsmpChunk.chunkData, 4);
|
|
210
|
+
sampleKey = readLittleEndian(wsmpChunk.chunkData, 2);
|
|
211
|
+
// section 1.14.2: Each relative pitch unit represents 1/65536 cents.
|
|
212
|
+
// but that doesn't seem true for this one: it's just cents.
|
|
213
|
+
samplePitch = signedInt16(
|
|
214
|
+
wsmpChunk.chunkData[wsmpChunk.chunkData.currentIndex++],
|
|
215
|
+
wsmpChunk.chunkData[wsmpChunk.chunkData.currentIndex++]
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
// pitch correction: convert hundreds to the root key
|
|
219
|
+
const samplePitchSemitones = Math.trunc(samplePitch / 100);
|
|
220
|
+
sampleKey += samplePitchSemitones;
|
|
221
|
+
samplePitch -= samplePitchSemitones * 100;
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
// gain is applied it manually here (literally multiplying the samples)
|
|
225
|
+
const gainCorrection = readLittleEndian(wsmpChunk.chunkData, 4);
|
|
226
|
+
// convert to signed and turn into decibels
|
|
227
|
+
sampleDbAttenuation = (gainCorrection | 0) / -655360;
|
|
228
|
+
// no idea about ful options
|
|
229
|
+
readLittleEndian(wsmpChunk.chunkData, 4);
|
|
230
|
+
const loopsAmount = readLittleEndian(wsmpChunk.chunkData, 4);
|
|
231
|
+
if (loopsAmount === 1)
|
|
232
|
+
{
|
|
233
|
+
// skip size and type
|
|
234
|
+
readLittleEndian(wsmpChunk.chunkData, 8);
|
|
235
|
+
sampleLoopStart = readLittleEndian(wsmpChunk.chunkData, 4);
|
|
236
|
+
const loopSize = readLittleEndian(wsmpChunk.chunkData, 4);
|
|
237
|
+
sampleLoopEnd = sampleLoopStart + loopSize;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
else
|
|
241
|
+
{
|
|
242
|
+
SpessaSynthWarn("No wsmp chunk in wave... using sane defaults.");
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (failed)
|
|
246
|
+
{
|
|
247
|
+
console.error(`Failed to load '${sampleName}': Unsupported format: (${waveFormat})`);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
this.samples.push(new DLSSample(
|
|
251
|
+
sampleName,
|
|
252
|
+
sampleRate,
|
|
253
|
+
sampleKey,
|
|
254
|
+
samplePitch,
|
|
255
|
+
sampleLoopStart,
|
|
256
|
+
sampleLoopEnd,
|
|
257
|
+
sampleData,
|
|
258
|
+
sampleDbAttenuation
|
|
259
|
+
));
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
sampleID++;
|
|
263
|
+
SpessaSynthInfo(
|
|
264
|
+
`%cLoaded sample %c${sampleName}`,
|
|
265
|
+
consoleColors.info,
|
|
266
|
+
consoleColors.recognized
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
SpessaSynthGroupEnd();
|
|
270
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { IndexedByteArray } from "../utils/indexed_array.js";
|
|
2
|
+
import { readBytesAsString } from "../utils/byte_functions/string.js";
|
|
3
|
+
import { DLSSoundFont } from "./dls/dls_soundfont.js";
|
|
4
|
+
import { SoundFont2 } from "./read_sf2/soundfont.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Loads a soundfont file
|
|
8
|
+
* @param buffer {ArrayBuffer}
|
|
9
|
+
* @returns {BasicSoundBank}
|
|
10
|
+
*/
|
|
11
|
+
export function loadSoundFont(buffer)
|
|
12
|
+
{
|
|
13
|
+
const check = buffer.slice(8, 12);
|
|
14
|
+
const a = new IndexedByteArray(check);
|
|
15
|
+
const id = readBytesAsString(a, 4, undefined, false).toLowerCase();
|
|
16
|
+
if (id === "dls ")
|
|
17
|
+
{
|
|
18
|
+
return new DLSSoundFont(buffer);
|
|
19
|
+
}
|
|
20
|
+
return new SoundFont2(buffer, false);
|
|
21
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { IndexedByteArray } from "../../utils/indexed_array.js";
|
|
2
|
+
import { RiffChunk } from "../basic_soundfont/riff_chunk.js";
|
|
3
|
+
import { signedInt16 } from "../../utils/byte_functions/little_endian.js";
|
|
4
|
+
import { Generator } from "../basic_soundfont/generator.js";
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
export class ReadGenerator extends Generator
|
|
8
|
+
{
|
|
9
|
+
/**
|
|
10
|
+
* Creates a generator
|
|
11
|
+
* @param dataArray {IndexedByteArray}
|
|
12
|
+
*/
|
|
13
|
+
constructor(dataArray)
|
|
14
|
+
{
|
|
15
|
+
super();
|
|
16
|
+
// 4 bytes:
|
|
17
|
+
// type, type, type, value
|
|
18
|
+
const i = dataArray.currentIndex;
|
|
19
|
+
/**
|
|
20
|
+
* @type {generatorTypes|number}
|
|
21
|
+
*/
|
|
22
|
+
this.generatorType = (dataArray[i + 1] << 8) | dataArray[i];
|
|
23
|
+
this.generatorValue = signedInt16(dataArray[i + 2], dataArray[i + 3]);
|
|
24
|
+
dataArray.currentIndex += 4;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Reads the generator read
|
|
30
|
+
* @param generatorChunk {RiffChunk}
|
|
31
|
+
* @returns {Generator[]}
|
|
32
|
+
*/
|
|
33
|
+
export function readGenerators(generatorChunk)
|
|
34
|
+
{
|
|
35
|
+
let gens = [];
|
|
36
|
+
while (generatorChunk.chunkData.length > generatorChunk.chunkData.currentIndex)
|
|
37
|
+
{
|
|
38
|
+
gens.push(new ReadGenerator(generatorChunk.chunkData));
|
|
39
|
+
}
|
|
40
|
+
if (gens.length > 1)
|
|
41
|
+
{
|
|
42
|
+
// remove terminal
|
|
43
|
+
gens.pop();
|
|
44
|
+
}
|
|
45
|
+
return gens;
|
|
46
|
+
}
|
|
@@ -1,27 +1,28 @@
|
|
|
1
|
-
import {RiffChunk} from "
|
|
2
|
-
import {InstrumentZone} from "./zones.js";
|
|
3
|
-
import {
|
|
1
|
+
import { RiffChunk } from "../basic_soundfont/riff_chunk.js";
|
|
2
|
+
import { InstrumentZone } from "./zones.js";
|
|
3
|
+
import { readLittleEndian } from "../../utils/byte_functions/little_endian.js";
|
|
4
|
+
import { readBytesAsString } from "../../utils/byte_functions/string.js";
|
|
5
|
+
import { BasicInstrument } from "../basic_soundfont/basic_instrument.js";
|
|
4
6
|
|
|
5
7
|
/**
|
|
6
8
|
* instrument.js
|
|
7
9
|
* purpose: parses soundfont instrument and stores them as a class
|
|
8
10
|
*/
|
|
9
11
|
|
|
10
|
-
export class Instrument
|
|
12
|
+
export class Instrument extends BasicInstrument
|
|
13
|
+
{
|
|
11
14
|
/**
|
|
12
15
|
* Creates an instrument
|
|
13
16
|
* @param instrumentChunk {RiffChunk}
|
|
14
17
|
*/
|
|
15
|
-
constructor(instrumentChunk)
|
|
18
|
+
constructor(instrumentChunk)
|
|
19
|
+
{
|
|
20
|
+
super();
|
|
16
21
|
this.instrumentName = readBytesAsString(instrumentChunk.chunkData, 20).trim();
|
|
17
|
-
this.instrumentZoneIndex =
|
|
22
|
+
this.instrumentZoneIndex = readLittleEndian(instrumentChunk.chunkData, 2);
|
|
18
23
|
this.instrumentZonesAmount = 0;
|
|
19
|
-
/**
|
|
20
|
-
* @type {InstrumentZone[]}
|
|
21
|
-
*/
|
|
22
|
-
this.instrumentZones = [];
|
|
23
24
|
}
|
|
24
|
-
|
|
25
|
+
|
|
25
26
|
/**
|
|
26
27
|
* Loads all the instrument zones, given the amount
|
|
27
28
|
* @param amount {number}
|
|
@@ -30,7 +31,7 @@ export class Instrument{
|
|
|
30
31
|
getInstrumentZones(amount, zones)
|
|
31
32
|
{
|
|
32
33
|
this.instrumentZonesAmount = amount;
|
|
33
|
-
for(let i = this.instrumentZoneIndex; i < this.instrumentZonesAmount + this.instrumentZoneIndex; i++)
|
|
34
|
+
for (let i = this.instrumentZoneIndex; i < this.instrumentZonesAmount + this.instrumentZoneIndex; i++)
|
|
34
35
|
{
|
|
35
36
|
this.instrumentZones.push(zones[i]);
|
|
36
37
|
}
|
|
@@ -46,15 +47,20 @@ export class Instrument{
|
|
|
46
47
|
export function readInstruments(instrumentChunk, instrumentZones)
|
|
47
48
|
{
|
|
48
49
|
let instruments = [];
|
|
49
|
-
while(instrumentChunk.chunkData.length > instrumentChunk.chunkData.currentIndex)
|
|
50
|
+
while (instrumentChunk.chunkData.length > instrumentChunk.chunkData.currentIndex)
|
|
50
51
|
{
|
|
51
52
|
let instrument = new Instrument(instrumentChunk);
|
|
52
|
-
if(instruments.length > 0)
|
|
53
|
+
if (instruments.length > 0)
|
|
53
54
|
{
|
|
54
55
|
let instrumentsAmount = instrument.instrumentZoneIndex - instruments[instruments.length - 1].instrumentZoneIndex;
|
|
55
56
|
instruments[instruments.length - 1].getInstrumentZones(instrumentsAmount, instrumentZones);
|
|
56
57
|
}
|
|
57
58
|
instruments.push(instrument);
|
|
58
59
|
}
|
|
60
|
+
if (instruments.length > 1)
|
|
61
|
+
{
|
|
62
|
+
// remove EOI
|
|
63
|
+
instruments.pop();
|
|
64
|
+
}
|
|
59
65
|
return instruments;
|
|
60
66
|
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { readLittleEndian, signedInt16 } from "../../utils/byte_functions/little_endian.js";
|
|
2
|
+
import { IndexedByteArray } from "../../utils/indexed_array.js";
|
|
3
|
+
import { Modulator } from "../basic_soundfont/modulator.js";
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
export class ReadModulator extends Modulator
|
|
7
|
+
{
|
|
8
|
+
/**
|
|
9
|
+
* Creates a modulator
|
|
10
|
+
* @param dataArray {IndexedByteArray}
|
|
11
|
+
*/
|
|
12
|
+
constructor(dataArray)
|
|
13
|
+
{
|
|
14
|
+
const srcEnum = readLittleEndian(dataArray, 2);
|
|
15
|
+
const destination = readLittleEndian(dataArray, 2);
|
|
16
|
+
const amount = signedInt16(dataArray[dataArray.currentIndex++], dataArray[dataArray.currentIndex++]);
|
|
17
|
+
const secSrcEnum = readLittleEndian(dataArray, 2);
|
|
18
|
+
const transformType = readLittleEndian(dataArray, 2);
|
|
19
|
+
super(srcEnum, secSrcEnum, destination, amount, transformType);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Reads the modulator read
|
|
25
|
+
* @param modulatorChunk {RiffChunk}
|
|
26
|
+
* @returns {Modulator[]}
|
|
27
|
+
*/
|
|
28
|
+
export function readModulators(modulatorChunk)
|
|
29
|
+
{
|
|
30
|
+
let gens = [];
|
|
31
|
+
while (modulatorChunk.chunkData.length > modulatorChunk.chunkData.currentIndex)
|
|
32
|
+
{
|
|
33
|
+
gens.push(new ReadModulator(modulatorChunk.chunkData));
|
|
34
|
+
}
|
|
35
|
+
return gens;
|
|
36
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { RiffChunk } from "../basic_soundfont/riff_chunk.js";
|
|
2
|
+
import { PresetZone } from "./zones.js";
|
|
3
|
+
import { readLittleEndian } from "../../utils/byte_functions/little_endian.js";
|
|
4
|
+
import { readBytesAsString } from "../../utils/byte_functions/string.js";
|
|
5
|
+
import { BasicPreset } from "../basic_soundfont/basic_preset.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* parses soundfont presets, also includes function for getting the generators and samples from midi note and velocity
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export class Preset extends BasicPreset
|
|
12
|
+
{
|
|
13
|
+
/**
|
|
14
|
+
* Creates a preset
|
|
15
|
+
* @param presetChunk {RiffChunk}
|
|
16
|
+
* @param sf2 {BasicSoundBank}
|
|
17
|
+
*/
|
|
18
|
+
constructor(presetChunk, sf2)
|
|
19
|
+
{
|
|
20
|
+
super(sf2);
|
|
21
|
+
this.presetName = readBytesAsString(presetChunk.chunkData, 20)
|
|
22
|
+
.trim()
|
|
23
|
+
.replace(/\d{3}:\d{3}/, ""); // remove those pesky "000:001"
|
|
24
|
+
|
|
25
|
+
this.program = readLittleEndian(presetChunk.chunkData, 2);
|
|
26
|
+
this.bank = readLittleEndian(presetChunk.chunkData, 2);
|
|
27
|
+
this.presetZoneStartIndex = readLittleEndian(presetChunk.chunkData, 2);
|
|
28
|
+
|
|
29
|
+
// read the dword
|
|
30
|
+
this.library = readLittleEndian(presetChunk.chunkData, 4);
|
|
31
|
+
this.genre = readLittleEndian(presetChunk.chunkData, 4);
|
|
32
|
+
this.morphology = readLittleEndian(presetChunk.chunkData, 4);
|
|
33
|
+
this.presetZonesAmount = 0;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Loads all the preset zones, given the amount
|
|
38
|
+
* @param amount {number}
|
|
39
|
+
* @param zones {PresetZone[]}
|
|
40
|
+
*/
|
|
41
|
+
getPresetZones(amount, zones)
|
|
42
|
+
{
|
|
43
|
+
this.presetZonesAmount = amount;
|
|
44
|
+
for (let i = this.presetZoneStartIndex; i < this.presetZonesAmount + this.presetZoneStartIndex; i++)
|
|
45
|
+
{
|
|
46
|
+
this.presetZones.push(zones[i]);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Reads the presets
|
|
53
|
+
* @param presetChunk {RiffChunk}
|
|
54
|
+
* @param presetZones {PresetZone[]}
|
|
55
|
+
* @param sf2 {BasicSoundBank}
|
|
56
|
+
* @returns {Preset[]}
|
|
57
|
+
*/
|
|
58
|
+
export function readPresets(presetChunk, presetZones, sf2)
|
|
59
|
+
{
|
|
60
|
+
/**
|
|
61
|
+
* @type {Preset[]}
|
|
62
|
+
*/
|
|
63
|
+
let presets = [];
|
|
64
|
+
while (presetChunk.chunkData.length > presetChunk.chunkData.currentIndex)
|
|
65
|
+
{
|
|
66
|
+
let preset = new Preset(presetChunk, sf2);
|
|
67
|
+
if (presets.length > 0)
|
|
68
|
+
{
|
|
69
|
+
let presetZonesAmount = preset.presetZoneStartIndex - presets[presets.length - 1].presetZoneStartIndex;
|
|
70
|
+
presets[presets.length - 1].getPresetZones(presetZonesAmount, presetZones);
|
|
71
|
+
}
|
|
72
|
+
presets.push(preset);
|
|
73
|
+
}
|
|
74
|
+
// remove EOP
|
|
75
|
+
if (presets.length > 1)
|
|
76
|
+
{
|
|
77
|
+
presets.pop();
|
|
78
|
+
}
|
|
79
|
+
return presets;
|
|
80
|
+
}
|