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
|
@@ -1,285 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* worklet_voice.js
|
|
3
|
-
* purpose: prepares workletvoices from sample and generator data and manages sample dumping
|
|
4
|
-
* note: sample dumping means sending it over to the AudioWorkletGlobalScope
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* @typedef {Object} WorkletSample
|
|
9
|
-
* @property {number} sampleID - ID of the sample
|
|
10
|
-
* @property {number} playbackStep - current playback step (rate)
|
|
11
|
-
* @property {number} cursor - current position in the sample
|
|
12
|
-
* @property {number} rootKey - root key of the sample
|
|
13
|
-
* @property {number} loopStart - start position of the loop
|
|
14
|
-
* @property {number} loopEnd - end position of the loop
|
|
15
|
-
* @property {number} end - end position of the sample
|
|
16
|
-
* @property {0|1|2} loopingMode - looping mode of the sample
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* @typedef {Object} WorkletVoice
|
|
21
|
-
* @property {WorkletSample} sample - sample ID for voice.
|
|
22
|
-
* @property {WorkletLowpassFilter} filter - lowpass filter applied to the voice
|
|
23
|
-
* @property {Int16Array} generators - the unmodulated (constant) generators of the voice
|
|
24
|
-
* @property {Modulator[]} modulators - the voice's modulators
|
|
25
|
-
* @property {Int16Array} modulatedGenerators - the generators modulated by the modulators
|
|
26
|
-
*
|
|
27
|
-
* @property {boolean} finished - indicates if the voice has finished
|
|
28
|
-
* @property {boolean} isInRelease - indicates if the voice is in the release phase
|
|
29
|
-
* @property {boolean} hasStarted - indicates if the voice has started rendering
|
|
30
|
-
*
|
|
31
|
-
* @property {number} channelNumber - MIDI channel number
|
|
32
|
-
* @property {number} velocity - velocity of the note
|
|
33
|
-
* @property {number} midiNote - MIDI note number
|
|
34
|
-
* @property {number} targetKey - target key for the note
|
|
35
|
-
*
|
|
36
|
-
* @property {WorkletVolumeEnvelope} volumeEnvelope
|
|
37
|
-
*
|
|
38
|
-
* @property {number} currentModEnvValue - current value of the modulation envelope
|
|
39
|
-
* @property {number} releaseStartModEnv - modenv value at the start of the release phase
|
|
40
|
-
*
|
|
41
|
-
* @property {number} startTime - start time of the voice
|
|
42
|
-
* @property {number} releaseStartTime - start time of the release phase
|
|
43
|
-
*
|
|
44
|
-
* @property {number} currentTuningCents - current tuning adjustment in cents
|
|
45
|
-
* @property {number} currentTuningCalculated - calculated tuning adjustment
|
|
46
|
-
* @property {number} currentPan - from 0 to 1
|
|
47
|
-
*/
|
|
48
|
-
|
|
49
|
-
import { addAndClampGenerator, generatorTypes } from '../../../soundfont/chunk/generators.js'
|
|
50
|
-
import { SpessaSynthTable } from '../../../utils/loggin.js'
|
|
51
|
-
import { DEFAULT_WORKLET_VOLUME_ENVELOPE } from './volume_envelope.js'
|
|
52
|
-
import { DEFAULT_WORKLET_LOWPASS_FILTER } from './lowpass_filter.js'
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* the sampleID is the index
|
|
57
|
-
* @type {boolean[]}
|
|
58
|
-
*/
|
|
59
|
-
let globalDumpedSamplesList = [];
|
|
60
|
-
|
|
61
|
-
export function clearSamplesList()
|
|
62
|
-
{
|
|
63
|
-
globalDumpedSamplesList = [];
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function /**
|
|
67
|
-
* This is how the logic works: since sf3 is compressed, we rely on an async decoder.
|
|
68
|
-
* So, if the sample isn't loaded yet:
|
|
69
|
-
* send the workletVoice (generators, modulators, etc) and the WorkletSample(sampleID + end offset + loop)
|
|
70
|
-
* once the voice is done, then we dump it.
|
|
71
|
-
*
|
|
72
|
-
* on the WorkletScope side:
|
|
73
|
-
* skip the voice if sampleID isn't valid
|
|
74
|
-
* once we receive a sample dump, adjust all voice endOffsets (loop is already correct in sf3)
|
|
75
|
-
* now the voice starts playing, yay!
|
|
76
|
-
* @param channel {number} channel hint for the processor to recalculate cursor positions
|
|
77
|
-
* @param sample {Sample}
|
|
78
|
-
* @param id {number}
|
|
79
|
-
* @param sampleDumpCallback {function({channel: number, sampleID: number, sampleData: Float32Array})}
|
|
80
|
-
*/
|
|
81
|
-
dumpSample(channel, sample, id, sampleDumpCallback)
|
|
82
|
-
{
|
|
83
|
-
// flag as defined, so it's currently being dumped
|
|
84
|
-
globalDumpedSamplesList[id] = false;
|
|
85
|
-
|
|
86
|
-
// load the data
|
|
87
|
-
sampleDumpCallback({
|
|
88
|
-
channel: channel,
|
|
89
|
-
sampleID: id,
|
|
90
|
-
sampleData: sample.getAudioData()
|
|
91
|
-
});
|
|
92
|
-
globalDumpedSamplesList[id] = true;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Deep clone function for the WorkletVoice object and its nested structures.
|
|
97
|
-
* This function handles Int16Array, objects, arrays, and primitives.
|
|
98
|
-
* It does not handle circular references.
|
|
99
|
-
* @param {WorkletVoice} obj - The object to clone.
|
|
100
|
-
* @returns {WorkletVoice} - Cloned object.
|
|
101
|
-
*/
|
|
102
|
-
function deepClone(obj) {
|
|
103
|
-
if (obj === null || typeof obj !== 'object') {
|
|
104
|
-
return obj;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// Handle Int16Array separately
|
|
108
|
-
if (obj instanceof Int16Array) {
|
|
109
|
-
return new Int16Array(obj);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// Handle objects and arrays
|
|
113
|
-
const clonedObj = Array.isArray(obj) ? [] : {};
|
|
114
|
-
for (let key in obj) {
|
|
115
|
-
if (obj.hasOwnProperty(key)) {
|
|
116
|
-
if (typeof obj[key] === 'object' && obj[key] !== null) {
|
|
117
|
-
clonedObj[key] = deepClone(obj[key]); // Recursively clone nested objects
|
|
118
|
-
} else if (obj[key] instanceof Int16Array) {
|
|
119
|
-
clonedObj[key] = new Int16Array(obj[key]); // Clone Int16Array
|
|
120
|
-
} else {
|
|
121
|
-
clonedObj[key] = obj[key]; // Copy primitives
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
return clonedObj;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* @param channel {number} a hint for the processor to recalculate sample cursors when sample dumping
|
|
131
|
-
* @param midiNote {number}
|
|
132
|
-
* @param velocity {number}
|
|
133
|
-
* @param preset {Preset}
|
|
134
|
-
* @param currentTime {number}
|
|
135
|
-
* @param sampleRate {number}
|
|
136
|
-
* @param sampleDumpCallback {function({channel: number, sampleID: number, sampleData: Float32Array})}
|
|
137
|
-
* @param cachedVoices {WorkletVoice[][][]} first is midi note, second is velocity. output is an array of WorkletVoices
|
|
138
|
-
* @param debug {boolean}
|
|
139
|
-
* @returns {WorkletVoice[]}
|
|
140
|
-
*/
|
|
141
|
-
export function getWorkletVoices(channel,
|
|
142
|
-
midiNote,
|
|
143
|
-
velocity,
|
|
144
|
-
preset,
|
|
145
|
-
currentTime,
|
|
146
|
-
sampleRate,
|
|
147
|
-
sampleDumpCallback,
|
|
148
|
-
cachedVoices,
|
|
149
|
-
debug=false)
|
|
150
|
-
{
|
|
151
|
-
/**
|
|
152
|
-
* @type {WorkletVoice[]}
|
|
153
|
-
*/
|
|
154
|
-
let workletVoices;
|
|
155
|
-
|
|
156
|
-
const cached = cachedVoices[midiNote][velocity];
|
|
157
|
-
if(cached)
|
|
158
|
-
{
|
|
159
|
-
workletVoices = cached.map(deepClone);
|
|
160
|
-
workletVoices.forEach(v => {
|
|
161
|
-
v.startTime = currentTime;
|
|
162
|
-
});
|
|
163
|
-
}
|
|
164
|
-
else
|
|
165
|
-
{
|
|
166
|
-
let canCache = true;
|
|
167
|
-
/**
|
|
168
|
-
* @returns {WorkletVoice}
|
|
169
|
-
*/
|
|
170
|
-
workletVoices = preset.getSamplesAndGenerators(midiNote, velocity).map(sampleAndGenerators => {
|
|
171
|
-
// dump the sample if haven't already
|
|
172
|
-
if (globalDumpedSamplesList[sampleAndGenerators.sampleID] !== true) {
|
|
173
|
-
// if the sample is currently being loaded, don't dump again (undefined means not loaded, false means is being loaded)
|
|
174
|
-
if(globalDumpedSamplesList[sampleAndGenerators.sampleID] === undefined) {
|
|
175
|
-
dumpSample(channel, sampleAndGenerators.sample, sampleAndGenerators.sampleID, sampleDumpCallback);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// can't cache the voice as the end in workletSample maybe is incorrect (the sample is still loading)
|
|
179
|
-
if(globalDumpedSamplesList[sampleAndGenerators.sampleID] !== true)
|
|
180
|
-
{
|
|
181
|
-
canCache = false;
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// create the generator list
|
|
186
|
-
const generators = new Int16Array(60);
|
|
187
|
-
// apply and sum the gens
|
|
188
|
-
for (let i = 0; i < 60; i++) {
|
|
189
|
-
generators[i] = addAndClampGenerator(i, sampleAndGenerators.presetGenerators, sampleAndGenerators.instrumentGenerators);
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// !! EMU initial attenuation correction, multiply initial attenuation by 0.4
|
|
193
|
-
generators[generatorTypes.initialAttenuation] = Math.floor(generators[generatorTypes.initialAttenuation] * 0.4);
|
|
194
|
-
|
|
195
|
-
// key override
|
|
196
|
-
let rootKey = sampleAndGenerators.sample.samplePitch;
|
|
197
|
-
if (generators[generatorTypes.overridingRootKey] > -1) {
|
|
198
|
-
rootKey = generators[generatorTypes.overridingRootKey];
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
let targetKey = midiNote;
|
|
202
|
-
if (generators[generatorTypes.keyNum] > -1) {
|
|
203
|
-
targetKey = generators[generatorTypes.keyNum];
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// determine looping mode now. if the loop is too small, disable
|
|
207
|
-
const loopStart = (sampleAndGenerators.sample.sampleLoopStartIndex / 2) + (generators[generatorTypes.startloopAddrsOffset] + (generators[generatorTypes.startloopAddrsCoarseOffset] * 32768));
|
|
208
|
-
const loopEnd = (sampleAndGenerators.sample.sampleLoopEndIndex / 2) + (generators[generatorTypes.endloopAddrsOffset] + (generators[generatorTypes.endloopAddrsCoarseOffset] * 32768));
|
|
209
|
-
let loopingMode = generators[generatorTypes.sampleModes];
|
|
210
|
-
if (loopEnd - loopStart < 1) {
|
|
211
|
-
loopingMode = 0;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// determine end
|
|
215
|
-
/**
|
|
216
|
-
* create the worklet sample
|
|
217
|
-
* @type {WorkletSample}
|
|
218
|
-
*/
|
|
219
|
-
const workletSample = {
|
|
220
|
-
sampleID: sampleAndGenerators.sampleID,
|
|
221
|
-
playbackStep: (sampleAndGenerators.sample.sampleRate / sampleRate) * Math.pow(2, sampleAndGenerators.sample.samplePitchCorrection / 1200),// cent tuning
|
|
222
|
-
cursor: generators[generatorTypes.startAddrsOffset] + (generators[generatorTypes.startAddrsCoarseOffset] * 32768),
|
|
223
|
-
rootKey: rootKey,
|
|
224
|
-
loopStart: loopStart,
|
|
225
|
-
loopEnd: loopEnd,
|
|
226
|
-
end: Math.floor( sampleAndGenerators.sample.sampleData.length) - 1 + (generators[generatorTypes.endAddrOffset] + (generators[generatorTypes.endAddrsCoarseOffset] * 32768)),
|
|
227
|
-
loopingMode: loopingMode
|
|
228
|
-
};
|
|
229
|
-
|
|
230
|
-
// velocity override
|
|
231
|
-
if (generators[generatorTypes.velocity] > -1) {
|
|
232
|
-
velocity = generators[generatorTypes.velocity];
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
if(debug)
|
|
236
|
-
{
|
|
237
|
-
SpessaSynthTable([{
|
|
238
|
-
Sample: sampleAndGenerators.sample.sampleName,
|
|
239
|
-
Generators: generators,
|
|
240
|
-
Modulators: sampleAndGenerators.modulators.map(m => m.debugString()),
|
|
241
|
-
Velocity: velocity,
|
|
242
|
-
TargetKey: targetKey,
|
|
243
|
-
MidiNote: midiNote,
|
|
244
|
-
WorkletSample: workletSample
|
|
245
|
-
}]);
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
return {
|
|
249
|
-
filter: deepClone(DEFAULT_WORKLET_LOWPASS_FILTER),
|
|
250
|
-
// generators and modulators
|
|
251
|
-
generators: generators,
|
|
252
|
-
modulators: sampleAndGenerators.modulators,
|
|
253
|
-
modulatedGenerators: new Int16Array(60),
|
|
254
|
-
|
|
255
|
-
// sample and playback data
|
|
256
|
-
sample: workletSample,
|
|
257
|
-
velocity: velocity,
|
|
258
|
-
midiNote: midiNote,
|
|
259
|
-
channelNumber: channel,
|
|
260
|
-
startTime: currentTime,
|
|
261
|
-
targetKey: targetKey,
|
|
262
|
-
currentTuningCalculated: 1,
|
|
263
|
-
currentTuningCents: 0,
|
|
264
|
-
releaseStartTime: Infinity,
|
|
265
|
-
|
|
266
|
-
// envelope data
|
|
267
|
-
finished: false,
|
|
268
|
-
isInRelease: false,
|
|
269
|
-
hasStarted: false,
|
|
270
|
-
currentModEnvValue: 0,
|
|
271
|
-
releaseStartModEnv: 1,
|
|
272
|
-
currentPan: 0.5,
|
|
273
|
-
|
|
274
|
-
volumeEnvelope: deepClone(DEFAULT_WORKLET_VOLUME_ENVELOPE)
|
|
275
|
-
};
|
|
276
|
-
|
|
277
|
-
});
|
|
278
|
-
// cache the voice
|
|
279
|
-
if(canCache) {
|
|
280
|
-
// clone it so the system won't mess with it!
|
|
281
|
-
cachedVoices[midiNote][velocity] = workletVoices.map(deepClone);
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
return workletVoices;
|
|
285
|
-
}
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
*
|
|
3
|
-
* @param sampleRate {number}
|
|
4
|
-
* @param left {Float32Array}
|
|
5
|
-
* @param right {Float32Array}
|
|
6
|
-
* @returns {ArrayBufferLike}
|
|
7
|
-
*/
|
|
8
|
-
export function rawDataToWav(sampleRate, left, right)
|
|
9
|
-
{
|
|
10
|
-
|
|
11
|
-
const length = left.length;
|
|
12
|
-
|
|
13
|
-
const bytesPerSample = 2; // 16-bit PCM
|
|
14
|
-
|
|
15
|
-
// Prepare the header
|
|
16
|
-
const headerSize = 44;
|
|
17
|
-
const dataSize = length * 2 * bytesPerSample; // 2 channels, 16-bit per channel
|
|
18
|
-
const fileSize = headerSize + dataSize - 8; // total file size minus the first 8 bytes
|
|
19
|
-
const header = new Uint8Array(headerSize);
|
|
20
|
-
|
|
21
|
-
// 'RIFF'
|
|
22
|
-
header.set([82, 73, 70, 70], 0);
|
|
23
|
-
// file length
|
|
24
|
-
header.set(new Uint8Array([fileSize & 0xff, (fileSize >> 8) & 0xff, (fileSize >> 16) & 0xff, (fileSize >> 24) & 0xff]), 4);
|
|
25
|
-
// 'WAVE'
|
|
26
|
-
header.set([87, 65, 86, 69], 8);
|
|
27
|
-
// 'fmt '
|
|
28
|
-
header.set([102, 109, 116, 32], 12);
|
|
29
|
-
// fmt chunk length
|
|
30
|
-
header.set([16, 0, 0, 0], 16); // 16 for PCM
|
|
31
|
-
// audio format (PCM)
|
|
32
|
-
header.set([1, 0], 20);
|
|
33
|
-
// number of channels (2)
|
|
34
|
-
header.set([2, 0], 22);
|
|
35
|
-
// sample rate
|
|
36
|
-
header.set(new Uint8Array([sampleRate & 0xff, (sampleRate >> 8) & 0xff, (sampleRate >> 16) & 0xff, (sampleRate >> 24) & 0xff]), 24);
|
|
37
|
-
// byte rate (sample rate * block align)
|
|
38
|
-
const byteRate = sampleRate * 2 * bytesPerSample; // 2 channels, 16-bit per channel
|
|
39
|
-
header.set(new Uint8Array([byteRate & 0xff, (byteRate >> 8) & 0xff, (byteRate >> 16) & 0xff, (byteRate >> 24) & 0xff]), 28);
|
|
40
|
-
// block align (channels * bytes per sample)
|
|
41
|
-
header.set([4, 0], 32); // 2 channels * 16-bit per channel / 8
|
|
42
|
-
// bits per sample
|
|
43
|
-
header.set([16, 0], 34); // 16-bit
|
|
44
|
-
|
|
45
|
-
// data chunk identifier 'data'
|
|
46
|
-
header.set([100, 97, 116, 97], 36);
|
|
47
|
-
// data chunk length
|
|
48
|
-
header.set(new Uint8Array([dataSize & 0xff, (dataSize >> 8) & 0xff, (dataSize >> 16) & 0xff, (dataSize >> 24) & 0xff]), 40);
|
|
49
|
-
|
|
50
|
-
const wavData = new Uint8Array(headerSize + dataSize);
|
|
51
|
-
wavData.set(header, 0);
|
|
52
|
-
|
|
53
|
-
// Interleave audio data (combine channels)
|
|
54
|
-
let offset = headerSize;
|
|
55
|
-
for (let i = 0; i < length; i++)
|
|
56
|
-
{
|
|
57
|
-
// interleave both channels
|
|
58
|
-
const sample1 = Math.max(-1, Math.min(1, left[i])) * 0x7FFF;
|
|
59
|
-
const sample2 = Math.max(-1, Math.min(1, right[i])) * 0x7FFF;
|
|
60
|
-
|
|
61
|
-
// convert to 16-bit
|
|
62
|
-
wavData[offset++] = sample1 & 0xff;
|
|
63
|
-
wavData[offset++] = (sample1 >> 8) & 0xff;
|
|
64
|
-
wavData[offset++] = sample2 & 0xff;
|
|
65
|
-
wavData[offset++] = (sample2 >> 8) & 0xff;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
return wavData.buffer;
|
|
70
|
-
}
|
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
import { ShiftableByteArray } from './shiftable_array.js'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* byte_functions.js
|
|
5
|
-
* purpose: contains various useful functions for bit manipulation and reading
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Reads as little endian
|
|
10
|
-
* @param dataArray {ShiftableByteArray}
|
|
11
|
-
* @param bytesAmount {number}
|
|
12
|
-
* @returns {number}
|
|
13
|
-
*/
|
|
14
|
-
export function readBytesAsUintLittleEndian(dataArray, bytesAmount){
|
|
15
|
-
let out = 0;
|
|
16
|
-
for(let i = 0; i < bytesAmount; i++)
|
|
17
|
-
{
|
|
18
|
-
out |= (readByte(dataArray) << i * 8);
|
|
19
|
-
}
|
|
20
|
-
// make sure it stays unsigned
|
|
21
|
-
return out >>> 0;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Reads as Big endian
|
|
26
|
-
* @param dataArray {ShiftableByteArray}
|
|
27
|
-
* @param bytesAmount {number}
|
|
28
|
-
* @returns {number}
|
|
29
|
-
*/
|
|
30
|
-
export function readBytesAsUintBigEndian(dataArray, bytesAmount){
|
|
31
|
-
let out = 0;
|
|
32
|
-
for (let i = 8 * (bytesAmount - 1); i >= 0; i -= 8) {
|
|
33
|
-
out |= (readByte(dataArray) << i);
|
|
34
|
-
}
|
|
35
|
-
return out >>> 0;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* @param dataArray {ShiftableByteArray}
|
|
40
|
-
* @returns {number}
|
|
41
|
-
*/
|
|
42
|
-
export function readByte(dataArray){
|
|
43
|
-
if(!dataArray.shift)
|
|
44
|
-
{
|
|
45
|
-
dataArray.currentIndex = 0;
|
|
46
|
-
dataArray.shift = function () {
|
|
47
|
-
this.currentIndex++;
|
|
48
|
-
return this[this.currentIndex - 1];
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
return dataArray.shift();
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* @param dataArray {ShiftableByteArray}
|
|
57
|
-
* @param bytes {number}
|
|
58
|
-
* @param encoding {string} the textElement encoding
|
|
59
|
-
* @param trimEnd {boolean} if we should trim once we reach an invalid byte
|
|
60
|
-
* @returns {string}
|
|
61
|
-
*/
|
|
62
|
-
export function readBytesAsString(dataArray, bytes, encoding=undefined, trimEnd=true){
|
|
63
|
-
if(!encoding) {
|
|
64
|
-
let finished = false;
|
|
65
|
-
let string = "";
|
|
66
|
-
for (let i = 0; i < bytes; i++) {
|
|
67
|
-
let byte = readByte(dataArray);
|
|
68
|
-
if(byte < 32 || byte > 127 || finished)
|
|
69
|
-
{
|
|
70
|
-
if(trimEnd) {
|
|
71
|
-
finished = true;
|
|
72
|
-
continue;
|
|
73
|
-
}
|
|
74
|
-
else
|
|
75
|
-
{
|
|
76
|
-
if(byte === 0)
|
|
77
|
-
{
|
|
78
|
-
finished = true;
|
|
79
|
-
continue;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
string += String.fromCharCode(byte);
|
|
84
|
-
}
|
|
85
|
-
return string;
|
|
86
|
-
}
|
|
87
|
-
else {
|
|
88
|
-
let byteBuffer = dataArray.slice(dataArray.currentIndex, dataArray.currentIndex + bytes)
|
|
89
|
-
dataArray.currentIndex += bytes;
|
|
90
|
-
let decoder = new TextDecoder(encoding);
|
|
91
|
-
return decoder.decode(byteBuffer.buffer);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* @param byte1 {number}
|
|
97
|
-
* @param byte2 {number}
|
|
98
|
-
* @returns {number}
|
|
99
|
-
*/
|
|
100
|
-
export function signedInt16(byte1, byte2){
|
|
101
|
-
let val = (byte2 << 8) | byte1;
|
|
102
|
-
if(val > 32767)
|
|
103
|
-
{
|
|
104
|
-
return val - 65536;
|
|
105
|
-
}
|
|
106
|
-
return val;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* @param byte {number}
|
|
111
|
-
* @returns {number}
|
|
112
|
-
*/
|
|
113
|
-
export function signedInt8(byte) {
|
|
114
|
-
if(byte > 127)
|
|
115
|
-
{
|
|
116
|
-
return byte - 256;
|
|
117
|
-
}
|
|
118
|
-
return byte;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* Reads VLQ From a MIDI byte array
|
|
123
|
-
* @param MIDIbyteArray {ShiftableByteArray}
|
|
124
|
-
* @returns {number}
|
|
125
|
-
*/
|
|
126
|
-
export function readVariableLengthQuantity(MIDIbyteArray){
|
|
127
|
-
let out = 0;
|
|
128
|
-
while(MIDIbyteArray)
|
|
129
|
-
{
|
|
130
|
-
const byte = readByte(MIDIbyteArray);
|
|
131
|
-
// extract the first 7 bytes
|
|
132
|
-
out = (out << 7) | (byte & 127);
|
|
133
|
-
|
|
134
|
-
// if the last byte isn't 1, stop reading
|
|
135
|
-
if((byte >> 7) !== 1)
|
|
136
|
-
{
|
|
137
|
-
break;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
return out;
|
|
141
|
-
}
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* other.js
|
|
3
|
-
* purpose: contains some useful functions that don't belong in any specific category
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Formats the given seconds to nice readable time
|
|
8
|
-
* @param totalSeconds {number} time in seconds
|
|
9
|
-
* @return {{seconds: number, minutes: number, time: string}}
|
|
10
|
-
*/
|
|
11
|
-
export function formatTime(totalSeconds) {
|
|
12
|
-
let minutes = Math.floor(totalSeconds / 60);
|
|
13
|
-
let seconds = Math.round(totalSeconds - (minutes * 60));
|
|
14
|
-
return {"minutes": minutes, "seconds": seconds, "time": `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`}
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* @param fileName {string}
|
|
19
|
-
* @returns {string}
|
|
20
|
-
*/
|
|
21
|
-
export function formatTitle(fileName)
|
|
22
|
-
{
|
|
23
|
-
return fileName.replaceAll(".mid", "").replaceAll("_", " ");
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Does what it says
|
|
28
|
-
* @param arr {number[]}
|
|
29
|
-
* @returns {string}
|
|
30
|
-
*/
|
|
31
|
-
export function arrayToHexString(arr) {
|
|
32
|
-
let hexString = '';
|
|
33
|
-
|
|
34
|
-
for (let i = 0; i < arr.length; i++) {
|
|
35
|
-
const hex = arr[i].toString(16).padStart(2, '0').toUpperCase();
|
|
36
|
-
hexString += hex;
|
|
37
|
-
hexString += ' ';
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
return hexString;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export const consoleColors = {
|
|
44
|
-
warn: "color: orange;",
|
|
45
|
-
unrecognized: "color: red;",
|
|
46
|
-
info: "color: aqua;",
|
|
47
|
-
recognized: "color: lime",
|
|
48
|
-
value: "color: yellow; background-color: black;"
|
|
49
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* shiftable_array.js
|
|
3
|
-
* purpose: exteds Uint8Array with the Array.shift() function
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
export class ShiftableByteArray extends Uint8Array
|
|
7
|
-
{
|
|
8
|
-
/**
|
|
9
|
-
* Creates a new instance of an Uint8Array with Array.shift() function
|
|
10
|
-
* @param args {any} same as for Uint8Array
|
|
11
|
-
*/
|
|
12
|
-
constructor(args) {
|
|
13
|
-
super(args);
|
|
14
|
-
this.currentIndex = 0;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* @param amount {number} - Amount of bytes to shift
|
|
19
|
-
* @returns {number} - The shifted byte
|
|
20
|
-
*/
|
|
21
|
-
shift = (amount = 1) =>
|
|
22
|
-
{
|
|
23
|
-
this.currentIndex += amount;
|
|
24
|
-
return this[this.currentIndex - amount];
|
|
25
|
-
};
|
|
26
|
-
}
|