spessasynth_lib 3.24.13 → 3.24.15
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/midi_parser/basic_midi.js +457 -68
- package/midi_parser/midi_loader.js +18 -503
- package/midi_parser/midi_message.js +18 -5
- package/midi_parser/midi_sequence.js +2 -2
- package/package.json +1 -1
- package/sequencer/worklet_sequencer/process_event.js +1 -6
- package/synthetizer/synthetizer.js +9 -6
- package/synthetizer/worklet_processor.min.js +12 -12
- package/synthetizer/worklet_system/README.md +2 -2
- package/synthetizer/worklet_system/main_processor.js +106 -95
- package/synthetizer/worklet_system/message_protocol/handle_message.js +22 -17
- package/synthetizer/worklet_system/message_protocol/worklet_message.js +2 -1
- package/synthetizer/worklet_system/snapshot/apply_synthesizer_snapshot.js +14 -0
- package/synthetizer/worklet_system/snapshot/channel_snapshot.js +166 -0
- package/synthetizer/worklet_system/snapshot/send_synthesizer_snapshot.js +14 -0
- package/synthetizer/worklet_system/snapshot/synthesizer_snapshot.js +121 -0
- package/synthetizer/worklet_system/worklet_methods/controller_control/controller_change.js +196 -0
- package/synthetizer/worklet_system/worklet_methods/controller_control/master_parameters.js +34 -0
- package/synthetizer/worklet_system/worklet_methods/{reset_controllers.js → controller_control/reset_controllers.js} +33 -39
- package/synthetizer/worklet_system/worklet_methods/create_worklet_channel.js +26 -0
- package/synthetizer/worklet_system/worklet_methods/{data_entry.js → data_entry/data_entry_coarse.js} +38 -105
- package/synthetizer/worklet_system/worklet_methods/data_entry/data_entry_fine.js +64 -0
- package/synthetizer/worklet_system/worklet_methods/mute_channel.js +17 -0
- package/synthetizer/worklet_system/worklet_methods/note_on.js +36 -34
- package/synthetizer/worklet_system/worklet_methods/program_change.js +49 -0
- package/synthetizer/worklet_system/worklet_methods/{voice_control.js → render_voice.js} +37 -120
- package/synthetizer/worklet_system/worklet_methods/soundfont_management/clear_sound_font.js +35 -0
- package/synthetizer/worklet_system/worklet_methods/soundfont_management/get_preset.js +20 -0
- package/synthetizer/worklet_system/worklet_methods/soundfont_management/reload_sound_font.js +43 -0
- package/synthetizer/worklet_system/worklet_methods/soundfont_management/send_preset_list.js +31 -0
- package/synthetizer/worklet_system/worklet_methods/soundfont_management/set_embedded_sound_font.js +21 -0
- package/synthetizer/worklet_system/worklet_methods/stopping_notes/kill_note.js +19 -0
- package/synthetizer/worklet_system/worklet_methods/stopping_notes/note_off.js +51 -0
- package/synthetizer/worklet_system/worklet_methods/stopping_notes/stop_all_channels.js +16 -0
- package/synthetizer/worklet_system/worklet_methods/stopping_notes/stop_all_notes.js +30 -0
- package/synthetizer/worklet_system/worklet_methods/stopping_notes/voice_killing.js +63 -0
- package/synthetizer/worklet_system/worklet_methods/system_exclusive.js +31 -30
- package/synthetizer/worklet_system/worklet_methods/tuning_control/channel_pressure.js +24 -0
- package/synthetizer/worklet_system/worklet_methods/tuning_control/pitch_wheel.js +33 -0
- package/synthetizer/worklet_system/worklet_methods/tuning_control/poly_pressure.js +31 -0
- package/synthetizer/worklet_system/worklet_methods/tuning_control/set_master_tuning.js +15 -0
- package/synthetizer/worklet_system/worklet_methods/tuning_control/set_modulation_depth.js +27 -0
- package/synthetizer/worklet_system/worklet_methods/tuning_control/set_octave_tuning.js +15 -0
- package/synthetizer/worklet_system/worklet_methods/tuning_control/set_tuning.js +24 -0
- package/synthetizer/worklet_system/worklet_methods/tuning_control/set_tuning_semitones.js +19 -0
- package/synthetizer/worklet_system/worklet_methods/tuning_control/transpose_all_channels.js +15 -0
- package/synthetizer/worklet_system/worklet_methods/tuning_control/transpose_channel.js +31 -0
- package/synthetizer/worklet_system/worklet_utilities/controller_tables.js +10 -1
- package/synthetizer/worklet_system/worklet_utilities/lfo.js +2 -1
- package/synthetizer/worklet_system/worklet_utilities/modulation_envelope.js +4 -4
- package/synthetizer/worklet_system/worklet_utilities/modulator_curves.js +4 -5
- package/synthetizer/worklet_system/worklet_utilities/stereo_panner.js +18 -18
- package/synthetizer/worklet_system/worklet_utilities/wavetable_oscillator.js +210 -206
- package/synthetizer/worklet_system/worklet_utilities/worklet_processor_channel.js +354 -108
- package/synthetizer/worklet_system/worklet_utilities/worklet_voice.js +22 -9
- package/synthetizer/worklet_system/snapshot/snapshot.js +0 -311
- package/synthetizer/worklet_system/worklet_methods/controller_control.js +0 -260
- package/synthetizer/worklet_system/worklet_methods/note_off.js +0 -119
- package/synthetizer/worklet_system/worklet_methods/program_control.js +0 -282
- package/synthetizer/worklet_system/worklet_methods/tuning_control.js +0 -233
- package/synthetizer/worklet_system/worklet_methods/vibrato_control.js +0 -29
|
@@ -215,7 +215,7 @@ export function systemExclusive(messageData, channelOffset = 0)
|
|
|
215
215
|
}
|
|
216
216
|
else
|
|
217
217
|
{
|
|
218
|
-
// 2 byte tuning. Like fine tune: 0 is -100 cents, 8192 is 0 cents,
|
|
218
|
+
// 2 byte tuning. Like fine tune: 0 is -100 cents, 8192 is 0 cents, 16,383 is +100 cents
|
|
219
219
|
for (let i = 0; i < 24; i += 2)
|
|
220
220
|
{
|
|
221
221
|
const tuning = ((messageData[7 + i] << 7) | messageData[8 + i]) - 8192;
|
|
@@ -226,11 +226,11 @@ export function systemExclusive(messageData, channelOffset = 0)
|
|
|
226
226
|
// bit 1: 14 and 15
|
|
227
227
|
if ((messageData[4] & 1) === 1)
|
|
228
228
|
{
|
|
229
|
-
this.
|
|
229
|
+
this.workletProcessorChannels[14 + channelOffset].setOctaveTuning(newOctaveTuning);
|
|
230
230
|
}
|
|
231
231
|
if (((messageData[4] >> 1) & 1) === 1)
|
|
232
232
|
{
|
|
233
|
-
this.
|
|
233
|
+
this.workletProcessorChannels[15 + channelOffset].setOctaveTuning(newOctaveTuning);
|
|
234
234
|
}
|
|
235
235
|
|
|
236
236
|
// bit 2: channels 7 to 13
|
|
@@ -239,7 +239,7 @@ export function systemExclusive(messageData, channelOffset = 0)
|
|
|
239
239
|
const bit = (messageData[5] >> i) & 1;
|
|
240
240
|
if (bit === 1)
|
|
241
241
|
{
|
|
242
|
-
this.
|
|
242
|
+
this.workletProcessorChannels[7 + i + channelOffset].setOctaveTuning(newOctaveTuning);
|
|
243
243
|
}
|
|
244
244
|
}
|
|
245
245
|
|
|
@@ -249,7 +249,7 @@ export function systemExclusive(messageData, channelOffset = 0)
|
|
|
249
249
|
const bit = (messageData[6] >> i) & 1;
|
|
250
250
|
if (bit === 1)
|
|
251
251
|
{
|
|
252
|
-
this.
|
|
252
|
+
this.workletProcessorChannels[i + channelOffset].setOctaveTuning(newOctaveTuning);
|
|
253
253
|
}
|
|
254
254
|
}
|
|
255
255
|
|
|
@@ -330,8 +330,9 @@ export function systemExclusive(messageData, channelOffset = 0)
|
|
|
330
330
|
{
|
|
331
331
|
// this is an individual part (channel) parameter
|
|
332
332
|
// determine the channel 0 means channel 10 (default), 1 means 1 etc.
|
|
333
|
-
|
|
334
|
-
// for example
|
|
333
|
+
const channel = [9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15][messageData[5] & 0x0F] + channelOffset;
|
|
334
|
+
// for example, 0x1A means A = 11, which corresponds to channel 12 (counting from 1)
|
|
335
|
+
const channelObject = this.workletProcessorChannels[channel];
|
|
335
336
|
switch (messageData[6])
|
|
336
337
|
{
|
|
337
338
|
default:
|
|
@@ -342,7 +343,7 @@ export function systemExclusive(messageData, channelOffset = 0)
|
|
|
342
343
|
case 0x15:
|
|
343
344
|
// this is the Use for Drum Part sysex (multiple drums)
|
|
344
345
|
const isDrums = messageValue > 0 && messageData[5] >> 4; // if set to other than 0, is a drum channel
|
|
345
|
-
|
|
346
|
+
channelObject.setDrums(isDrums);
|
|
346
347
|
SpessaSynthInfo(
|
|
347
348
|
`%cChannel %c${channel}%c ${isDrums ?
|
|
348
349
|
"is now a drum channel"
|
|
@@ -360,7 +361,7 @@ export function systemExclusive(messageData, channelOffset = 0)
|
|
|
360
361
|
case 0x16:
|
|
361
362
|
// this is the pitch key shift sysex
|
|
362
363
|
const keyShift = messageValue - 64;
|
|
363
|
-
|
|
364
|
+
channelObject.transposeChannel(keyShift);
|
|
364
365
|
SpessaSynthInfo(
|
|
365
366
|
`%cChannel %c${channel}%c pitch shift. Semitones %c${keyShift}%c, with %c${arrayToHexString(
|
|
366
367
|
messageData)}`,
|
|
@@ -379,7 +380,7 @@ export function systemExclusive(messageData, channelOffset = 0)
|
|
|
379
380
|
let panpot = messageValue;
|
|
380
381
|
if (panpot === 0)
|
|
381
382
|
{
|
|
382
|
-
|
|
383
|
+
channelObject.randomPan = true;
|
|
383
384
|
SpessaSynthInfo(
|
|
384
385
|
`%cRandom pan is set to %cON%c for %c${channel}`,
|
|
385
386
|
consoleColors.info,
|
|
@@ -390,19 +391,19 @@ export function systemExclusive(messageData, channelOffset = 0)
|
|
|
390
391
|
}
|
|
391
392
|
else
|
|
392
393
|
{
|
|
393
|
-
|
|
394
|
-
|
|
394
|
+
channelObject.randomPan = false;
|
|
395
|
+
channelObject.controllerChange(midiControllers.pan, panpot);
|
|
395
396
|
}
|
|
396
397
|
break;
|
|
397
398
|
|
|
398
399
|
// chorus send
|
|
399
400
|
case 0x21:
|
|
400
|
-
|
|
401
|
+
channelObject.controllerChange(midiControllers.chorusDepth, messageValue);
|
|
401
402
|
break;
|
|
402
403
|
|
|
403
404
|
// reverb send
|
|
404
405
|
case 0x22:
|
|
405
|
-
|
|
406
|
+
channelObject.controllerChange(midiControllers.reverbDepth, messageValue);
|
|
406
407
|
break;
|
|
407
408
|
|
|
408
409
|
case 0x40:
|
|
@@ -418,14 +419,14 @@ export function systemExclusive(messageData, channelOffset = 0)
|
|
|
418
419
|
case 0x4A:
|
|
419
420
|
case 0x4B:
|
|
420
421
|
// scale tuning: up to 12 bytes
|
|
421
|
-
const tuningBytes = messageData.length - 9; // data starts at 7
|
|
422
|
+
const tuningBytes = messageData.length - 9; // data starts at 7, minus checksum and f7
|
|
422
423
|
// read em bytes
|
|
423
424
|
const newTuning = new Int8Array(12);
|
|
424
425
|
for (let i = 0; i < tuningBytes; i++)
|
|
425
426
|
{
|
|
426
427
|
newTuning[i] = messageData[i + 7] - 64;
|
|
427
428
|
}
|
|
428
|
-
|
|
429
|
+
channelObject.setOctaveTuning(newTuning);
|
|
429
430
|
const cents = messageValue - 64;
|
|
430
431
|
SpessaSynthInfo(
|
|
431
432
|
`%cChannel %c${channel}%c octave scale tuning. Cents %c${newTuning.join(
|
|
@@ -437,7 +438,7 @@ export function systemExclusive(messageData, channelOffset = 0)
|
|
|
437
438
|
consoleColors.info,
|
|
438
439
|
consoleColors.value
|
|
439
440
|
);
|
|
440
|
-
|
|
441
|
+
channelObject.setTuning(cents);
|
|
441
442
|
break;
|
|
442
443
|
}
|
|
443
444
|
return;
|
|
@@ -623,38 +624,38 @@ export function systemExclusive(messageData, channelOffset = 0)
|
|
|
623
624
|
// invalid channel
|
|
624
625
|
return;
|
|
625
626
|
}
|
|
627
|
+
const channelObject = this.workletProcessorChannels[channel];
|
|
626
628
|
const value = messageData[6];
|
|
627
629
|
switch (messageData[5])
|
|
628
630
|
{
|
|
629
|
-
// bank
|
|
631
|
+
// bank-select MSB
|
|
630
632
|
case 0x01:
|
|
631
|
-
|
|
633
|
+
channelObject.controllerChange(midiControllers.bankSelect, value);
|
|
632
634
|
break;
|
|
633
635
|
|
|
634
|
-
// bank
|
|
636
|
+
// bank-select LSB
|
|
635
637
|
case 0x02:
|
|
636
|
-
|
|
638
|
+
channelObject.controllerChange(midiControllers.lsbForControl0BankSelect, value);
|
|
637
639
|
break;
|
|
638
640
|
|
|
639
641
|
// program change
|
|
640
642
|
case 0x03:
|
|
641
|
-
|
|
643
|
+
channelObject.programChange(value);
|
|
642
644
|
break;
|
|
643
645
|
|
|
644
646
|
// note shift
|
|
645
647
|
case 0x08:
|
|
646
|
-
|
|
647
|
-
if (chan.drumChannel)
|
|
648
|
+
if (channelObject.drumChannel)
|
|
648
649
|
{
|
|
649
650
|
return;
|
|
650
651
|
}
|
|
651
652
|
const semitones = value - 64;
|
|
652
|
-
|
|
653
|
+
channelObject.channelTransposeKeyShift = semitones;
|
|
653
654
|
break;
|
|
654
655
|
|
|
655
656
|
// volume
|
|
656
657
|
case 0x0B:
|
|
657
|
-
|
|
658
|
+
channelObject.controllerChange(midiControllers.mainVolume, value);
|
|
658
659
|
break;
|
|
659
660
|
|
|
660
661
|
// pan position
|
|
@@ -663,7 +664,7 @@ export function systemExclusive(messageData, channelOffset = 0)
|
|
|
663
664
|
if (pan === 0)
|
|
664
665
|
{
|
|
665
666
|
// 0 means random
|
|
666
|
-
|
|
667
|
+
channelObject.randomPan = true;
|
|
667
668
|
SpessaSynthInfo(
|
|
668
669
|
`%cRandom pan is set to %cON%c for %c${channel}`,
|
|
669
670
|
consoleColors.info,
|
|
@@ -674,18 +675,18 @@ export function systemExclusive(messageData, channelOffset = 0)
|
|
|
674
675
|
}
|
|
675
676
|
else
|
|
676
677
|
{
|
|
677
|
-
|
|
678
|
+
channelObject.controllerChange(midiControllers.pan, pan);
|
|
678
679
|
}
|
|
679
680
|
break;
|
|
680
681
|
|
|
681
682
|
// reverb
|
|
682
683
|
case 0x13:
|
|
683
|
-
|
|
684
|
+
channelObject.controllerChange(midiControllers.reverbDepth, value);
|
|
684
685
|
break;
|
|
685
686
|
|
|
686
687
|
// chorus
|
|
687
688
|
case 0x12:
|
|
688
|
-
|
|
689
|
+
channelObject.controllerChange(midiControllers.chorusDepth, value);
|
|
689
690
|
break;
|
|
690
691
|
|
|
691
692
|
default:
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { NON_CC_INDEX_OFFSET } from "../../worklet_utilities/controller_tables.js";
|
|
2
|
+
import { modulatorSources } from "../../../../soundfont/basic_soundfont/modulator.js";
|
|
3
|
+
import { computeModulators } from "../../worklet_utilities/worklet_modulator.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Sets the pressure of the given channel
|
|
7
|
+
* @this {WorkletProcessorChannel}
|
|
8
|
+
* @param pressure {number} the pressure of the channel
|
|
9
|
+
*/
|
|
10
|
+
export function channelPressure(pressure)
|
|
11
|
+
{
|
|
12
|
+
this.midiControllers[NON_CC_INDEX_OFFSET + modulatorSources.channelPressure] = pressure << 7;
|
|
13
|
+
this.voices.forEach(v =>
|
|
14
|
+
computeModulators(
|
|
15
|
+
v,
|
|
16
|
+
this.midiControllers,
|
|
17
|
+
0,
|
|
18
|
+
modulatorSources.channelPressure
|
|
19
|
+
));
|
|
20
|
+
this.synth.callEvent("channelpressure", {
|
|
21
|
+
channel: this.channelNumber,
|
|
22
|
+
pressure: pressure
|
|
23
|
+
});
|
|
24
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { NON_CC_INDEX_OFFSET } from "../../worklet_utilities/controller_tables.js";
|
|
2
|
+
import { modulatorSources } from "../../../../soundfont/basic_soundfont/modulator.js";
|
|
3
|
+
import { computeModulators } from "../../worklet_utilities/worklet_modulator.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Sets the pitch of the given channel
|
|
7
|
+
* @this {WorkletProcessorChannel}
|
|
8
|
+
* @param MSB {number} SECOND byte of the MIDI pitchWheel message
|
|
9
|
+
* @param LSB {number} FIRST byte of the MIDI pitchWheel message
|
|
10
|
+
*/
|
|
11
|
+
export function pitchWheel(MSB, LSB)
|
|
12
|
+
{
|
|
13
|
+
if (this.lockedControllers[NON_CC_INDEX_OFFSET + modulatorSources.pitchWheel])
|
|
14
|
+
{
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const bend = (LSB | (MSB << 7));
|
|
18
|
+
this.synth.callEvent("pitchwheel", {
|
|
19
|
+
channel: this.channelNumber,
|
|
20
|
+
MSB: MSB,
|
|
21
|
+
LSB: LSB
|
|
22
|
+
});
|
|
23
|
+
this.midiControllers[NON_CC_INDEX_OFFSET + modulatorSources.pitchWheel] = bend;
|
|
24
|
+
this.voices.forEach(v =>
|
|
25
|
+
// compute pitch modulators
|
|
26
|
+
computeModulators(
|
|
27
|
+
v,
|
|
28
|
+
this.midiControllers,
|
|
29
|
+
0,
|
|
30
|
+
modulatorSources.pitchWheel
|
|
31
|
+
));
|
|
32
|
+
this.synth.sendChannelProperties();
|
|
33
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { computeModulators } from "../../worklet_utilities/worklet_modulator.js";
|
|
2
|
+
import { modulatorSources } from "../../../../soundfont/basic_soundfont/modulator.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Sets the pressure of the given note on a specific channel
|
|
6
|
+
* @this {WorkletProcessorChannel}
|
|
7
|
+
* @param midiNote {number} 0-127
|
|
8
|
+
* @param pressure {number} the pressure of the note
|
|
9
|
+
*/
|
|
10
|
+
export function polyPressure(midiNote, pressure)
|
|
11
|
+
{
|
|
12
|
+
this.voices.forEach(v =>
|
|
13
|
+
{
|
|
14
|
+
if (v.midiNote !== midiNote)
|
|
15
|
+
{
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
v.pressure = pressure;
|
|
19
|
+
computeModulators(
|
|
20
|
+
v,
|
|
21
|
+
this.midiControllers,
|
|
22
|
+
0,
|
|
23
|
+
modulatorSources.polyPressure
|
|
24
|
+
);
|
|
25
|
+
});
|
|
26
|
+
this.synth.callEvent("polypressure", {
|
|
27
|
+
channel: this.channelNumber,
|
|
28
|
+
midiNote: midiNote,
|
|
29
|
+
pressure: pressure
|
|
30
|
+
});
|
|
31
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { customControllers } from "../../worklet_utilities/controller_tables.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Sets the worklet's primary tuning
|
|
5
|
+
* @this {SpessaSynthProcessor}
|
|
6
|
+
* @param cents {number}
|
|
7
|
+
*/
|
|
8
|
+
export function setMasterTuning(cents)
|
|
9
|
+
{
|
|
10
|
+
cents = Math.round(cents);
|
|
11
|
+
for (let i = 0; i < this.workletProcessorChannels.length; i++)
|
|
12
|
+
{
|
|
13
|
+
this.workletProcessorChannels[i].customControllers[customControllers.masterTuning] = cents;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { SpessaSynthInfo } from "../../../../utils/loggin.js";
|
|
2
|
+
import { consoleColors } from "../../../../utils/other.js";
|
|
3
|
+
import { customControllers } from "../../worklet_utilities/controller_tables.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @this {WorkletProcessorChannel}
|
|
7
|
+
* @param cents {number}
|
|
8
|
+
*/
|
|
9
|
+
export function setModulationDepth(cents)
|
|
10
|
+
{
|
|
11
|
+
cents = Math.round(cents);
|
|
12
|
+
SpessaSynthInfo(
|
|
13
|
+
`%cChannel ${this.channelNumber} modulation depth. Cents: %c${cents}`,
|
|
14
|
+
consoleColors.info,
|
|
15
|
+
consoleColors.value
|
|
16
|
+
);
|
|
17
|
+
/* ==============
|
|
18
|
+
IMPORTANT
|
|
19
|
+
here we convert cents into a multiplier.
|
|
20
|
+
midi spec assumes the default is 50 cents,
|
|
21
|
+
but it might be different for the soundfont,
|
|
22
|
+
so we create a multiplier by dividing cents by 50.
|
|
23
|
+
for example, if we want 100 cents, then multiplier will be 2,
|
|
24
|
+
which for a preset with depth of 50 will create 100.
|
|
25
|
+
================ */
|
|
26
|
+
this.customControllers[customControllers.modulationMultiplier] = cents / 50;
|
|
27
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sets the octave tuning for a given channel
|
|
3
|
+
* @this {WorkletProcessorChannel}
|
|
4
|
+
* @param tuning {Int8Array} LENGTH of 12!
|
|
5
|
+
* relative cent tuning.
|
|
6
|
+
* min -128 max 127.
|
|
7
|
+
*/
|
|
8
|
+
export function setOctaveTuning(tuning)
|
|
9
|
+
{
|
|
10
|
+
if (tuning.length !== 12)
|
|
11
|
+
{
|
|
12
|
+
throw new Error("Tuning is not the length of 12.");
|
|
13
|
+
}
|
|
14
|
+
this.channelOctaveTuning = tuning;
|
|
15
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { customControllers } from "../../worklet_utilities/controller_tables.js";
|
|
2
|
+
import { SpessaSynthInfo } from "../../../../utils/loggin.js";
|
|
3
|
+
import { consoleColors } from "../../../../utils/other.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Sets the channel's tuning
|
|
7
|
+
* @this {WorkletProcessorChannel}
|
|
8
|
+
* @param cents {number}
|
|
9
|
+
* @param log {boolean}
|
|
10
|
+
*/
|
|
11
|
+
export function setTuning(cents, log = true)
|
|
12
|
+
{
|
|
13
|
+
cents = Math.round(cents);
|
|
14
|
+
this.customControllers[customControllers.channelTuning] = cents;
|
|
15
|
+
if (!log)
|
|
16
|
+
{
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
SpessaSynthInfo(
|
|
20
|
+
`%cChannel ${this.channelNumber} fine tuning. Cents: %c${cents}`,
|
|
21
|
+
consoleColors.info,
|
|
22
|
+
consoleColors.value
|
|
23
|
+
);
|
|
24
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { customControllers } from "../../worklet_utilities/controller_tables.js";
|
|
2
|
+
import { SpessaSynthInfo } from "../../../../utils/loggin.js";
|
|
3
|
+
import { consoleColors } from "../../../../utils/other.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Sets the channel's tuning in semitones
|
|
7
|
+
* @param semitones {number}
|
|
8
|
+
* @this {WorkletProcessorChannel}
|
|
9
|
+
*/
|
|
10
|
+
export function setTuningSemitones(semitones)
|
|
11
|
+
{
|
|
12
|
+
semitones = Math.round(semitones);
|
|
13
|
+
this.customControllers[customControllers.channelTuningSemitones] = semitones;
|
|
14
|
+
SpessaSynthInfo(
|
|
15
|
+
`%cChannel ${this.channelNumber} coarse tuning. Semitones: %c${semitones}`,
|
|
16
|
+
consoleColors.info,
|
|
17
|
+
consoleColors.value
|
|
18
|
+
);
|
|
19
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transposes all channels by given amount of semitones
|
|
3
|
+
* @this {SpessaSynthProcessor}
|
|
4
|
+
* @param semitones {number} Can be float
|
|
5
|
+
* @param force {boolean} defaults to false, if true transposes the channel even if it's a drum channel
|
|
6
|
+
*/
|
|
7
|
+
export function transposeAllChannels(semitones, force = false)
|
|
8
|
+
{
|
|
9
|
+
this.transposition = 0;
|
|
10
|
+
for (let i = 0; i < this.workletProcessorChannels.length; i++)
|
|
11
|
+
{
|
|
12
|
+
this.workletProcessorChannels[i].transposeChannel(semitones, force);
|
|
13
|
+
}
|
|
14
|
+
this.transposition = semitones;
|
|
15
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { customControllers } from "../../worklet_utilities/controller_tables.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Transposes the channel by given amount of semitones
|
|
5
|
+
* @this {WorkletProcessorChannel}
|
|
6
|
+
* @param semitones {number} Can be float
|
|
7
|
+
* @param force {boolean} defaults to false, if true transposes the channel even if it's a drum channel
|
|
8
|
+
*/
|
|
9
|
+
export function transposeChannel(semitones, force = false)
|
|
10
|
+
{
|
|
11
|
+
if (!this.drumChannel)
|
|
12
|
+
{
|
|
13
|
+
semitones += this.synth.transposition;
|
|
14
|
+
}
|
|
15
|
+
const keyShift = Math.trunc(semitones);
|
|
16
|
+
const currentTranspose = this.channelTransposeKeyShift + this.customControllers[customControllers.channelTransposeFine] / 100;
|
|
17
|
+
if (
|
|
18
|
+
(this.drumChannel && !force)
|
|
19
|
+
|| semitones === currentTranspose
|
|
20
|
+
)
|
|
21
|
+
{
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
if (keyShift !== this.channelTransposeKeyShift)
|
|
25
|
+
{
|
|
26
|
+
this.stopAllNotes(false);
|
|
27
|
+
}
|
|
28
|
+
// apply transpose
|
|
29
|
+
this.channelTransposeKeyShift = keyShift;
|
|
30
|
+
this.customControllers[customControllers.channelTransposeFine] = (semitones - keyShift) * 100;
|
|
31
|
+
}
|
|
@@ -22,6 +22,8 @@ setResetValue(midiControllers.balance, 64);
|
|
|
22
22
|
setResetValue(midiControllers.expressionController, 127);
|
|
23
23
|
setResetValue(midiControllers.pan, 64);
|
|
24
24
|
|
|
25
|
+
setResetValue(midiControllers.portamentoOnOff, 127);
|
|
26
|
+
|
|
25
27
|
setResetValue(midiControllers.timbreHarmonicContent, 64);
|
|
26
28
|
setResetValue(midiControllers.releaseTime, 64);
|
|
27
29
|
setResetValue(midiControllers.attackTime, 64);
|
|
@@ -33,7 +35,7 @@ setResetValue(midiControllers.soundController8, 64);
|
|
|
33
35
|
setResetValue(midiControllers.soundController9, 64);
|
|
34
36
|
setResetValue(midiControllers.generalPurposeController6, 64);
|
|
35
37
|
setResetValue(midiControllers.generalPurposeController8, 64);
|
|
36
|
-
setResetValue(midiControllers.portamentoControl,
|
|
38
|
+
setResetValue(midiControllers.portamentoControl, 60);
|
|
37
39
|
|
|
38
40
|
// pitch wheel
|
|
39
41
|
setResetValue(NON_CC_INDEX_OFFSET + modulatorSources.pitchWheel, 64);
|
|
@@ -61,4 +63,11 @@ export const dataEntryStates = {
|
|
|
61
63
|
NRPFine: 4,
|
|
62
64
|
DataCoarse: 5,
|
|
63
65
|
DataFine: 6
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* This is a channel configuration enum, it is internally sent from Synthetizer via controller change
|
|
69
|
+
* @enum {number}
|
|
70
|
+
*/
|
|
71
|
+
export const channelConfiguration = {
|
|
72
|
+
velocityOverride: 128 // overrides velocity for the given channel
|
|
64
73
|
};
|
|
@@ -18,7 +18,8 @@ export function getLFOValue(startTime, frequency, currentTime)
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
const xVal = (currentTime - startTime) / (1 / frequency) + 0.25;
|
|
21
|
-
// offset by -0.25, otherwise we start at -1 and can have unexpected jump in pitch or
|
|
21
|
+
// offset by -0.25, otherwise we start at -1 and can have unexpected jump in pitch or low-pass
|
|
22
|
+
// (happened with Synth Strings 2)
|
|
22
23
|
|
|
23
24
|
// triangle, not sine
|
|
24
25
|
return Math.abs(xVal - (~~(xVal + 0.5))) * 4 - 1;
|
|
@@ -13,7 +13,7 @@ const MODENV_PEAK = 1;
|
|
|
13
13
|
const CONVEX_ATTACK = new Float32Array(1000);
|
|
14
14
|
for (let i = 0; i < CONVEX_ATTACK.length; i++)
|
|
15
15
|
{
|
|
16
|
-
// this makes the db linear (
|
|
16
|
+
// this makes the db linear (I think)
|
|
17
17
|
CONVEX_ATTACK[i] = getModulatorCurveValue(0, modulatorCurveTypes.convex, i / 1000, 0);
|
|
18
18
|
}
|
|
19
19
|
|
|
@@ -110,7 +110,7 @@ export class WorkletModulationEnvelope
|
|
|
110
110
|
const decayKeyExcursionCents = ((60 - voice.midiNote) * voice.modulatedGenerators[generatorTypes.keyNumToModEnvDecay]);
|
|
111
111
|
const decayTime = timecentsToSeconds(voice.modulatedGenerators[generatorTypes.decayModEnv] + decayKeyExcursionCents);
|
|
112
112
|
// according to the specification, the decay time is the time it takes to reach 0% from 100%.
|
|
113
|
-
// calculate the time to reach actual sustain level
|
|
113
|
+
// calculate the time to reach actual sustain level,
|
|
114
114
|
// for example, sustain 0.6 will be 0.4 of the decay time
|
|
115
115
|
env.decayDuration = decayTime * (1 - env.sustainLevel);
|
|
116
116
|
|
|
@@ -130,7 +130,7 @@ export class WorkletModulationEnvelope
|
|
|
130
130
|
|
|
131
131
|
/**
|
|
132
132
|
* Calculates the current modulation envelope value for the given time and voice
|
|
133
|
-
* @param voice {WorkletVoice} the voice we
|
|
133
|
+
* @param voice {WorkletVoice} the voice we are working on
|
|
134
134
|
* @param currentTime {number} in seconds
|
|
135
135
|
* @param ignoreRelease {boolean} if true, it will compute the value as if the voice was not released
|
|
136
136
|
* @returns {number} modenv value, from 0 to 1
|
|
@@ -141,7 +141,7 @@ export class WorkletModulationEnvelope
|
|
|
141
141
|
if (voice.isInRelease && !ignoreRelease)
|
|
142
142
|
{
|
|
143
143
|
// if the voice is still in the delay phase,
|
|
144
|
-
// start level will be 0
|
|
144
|
+
// start level will be 0 that will result in divide by zero
|
|
145
145
|
if (env.releaseStartLevel === 0)
|
|
146
146
|
{
|
|
147
147
|
return 0;
|
|
@@ -8,12 +8,11 @@ import { modulatorCurveTypes } from "../../../soundfont/basic_soundfont/modulato
|
|
|
8
8
|
// the length of the precomputed curve tables
|
|
9
9
|
export const MOD_PRECOMPUTED_LENGTH = 16384;
|
|
10
10
|
|
|
11
|
-
// Precalculate lookup tables for concave and
|
|
11
|
+
// Precalculate lookup tables for concave and convex curves
|
|
12
12
|
const concave = new Float32Array(MOD_PRECOMPUTED_LENGTH + 1);
|
|
13
13
|
const convex = new Float32Array(MOD_PRECOMPUTED_LENGTH + 1);
|
|
14
14
|
// the equation is taken from FluidSynth as it's the standard for soundFonts
|
|
15
|
-
// more precisely,
|
|
16
|
-
// https://github.com/FluidSynth/fluidsynth/blob/cb8da1e1e2c0a5cff2bab6a419755b598b793384/src/gentables/gen_conv.c#L55
|
|
15
|
+
// more precisely, the gen_conv.c file
|
|
17
16
|
concave[0] = 0;
|
|
18
17
|
concave[concave.length - 1] = 1;
|
|
19
18
|
|
|
@@ -32,7 +31,7 @@ for (let i = 1; i < MOD_PRECOMPUTED_LENGTH - 1; i++)
|
|
|
32
31
|
* @param direction {number} 0 or 1
|
|
33
32
|
* @param curveType {number} see modulatorCurveTypes in modulators.js
|
|
34
33
|
* @param value {number} the linear value, 0 to 1
|
|
35
|
-
* @returns {number} the transformed value, 0 to 1 or -1 to 1
|
|
34
|
+
* @returns {number} the transformed value, 0 to 1, or -1 to 1
|
|
36
35
|
*/
|
|
37
36
|
export function getModulatorCurveValue(direction, curveType, value, polarity)
|
|
38
37
|
{
|
|
@@ -46,7 +45,7 @@ export function getModulatorCurveValue(direction, curveType, value, polarity)
|
|
|
46
45
|
case modulatorCurveTypes.linear:
|
|
47
46
|
if (polarity)
|
|
48
47
|
{
|
|
49
|
-
// bipolar
|
|
48
|
+
// bipolar curve
|
|
50
49
|
return value * 2 - 1;
|
|
51
50
|
}
|
|
52
51
|
return value;
|
|
@@ -33,15 +33,17 @@ for (let pan = MIN_PAN; pan <= MAX_PAN; pan++)
|
|
|
33
33
|
* @param inputBuffer {Float32Array} the input buffer in mono
|
|
34
34
|
* @param outputLeft {Float32Array} left output buffer
|
|
35
35
|
* @param outputRight {Float32Array} right output buffer
|
|
36
|
-
* @param
|
|
37
|
-
* @param
|
|
38
|
-
* @
|
|
36
|
+
* @param reverbLeft {Float32Array} left reverb input
|
|
37
|
+
* @param reverbRight {Float32Array} right reverb input
|
|
38
|
+
* @param chorusLeft {Float32Array} left chorus buffer
|
|
39
|
+
* @param chorusRight {Float32Array} right chorus buffer
|
|
40
|
+
* @this {WorkletProcessorChannel}
|
|
39
41
|
*/
|
|
40
42
|
export function panVoice(voice,
|
|
41
43
|
inputBuffer,
|
|
42
44
|
outputLeft, outputRight,
|
|
43
|
-
|
|
44
|
-
|
|
45
|
+
reverbLeft, reverbRight,
|
|
46
|
+
chorusLeft, chorusRight)
|
|
45
47
|
{
|
|
46
48
|
if (isNaN(inputBuffer[0]))
|
|
47
49
|
{
|
|
@@ -58,40 +60,38 @@ export function panVoice(voice,
|
|
|
58
60
|
}
|
|
59
61
|
else
|
|
60
62
|
{
|
|
61
|
-
|
|
63
|
+
const target = Math.max(-500, Math.min(500, voice.modulatedGenerators[generatorTypes.pan]));
|
|
62
64
|
// smooth out pan to prevent clicking
|
|
63
|
-
voice.currentPan += (
|
|
65
|
+
voice.currentPan += (target - voice.currentPan) * this.synth.panSmoothingFactor;
|
|
66
|
+
pan = voice.currentPan;
|
|
64
67
|
}
|
|
65
68
|
|
|
66
|
-
const gain = this.currentGain;
|
|
69
|
+
const gain = this.synth.currentGain;
|
|
67
70
|
const index = ~~(pan + 500);
|
|
68
71
|
// get voice's gain levels for each channel
|
|
69
|
-
const gainLeft = panTableLeft[index] * gain * this.panLeft;
|
|
70
|
-
const gainRight = panTableRight[index] * gain * this.panRight;
|
|
72
|
+
const gainLeft = panTableLeft[index] * gain * this.synth.panLeft;
|
|
73
|
+
const gainRight = panTableRight[index] * gain * this.synth.panRight;
|
|
71
74
|
|
|
72
75
|
// disable reverb and chorus in one output mode
|
|
73
|
-
if (!this.oneOutputMode)
|
|
76
|
+
if (!this.synth.oneOutputMode)
|
|
74
77
|
{
|
|
75
78
|
// reverb is mono so we need to multiply by gain
|
|
76
|
-
const reverbLevel = this.reverbGain * voice.modulatedGenerators[generatorTypes.reverbEffectsSend] / WORKLET_SYSTEM_REVERB_DIVIDER * gain;
|
|
79
|
+
const reverbLevel = this.synth.reverbGain * voice.modulatedGenerators[generatorTypes.reverbEffectsSend] / WORKLET_SYSTEM_REVERB_DIVIDER * gain;
|
|
77
80
|
// chorus is stereo so we do not need to
|
|
78
|
-
const chorusLevel = this.chorusGain * voice.modulatedGenerators[generatorTypes.chorusEffectsSend] / WORKLET_SYSTEM_CHORUS_DIVIDER;
|
|
81
|
+
const chorusLevel = this.synth.chorusGain * voice.modulatedGenerators[generatorTypes.chorusEffectsSend] / WORKLET_SYSTEM_CHORUS_DIVIDER;
|
|
79
82
|
|
|
80
83
|
if (reverbLevel > 0)
|
|
81
84
|
{
|
|
82
|
-
const reverbLeft = reverb[0];
|
|
83
|
-
const reverbRight = reverb[1];
|
|
84
85
|
for (let i = 0; i < inputBuffer.length; i++)
|
|
85
86
|
{
|
|
86
87
|
reverbLeft[i] += reverbLevel * inputBuffer[i];
|
|
87
|
-
reverbRight[i] += reverbLevel * inputBuffer[i];
|
|
88
88
|
}
|
|
89
|
+
// copy as its mono
|
|
90
|
+
reverbRight.set(reverbLeft);
|
|
89
91
|
}
|
|
90
92
|
|
|
91
93
|
if (chorusLevel > 0)
|
|
92
94
|
{
|
|
93
|
-
const chorusLeft = chorus[0];
|
|
94
|
-
const chorusRight = chorus[1];
|
|
95
95
|
const chorusLeftGain = gainLeft * chorusLevel;
|
|
96
96
|
const chorusRightGain = gainRight * chorusLevel;
|
|
97
97
|
for (let i = 0; i < inputBuffer.length; i++)
|