spessasynth_core 4.4.5 → 4.4.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -16
- package/dist/index.d.ts +383 -125
- package/dist/index.js +795 -299
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -90,7 +90,6 @@ function readLittleEndian(dataArray, bytesAmount, offset = 0) {
|
|
|
90
90
|
* @param dataArray the IndexedByteArray to write to.
|
|
91
91
|
* @param number the number to write.
|
|
92
92
|
* @param byteTarget the amount of bytes to use. Excess bytes will be set to zero.
|
|
93
|
-
* @returns the little endian representation of the number.
|
|
94
93
|
*/
|
|
95
94
|
function writeLittleEndianIndexed(dataArray, number, byteTarget) {
|
|
96
95
|
for (let i = 0; i < byteTarget; i++) dataArray[dataArray.currentIndex++] = number >> i * 8 & 255;
|
|
@@ -359,8 +358,8 @@ var RIFFChunk = class RIFFChunk {
|
|
|
359
358
|
* Writes a RIFF chunk correctly.
|
|
360
359
|
* @param header the fourCC code of the header.
|
|
361
360
|
* @param data the binary chunk data.
|
|
362
|
-
* @param isList if a "LIST" should be set as the chunk type and the actual type should be written at the start of the data.
|
|
363
361
|
* @param rf64 if the chunk uses a 64-bit size.
|
|
362
|
+
* @param isList if a "LIST" should be set as the chunk type and the actual type should be written at the start of the data.
|
|
364
363
|
* @returns the binary data.
|
|
365
364
|
*/
|
|
366
365
|
static write(header, data, rf64 = false, isList = false) {
|
|
@@ -391,8 +390,8 @@ var RIFFChunk = class RIFFChunk {
|
|
|
391
390
|
* This allows avoiding large array allocations and only one writeParts call at the end.
|
|
392
391
|
* @param header the fourCC code of the header.
|
|
393
392
|
* @param chunks binary chunk data parts, will be combined in order.
|
|
394
|
-
* @param isList if a "LIST" should be set as the chunk type and the actual type should be written at the start of the data.
|
|
395
393
|
* @param rf64 if the chunk uses a 64-bit size.
|
|
394
|
+
* @param isList if a "LIST" should be set as the chunk type and the actual type should be written at the start of the data.
|
|
396
395
|
* @returns the chunk as binary blobs.
|
|
397
396
|
*/
|
|
398
397
|
static getParts(header, chunks, rf64 = false, isList = false) {
|
|
@@ -421,8 +420,8 @@ var RIFFChunk = class RIFFChunk {
|
|
|
421
420
|
* It merges them together into data and allocates one large array.
|
|
422
421
|
* @param header the fourCC code of the header.
|
|
423
422
|
* @param chunks binary chunk data parts, will be combined in order.
|
|
424
|
-
* @param isList if a "LIST" should be set as the chunk type and the actual type should be written at the start of the data.
|
|
425
423
|
* @param rf64 if the chunk uses a 64-bit size.
|
|
424
|
+
* @param isList if a "LIST" should be set as the chunk type and the actual type should be written at the start of the data.
|
|
426
425
|
* @returns the binary data.
|
|
427
426
|
*/
|
|
428
427
|
static writeParts(header, chunks, rf64 = false, isList = false) {
|
|
@@ -673,8 +672,9 @@ var SpessaLog = class {
|
|
|
673
672
|
/**
|
|
674
673
|
* Enables or disables logging at various levels.
|
|
675
674
|
* All the input variables are booleans corresponding to the things SpessaSynth logs.
|
|
676
|
-
* @param enableInfo enables info: all general info such as parsing sound banks, MIDI files, RPN changes,
|
|
677
|
-
*
|
|
675
|
+
* @param enableInfo enables info: all general info such as parsing sound banks, MIDI files, RPN changes,
|
|
676
|
+
* messages unrecognized by the synthesizer, etc.
|
|
677
|
+
* @param enableWarn enables warning: unexpected and erroneous behavior, such as invalid data or missing presets.
|
|
678
678
|
* @param enableGroup enables groups: the groups for parsing the sound banks and MIDI files.
|
|
679
679
|
*/
|
|
680
680
|
static setLogLevel(enableInfo, enableWarn, enableGroup) {
|
|
@@ -831,7 +831,7 @@ const MIDIMessageTypes = {
|
|
|
831
831
|
sequenceSpecific: 127
|
|
832
832
|
};
|
|
833
833
|
/**
|
|
834
|
-
* All MIDI 1.0 controller
|
|
834
|
+
* All MIDI 1.0 controller numbers.
|
|
835
835
|
*
|
|
836
836
|
* > **Tip**
|
|
837
837
|
* >
|
|
@@ -888,10 +888,10 @@ const MIDIControllers = {
|
|
|
888
888
|
effectControl2LSB: 45,
|
|
889
889
|
undefinedCC14LSB: 46,
|
|
890
890
|
undefinedCC15LSB: 47,
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
891
|
+
generalPurposeController1LSB: 48,
|
|
892
|
+
generalPurposeController2LSB: 49,
|
|
893
|
+
generalPurposeController3LSB: 50,
|
|
894
|
+
generalPurposeController4LSB: 51,
|
|
895
895
|
undefinedCC20LSB: 52,
|
|
896
896
|
undefinedCC21LSB: 53,
|
|
897
897
|
undefinedCC22LSB: 54,
|
|
@@ -942,32 +942,120 @@ const MIDIControllers = {
|
|
|
942
942
|
nonRegisteredParameterMSB: 99,
|
|
943
943
|
registeredParameterLSB: 100,
|
|
944
944
|
registeredParameterMSB: 101,
|
|
945
|
+
undefinedCC102: 102,
|
|
946
|
+
undefinedCC103: 103,
|
|
947
|
+
undefinedCC104: 104,
|
|
948
|
+
undefinedCC105: 105,
|
|
949
|
+
undefinedCC106: 106,
|
|
950
|
+
undefinedCC107: 107,
|
|
951
|
+
undefinedCC108: 108,
|
|
952
|
+
undefinedCC109: 109,
|
|
953
|
+
undefinedCC110: 110,
|
|
954
|
+
undefinedCC111: 111,
|
|
955
|
+
undefinedCC112: 112,
|
|
956
|
+
undefinedCC113: 113,
|
|
957
|
+
undefinedCC114: 114,
|
|
958
|
+
undefinedCC115: 115,
|
|
959
|
+
undefinedCC116: 116,
|
|
960
|
+
undefinedCC117: 117,
|
|
961
|
+
undefinedCC118: 118,
|
|
962
|
+
undefinedCC119: 119,
|
|
963
|
+
allSoundOff: 120,
|
|
964
|
+
resetAllControllers: 121,
|
|
965
|
+
localControlOnOff: 122,
|
|
966
|
+
allNotesOff: 123,
|
|
967
|
+
omniModeOff: 124,
|
|
968
|
+
omniModeOn: 125,
|
|
969
|
+
monoModeOn: 126,
|
|
970
|
+
polyModeOn: 127,
|
|
971
|
+
/**
|
|
972
|
+
* @deprecated Use {@link MIDIControllers.generalPurposeController1LSB} instead.
|
|
973
|
+
*/
|
|
974
|
+
undefinedCC16LSB: 48,
|
|
975
|
+
/**
|
|
976
|
+
* @deprecated Use {@link MIDIControllers.generalPurposeController2LSB} instead.
|
|
977
|
+
*/
|
|
978
|
+
undefinedCC17LSB: 49,
|
|
979
|
+
/**
|
|
980
|
+
* @deprecated Use {@link MIDIControllers.generalPurposeController3LSB} instead.
|
|
981
|
+
*/
|
|
982
|
+
undefinedCC18LSB: 50,
|
|
983
|
+
/**
|
|
984
|
+
* @deprecated Use {@link MIDIControllers.generalPurposeController4LSB} instead.
|
|
985
|
+
*/
|
|
986
|
+
undefinedCC19LSB: 51,
|
|
987
|
+
/**
|
|
988
|
+
* @deprecated Use {@link MIDIControllers.undefinedCC102} instead.
|
|
989
|
+
*/
|
|
945
990
|
undefinedCC102LSB: 102,
|
|
991
|
+
/**
|
|
992
|
+
* @deprecated Use {@link MIDIControllers.undefinedCC103} instead.
|
|
993
|
+
*/
|
|
946
994
|
undefinedCC103LSB: 103,
|
|
995
|
+
/**
|
|
996
|
+
* @deprecated Use {@link MIDIControllers.undefinedCC104} instead.
|
|
997
|
+
*/
|
|
947
998
|
undefinedCC104LSB: 104,
|
|
999
|
+
/**
|
|
1000
|
+
* @deprecated Use {@link MIDIControllers.undefinedCC105} instead.
|
|
1001
|
+
*/
|
|
948
1002
|
undefinedCC105LSB: 105,
|
|
1003
|
+
/**
|
|
1004
|
+
* @deprecated Use {@link MIDIControllers.undefinedCC106} instead.
|
|
1005
|
+
*/
|
|
949
1006
|
undefinedCC106LSB: 106,
|
|
1007
|
+
/**
|
|
1008
|
+
* @deprecated Use {@link MIDIControllers.undefinedCC107} instead.
|
|
1009
|
+
*/
|
|
950
1010
|
undefinedCC107LSB: 107,
|
|
1011
|
+
/**
|
|
1012
|
+
* @deprecated Use {@link MIDIControllers.undefinedCC108} instead.
|
|
1013
|
+
*/
|
|
951
1014
|
undefinedCC108LSB: 108,
|
|
1015
|
+
/**
|
|
1016
|
+
* @deprecated Use {@link MIDIControllers.undefinedCC109} instead.
|
|
1017
|
+
*/
|
|
952
1018
|
undefinedCC109LSB: 109,
|
|
1019
|
+
/**
|
|
1020
|
+
* @deprecated Use {@link MIDIControllers.undefinedCC110} instead.
|
|
1021
|
+
*/
|
|
953
1022
|
undefinedCC110LSB: 110,
|
|
1023
|
+
/**
|
|
1024
|
+
* @deprecated Use {@link MIDIControllers.undefinedCC111} instead.
|
|
1025
|
+
*/
|
|
954
1026
|
undefinedCC111LSB: 111,
|
|
1027
|
+
/**
|
|
1028
|
+
* @deprecated Use {@link MIDIControllers.undefinedCC112} instead.
|
|
1029
|
+
*/
|
|
955
1030
|
undefinedCC112LSB: 112,
|
|
1031
|
+
/**
|
|
1032
|
+
* @deprecated Use {@link MIDIControllers.undefinedCC113} instead.
|
|
1033
|
+
*/
|
|
956
1034
|
undefinedCC113LSB: 113,
|
|
1035
|
+
/**
|
|
1036
|
+
* @deprecated Use {@link MIDIControllers.undefinedCC114} instead.
|
|
1037
|
+
*/
|
|
957
1038
|
undefinedCC114LSB: 114,
|
|
1039
|
+
/**
|
|
1040
|
+
* @deprecated Use {@link MIDIControllers.undefinedCC115} instead.
|
|
1041
|
+
*/
|
|
958
1042
|
undefinedCC115LSB: 115,
|
|
1043
|
+
/**
|
|
1044
|
+
* @deprecated Use {@link MIDIControllers.undefinedCC116} instead.
|
|
1045
|
+
*/
|
|
959
1046
|
undefinedCC116LSB: 116,
|
|
1047
|
+
/**
|
|
1048
|
+
* @deprecated Use {@link MIDIControllers.undefinedCC117} instead.
|
|
1049
|
+
*/
|
|
960
1050
|
undefinedCC117LSB: 117,
|
|
1051
|
+
/**
|
|
1052
|
+
* @deprecated Use {@link MIDIControllers.undefinedCC118} instead.
|
|
1053
|
+
*/
|
|
961
1054
|
undefinedCC118LSB: 118,
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
allNotesOff: 123,
|
|
967
|
-
omniModeOff: 124,
|
|
968
|
-
omniModeOn: 125,
|
|
969
|
-
monoModeOn: 126,
|
|
970
|
-
polyModeOn: 127
|
|
1055
|
+
/**
|
|
1056
|
+
* @deprecated Use {@link MIDIControllers.undefinedCC119} instead.
|
|
1057
|
+
*/
|
|
1058
|
+
undefinedCC119LSB: 119
|
|
971
1059
|
};
|
|
972
1060
|
/**
|
|
973
1061
|
* General MIDI Registered Parameter Numbers.
|
|
@@ -1024,7 +1112,7 @@ const NonRegisteredParameterTypesLSB = {
|
|
|
1024
1112
|
// src/midi/midi_message.ts
|
|
1025
1113
|
/**
|
|
1026
1114
|
* Midi_message.ts
|
|
1027
|
-
* purpose: contains
|
|
1115
|
+
* purpose: contains the MIDIMessage class and functions to create common messages
|
|
1028
1116
|
*/
|
|
1029
1117
|
/**
|
|
1030
1118
|
* This class represents a single MIDI 1.0 message.
|
|
@@ -1251,7 +1339,7 @@ const EMBEDDED_SOUND_BANK_ID = `SPESSASYNTH_EMBEDDED_BANK_${Math.random()}_DO_NO
|
|
|
1251
1339
|
const GENERATOR_OVERRIDE_NO_CHANGE_VALUE = 32767;
|
|
1252
1340
|
const DEFAULT_SYNTH_METHOD_OPTIONS = { time: 0 };
|
|
1253
1341
|
/**
|
|
1254
|
-
* If the note is released faster than that, it forced to last that long
|
|
1342
|
+
* If the note is released faster than that, it is forced to last that long
|
|
1255
1343
|
* This is used mostly for drum channels, where a lot of midis like to send instant note off after a note on
|
|
1256
1344
|
* @internal
|
|
1257
1345
|
*/
|
|
@@ -1900,13 +1988,13 @@ const GeneratorLimits = Object.freeze({
|
|
|
1900
1988
|
},
|
|
1901
1989
|
[GeneratorTypes.vibLfoWaveform]: {
|
|
1902
1990
|
min: 0,
|
|
1903
|
-
max:
|
|
1991
|
+
max: 2,
|
|
1904
1992
|
def: 0,
|
|
1905
1993
|
nrpn: 0
|
|
1906
1994
|
},
|
|
1907
1995
|
[GeneratorTypes.modLfoWaveform]: {
|
|
1908
1996
|
min: 0,
|
|
1909
|
-
max:
|
|
1997
|
+
max: 2,
|
|
1910
1998
|
def: 0,
|
|
1911
1999
|
nrpn: 0
|
|
1912
2000
|
},
|
|
@@ -2051,7 +2139,7 @@ const ModulatorControllerSources = {
|
|
|
2051
2139
|
*/
|
|
2052
2140
|
pitchWheel: 14,
|
|
2053
2141
|
/**
|
|
2054
|
-
* The current pitch wheel range (where 1 means
|
|
2142
|
+
* The current pitch wheel range (where 1 means 128 semitones) on the channel.
|
|
2055
2143
|
*/
|
|
2056
2144
|
pitchWheelRange: 16,
|
|
2057
2145
|
/**
|
|
@@ -2074,7 +2162,7 @@ const ModulatorCurveTypes = {
|
|
|
2074
2162
|
*/
|
|
2075
2163
|
concave: 1,
|
|
2076
2164
|
/**
|
|
2077
|
-
*
|
|
2165
|
+
* Exponential ramp, the inverse of concave.
|
|
2078
2166
|
*/
|
|
2079
2167
|
convex: 2,
|
|
2080
2168
|
/**
|
|
@@ -2136,6 +2224,7 @@ DEFAULT_DRUM_REVERB[36] = 0;
|
|
|
2136
2224
|
* Reset all controllers for channel.
|
|
2137
2225
|
* This will reset all controllers to their default values,
|
|
2138
2226
|
* except for the locked controllers.
|
|
2227
|
+
* @param sendCCEvents If controller change events should be emitted for the reset controllers.
|
|
2139
2228
|
*/
|
|
2140
2229
|
function resetChannelInternal(sendCCEvents = true) {
|
|
2141
2230
|
for (let cc = 0; cc < 128; cc++) {
|
|
@@ -2170,6 +2259,7 @@ function resetChannelInternal(sendCCEvents = true) {
|
|
|
2170
2259
|
this.resetMIDIParameter("dryLevel");
|
|
2171
2260
|
this.octaveTuning.fill(0);
|
|
2172
2261
|
this.lastPortamentoNote = this.channelSystem === "xg" ? 60 : -1;
|
|
2262
|
+
this.portamentoForce = false;
|
|
2173
2263
|
this.resetDrumParams();
|
|
2174
2264
|
this.resetGeneratorOverrides();
|
|
2175
2265
|
this.resetGeneratorOffsets();
|
|
@@ -2293,7 +2383,7 @@ var DrumParameterUtils = class {
|
|
|
2293
2383
|
dest.rxNoteOn = source.rxNoteOn;
|
|
2294
2384
|
}
|
|
2295
2385
|
/**
|
|
2296
|
-
* Checks if this
|
|
2386
|
+
* Checks if this drum parameter is the default.
|
|
2297
2387
|
* @param param the param to check.
|
|
2298
2388
|
* @param midiNote the MIDI note of this parameter.
|
|
2299
2389
|
*/
|
|
@@ -2389,6 +2479,7 @@ function applySnapshotInternal(midi, snapshot) {
|
|
|
2389
2479
|
// src/midi/midi_tools/get_note_times.ts
|
|
2390
2480
|
/**
|
|
2391
2481
|
* Calculates all note times in seconds.
|
|
2482
|
+
* MIDI ports are ignored, every port is merged into the same 16 channels.
|
|
2392
2483
|
* @param midi the midi to use
|
|
2393
2484
|
* @param minDrumLength the shortest a drum note (channel 10) can be, in seconds.
|
|
2394
2485
|
* @returns an array of 16 channels, each channel containing its notes,
|
|
@@ -2462,26 +2553,32 @@ var MIDIPatchTools = class MIDIPatchTools {
|
|
|
2462
2553
|
/**
|
|
2463
2554
|
* Converts a given {@link MIDIPatch} to a string.
|
|
2464
2555
|
* The format is:
|
|
2465
|
-
* - `DRUM:program` for `isGMGSDrum` set to `true`.
|
|
2556
|
+
* - `DRUM:bankMSB:bankLSB:program` for `isGMGSDrum` set to `true`.
|
|
2466
2557
|
* - `bankLSB:bankMSB:program` for `isGMGSDrum` set to `false`.
|
|
2558
|
+
* @param patch The patch to convert.
|
|
2467
2559
|
*/
|
|
2468
2560
|
static toMIDIString(patch) {
|
|
2469
|
-
if (patch.isGMGSDrum) return `DRUM:${patch.program}`;
|
|
2561
|
+
if (patch.isGMGSDrum) return `DRUM:${patch.bankMSB}:${patch.bankLSB}:${patch.program}`;
|
|
2470
2562
|
return `${patch.bankLSB}:${patch.bankMSB}:${patch.program}`;
|
|
2471
2563
|
}
|
|
2472
2564
|
/**
|
|
2473
2565
|
* Gets {@link MIDIPatch} from a given string.
|
|
2566
|
+
* @param string The string in the {@link MIDIPatchTools.toMIDIString} format.
|
|
2474
2567
|
*/
|
|
2475
2568
|
static fromMIDIString(string) {
|
|
2476
2569
|
const parts = string.split(":");
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2570
|
+
if (string.startsWith("DRUM")) {
|
|
2571
|
+
if (parts.length !== 4 && parts.length !== 2) throw new Error(`Invalid MIDI string: ${string}`);
|
|
2572
|
+
const long = parts.length === 4;
|
|
2573
|
+
return {
|
|
2574
|
+
bankMSB: long ? Number.parseInt(parts[1]) : 0,
|
|
2575
|
+
bankLSB: long ? Number.parseInt(parts[2]) : 0,
|
|
2576
|
+
program: Number.parseInt(parts[parts.length - 1]),
|
|
2577
|
+
isGMGSDrum: true
|
|
2578
|
+
};
|
|
2579
|
+
}
|
|
2580
|
+
if (parts.length !== 3) throw new Error(`Invalid MIDI string: ${string}`);
|
|
2581
|
+
return {
|
|
2485
2582
|
bankLSB: Number.parseInt(parts[0]),
|
|
2486
2583
|
bankMSB: Number.parseInt(parts[1]),
|
|
2487
2584
|
program: Number.parseInt(parts[2]),
|
|
@@ -2493,12 +2590,14 @@ var MIDIPatchTools = class MIDIPatchTools {
|
|
|
2493
2590
|
* The format is:
|
|
2494
2591
|
* - `<MIDIPatch string> D <name>` for `isDrum` set to `true`.
|
|
2495
2592
|
* - `<MIDIPatch string> M <name>` for `isDrum` set to `false`.
|
|
2593
|
+
* @param patch The patch to convert.
|
|
2496
2594
|
*/
|
|
2497
2595
|
static toFullMIDIString(patch) {
|
|
2498
2596
|
return `${this.toMIDIString(patch)} ${patch.isDrum ? "D" : "M"} ${patch.name}`;
|
|
2499
2597
|
}
|
|
2500
2598
|
/**
|
|
2501
2599
|
* Gets {@link MIDIPatchFull} from a given string.
|
|
2600
|
+
* @param string The string in the {@link MIDIPatchTools.toFullMIDIString} format.
|
|
2502
2601
|
*/
|
|
2503
2602
|
static fromFullMIDIString(string) {
|
|
2504
2603
|
const firstSpace = string.indexOf(" ");
|
|
@@ -2515,14 +2614,18 @@ var MIDIPatchTools = class MIDIPatchTools {
|
|
|
2515
2614
|
}
|
|
2516
2615
|
/**
|
|
2517
2616
|
* Checks if two MIDI patches represent the same one.
|
|
2617
|
+
* @param patch1 The first patch.
|
|
2618
|
+
* @param patch2 The second patch.
|
|
2518
2619
|
*/
|
|
2519
2620
|
static matches(patch1, patch2) {
|
|
2520
|
-
if (patch1.isGMGSDrum || patch2.isGMGSDrum) return patch1.isGMGSDrum === patch2.isGMGSDrum && patch1.program === patch2.program;
|
|
2621
|
+
if (patch1.isGMGSDrum || patch2.isGMGSDrum) return patch1.isGMGSDrum === patch2.isGMGSDrum && patch1.program === patch2.program && patch1.bankMSB === patch2.bankMSB && patch1.bankLSB === patch2.bankLSB;
|
|
2521
2622
|
return patch1.program === patch2.program && patch1.bankLSB === patch2.bankLSB && patch1.bankMSB === patch2.bankMSB;
|
|
2522
2623
|
}
|
|
2523
2624
|
/**
|
|
2524
2625
|
* A comparison function for `.sort()` or `.toSorted()`,
|
|
2525
|
-
* ordering the patches in ascending order.
|
|
2626
|
+
* ordering the patches in ascending order: by program, then bank MSB, then bank LSB, with GM/GS drums last.
|
|
2627
|
+
* @param a The first patch.
|
|
2628
|
+
* @param b The second patch.
|
|
2526
2629
|
*/
|
|
2527
2630
|
static compare(a, b) {
|
|
2528
2631
|
if (a.isGMGSDrum && !b.isGMGSDrum) return 1;
|
|
@@ -2533,6 +2636,7 @@ var MIDIPatchTools = class MIDIPatchTools {
|
|
|
2533
2636
|
}
|
|
2534
2637
|
/**
|
|
2535
2638
|
* Checks if the given {@link MIDIPatchFull} is an XG/GM2 drum patch.
|
|
2639
|
+
* @param p The patch to check.
|
|
2536
2640
|
*/
|
|
2537
2641
|
static isXGDrum(p) {
|
|
2538
2642
|
return p.isDrum && !p.isGMGSDrum;
|
|
@@ -2544,7 +2648,7 @@ var MIDIPatchTools = class MIDIPatchTools {
|
|
|
2544
2648
|
* @param patch The {@link MIDIPatch} to select.
|
|
2545
2649
|
* @param system The MIDI system to select for.
|
|
2546
2650
|
* @returns The selected patch.
|
|
2547
|
-
* @throws Error An error if the array is empty
|
|
2651
|
+
* @throws Error An error if the array is empty.
|
|
2548
2652
|
*/
|
|
2549
2653
|
static selectPatch(patches, patch, system) {
|
|
2550
2654
|
if (patches.length === 0) throw new Error("No presets!");
|
|
@@ -2563,7 +2667,7 @@ var MIDIPatchTools = class MIDIPatchTools {
|
|
|
2563
2667
|
SpessaLog.info(`%cPreset %c${MIDIPatchTools.toMIDIString(patch)}%c not found. (${system}) Replaced with %c${this.toFullMIDIString(pres)}`, ConsoleColors.warn, ConsoleColors.unrecognized, ConsoleColors.warn, ConsoleColors.value);
|
|
2564
2668
|
};
|
|
2565
2669
|
if (isGMGSDrum) {
|
|
2566
|
-
let p = patches.find((p) => p.isGMGSDrum && p.program === program);
|
|
2670
|
+
let p = patches.find((p) => p.isGMGSDrum && p.program === program && p.bankLSB === bankLSB) ?? patches.find((p) => p.isGMGSDrum && p.program === program);
|
|
2567
2671
|
if (p) {
|
|
2568
2672
|
returnReplacement(p);
|
|
2569
2673
|
return p;
|
|
@@ -2811,7 +2915,7 @@ var MIDIUtils = class MIDIUtils {
|
|
|
2811
2915
|
* @param channel The MIDI channel number.
|
|
2812
2916
|
* @param rpn The 14-bit RPN number.
|
|
2813
2917
|
* @param value The 14-bit value for that number.
|
|
2814
|
-
* @returns The analysis result
|
|
2918
|
+
* @returns The analysis result of the message.
|
|
2815
2919
|
*/
|
|
2816
2920
|
static analyzeRPN(channel, rpn, value) {
|
|
2817
2921
|
switch (rpn) {
|
|
@@ -2850,7 +2954,7 @@ var MIDIUtils = class MIDIUtils {
|
|
|
2850
2954
|
* @param value The 14-bit value for that number.
|
|
2851
2955
|
* @param drumPitchStep The drum pitch coarse resolution in semitones.
|
|
2852
2956
|
* 0.5 for GS (SC-88 and above), 1 for XG and the SC-55 drum map (bank LSB 1).
|
|
2853
|
-
* @returns The analysis result
|
|
2957
|
+
* @returns The analysis result of the message.
|
|
2854
2958
|
*/
|
|
2855
2959
|
static analyzeNRPN(channel, nrpn, value, drumPitchStep = .5) {
|
|
2856
2960
|
const msb = nrpn >> 7;
|
|
@@ -4877,7 +4981,7 @@ var MIDIEditor = class {
|
|
|
4877
4981
|
* GM -> GS -> note data
|
|
4878
4982
|
*
|
|
4879
4983
|
* That GM reset is meaningless, so it can stay unmodified as the actual reset that matters is GS.
|
|
4880
|
-
* Meaningful ones are replaced with
|
|
4984
|
+
* Meaningful GM ones are replaced with the fallback reset in-place
|
|
4881
4985
|
* (unless the `system` parameter is explicitly set).
|
|
4882
4986
|
*/
|
|
4883
4987
|
meaningfulResets = new Array();
|
|
@@ -5946,7 +6050,7 @@ var MIDITrack = class MIDITrack {
|
|
|
5946
6050
|
*/
|
|
5947
6051
|
port = 0;
|
|
5948
6052
|
/**
|
|
5949
|
-
* A set that contains the MIDI channel numbers
|
|
6053
|
+
* A set that contains the MIDI channel numbers (0-15) that have note on events in this track.
|
|
5950
6054
|
*/
|
|
5951
6055
|
channels = /* @__PURE__ */ new Set();
|
|
5952
6056
|
/**
|
|
@@ -5975,7 +6079,7 @@ var MIDITrack = class MIDITrack {
|
|
|
5975
6079
|
}
|
|
5976
6080
|
/**
|
|
5977
6081
|
* Adds events to the track.
|
|
5978
|
-
* @param index The index at which to add these
|
|
6082
|
+
* @param index The index at which to add these events.
|
|
5979
6083
|
* @param events The events to add.
|
|
5980
6084
|
*/
|
|
5981
6085
|
addEvents(index, ...events) {
|
|
@@ -7043,7 +7147,7 @@ var BasicSample = class {
|
|
|
7043
7147
|
*
|
|
7044
7148
|
* > **Warning**
|
|
7045
7149
|
* >
|
|
7046
|
-
* > Do not change this value directly.
|
|
7150
|
+
* > Do not change this value directly. Use {@link BasicSample.setSampleType},
|
|
7047
7151
|
* > {@link BasicSample.setLinkedSample} or {@link BasicSample.unlinkSample} instead.
|
|
7048
7152
|
*/
|
|
7049
7153
|
sampleType;
|
|
@@ -7105,7 +7209,7 @@ var BasicSample = class {
|
|
|
7105
7209
|
* @param sampleRate The sample's rate in Hz.
|
|
7106
7210
|
* @param originalKey The sample's pitch as a MIDI note number.
|
|
7107
7211
|
* @param pitchCorrection The sample's pitch correction in cents.
|
|
7108
|
-
* @param sampleType The sample's type,
|
|
7212
|
+
* @param sampleType The sample's type, one of the {@link SampleTypes}.
|
|
7109
7213
|
* @param loopStart The sample's loop start relative to the sample start in sample points.
|
|
7110
7214
|
* @param loopEnd The sample's loop end relative to the sample start in sample points. Exclusive.
|
|
7111
7215
|
*/
|
|
@@ -7705,7 +7809,7 @@ var ModulatorSource = class ModulatorSource {
|
|
|
7705
7809
|
/**
|
|
7706
7810
|
* If this field is set true, the direction of the controller should be from the maximum value to the minimum value. So, for
|
|
7707
7811
|
* example, if the controller source is Key Number, then a Key Number value of 0 corresponds to the maximum possible
|
|
7708
|
-
* controller output, and the Key Number value of 127 corresponds to the minimum possible controller
|
|
7812
|
+
* controller output, and the Key Number value of 127 corresponds to the minimum possible controller output.
|
|
7709
7813
|
*/
|
|
7710
7814
|
isNegative;
|
|
7711
7815
|
/**
|
|
@@ -8519,7 +8623,7 @@ var BasicPreset = class BasicPreset {
|
|
|
8519
8623
|
* >
|
|
8520
8624
|
* > Consider reading about [the DLS conversion problem](../../../docs/extra/dls-conversion-problem.md).
|
|
8521
8625
|
*
|
|
8522
|
-
* @returns The instrument containing the flattened zones. If flattening was successful, it should exactly the same as this preset.
|
|
8626
|
+
* @returns The instrument containing the flattened zones. If flattening was successful, it should sound exactly the same as this preset.
|
|
8523
8627
|
*/
|
|
8524
8628
|
toFlattenedInstrument() {
|
|
8525
8629
|
const addUnique = (main, adder) => {
|
|
@@ -8710,7 +8814,7 @@ var BasicInstrument = class {
|
|
|
8710
8814
|
return zone;
|
|
8711
8815
|
}
|
|
8712
8816
|
/**
|
|
8713
|
-
* Links the instrument
|
|
8817
|
+
* Links the instrument to a given preset.
|
|
8714
8818
|
* @param preset The preset to link to.
|
|
8715
8819
|
* @internal
|
|
8716
8820
|
*/
|
|
@@ -10170,8 +10274,8 @@ var DownloadableSoundsArticulation = class extends DLSVerifier {
|
|
|
10170
10274
|
for (const modulator of zone.modulators) ConnectionBlock.fromSFModulator(modulator, this);
|
|
10171
10275
|
}
|
|
10172
10276
|
/**
|
|
10173
|
-
*
|
|
10174
|
-
* @param chunks
|
|
10277
|
+
* Reads the articulation from a chunk list.
|
|
10278
|
+
* @param chunks Chunk list for the region/instrument (containing lar2 or lart)
|
|
10175
10279
|
*/
|
|
10176
10280
|
read(chunks) {
|
|
10177
10281
|
const lart = RIFFChunk.findListType(chunks, "lart");
|
|
@@ -10493,7 +10597,6 @@ var DownloadableSoundsRegion = class DownloadableSoundsRegion extends DLSVerifie
|
|
|
10493
10597
|
// src/soundbank/downloadable_sounds/instrument.ts
|
|
10494
10598
|
/**
|
|
10495
10599
|
* Represents a proper DLS instrument, with regions and articulation.
|
|
10496
|
-
* DLS
|
|
10497
10600
|
*/
|
|
10498
10601
|
var DownloadableSoundsInstrument = class DownloadableSoundsInstrument extends DLSVerifier {
|
|
10499
10602
|
articulation = new DownloadableSoundsArticulation();
|
|
@@ -10765,7 +10868,7 @@ var DownloadableSounds = class DownloadableSounds extends DLSVerifier {
|
|
|
10765
10868
|
for (let i = 0; i < bank.samples.length; i++) {
|
|
10766
10869
|
const s = bank.samples[i];
|
|
10767
10870
|
dls.samples.push(DownloadableSoundsSample.fromSFSample(s));
|
|
10768
|
-
progressFunc?.(i / bank.samples.length);
|
|
10871
|
+
progressFunc?.((i + 1) / bank.samples.length);
|
|
10769
10872
|
}
|
|
10770
10873
|
for (const p of bank.presets) dls.instruments.push(DownloadableSoundsInstrument.fromSFPreset(p, bank.samples));
|
|
10771
10874
|
SpessaLog.info("%cConversion complete!", ConsoleColors.recognized);
|
|
@@ -10799,12 +10902,12 @@ var DownloadableSounds = class DownloadableSounds extends DLSVerifier {
|
|
|
10799
10902
|
let written = 0;
|
|
10800
10903
|
for (const s of this.samples) {
|
|
10801
10904
|
const out = s.write();
|
|
10905
|
+
written++;
|
|
10802
10906
|
options.progressFunction?.(written / this.samples.length);
|
|
10803
10907
|
SpessaLog.info(`%cWrote sample %c${written}. ${s.name}%c of %c${this.samples.length}.`, ConsoleColors.info, ConsoleColors.recognized, ConsoleColors.info, ConsoleColors.recognized);
|
|
10804
10908
|
ptblOffsets.push(currentIndex);
|
|
10805
10909
|
currentIndex += out.reduce((sum, cur) => sum + cur.length, 0);
|
|
10806
10910
|
samples.push(...out);
|
|
10807
|
-
written++;
|
|
10808
10911
|
}
|
|
10809
10912
|
const wvpl = RIFFChunk.getParts("wvpl", samples, false, true);
|
|
10810
10913
|
SpessaLog.info("%cSucceeded!", ConsoleColors.recognized);
|
|
@@ -11016,9 +11119,8 @@ var SoundBankCloner = class {
|
|
|
11016
11119
|
* This module handles parsing and writing SoundFont2 (`.sf2`, `.sf3` and `.sfogg`) files.
|
|
11017
11120
|
* It represents a single sound bank.
|
|
11018
11121
|
*
|
|
11019
|
-
* It also contains support for `.dls` files and experimental read and write support for [64-bit SFE](https://
|
|
11122
|
+
* It also contains support for `.dls` files and experimental read and write support for [64-bit SFE](https://sfe-team-was-taken.github.io/SFE/).
|
|
11020
11123
|
*
|
|
11021
|
-
|
|
11022
11124
|
* **Specifications:**
|
|
11023
11125
|
*
|
|
11024
11126
|
* - [SoundFont2 Specification](http://www.synthfont.com/sfspec24.pdf)
|
|
@@ -11062,7 +11164,7 @@ var BasicSoundBank = class BasicSoundBank {
|
|
|
11062
11164
|
}
|
|
11063
11165
|
};
|
|
11064
11166
|
/**
|
|
11065
|
-
* An array of all presets in the bank, ordered by
|
|
11167
|
+
* An array of all presets in the bank, ordered by program and bank number, with GM/GS drum presets last.
|
|
11066
11168
|
*/
|
|
11067
11169
|
presets = [];
|
|
11068
11170
|
/**
|
|
@@ -11129,8 +11231,9 @@ var BasicSoundBank = class BasicSoundBank {
|
|
|
11129
11231
|
return b;
|
|
11130
11232
|
}
|
|
11131
11233
|
/**
|
|
11132
|
-
* Creates a simple
|
|
11234
|
+
* Creates a simple SF2 file with a single saw wave preset.
|
|
11133
11235
|
* Useful for testing synthesizer's functionality without providing a file.
|
|
11236
|
+
* @returns The binary representation of the SF2 file.
|
|
11134
11237
|
*/
|
|
11135
11238
|
static getSampleSoundBankFile() {
|
|
11136
11239
|
const font = new BasicSoundBank();
|
|
@@ -11158,12 +11261,17 @@ var BasicSoundBank = class BasicSoundBank {
|
|
|
11158
11261
|
return font.writeSF2();
|
|
11159
11262
|
}
|
|
11160
11263
|
/**
|
|
11161
|
-
* Copies a given sound bank,
|
|
11264
|
+
* Copies a given sound bank, cloning all presets, instruments and samples.
|
|
11265
|
+
* The sample data is shared with the original, not copied.
|
|
11266
|
+
* Elements sharing a name are all copied, the repeats get a free name like `Piano-1`.
|
|
11162
11267
|
* @param bank The sound bank to copy.
|
|
11163
11268
|
*/
|
|
11164
11269
|
static copyFrom(bank) {
|
|
11165
11270
|
const copied = new BasicSoundBank(bank.type);
|
|
11166
|
-
const cloner = new SoundBankCloner(copied
|
|
11271
|
+
const cloner = new SoundBankCloner(copied, {
|
|
11272
|
+
reuseByName: false,
|
|
11273
|
+
reuseChildrenByName: false
|
|
11274
|
+
});
|
|
11167
11275
|
for (const p of bank.presets) cloner.clonePreset(p);
|
|
11168
11276
|
copied.soundBankInfo = { ...bank.soundBankInfo };
|
|
11169
11277
|
copied.defaultModulators = bank.defaultModulators.map((m) => Modulator.copyFrom(m));
|
|
@@ -11189,8 +11297,8 @@ var BasicSoundBank = class BasicSoundBank {
|
|
|
11189
11297
|
*
|
|
11190
11298
|
* > **Warning**
|
|
11191
11299
|
* >
|
|
11192
|
-
* > Note that
|
|
11193
|
-
* > in permanent sample quality loss!
|
|
11300
|
+
* > Note that compressing (sample format `compressed`) usually results
|
|
11301
|
+
* > in permanent sample quality loss! Decompressing does not restore it.
|
|
11194
11302
|
* > This method is memory and CPU intensive with large sound banks.
|
|
11195
11303
|
*
|
|
11196
11304
|
* @param options Options associated with setting the sample format.
|
|
@@ -11234,7 +11342,7 @@ var BasicSoundBank = class BasicSoundBank {
|
|
|
11234
11342
|
}
|
|
11235
11343
|
/**
|
|
11236
11344
|
* Writes out a DLS Level 2 sound bank. This may not be 100% accurate.
|
|
11237
|
-
*
|
|
11345
|
+
* Sample data is always written in the S16LE PCM encoding.
|
|
11238
11346
|
*
|
|
11239
11347
|
* > **Note**
|
|
11240
11348
|
* >
|
|
@@ -11357,16 +11465,15 @@ var BasicSoundBank = class BasicSoundBank {
|
|
|
11357
11465
|
/**
|
|
11358
11466
|
* Trims the sound bank _in-place_ to only contain samples in a given MIDI file.
|
|
11359
11467
|
*
|
|
11360
|
-
|
|
11361
11468
|
* Absent presets will be removed from the sound bank,
|
|
11362
11469
|
* and samples that don't get activated in the remaining presets will be removed as well.
|
|
11363
11470
|
*
|
|
11364
11471
|
* > **Note**
|
|
11365
11472
|
* >
|
|
11366
|
-
* > This exact type is returned from {@link BasicMIDI.getUsedProgramsAndKeys}
|
|
11473
|
+
* > This exact type is returned from {@link BasicMIDI.getUsedProgramsAndKeys}.
|
|
11367
11474
|
* > Consider reading the page for more explanation about the parameter.
|
|
11368
11475
|
*
|
|
11369
|
-
* @param presetData
|
|
11476
|
+
* @param presetData A `Map`: `BasicPreset` -> `Map<MIDI note, Set<velocity>>`.
|
|
11370
11477
|
* Absent presets will be removed from the sound bank,
|
|
11371
11478
|
* and samples that don't get activated in the remaining presets will be removed as well.
|
|
11372
11479
|
*/
|
|
@@ -13766,11 +13873,13 @@ const DEFAULT_RMIDI_WRITE_OPTIONS = {
|
|
|
13766
13873
|
*/
|
|
13767
13874
|
function writeRMIDIInternal(mid, soundBankBinary, options) {
|
|
13768
13875
|
const metadata = { ...options.metadata };
|
|
13769
|
-
if (options.correctBankOffset && !options.soundBank) throw new Error("Sound bank must be provided if correcting bank offset.");
|
|
13770
13876
|
SpessaLog.group("%cWriting the RMIDI File...", ConsoleColors.info);
|
|
13771
13877
|
SpessaLog.info("metadata", metadata);
|
|
13772
13878
|
SpessaLog.info("Initial bank offset", mid.bankOffset);
|
|
13773
|
-
if (options.correctBankOffset
|
|
13879
|
+
if (options.correctBankOffset) {
|
|
13880
|
+
if (options.soundBank) correctBankOffsetInternal(mid, options.bankOffset, options.soundBank);
|
|
13881
|
+
else SpessaLog.warn("No sound bank provided, skipping the bank offset correction.");
|
|
13882
|
+
}
|
|
13774
13883
|
const newMid = new IndexedByteArray(mid.writeMIDI());
|
|
13775
13884
|
metadata.name ??= mid.getName();
|
|
13776
13885
|
metadata.creationDate ??= /* @__PURE__ */ new Date();
|
|
@@ -13958,7 +14067,8 @@ var BasicMIDI = class BasicMIDI {
|
|
|
13958
14067
|
* The loop points (in ticks) of the sequence, including both start and end points.
|
|
13959
14068
|
*
|
|
13960
14069
|
* If there's nothing detected, the loop will start from the first note on event and end will be the last voice message.
|
|
13961
|
-
* Current looping detection is: CC 2/4,
|
|
14070
|
+
* Current looping detection is: CC 2/4 (Touhou), CC 111 (RPG Maker), CC 116/117 and 118/119 (EMIDI/XMI)
|
|
14071
|
+
* and the "start", "loopStart" and "loopEnd" markers.
|
|
13962
14072
|
*/
|
|
13963
14073
|
loop = {
|
|
13964
14074
|
start: 0,
|
|
@@ -13976,6 +14086,7 @@ var BasicMIDI = class BasicMIDI {
|
|
|
13976
14086
|
format = 0;
|
|
13977
14087
|
/**
|
|
13978
14088
|
* The RMID (Resource-Interchangeable MIDI) info data, if the file is RMID formatted.
|
|
14089
|
+
* XMF metadata and converted tracker modules are stored here as well.
|
|
13979
14090
|
* Otherwise, this object is empty.
|
|
13980
14091
|
* Info type: Chunk data as a binary array.
|
|
13981
14092
|
*
|
|
@@ -14047,7 +14158,7 @@ var BasicMIDI = class BasicMIDI {
|
|
|
14047
14158
|
* This function reads the MIDI file format, extracts the header and track chunks,
|
|
14048
14159
|
* and populates the BasicMIDI instance with the parsed data.
|
|
14049
14160
|
* It supports Standard MIDI Files (SMF), RIFF MIDI (RMIDI), and Extensible Music Format (XMF).
|
|
14050
|
-
* It also handles embedded soundbanks in RMIDI files.
|
|
14161
|
+
* It also handles embedded soundbanks in RMIDI and XMF files.
|
|
14051
14162
|
* Tracker modules (ProTracker MOD, Fast Tracker 2 XM, Scream Tracker 3 S3M) are converted
|
|
14052
14163
|
* into MIDI with an embedded sound bank.
|
|
14053
14164
|
*/
|
|
@@ -14086,7 +14197,7 @@ var BasicMIDI = class BasicMIDI {
|
|
|
14086
14197
|
return m;
|
|
14087
14198
|
}
|
|
14088
14199
|
/**
|
|
14089
|
-
* Copies another instance this `BasicMIDI` instance, including track data.
|
|
14200
|
+
* Copies another instance into this `BasicMIDI` instance, including track data.
|
|
14090
14201
|
* @param mid The MIDI to copy.
|
|
14091
14202
|
*/
|
|
14092
14203
|
copyFrom(mid) {
|
|
@@ -14159,7 +14270,7 @@ var BasicMIDI = class BasicMIDI {
|
|
|
14159
14270
|
}
|
|
14160
14271
|
/**
|
|
14161
14272
|
* Preloads all voices for this sequence in a given {@link SpessaSynthProcessor}.
|
|
14162
|
-
* This caches all the needed voices for playing back this
|
|
14273
|
+
* This caches all the needed voices for playing back this sequence, resulting in a smooth playback.
|
|
14163
14274
|
* The sequencer calls this function by default when loading the songs. (it can be disabled: {@link SpessaSynthSequencer.preload}).
|
|
14164
14275
|
* @param synth The synthesizer to preload.
|
|
14165
14276
|
*/
|
|
@@ -14189,6 +14300,12 @@ var BasicMIDI = class BasicMIDI {
|
|
|
14189
14300
|
}
|
|
14190
14301
|
/**
|
|
14191
14302
|
* Calculates all note times in seconds.
|
|
14303
|
+
*
|
|
14304
|
+
* > **Note**
|
|
14305
|
+
* >
|
|
14306
|
+
* > MIDI ports are ignored, so the channels of every port are merged into the 16 channels.
|
|
14307
|
+
* > For example, channel 1 of port 2 is returned as channel 1.
|
|
14308
|
+
*
|
|
14192
14309
|
* @param minDrumLength In seconds, represents the minimum allowed time for a drum note,
|
|
14193
14310
|
* since they sometimes have a length of 0.
|
|
14194
14311
|
* @returns An array of 16 channels, each channel containing its notes,
|
|
@@ -14241,7 +14358,7 @@ var BasicMIDI = class BasicMIDI {
|
|
|
14241
14358
|
return writeMIDIInternal(this);
|
|
14242
14359
|
}
|
|
14243
14360
|
/**
|
|
14244
|
-
* Writes out an RMIDI file (MIDI + SF2).
|
|
14361
|
+
* Writes out an RMIDI file (MIDI + SF2 or DLS).
|
|
14245
14362
|
* [See more info about this format](https://github.com/spessasus/sf2-rmidi-specification#readme).
|
|
14246
14363
|
*
|
|
14247
14364
|
* Note that this method modifies the MIDI file in-place.
|
|
@@ -14250,11 +14367,16 @@ var BasicMIDI = class BasicMIDI {
|
|
|
14250
14367
|
* The method is called on a `BasicMIDI` instance;
|
|
14251
14368
|
* that instance is the MIDI file to embed.
|
|
14252
14369
|
*
|
|
14370
|
+
* > **Warning**
|
|
14371
|
+
* >
|
|
14372
|
+
* > Do not call in audioWorkletGlobalScope as it uses TextDecoder and TextEncoder.
|
|
14373
|
+
* > Bank offset correction (on by default) needs `soundBank`. Without it, the correction is skipped with a warning.
|
|
14374
|
+
*
|
|
14253
14375
|
* > **Tip**
|
|
14254
14376
|
* >
|
|
14255
14377
|
* > Use {@link BasicSoundBank.trim} to drastically reduce the file size.
|
|
14256
|
-
* >
|
|
14257
|
-
* > (using these
|
|
14378
|
+
* > Consider also using compression (like shown in example) to save even more space.
|
|
14379
|
+
* > (using both of these methods, I managed to shrink a 1GB sound bank into a 5MB RMIDI!)
|
|
14258
14380
|
*
|
|
14259
14381
|
* @param soundBankBinary The binary sound bank (SF2 or DLS) to embed into the file.
|
|
14260
14382
|
* @param configuration Extra options for writing the file.
|
|
@@ -14292,7 +14414,7 @@ var BasicMIDI = class BasicMIDI {
|
|
|
14292
14414
|
*
|
|
14293
14415
|
* // trim the soundfont
|
|
14294
14416
|
* soundBank.trim(midi.getUsedProgramsAndKeys(soundBank));
|
|
14295
|
-
* // write out with compression to save space
|
|
14417
|
+
* // write out with compression to save space
|
|
14296
14418
|
* await soundBank.setSampleFormat({
|
|
14297
14419
|
* format: "compressed",
|
|
14298
14420
|
* compressionFunction: SampleEncodingFunction // Remember to get your compression function
|
|
@@ -14330,6 +14452,8 @@ var BasicMIDI = class BasicMIDI {
|
|
|
14330
14452
|
* and inserts/deletes messages appropriately.
|
|
14331
14453
|
*
|
|
14332
14454
|
* This modifies the MIDI sequence _in-place_.
|
|
14455
|
+
*
|
|
14456
|
+
* @param opts The modifications to apply.
|
|
14333
14457
|
*/
|
|
14334
14458
|
modify(opts) {
|
|
14335
14459
|
new MIDIEditor(this, opts).apply();
|
|
@@ -14339,8 +14463,10 @@ var BasicMIDI = class BasicMIDI {
|
|
|
14339
14463
|
* This means changing the programs and controllers if they are locked.
|
|
14340
14464
|
*
|
|
14341
14465
|
* For example, if channel 1 has locked preset on `Drawbar Organ`,
|
|
14342
|
-
* this will remove all program changes for channel 1 and add one
|
|
14466
|
+
* this will remove all program changes for channel 1 and add one before the channel's first note on
|
|
14467
|
+
* (and again after every system reset) to change the program to
|
|
14343
14468
|
* `Drawbar organ` (using bank MSB/LSB and program change).
|
|
14469
|
+
* Muted channels are removed from the sequence.
|
|
14344
14470
|
*
|
|
14345
14471
|
* > **Warning**
|
|
14346
14472
|
* >
|
|
@@ -14413,11 +14539,31 @@ var BasicMIDI = class BasicMIDI {
|
|
|
14413
14539
|
* > **Note**
|
|
14414
14540
|
* >
|
|
14415
14541
|
* > This sets the Info encoding to `utf-8`.
|
|
14542
|
+
* > Text chunks that are already present get re-encoded from their old encoding (IENC) to UTF-8,
|
|
14543
|
+
* > so they stay readable. Without IENC, they are assumed to be UTF-8 already, like {@link BasicMIDI.getRMIDInfo} does.
|
|
14544
|
+
*
|
|
14545
|
+
* > **Warning**
|
|
14546
|
+
* >
|
|
14547
|
+
* > Do not call in audioWorkletGlobalScope as it uses TextDecoder and TextEncoder.
|
|
14416
14548
|
*
|
|
14417
14549
|
* @param infoType The type to set.
|
|
14418
14550
|
* @param infoData The value to set it to.
|
|
14419
14551
|
*/
|
|
14420
14552
|
setRMIDInfo(infoType, infoData) {
|
|
14553
|
+
const oldEncoding = this.infoEncoding;
|
|
14554
|
+
if (oldEncoding !== void 0 && !/^utf-?8$/i.test(oldEncoding.trim())) try {
|
|
14555
|
+
const decoder = new TextDecoder(oldEncoding.trim());
|
|
14556
|
+
const encoder = new TextEncoder();
|
|
14557
|
+
for (const key of Object.keys(this.rmidiInfo)) {
|
|
14558
|
+
if (key === "picture" || key === "creationDate" || key === "infoEncoding") continue;
|
|
14559
|
+
const data = this.rmidiInfo[key];
|
|
14560
|
+
if (!data) continue;
|
|
14561
|
+
const text = decoder.decode(data[data.length - 1] === 0 ? data.subarray(0, -1) : data);
|
|
14562
|
+
this.rmidiInfo[key] = new Uint8Array([...encoder.encode(text), 0]);
|
|
14563
|
+
}
|
|
14564
|
+
} catch (error) {
|
|
14565
|
+
SpessaLog.warn(`Failed to re-encode the RMIDI info from ${oldEncoding}: ${error}`);
|
|
14566
|
+
}
|
|
14421
14567
|
this.rmidiInfo.infoEncoding = getStringBytes("utf-8", true);
|
|
14422
14568
|
if (infoType === "picture") this.rmidiInfo.picture = new Uint8Array(infoData);
|
|
14423
14569
|
else if (infoType === "creationDate") this.rmidiInfo.creationDate = getStringBytes(toISODateString(infoData), true);
|
|
@@ -14428,6 +14574,11 @@ var BasicMIDI = class BasicMIDI {
|
|
|
14428
14574
|
}
|
|
14429
14575
|
/**
|
|
14430
14576
|
* Gets a given chunk from the RMIDI information.
|
|
14577
|
+
*
|
|
14578
|
+
* > **Warning**
|
|
14579
|
+
* >
|
|
14580
|
+
* > Do not call in audioWorkletGlobalScope as it uses TextDecoder.
|
|
14581
|
+
*
|
|
14431
14582
|
* @param infoType The metadata type.
|
|
14432
14583
|
* @returns `string`, `Date`, `ArrayBuffer` or undefined if the info is not set.
|
|
14433
14584
|
*/
|
|
@@ -14455,7 +14606,7 @@ var BasicMIDI = class BasicMIDI {
|
|
|
14455
14606
|
* > if you are not editing the MIDI file in your loop.
|
|
14456
14607
|
* > It is usually a faster solution and allows custom loops.
|
|
14457
14608
|
*
|
|
14458
|
-
*
|
|
14609
|
+
* If the track data is being edited, remember to call {@link BasicMIDI.flush} after editing!
|
|
14459
14610
|
*
|
|
14460
14611
|
* @param callback The callback function to process each event.
|
|
14461
14612
|
*/
|
|
@@ -14562,11 +14713,15 @@ var BasicMIDI = class BasicMIDI {
|
|
|
14562
14713
|
case MIDIMessageTypes.controllerChange:
|
|
14563
14714
|
switch (e.data[0]) {
|
|
14564
14715
|
case MIDIControllers.breathController:
|
|
14565
|
-
case MIDIControllers.undefinedCC111LSB:
|
|
14566
14716
|
if (e.data[1] === 0) loopStart = e.ticks;
|
|
14567
14717
|
break;
|
|
14568
|
-
case MIDIControllers.
|
|
14569
|
-
|
|
14718
|
+
case MIDIControllers.undefinedCC111:
|
|
14719
|
+
if (e.data[1] !== 0) break;
|
|
14720
|
+
loopStart = e.ticks;
|
|
14721
|
+
loopType = "soft";
|
|
14722
|
+
break;
|
|
14723
|
+
case MIDIControllers.undefinedCC116:
|
|
14724
|
+
case MIDIControllers.undefinedCC118:
|
|
14570
14725
|
loopStart = e.ticks;
|
|
14571
14726
|
break;
|
|
14572
14727
|
case MIDIControllers.footController:
|
|
@@ -14579,8 +14734,8 @@ var BasicMIDI = class BasicMIDI {
|
|
|
14579
14734
|
loopType = "hard";
|
|
14580
14735
|
}
|
|
14581
14736
|
break;
|
|
14582
|
-
case MIDIControllers.
|
|
14583
|
-
case MIDIControllers.
|
|
14737
|
+
case MIDIControllers.undefinedCC117:
|
|
14738
|
+
case MIDIControllers.undefinedCC119:
|
|
14584
14739
|
loopEnd = loopEnd === null ? e.ticks : 0;
|
|
14585
14740
|
loopType = "hard";
|
|
14586
14741
|
break;
|
|
@@ -14776,6 +14931,7 @@ const DEFAULT_MIDI_BUILDER_OPTIONS = {
|
|
|
14776
14931
|
* > **Warning**
|
|
14777
14932
|
* >
|
|
14778
14933
|
* > Remember to call {@link BasicMIDI.flush} after you're done building the file!
|
|
14934
|
+
* > Do not use in audioWorkletGlobalScope as it uses TextEncoder.
|
|
14779
14935
|
*
|
|
14780
14936
|
* @group MIDI.Files
|
|
14781
14937
|
*/
|
|
@@ -15071,7 +15227,7 @@ function processEventInternal(event, trackIndex) {
|
|
|
15071
15227
|
}
|
|
15072
15228
|
// src/sequencer/process_tick.ts
|
|
15073
15229
|
/**
|
|
15074
|
-
* Processes a single MIDI
|
|
15230
|
+
* Processes a single sequencer tick, playing all MIDI events up to the current time.
|
|
15075
15231
|
* Call this every rendering quantum to process the sequencer events in real-time.
|
|
15076
15232
|
*/
|
|
15077
15233
|
function processTick() {
|
|
@@ -17958,7 +18114,8 @@ function wordToFloat(word, mode) {
|
|
|
17958
18114
|
return mode === 1 ? signed / 128 : signed / 512;
|
|
17959
18115
|
}
|
|
17960
18116
|
/**
|
|
17961
|
-
*
|
|
18117
|
+
* Adds the DLL's half step bias (away from zero) to a scaled coefficient.
|
|
18118
|
+
* Truncating the result rounds half away from zero, like the DLL's biased truncation.
|
|
17962
18119
|
* @param value The exact scaled value.
|
|
17963
18120
|
*/
|
|
17964
18121
|
function biasedRound(value) {
|
|
@@ -18487,6 +18644,8 @@ var XGDSPProgram = class {
|
|
|
18487
18644
|
var DefaultXGEffect = class {
|
|
18488
18645
|
/**
|
|
18489
18646
|
* The currently used processor.
|
|
18647
|
+
* Hidden from the docs as the processor types are internal.
|
|
18648
|
+
* @hidden
|
|
18490
18649
|
*/
|
|
18491
18650
|
processor;
|
|
18492
18651
|
outputLeft;
|
|
@@ -18506,8 +18665,9 @@ var DefaultXGEffect = class {
|
|
|
18506
18665
|
* @param defaultType The default effect type.
|
|
18507
18666
|
* @param sampleRate The sample rate, in Hertz.
|
|
18508
18667
|
* @param maxBufferSize The maximum buffer size the synthesizer can render at once.
|
|
18509
|
-
*
|
|
18668
|
+
* Processing more samples than this is not supported and produces undefined output.
|
|
18510
18669
|
* @param variation True for the variation and insertion blocks.
|
|
18670
|
+
* @hidden
|
|
18511
18671
|
*/
|
|
18512
18672
|
constructor(effectMap, fallbackProcessor, defaultType, sampleRate, maxBufferSize, variation) {
|
|
18513
18673
|
let memoryLength = getMemorySize(sampleRate, fallbackProcessor.memorySize);
|
|
@@ -18569,11 +18729,17 @@ for (let pan = MIN_PAN$1; pan <= MAX_PAN$1; pan++) {
|
|
|
18569
18729
|
var DefaultXGSystemEffect = class extends DefaultXGEffect {
|
|
18570
18730
|
return = 64;
|
|
18571
18731
|
pan = 64;
|
|
18732
|
+
/**
|
|
18733
|
+
* Resets this block to default values, including the processor type.
|
|
18734
|
+
*/
|
|
18572
18735
|
reset() {
|
|
18573
18736
|
super.reset();
|
|
18574
18737
|
this.return = 64;
|
|
18575
18738
|
this.pan = 64;
|
|
18576
18739
|
}
|
|
18740
|
+
/**
|
|
18741
|
+
* Gets a snapshot of this XG effect block.
|
|
18742
|
+
*/
|
|
18577
18743
|
getSnapshot() {
|
|
18578
18744
|
return {
|
|
18579
18745
|
...super.getSnapshot(),
|
|
@@ -18581,6 +18747,10 @@ var DefaultXGSystemEffect = class extends DefaultXGEffect {
|
|
|
18581
18747
|
pan: this.pan
|
|
18582
18748
|
};
|
|
18583
18749
|
}
|
|
18750
|
+
/**
|
|
18751
|
+
* Restores this XG effect block from a snapshot.
|
|
18752
|
+
* @param snapshot The snapshot to restore.
|
|
18753
|
+
*/
|
|
18584
18754
|
applySnapshot(snapshot) {
|
|
18585
18755
|
super.applySnapshot(snapshot);
|
|
18586
18756
|
this.return = snapshot.return;
|
|
@@ -18721,9 +18891,9 @@ const TIME_OFFSETS = new Int8Array([
|
|
|
18721
18891
|
107
|
|
18722
18892
|
]);
|
|
18723
18893
|
/**
|
|
18724
|
-
* Damping line
|
|
18894
|
+
* Damping line input gain coefficient words.
|
|
18725
18895
|
*/
|
|
18726
|
-
const
|
|
18896
|
+
const DAMP_INPUT = new Uint16Array([
|
|
18727
18897
|
105,
|
|
18728
18898
|
105,
|
|
18729
18899
|
105,
|
|
@@ -18982,9 +19152,9 @@ const DAMP_FEEDBACK = new Uint16Array([
|
|
|
18982
19152
|
2
|
|
18983
19153
|
]);
|
|
18984
19154
|
/**
|
|
18985
|
-
* Damping line
|
|
19155
|
+
* Damping line feedback coefficient words, used for both taps of the two-tap feedback filter.
|
|
18986
19156
|
*/
|
|
18987
|
-
const
|
|
19157
|
+
const DAMP_FEEDBACK = new Uint16Array([
|
|
18988
19158
|
1,
|
|
18989
19159
|
1,
|
|
18990
19160
|
1,
|
|
@@ -19756,7 +19926,7 @@ const LIST_LPF = [
|
|
|
19756
19926
|
50
|
|
19757
19927
|
];
|
|
19758
19928
|
/**
|
|
19759
|
-
*
|
|
19929
|
+
* Damped delay line addresses, 4 per line: write, feedback read, left output tap, right output tap.
|
|
19760
19930
|
*/
|
|
19761
19931
|
const LIST_DIFFUSION = [
|
|
19762
19932
|
31,
|
|
@@ -19777,7 +19947,7 @@ const LIST_DIFFUSION = [
|
|
|
19777
19947
|
7
|
|
19778
19948
|
];
|
|
19779
19949
|
/**
|
|
19780
|
-
* Damping line coefficients, 3 per line:
|
|
19950
|
+
* Damping line coefficients, 3 per line: input, read and previous read gains.
|
|
19781
19951
|
*/
|
|
19782
19952
|
const LIST_TIME = [
|
|
19783
19953
|
91,
|
|
@@ -20059,13 +20229,14 @@ var XGReverb = class extends XGDSPProgram {
|
|
|
20059
20229
|
* @param offset The first list index.
|
|
20060
20230
|
*/
|
|
20061
20231
|
writeDamping(index, list, offset) {
|
|
20062
|
-
this.coefficientWords[list[offset]] =
|
|
20063
|
-
const
|
|
20064
|
-
this.coefficientWords[list[offset + 1]] =
|
|
20065
|
-
this.coefficientWords[list[offset + 2]] =
|
|
20232
|
+
this.coefficientWords[list[offset]] = DAMP_INPUT[index];
|
|
20233
|
+
const feedback = index > 202 ? 255 : DAMP_FEEDBACK[index];
|
|
20234
|
+
this.coefficientWords[list[offset + 1]] = feedback;
|
|
20235
|
+
this.coefficientWords[list[offset + 2]] = feedback;
|
|
20066
20236
|
}
|
|
20067
20237
|
/**
|
|
20068
|
-
* Places the
|
|
20238
|
+
* Places the delay line read and output taps from the diffusion parameter.
|
|
20239
|
+
* The diffusion spreads the left and right output taps of each line apart.
|
|
20069
20240
|
*/
|
|
20070
20241
|
diffusion() {
|
|
20071
20242
|
const scale = DIFFUSION_SCALE[this.params[1]];
|
|
@@ -20221,7 +20392,7 @@ var XGRoomReverb = class extends XGReverb {
|
|
|
20221
20392
|
this.writeDamping(extra + base & 255, LIST_TIME, offset);
|
|
20222
20393
|
}
|
|
20223
20394
|
/**
|
|
20224
|
-
* Places a
|
|
20395
|
+
* Places a delay line's length and output taps from a room dimension.
|
|
20225
20396
|
* @param step The room diffusion step index.
|
|
20226
20397
|
* @param size The dimension size.
|
|
20227
20398
|
* @param offset The first diffusion list index.
|
|
@@ -20242,7 +20413,7 @@ var XGRoomReverb = class extends XGReverb {
|
|
|
20242
20413
|
}
|
|
20243
20414
|
}
|
|
20244
20415
|
/**
|
|
20245
|
-
* Updates one dimension's
|
|
20416
|
+
* Updates one dimension's delay line (damping, length and taps), then pushes it.
|
|
20246
20417
|
* @param dimension 0 width, 1 height, 2 depth.
|
|
20247
20418
|
* @param step The room diffusion step index.
|
|
20248
20419
|
* @param size The dimension size.
|
|
@@ -22066,6 +22237,8 @@ const REVERB_MAP = /* @__PURE__ */ new Map([
|
|
|
22066
22237
|
/**
|
|
22067
22238
|
* The default implementation for {@link XGReverbBlock}.
|
|
22068
22239
|
*
|
|
22240
|
+
* The default type is HALL 1. Unknown types use the type with LSB 0, or NO EFFECT (silence) if that is missing too.
|
|
22241
|
+
*
|
|
22069
22242
|
* @group Synthesizer.XG Effects
|
|
22070
22243
|
* @sealed
|
|
22071
22244
|
*/
|
|
@@ -22074,7 +22247,7 @@ var DefaultXGReverb = class extends DefaultXGSystemEffect {
|
|
|
22074
22247
|
* Constructs a new default XG reverb processor.
|
|
22075
22248
|
* @param sampleRate The sample rate, in Hertz.
|
|
22076
22249
|
* @param maxBufferSize The maximum buffer size the synthesizer can render at once.
|
|
22077
|
-
*
|
|
22250
|
+
* Processing more samples than this is not supported and produces undefined output.
|
|
22078
22251
|
*/
|
|
22079
22252
|
constructor(sampleRate, maxBufferSize) {
|
|
22080
22253
|
super(REVERB_MAP, XGNoEffect, 256, sampleRate, maxBufferSize, false);
|
|
@@ -22166,7 +22339,7 @@ const LIST_RIGHT_TAPS = [
|
|
|
22166
22339
|
25,
|
|
22167
22340
|
26
|
|
22168
22341
|
];
|
|
22169
|
-
const LIST_DRY_WET = [
|
|
22342
|
+
const LIST_DRY_WET$4 = [
|
|
22170
22343
|
95,
|
|
22171
22344
|
96,
|
|
22172
22345
|
97,
|
|
@@ -22407,7 +22580,7 @@ var XGChorusBase = class extends XGDSPProgram {
|
|
|
22407
22580
|
const w = this.coefficientWords;
|
|
22408
22581
|
w[95] = w[104] = dry & 1023;
|
|
22409
22582
|
w[96] = w[105] = w[97] = w[106] = wet & 1023;
|
|
22410
|
-
this.pushCoefficients(LIST_DRY_WET);
|
|
22583
|
+
this.pushCoefficients(LIST_DRY_WET$4);
|
|
22411
22584
|
break;
|
|
22412
22585
|
}
|
|
22413
22586
|
}
|
|
@@ -23679,6 +23852,8 @@ const CHORUS_MAP = /* @__PURE__ */ new Map([
|
|
|
23679
23852
|
/**
|
|
23680
23853
|
* The default implementation for {@link XGChorusBlock}.
|
|
23681
23854
|
*
|
|
23855
|
+
* The default type is CHORUS 1. Unknown types use the type with LSB 0, or NO EFFECT (silence) if that is missing too.
|
|
23856
|
+
*
|
|
23682
23857
|
* @group Synthesizer.XG Effects
|
|
23683
23858
|
* @sealed
|
|
23684
23859
|
*/
|
|
@@ -23688,11 +23863,14 @@ var DefaultXGChorus = class extends DefaultXGSystemEffect {
|
|
|
23688
23863
|
* Constructs a new default XG chorus processor.
|
|
23689
23864
|
* @param sampleRate The sample rate, in Hertz.
|
|
23690
23865
|
* @param maxBufferSize The maximum buffer size the synthesizer can render at once.
|
|
23691
|
-
*
|
|
23866
|
+
* Processing more samples than this is not supported and produces undefined output.
|
|
23692
23867
|
*/
|
|
23693
23868
|
constructor(sampleRate, maxBufferSize) {
|
|
23694
23869
|
super(CHORUS_MAP, XGNoEffect, 16640, sampleRate, maxBufferSize, false);
|
|
23695
23870
|
}
|
|
23871
|
+
/**
|
|
23872
|
+
* Resets this block to default values, including the processor type.
|
|
23873
|
+
*/
|
|
23696
23874
|
reset() {
|
|
23697
23875
|
super.reset();
|
|
23698
23876
|
this.sendToReverb = 0;
|
|
@@ -23707,12 +23885,19 @@ var DefaultXGChorus = class extends DefaultXGSystemEffect {
|
|
|
23707
23885
|
reverbRight[i] += this.outputRight[i] * gain;
|
|
23708
23886
|
}
|
|
23709
23887
|
}
|
|
23888
|
+
/**
|
|
23889
|
+
* Gets a snapshot of this XG effect block.
|
|
23890
|
+
*/
|
|
23710
23891
|
getSnapshot() {
|
|
23711
23892
|
return {
|
|
23712
23893
|
...super.getSnapshot(),
|
|
23713
23894
|
sendToReverb: this.sendToReverb
|
|
23714
23895
|
};
|
|
23715
23896
|
}
|
|
23897
|
+
/**
|
|
23898
|
+
* Restores this XG effect block from a snapshot.
|
|
23899
|
+
* @param snapshot The snapshot to restore.
|
|
23900
|
+
*/
|
|
23716
23901
|
applySnapshot(snapshot) {
|
|
23717
23902
|
super.applySnapshot(snapshot);
|
|
23718
23903
|
this.sendToReverb = snapshot.sendToReverb;
|
|
@@ -24141,6 +24326,16 @@ const PARAMETER_RANGES$15 = {
|
|
|
24141
24326
|
13: [4, 124],
|
|
24142
24327
|
14: [0, 1]
|
|
24143
24328
|
};
|
|
24329
|
+
const LIST_INPUT_COEFFICIENT = [
|
|
24330
|
+
170,
|
|
24331
|
+
180,
|
|
24332
|
+
185
|
|
24333
|
+
];
|
|
24334
|
+
const LIST_INPUT_ADDRESS = [
|
|
24335
|
+
59,
|
|
24336
|
+
45,
|
|
24337
|
+
46
|
|
24338
|
+
];
|
|
24144
24339
|
/**
|
|
24145
24340
|
* TREMOLO (`46 00`): amplitude and pitch modulation.
|
|
24146
24341
|
*/
|
|
@@ -24328,16 +24523,8 @@ var XGTremolo = class extends XGTremoloBase {
|
|
|
24328
24523
|
a[45] = 1025;
|
|
24329
24524
|
a[46] = 1026;
|
|
24330
24525
|
}
|
|
24331
|
-
this.pushCoefficients(
|
|
24332
|
-
|
|
24333
|
-
180,
|
|
24334
|
-
185
|
|
24335
|
-
]);
|
|
24336
|
-
this.pushAddresses([
|
|
24337
|
-
59,
|
|
24338
|
-
45,
|
|
24339
|
-
46
|
|
24340
|
-
]);
|
|
24526
|
+
this.pushCoefficients(LIST_INPUT_COEFFICIENT);
|
|
24527
|
+
this.pushAddresses(LIST_INPUT_ADDRESS);
|
|
24341
24528
|
this.convert(5);
|
|
24342
24529
|
this.convert(7);
|
|
24343
24530
|
break;
|
|
@@ -24353,6 +24540,7 @@ const PARAMETER_RANGES$14 = {
|
|
|
24353
24540
|
7: [28, 58],
|
|
24354
24541
|
8: [52, 76]
|
|
24355
24542
|
};
|
|
24543
|
+
const LIST_OUTPUT$3 = [180, 185];
|
|
24356
24544
|
/**
|
|
24357
24545
|
* LFO phases (left, right) per pan direction: L<->R, L->R, L<-R, L turn, R turn.
|
|
24358
24546
|
*/
|
|
@@ -24555,7 +24743,7 @@ var XGAutoPan = class extends XGTremoloBase {
|
|
|
24555
24743
|
break;
|
|
24556
24744
|
case 9:
|
|
24557
24745
|
w[185] = w[180] = this.outputWord(this.insertion ? 362 : 724);
|
|
24558
|
-
this.pushCoefficients(
|
|
24746
|
+
this.pushCoefficients(LIST_OUTPUT$3);
|
|
24559
24747
|
}
|
|
24560
24748
|
}
|
|
24561
24749
|
};
|
|
@@ -24779,6 +24967,11 @@ const PARAMETER_RANGES$13 = {
|
|
|
24779
24967
|
8: [52, 76],
|
|
24780
24968
|
10: [6, 10]
|
|
24781
24969
|
};
|
|
24970
|
+
const LIST_DRY_LEFT = [162];
|
|
24971
|
+
const LIST_MIX_RIGHT$1 = [166, 167];
|
|
24972
|
+
const LIST_WET_SECOND = [164];
|
|
24973
|
+
const LIST_WET_FOURTH = [165];
|
|
24974
|
+
const LIST_INPUT_MIX = [132, 133];
|
|
24782
24975
|
/**
|
|
24783
24976
|
* The converter index of the input mix level, which depends on the connection.
|
|
24784
24977
|
*/
|
|
@@ -25001,14 +25194,14 @@ var XGPhaser1 = class extends XGPhaserBase {
|
|
|
25001
25194
|
case 9:
|
|
25002
25195
|
w[166] = w[162] = PAN_LEFT[mix];
|
|
25003
25196
|
w[167] = PAN_RIGHT[mix];
|
|
25004
|
-
this.pushCoefficients(
|
|
25005
|
-
this.pushCoefficients(
|
|
25197
|
+
this.pushCoefficients(LIST_DRY_LEFT);
|
|
25198
|
+
this.pushCoefficients(LIST_MIX_RIGHT$1);
|
|
25006
25199
|
if (p[10] < 9) {
|
|
25007
25200
|
w[164] = PAN_RIGHT[mix];
|
|
25008
|
-
this.pushCoefficients(
|
|
25201
|
+
this.pushCoefficients(LIST_WET_SECOND);
|
|
25009
25202
|
} else {
|
|
25010
25203
|
w[165] = PAN_RIGHT[mix];
|
|
25011
|
-
this.pushCoefficients(
|
|
25204
|
+
this.pushCoefficients(LIST_WET_FOURTH);
|
|
25012
25205
|
}
|
|
25013
25206
|
break;
|
|
25014
25207
|
case 10: {
|
|
@@ -25032,7 +25225,7 @@ var XGPhaser1 = class extends XGPhaserBase {
|
|
|
25032
25225
|
}
|
|
25033
25226
|
case INPUT_MIX:
|
|
25034
25227
|
w[132] = w[133] = this.insertion ? 256 : 512;
|
|
25035
|
-
this.pushCoefficients(
|
|
25228
|
+
this.pushCoefficients(LIST_INPUT_MIX);
|
|
25036
25229
|
}
|
|
25037
25230
|
}
|
|
25038
25231
|
};
|
|
@@ -25046,6 +25239,8 @@ const PARAMETER_RANGES$12 = {
|
|
|
25046
25239
|
10: [3, 5],
|
|
25047
25240
|
12: [4, 124]
|
|
25048
25241
|
};
|
|
25242
|
+
const LIST_MIX_LEFT = [162, 163];
|
|
25243
|
+
const LIST_MIX_RIGHT = [166, 167];
|
|
25049
25244
|
/**
|
|
25050
25245
|
* PHASER 2 (`48 08`): a stereo phaser with 3 to 5 stages and an LFO phase difference between the sides.
|
|
25051
25246
|
*/
|
|
@@ -25218,8 +25413,8 @@ var XGPhaser2 = class extends XGPhaserBase {
|
|
|
25218
25413
|
const mix = this.mixIndex();
|
|
25219
25414
|
w[166] = w[162] = PAN_LEFT[mix];
|
|
25220
25415
|
w[167] = w[163] = PAN_RIGHT[mix];
|
|
25221
|
-
this.pushCoefficients(
|
|
25222
|
-
this.pushCoefficients(
|
|
25416
|
+
this.pushCoefficients(LIST_MIX_LEFT);
|
|
25417
|
+
this.pushCoefficients(LIST_MIX_RIGHT);
|
|
25223
25418
|
break;
|
|
25224
25419
|
}
|
|
25225
25420
|
case 10: {
|
|
@@ -25616,6 +25811,8 @@ const LIST_HIGH_LEFT = [
|
|
|
25616
25811
|
131,
|
|
25617
25812
|
133
|
|
25618
25813
|
];
|
|
25814
|
+
const LIST_OUTPUT$2 = [185, 186];
|
|
25815
|
+
const LIST_DRY_WET$3 = [171, 173];
|
|
25619
25816
|
/**
|
|
25620
25817
|
* AUTO WAH (`4E 00`): a resonant filter per side, swept by an exponential LFO.
|
|
25621
25818
|
*/
|
|
@@ -25869,8 +26066,8 @@ var XGAutoWah = class extends XGFilterVariationProgram {
|
|
|
25869
26066
|
const mix = this.mixIndex();
|
|
25870
26067
|
w[171] = w[185] = PAN_LEFT[mix];
|
|
25871
26068
|
w[173] = w[186] = PAN_RIGHT[mix];
|
|
25872
|
-
this.pushCoefficients(
|
|
25873
|
-
this.pushCoefficients(
|
|
26069
|
+
this.pushCoefficients(LIST_OUTPUT$2);
|
|
26070
|
+
this.pushCoefficients(LIST_DRY_WET$3);
|
|
25874
26071
|
break;
|
|
25875
26072
|
}
|
|
25876
26073
|
}
|
|
@@ -25897,16 +26094,17 @@ const UNWRITTEN_CATEGORY_2 = [
|
|
|
25897
26094
|
128,
|
|
25898
26095
|
174
|
|
25899
26096
|
];
|
|
26097
|
+
const LIST_MONO_INPUT$1 = [109, 110];
|
|
25900
26098
|
/**
|
|
25901
26099
|
* The base for the S-YXG50 category 2 variation programs (3-band EQ, distortion, early reflections).
|
|
25902
|
-
* They process a mono sum of the input through
|
|
26100
|
+
* They process a mono sum of the input through two first-order input sections, keeping scaled copies
|
|
25903
26101
|
* of the dry input that the routines mix back in.
|
|
25904
26102
|
* Any of 0x76, 0x7a, 0x7d and 0x80 sets 0x76 to their product.
|
|
25905
26103
|
*/
|
|
25906
26104
|
var XGMonoVariationProgram = class extends XGVariationProgram {
|
|
25907
26105
|
unwrittenCoefficients = UNWRITTEN_CATEGORY_2;
|
|
25908
26106
|
/**
|
|
25909
|
-
* The mono input after the
|
|
26107
|
+
* The mono input after the input sections.
|
|
25910
26108
|
*/
|
|
25911
26109
|
mono;
|
|
25912
26110
|
/**
|
|
@@ -26027,7 +26225,7 @@ var XGMonoVariationProgram = class extends XGVariationProgram {
|
|
|
26027
26225
|
monoInputLevel() {
|
|
26028
26226
|
const w = this.coefficientWords;
|
|
26029
26227
|
w[109] = w[110] = this.insertion ? 256 : 512;
|
|
26030
|
-
this.pushCoefficients(
|
|
26228
|
+
this.pushCoefficients(LIST_MONO_INPUT$1);
|
|
26031
26229
|
}
|
|
26032
26230
|
};
|
|
26033
26231
|
// src/synthesizer/audio_engine/effects/xg/implementation/dsp/equalizer/three_band_eq.ts
|
|
@@ -26387,6 +26585,12 @@ const LIST_MID = [
|
|
|
26387
26585
|
166,
|
|
26388
26586
|
165
|
|
26389
26587
|
];
|
|
26588
|
+
const LIST_DRY_WET$2 = [
|
|
26589
|
+
173,
|
|
26590
|
+
176,
|
|
26591
|
+
177,
|
|
26592
|
+
179
|
|
26593
|
+
];
|
|
26390
26594
|
/**
|
|
26391
26595
|
* The clipping range (the original works on a 16-bit scale, which is 4 times the full scale).
|
|
26392
26596
|
*/
|
|
@@ -26480,12 +26684,7 @@ var XGDistortionBase = class extends XGMonoVariationProgram {
|
|
|
26480
26684
|
if (this.insertion) wet >>= 1;
|
|
26481
26685
|
w[173] = w[177] = PAN_LEFT[mix];
|
|
26482
26686
|
w[176] = w[179] = VARIATION_GAIN * wet >> 6;
|
|
26483
|
-
this.pushCoefficients(
|
|
26484
|
-
173,
|
|
26485
|
-
176,
|
|
26486
|
-
177,
|
|
26487
|
-
179
|
|
26488
|
-
]);
|
|
26687
|
+
this.pushCoefficients(LIST_DRY_WET$2);
|
|
26489
26688
|
}
|
|
26490
26689
|
};
|
|
26491
26690
|
// src/synthesizer/audio_engine/effects/xg/implementation/dsp/distortion/distortion.ts
|
|
@@ -28425,6 +28624,12 @@ const LIST_HIGH_DAMP = [
|
|
|
28425
28624
|
119,
|
|
28426
28625
|
121
|
|
28427
28626
|
];
|
|
28627
|
+
const LIST_DRY_WET$1 = [
|
|
28628
|
+
173,
|
|
28629
|
+
175,
|
|
28630
|
+
177,
|
|
28631
|
+
178
|
|
28632
|
+
];
|
|
28428
28633
|
/**
|
|
28429
28634
|
* The converter index of the mono input level, which depends on the connection.
|
|
28430
28635
|
*/
|
|
@@ -28544,12 +28749,7 @@ var XGEarlyReflectionsBase = class extends XGMonoVariationProgram {
|
|
|
28544
28749
|
const mix = this.mixIndex();
|
|
28545
28750
|
w[173] = w[177] = MIX_DRY[mix] & 1023;
|
|
28546
28751
|
w[175] = w[178] = MIX_WET[mix] & 1023;
|
|
28547
|
-
this.pushCoefficients(
|
|
28548
|
-
173,
|
|
28549
|
-
175,
|
|
28550
|
-
177,
|
|
28551
|
-
178
|
|
28552
|
-
]);
|
|
28752
|
+
this.pushCoefficients(LIST_DRY_WET$1);
|
|
28553
28753
|
break;
|
|
28554
28754
|
}
|
|
28555
28755
|
case 10:
|
|
@@ -29308,7 +29508,7 @@ const DELAY_LEVEL = new Float32Array([
|
|
|
29308
29508
|
]);
|
|
29309
29509
|
// src/synthesizer/audio_engine/effects/xg/implementation/dsp/delay/xg_delay.ts
|
|
29310
29510
|
/**
|
|
29311
|
-
* The right line input position used by the two-line delays (
|
|
29511
|
+
* The right line input position used by the two-line delays (past the longest left line).
|
|
29312
29512
|
*/
|
|
29313
29513
|
const RIGHT_LINE_START = 16384;
|
|
29314
29514
|
const LIST_DAMP_A = [159, 158];
|
|
@@ -29373,7 +29573,7 @@ var XGDelayBase = class extends XGStereoVariationProgram {
|
|
|
29373
29573
|
this.pushAddresses([tap]);
|
|
29374
29574
|
}
|
|
29375
29575
|
/**
|
|
29376
|
-
* Designs a feedback damping filter (a
|
|
29576
|
+
* Designs a feedback damping filter (a two-tap low-pass) from the high damp parameter.
|
|
29377
29577
|
* @param damp The high damp value (1-10).
|
|
29378
29578
|
* @param list The coefficient indices for the input and feedback coefficients.
|
|
29379
29579
|
*/
|
|
@@ -29402,6 +29602,14 @@ const PARAMETER_RANGES$4 = {
|
|
|
29402
29602
|
14: [28, 58],
|
|
29403
29603
|
15: [52, 76]
|
|
29404
29604
|
};
|
|
29605
|
+
const LIST_MIX$3 = [
|
|
29606
|
+
178,
|
|
29607
|
+
181,
|
|
29608
|
+
184,
|
|
29609
|
+
185,
|
|
29610
|
+
187,
|
|
29611
|
+
182
|
|
29612
|
+
];
|
|
29405
29613
|
/**
|
|
29406
29614
|
* DELAY L,C,R (`05 00`): left, right and center taps with a separate feedback tap.
|
|
29407
29615
|
*/
|
|
@@ -29575,14 +29783,7 @@ var XGDelayLCR = class extends XGDelayBase {
|
|
|
29575
29783
|
if (this.insertion) wet >>= 1;
|
|
29576
29784
|
w[185] = w[181] = wet & 1023;
|
|
29577
29785
|
w[187] = w[182] = Math.trunc(wet * DELAY_LEVEL[p[5]]) & 1023;
|
|
29578
|
-
this.pushCoefficients(
|
|
29579
|
-
178,
|
|
29580
|
-
181,
|
|
29581
|
-
184,
|
|
29582
|
-
185,
|
|
29583
|
-
187,
|
|
29584
|
-
182
|
|
29585
|
-
]);
|
|
29786
|
+
this.pushCoefficients(LIST_MIX$3);
|
|
29586
29787
|
break;
|
|
29587
29788
|
}
|
|
29588
29789
|
case 6:
|
|
@@ -29611,6 +29812,12 @@ const PARAMETER_RANGES$3 = {
|
|
|
29611
29812
|
14: [28, 58],
|
|
29612
29813
|
15: [52, 76]
|
|
29613
29814
|
};
|
|
29815
|
+
const LIST_MIX$2 = [
|
|
29816
|
+
178,
|
|
29817
|
+
181,
|
|
29818
|
+
184,
|
|
29819
|
+
187
|
|
29820
|
+
];
|
|
29614
29821
|
/**
|
|
29615
29822
|
* DELAY L,R (`06 00`): left and right taps with two feedback taps.
|
|
29616
29823
|
*/
|
|
@@ -29786,12 +29993,7 @@ var XGDelayLR = class extends XGDelayBase {
|
|
|
29786
29993
|
let wet = MIX_WET[mix];
|
|
29787
29994
|
if (this.insertion) wet >>= 1;
|
|
29788
29995
|
w[187] = w[181] = wet & 1023;
|
|
29789
|
-
this.pushCoefficients(
|
|
29790
|
-
178,
|
|
29791
|
-
181,
|
|
29792
|
-
184,
|
|
29793
|
-
187
|
|
29794
|
-
]);
|
|
29996
|
+
this.pushCoefficients(LIST_MIX$2);
|
|
29795
29997
|
break;
|
|
29796
29998
|
}
|
|
29797
29999
|
case 12:
|
|
@@ -29818,6 +30020,14 @@ const PARAMETER_RANGES$2 = {
|
|
|
29818
30020
|
14: [28, 58],
|
|
29819
30021
|
15: [52, 76]
|
|
29820
30022
|
};
|
|
30023
|
+
const LIST_MIX$1 = [
|
|
30024
|
+
178,
|
|
30025
|
+
180,
|
|
30026
|
+
181,
|
|
30027
|
+
184,
|
|
30028
|
+
185,
|
|
30029
|
+
187
|
|
30030
|
+
];
|
|
29821
30031
|
/**
|
|
29822
30032
|
* ECHO (`07 00`): two independent delay lines with feedback and a second tap each.
|
|
29823
30033
|
*/
|
|
@@ -29999,14 +30209,7 @@ var XGEcho = class extends XGDelayBase {
|
|
|
29999
30209
|
const wet = MIX_WET[mix];
|
|
30000
30210
|
w[185] = w[180] = wet & 1023;
|
|
30001
30211
|
w[187] = w[181] = Math.trunc(wet * DELAY_LEVEL[p[7]]) & 1023;
|
|
30002
|
-
this.pushCoefficients(
|
|
30003
|
-
178,
|
|
30004
|
-
180,
|
|
30005
|
-
181,
|
|
30006
|
-
184,
|
|
30007
|
-
185,
|
|
30008
|
-
187
|
|
30009
|
-
]);
|
|
30212
|
+
this.pushCoefficients(LIST_MIX$1);
|
|
30010
30213
|
break;
|
|
30011
30214
|
}
|
|
30012
30215
|
case 12:
|
|
@@ -30031,6 +30234,12 @@ const PARAMETER_RANGES$1 = {
|
|
|
30031
30234
|
14: [28, 58],
|
|
30032
30235
|
15: [52, 76]
|
|
30033
30236
|
};
|
|
30237
|
+
const LIST_MIX = [
|
|
30238
|
+
178,
|
|
30239
|
+
180,
|
|
30240
|
+
184,
|
|
30241
|
+
185
|
|
30242
|
+
];
|
|
30034
30243
|
/**
|
|
30035
30244
|
* CROSS DELAY (`08 00`): two delay lines feeding back into each other.
|
|
30036
30245
|
*/
|
|
@@ -30221,12 +30430,7 @@ var XGCrossDelay = class extends XGDelayBase {
|
|
|
30221
30430
|
let wet = MIX_WET[mix];
|
|
30222
30431
|
if (p[3] < 2) wet >>= 1;
|
|
30223
30432
|
w[185] = w[180] = wet & 1023;
|
|
30224
|
-
this.pushCoefficients(
|
|
30225
|
-
178,
|
|
30226
|
-
180,
|
|
30227
|
-
184,
|
|
30228
|
-
185
|
|
30229
|
-
]);
|
|
30433
|
+
this.pushCoefficients(LIST_MIX);
|
|
30230
30434
|
break;
|
|
30231
30435
|
}
|
|
30232
30436
|
case 12:
|
|
@@ -30421,6 +30625,10 @@ const LIST_DENSITY_ADDRESS = [
|
|
|
30421
30625
|
54,
|
|
30422
30626
|
51
|
|
30423
30627
|
];
|
|
30628
|
+
const LIST_DRY = [185];
|
|
30629
|
+
const LIST_WET = [187];
|
|
30630
|
+
const LIST_DRY_WET = [174, 172];
|
|
30631
|
+
const LIST_MONO_INPUT = [109, 110];
|
|
30424
30632
|
/**
|
|
30425
30633
|
* The converter index of the mono input level, which depends on the connection.
|
|
30426
30634
|
*/
|
|
@@ -30543,9 +30751,9 @@ var XGKaraokeBase = class extends XGFilterVariationProgram {
|
|
|
30543
30751
|
const mix = this.mixIndex();
|
|
30544
30752
|
w[172] = w[185] = MIX_DRY[mix] & 1023;
|
|
30545
30753
|
w[174] = w[187] = MIX_WET[mix] & 1023;
|
|
30546
|
-
this.pushCoefficients(
|
|
30547
|
-
this.pushCoefficients(
|
|
30548
|
-
this.pushCoefficients(
|
|
30754
|
+
this.pushCoefficients(LIST_DRY);
|
|
30755
|
+
this.pushCoefficients(LIST_WET);
|
|
30756
|
+
this.pushCoefficients(LIST_DRY_WET);
|
|
30549
30757
|
break;
|
|
30550
30758
|
}
|
|
30551
30759
|
case 10:
|
|
@@ -30554,7 +30762,7 @@ var XGKaraokeBase = class extends XGFilterVariationProgram {
|
|
|
30554
30762
|
break;
|
|
30555
30763
|
case MONO_INPUT$1:
|
|
30556
30764
|
w[109] = w[110] = this.insertion ? 181 : 362;
|
|
30557
|
-
this.pushCoefficients(
|
|
30765
|
+
this.pushCoefficients(LIST_MONO_INPUT);
|
|
30558
30766
|
}
|
|
30559
30767
|
}
|
|
30560
30768
|
};
|
|
@@ -31013,6 +31221,9 @@ const VARIATION_MAP = /* @__PURE__ */ new Map([
|
|
|
31013
31221
|
/**
|
|
31014
31222
|
* The default implementation for {@link XGVariationBlock}.
|
|
31015
31223
|
*
|
|
31224
|
+
* The default type is DELAY L,C,R in the insertion connection.
|
|
31225
|
+
* Unknown types use the type with LSB 0, or THRU if that is missing too.
|
|
31226
|
+
*
|
|
31016
31227
|
* @group Synthesizer.XG Effects
|
|
31017
31228
|
* @sealed
|
|
31018
31229
|
*/
|
|
@@ -31025,11 +31236,14 @@ var DefaultXGVariation = class extends DefaultXGSystemEffect {
|
|
|
31025
31236
|
* Constructs a new default XG variation processor.
|
|
31026
31237
|
* @param sampleRate The sample rate, in Hertz.
|
|
31027
31238
|
* @param maxBufferSize The maximum buffer size the synthesizer can render at once.
|
|
31028
|
-
*
|
|
31239
|
+
* Processing more samples than this is not supported and produces undefined output.
|
|
31029
31240
|
*/
|
|
31030
31241
|
constructor(sampleRate, maxBufferSize) {
|
|
31031
31242
|
super(VARIATION_MAP, XGThru, 1280, sampleRate, maxBufferSize, true);
|
|
31032
31243
|
}
|
|
31244
|
+
/**
|
|
31245
|
+
* Resets this block to default values, including the processor type.
|
|
31246
|
+
*/
|
|
31033
31247
|
reset() {
|
|
31034
31248
|
super.reset();
|
|
31035
31249
|
this.insertionMode = true;
|
|
@@ -31063,6 +31277,9 @@ var DefaultXGVariation = class extends DefaultXGSystemEffect {
|
|
|
31063
31277
|
inputRight[i] = outputRight[i];
|
|
31064
31278
|
}
|
|
31065
31279
|
}
|
|
31280
|
+
/**
|
|
31281
|
+
* Gets a snapshot of this XG effect block.
|
|
31282
|
+
*/
|
|
31066
31283
|
getSnapshot() {
|
|
31067
31284
|
return {
|
|
31068
31285
|
...super.getSnapshot(),
|
|
@@ -31072,6 +31289,10 @@ var DefaultXGVariation = class extends DefaultXGSystemEffect {
|
|
|
31072
31289
|
sendToChorus: this.sendToChorus
|
|
31073
31290
|
};
|
|
31074
31291
|
}
|
|
31292
|
+
/**
|
|
31293
|
+
* Restores this XG effect block from a snapshot.
|
|
31294
|
+
* @param snapshot The snapshot to restore.
|
|
31295
|
+
*/
|
|
31075
31296
|
applySnapshot(snapshot) {
|
|
31076
31297
|
super.applySnapshot(snapshot);
|
|
31077
31298
|
this.insertionMode = snapshot.insertionMode;
|
|
@@ -31124,6 +31345,10 @@ const INSERTION_MAP = /* @__PURE__ */ new Map([
|
|
|
31124
31345
|
/**
|
|
31125
31346
|
* The default implementation for {@link XGInsertionBlock}.
|
|
31126
31347
|
*
|
|
31348
|
+
* The default type is DISTORTION. The EFFECT 2 types run on the same processors as the variation block types.
|
|
31349
|
+
* The early reflection types and PHASER 2 are variation only, so PHASER 2 falls back to PHASER 1 here.
|
|
31350
|
+
* Unknown types use the type with LSB 0, or THRU if that is missing too.
|
|
31351
|
+
*
|
|
31127
31352
|
* @group Synthesizer.XG Effects
|
|
31128
31353
|
* @sealed
|
|
31129
31354
|
*/
|
|
@@ -31133,11 +31358,14 @@ var DefaultXGInsertion = class extends DefaultXGEffect {
|
|
|
31133
31358
|
* Constructs a new default XG insertion processor.
|
|
31134
31359
|
* @param sampleRate The sample rate, in Hertz.
|
|
31135
31360
|
* @param maxBufferSize The maximum buffer size the synthesizer can render at once.
|
|
31136
|
-
*
|
|
31361
|
+
* Processing more samples than this is not supported and produces undefined output.
|
|
31137
31362
|
*/
|
|
31138
31363
|
constructor(sampleRate, maxBufferSize) {
|
|
31139
31364
|
super(INSERTION_MAP, XGThru, 18688, sampleRate, maxBufferSize, true);
|
|
31140
31365
|
}
|
|
31366
|
+
/**
|
|
31367
|
+
* Resets this block to default values, including the processor type.
|
|
31368
|
+
*/
|
|
31141
31369
|
reset() {
|
|
31142
31370
|
super.reset();
|
|
31143
31371
|
this.partNumber = 127;
|
|
@@ -31150,12 +31378,19 @@ var DefaultXGInsertion = class extends DefaultXGEffect {
|
|
|
31150
31378
|
inputRight[i] = outputRight[i];
|
|
31151
31379
|
}
|
|
31152
31380
|
}
|
|
31381
|
+
/**
|
|
31382
|
+
* Gets a snapshot of this XG effect block.
|
|
31383
|
+
*/
|
|
31153
31384
|
getSnapshot() {
|
|
31154
31385
|
return {
|
|
31155
31386
|
...super.getSnapshot(),
|
|
31156
31387
|
partNumber: this.partNumber
|
|
31157
31388
|
};
|
|
31158
31389
|
}
|
|
31390
|
+
/**
|
|
31391
|
+
* Restores this XG effect block from a snapshot.
|
|
31392
|
+
* @param snapshot The snapshot to restore.
|
|
31393
|
+
*/
|
|
31159
31394
|
applySnapshot(snapshot) {
|
|
31160
31395
|
super.applySnapshot(snapshot);
|
|
31161
31396
|
this.partNumber = snapshot.partNumber;
|
|
@@ -31181,9 +31416,28 @@ const InterpolationTypes = {
|
|
|
31181
31416
|
*/
|
|
31182
31417
|
nearestNeighbor: 1,
|
|
31183
31418
|
/**
|
|
31184
|
-
*
|
|
31419
|
+
* Cubic Hermite interpolation. Good quality with little artifacting. (Default)
|
|
31420
|
+
*
|
|
31421
|
+
* Recommended for most use-cases.
|
|
31185
31422
|
*/
|
|
31186
|
-
hermite: 2
|
|
31423
|
+
hermite: 2,
|
|
31424
|
+
/**
|
|
31425
|
+
* 32-point windowed sinc interpolation.
|
|
31426
|
+
*
|
|
31427
|
+
* Very high quality with almost no aliasing or harmonic distortion,
|
|
31428
|
+
* usable in real time, but at a significantly higher CPU cost.
|
|
31429
|
+
*/
|
|
31430
|
+
sinc32: 3,
|
|
31431
|
+
/**
|
|
31432
|
+
* 128-point windowed sinc interpolation with antialiasing.
|
|
31433
|
+
*
|
|
31434
|
+
* The best quality, but extremely high cpu cost.
|
|
31435
|
+
*
|
|
31436
|
+
* > **Warning**
|
|
31437
|
+
* >
|
|
31438
|
+
* > This is very slow (especially for pitched up samples) and is meant for offline rendering, not real time playback.
|
|
31439
|
+
*/
|
|
31440
|
+
sinc128: 4
|
|
31187
31441
|
};
|
|
31188
31442
|
/**
|
|
31189
31443
|
* The special EFX control sources of a {@link GSInsertionBlock} (EFX CONTROL SOURCE 1/2).
|
|
@@ -31612,7 +31866,7 @@ var DefaultGSReverb = class {
|
|
|
31612
31866
|
* Constructs a new default GS reverb processor.
|
|
31613
31867
|
* @param sampleRate The sample rate, in Hertz.
|
|
31614
31868
|
* @param maxBufferSize The maximum buffer size the synthesizer can render at once.
|
|
31615
|
-
*
|
|
31869
|
+
* Processing more samples than this is not supported and produces undefined output.
|
|
31616
31870
|
*/
|
|
31617
31871
|
constructor(sampleRate, maxBufferSize) {
|
|
31618
31872
|
this.sampleRate = sampleRate;
|
|
@@ -31735,12 +31989,12 @@ var DefaultGSReverb = class {
|
|
|
31735
31989
|
this.updateLowpass();
|
|
31736
31990
|
}
|
|
31737
31991
|
/**
|
|
31738
|
-
*
|
|
31739
|
-
* @param input 0
|
|
31740
|
-
* @param outputLeft
|
|
31741
|
-
* @param outputRight
|
|
31742
|
-
* @param startIndex
|
|
31743
|
-
* @param sampleCount
|
|
31992
|
+
* Process the effect and **adds** it to the output.
|
|
31993
|
+
* @param input The input buffer to process. It always starts at index 0.
|
|
31994
|
+
* @param outputLeft The left output buffer.
|
|
31995
|
+
* @param outputRight The right output buffer.
|
|
31996
|
+
* @param startIndex The index to start mixing at into the output buffers.
|
|
31997
|
+
* @param sampleCount The amount of samples to mix.
|
|
31744
31998
|
*/
|
|
31745
31999
|
process(input, outputLeft, outputRight, startIndex, sampleCount) {
|
|
31746
32000
|
switch (this._character) {
|
|
@@ -31879,7 +32133,7 @@ var DefaultGSChorus = class {
|
|
|
31879
32133
|
* Constructs a new default GS chorus processor.
|
|
31880
32134
|
* @param sampleRate The sample rate, in Hertz.
|
|
31881
32135
|
* @param maxBufferSize The maximum buffer size the synthesizer can render at once.
|
|
31882
|
-
*
|
|
32136
|
+
* Processing more samples than this is not supported and produces undefined output.
|
|
31883
32137
|
*/
|
|
31884
32138
|
constructor(sampleRate, maxBufferSize) {
|
|
31885
32139
|
this.sampleRate = sampleRate;
|
|
@@ -32138,7 +32392,7 @@ var DefaultGSDelay = class {
|
|
|
32138
32392
|
* Constructs a new default GS delay processor.
|
|
32139
32393
|
* @param sampleRate The sample rate, in Hertz.
|
|
32140
32394
|
* @param maxBufferSize The maximum buffer size the synthesizer can render at once.
|
|
32141
|
-
*
|
|
32395
|
+
* Processing more samples than this is not supported and produces undefined output.
|
|
32142
32396
|
*/
|
|
32143
32397
|
constructor(sampleRate, maxBufferSize) {
|
|
32144
32398
|
this.sampleRate = sampleRate;
|
|
@@ -32838,6 +33092,16 @@ function prewarp(frequency, sampleRate) {
|
|
|
32838
33092
|
return Math.tan(Math.PI * Math.min(frequency, sampleRate * .49) / sampleRate);
|
|
32839
33093
|
}
|
|
32840
33094
|
/**
|
|
33095
|
+
* The corner of the hardware's one-pole low pass `(1 - B) x + B y[n-1]` at 32 kHz (-3 dB, the inverse of
|
|
33096
|
+
* `FirstOrderFilter.setDamp`).
|
|
33097
|
+
* @param pole B.
|
|
33098
|
+
* @internal
|
|
33099
|
+
*/
|
|
33100
|
+
function dampCorner(pole) {
|
|
33101
|
+
const k = (1 + pole * pole) / (2 * pole);
|
|
33102
|
+
return Math.acos(2 - k) * 32e3 / (2 * Math.PI);
|
|
33103
|
+
}
|
|
33104
|
+
/**
|
|
32841
33105
|
* A first order section, `y = b0 x + b1 x[n-1] + a1 y[n-1]`, designed with the bilinear transform.
|
|
32842
33106
|
* The tone filters, the shelves and the DC blockers of the Sound Canvas are all this section.
|
|
32843
33107
|
* @internal
|
|
@@ -39632,7 +39896,7 @@ var GSEqualizer = class extends GSEffect {
|
|
|
39632
39896
|
* @param x The argument.
|
|
39633
39897
|
* @internal
|
|
39634
39898
|
*/
|
|
39635
|
-
function besselI0(x) {
|
|
39899
|
+
function besselI0$1(x) {
|
|
39636
39900
|
let sum = 1;
|
|
39637
39901
|
let term = 1;
|
|
39638
39902
|
for (let k = 1; k < 32; k++) {
|
|
@@ -39648,7 +39912,7 @@ function besselI0(x) {
|
|
|
39648
39912
|
* @internal
|
|
39649
39913
|
*/
|
|
39650
39914
|
function kaiser(t, beta) {
|
|
39651
|
-
return besselI0(beta * Math.sqrt(1 - t * t)) / besselI0(beta);
|
|
39915
|
+
return besselI0$1(beta * Math.sqrt(1 - t * t)) / besselI0$1(beta);
|
|
39652
39916
|
}
|
|
39653
39917
|
// src/synthesizer/audio_engine/effects/gs/implementation/dsp/blocks/delay_line.ts
|
|
39654
39918
|
/**
|
|
@@ -42963,7 +43227,7 @@ function cutoff(value) {
|
|
|
42963
43227
|
*/
|
|
42964
43228
|
const STEP_MS = 1 / 32;
|
|
42965
43229
|
/**
|
|
42966
|
-
*
|
|
43230
|
+
* A bound on the swing of `modulationDepth` (12.4 ms at depth 127), in ms, for sizing the lines.
|
|
42967
43231
|
* @internal
|
|
42968
43232
|
*/
|
|
42969
43233
|
const SWING_MS = 12.5;
|
|
@@ -43526,7 +43790,8 @@ var Dynamics = class {
|
|
|
43526
43790
|
};
|
|
43527
43791
|
/**
|
|
43528
43792
|
* The compressor: 1 ms lookahead, the gain recovers linearly, the output has a makeup gain (the "Attack" parameter,
|
|
43529
|
-
* which sets how far below the fixed output ceiling the knee sits
|
|
43793
|
+
* which sets how far below the fixed output ceiling the knee sits, the manuals call it Sustain, see `GSCompressor`)
|
|
43794
|
+
* and a two-sample average.
|
|
43530
43795
|
* @internal
|
|
43531
43796
|
*/
|
|
43532
43797
|
var Compressor = class extends Dynamics {
|
|
@@ -43670,6 +43935,10 @@ var GSDynamicsEffect = class extends GSEffect {
|
|
|
43670
43935
|
* Compressor (`01 30`).
|
|
43671
43936
|
* Parameters: 1 sustain (how fast the gain comes back), 2 attack (the makeup gain: the output ceiling stays, the
|
|
43672
43937
|
* knee moves), 3 post gain, then the EQ, pan and level.
|
|
43938
|
+
*
|
|
43939
|
+
* The SC-VA and SC-8850 manuals list parameter 1 as Attack and 2 as Sustain, the other way round from the names
|
|
43940
|
+
* here. Only the names differ: parameter 1 still sets how fast the gain recovers (0.09 to 2.25 per second) and
|
|
43941
|
+
* parameter 2 the makeup gain, like in Sound Canvas VA. The multis' compressors and Lo-Fi 1 use the same names.
|
|
43673
43942
|
* @internal
|
|
43674
43943
|
*/
|
|
43675
43944
|
var GSCompressor = class extends GSDynamicsEffect {
|
|
@@ -46868,7 +47137,7 @@ const LINE_LENGTHS = [
|
|
|
46868
47137
|
];
|
|
46869
47138
|
/**
|
|
46870
47139
|
* How many host samples later the whole forward path runs, and how much earlier the outputs read, so that even the
|
|
46871
|
-
* shortest pre delay is read band-limited (the interpolator needs 7 newer samples). The outputs read
|
|
47140
|
+
* shortest pre delay is read band-limited (the interpolator needs 7 newer samples). The outputs read 140 32 kHz
|
|
46872
47141
|
* samples or more, so they have room.
|
|
46873
47142
|
*/
|
|
46874
47143
|
const LOOKAHEAD$1 = 8;
|
|
@@ -46881,15 +47150,6 @@ const ALLPASS$1 = .5;
|
|
|
46881
47150
|
*/
|
|
46882
47151
|
const PRE_GAIN$1 = (127 / 128) ** 2;
|
|
46883
47152
|
/**
|
|
46884
|
-
* The corner of the hardware's one-pole low pass `(1 - B) x + B y[n-1]` at 32 kHz (-3 dB, the inverse of
|
|
46885
|
-
* `FirstOrderFilter.setDamp`).
|
|
46886
|
-
* @param pole B.
|
|
46887
|
-
*/
|
|
46888
|
-
function dampCorner(pole) {
|
|
46889
|
-
const k = (1 + pole * pole) / (2 * pole);
|
|
46890
|
-
return Math.acos(2 - k) * 32e3 / (2 * Math.PI);
|
|
46891
|
-
}
|
|
46892
|
-
/**
|
|
46893
47153
|
* The Reverb: a mono input through a DC blocker and an input low pass into a pre delay, a diffuser of three
|
|
46894
47154
|
* Schroeder allpasses and a delay, then two independent tanks (allpass, delay, allpass, delay, a damping low pass
|
|
46895
47155
|
* and the time gain back to the start). The stereo image comes from the output taps alone, four per side spread
|
|
@@ -47597,7 +47857,7 @@ var LoFiQuantizer = class {
|
|
|
47597
47857
|
};
|
|
47598
47858
|
// src/synthesizer/audio_engine/effects/gs/implementation/dsp/effects/lo_fi_1.ts
|
|
47599
47859
|
/**
|
|
47600
|
-
* The gain after the compressor per pre
|
|
47860
|
+
* The gain after the compressor per pre-ilter (1 bypasses the compressor).
|
|
47601
47861
|
*/
|
|
47602
47862
|
const PRE_GAINS = [
|
|
47603
47863
|
1,
|
|
@@ -47652,9 +47912,9 @@ const POST_FILTERS = [
|
|
|
47652
47912
|
}
|
|
47653
47913
|
];
|
|
47654
47914
|
/**
|
|
47655
|
-
* Lo-Fi 1 (`01 72`): `L+R -> compressor (pre
|
|
47915
|
+
* Lo-Fi 1 (`01 72`): `L+R -> compressor (pre-filter) -> low pass -> quantizer -> high pass and dip (post filter)`,
|
|
47656
47916
|
* mixed with the mono input by the balance, then the EQ and the pan.
|
|
47657
|
-
* Parameters: 1 pre
|
|
47917
|
+
* Parameters: 1 pre-filter (1 dry, 2-6 the compressor with a gain of 2 to 7.94, 3 and 5 with a 4 kHz low pass),
|
|
47658
47918
|
* 2 lo-fi type (15 or 11 bits at 16000, 10667 or 8000 Hz), 3 post filter (high pass at 633 or 1595 Hz and a dip at
|
|
47659
47919
|
* 1 kHz of Q 2, 1 or 0.5), 16 balance (the pan law), 17 low gain, 18 high gain, 19 pan, 20 level. EFX control source
|
|
47660
47920
|
* 1 modulates the balance, source 2 the pan.
|
|
@@ -47692,7 +47952,7 @@ var GSLoFi1 = class extends GSEffect {
|
|
|
47692
47952
|
mix;
|
|
47693
47953
|
compressor;
|
|
47694
47954
|
/**
|
|
47695
|
-
* The compressor's own DC blocker, for the dry path and the pre
|
|
47955
|
+
* The compressor's own DC blocker, for the dry path and the pre-filter that bypasses it.
|
|
47696
47956
|
*/
|
|
47697
47957
|
dcBlocker;
|
|
47698
47958
|
lowPass;
|
|
@@ -47891,7 +48151,7 @@ function clamp32(x) {
|
|
|
47891
48151
|
*
|
|
47892
48152
|
* - White (x 1/32) or "pink": the white noise times a 500 Hz saw (on the SC-88Pro too).
|
|
47893
48153
|
* - Disc: one click per turn of a record at 33 1/3, 45 or 78 rpm, 0.125 + 0.25 abs(saw) while the turn's phase is
|
|
47894
|
-
* within 0.
|
|
48154
|
+
* within 0.014 % of its wrap. RND adds a second train whose phase is offset by twice the hum's (pseudo-random
|
|
47895
48155
|
* times).
|
|
47896
48156
|
* - Hum: a unipolar 50 or 60 Hz square, 0 or 1/16 (it carries DC, on the hardware too).
|
|
47897
48157
|
*
|
|
@@ -48239,7 +48499,7 @@ var GSLoFi2 = class extends GSEffect {
|
|
|
48239
48499
|
this.chainR = new LoFiShaperChain(sampleRate);
|
|
48240
48500
|
this.noise = new NoiseGenerator(sampleRate);
|
|
48241
48501
|
this.radioNoise = new FirstOrderFilter(sampleRate);
|
|
48242
|
-
this.radioNoise.setDamp(
|
|
48502
|
+
this.radioNoise.setDamp(dampCorner(.5));
|
|
48243
48503
|
this.shelvesL = new ShelfEQ(sampleRate);
|
|
48244
48504
|
this.shelvesR = new ShelfEQ(sampleRate);
|
|
48245
48505
|
this.pan = new MonoPan(sampleRate);
|
|
@@ -50683,8 +50943,9 @@ const MULTI_WAH_GAIN = 3.937744140625;
|
|
|
50683
50943
|
const DRIVE_INPUT = 128 / 127;
|
|
50684
50944
|
const DRIVE_OUTPUT = 127 / 128;
|
|
50685
50945
|
/**
|
|
50686
|
-
* The compressor of the multis (parameters 1-4: sustain, attack, level, switch
|
|
50687
|
-
* its post gain at +6 dB and a level after it. The
|
|
50946
|
+
* The compressor of the multis (parameters 1-4: sustain, attack, level, switch, the manuals swap the first two
|
|
50947
|
+
* names, see `GSCompressor`): the Compressor's dynamics with its post gain at +6 dB and a level after it. The
|
|
50948
|
+
* compressor runs when it is switched off too.
|
|
50688
50949
|
* @internal
|
|
50689
50950
|
*/
|
|
50690
50951
|
var MultiCompressor = class {
|
|
@@ -51466,10 +51727,10 @@ var GSDriveDelayMulti = class extends GSMulti {
|
|
|
51466
51727
|
/**
|
|
51467
51728
|
* GTR Multi 1 (`04 00`): `L+R -> compressor -> overdrive/distortion -> 2-band EQ -> amp simulator -> chorus/flanger
|
|
51468
51729
|
* -> delay`.
|
|
51469
|
-
* Parameters: 1-4 compressor sustain, attack
|
|
51470
|
-
* switch,
|
|
51471
|
-
*
|
|
51472
|
-
* EFX control source 1 moves the drive, source 2 the delay level.
|
|
51730
|
+
* Parameters: 1-4 compressor sustain, attack (the manuals swap these two names, see `GSCompressor`), level and
|
|
51731
|
+
* switch, 5 drive type (OD, DS), 6 drive, 7 amp type, 8 amp switch, 9 low gain, 10 high gain, 11 drive switch,
|
|
51732
|
+
* 12 chorus or flanger, 13 rate (Rate2 column), 14 depth, 15 flanger feedback, 16 chorus level, 17 delay time (Dly4
|
|
51733
|
+
* column), 18 feedback, 19 delay level, 20 level. EFX control source 1 moves the drive, source 2 the delay level.
|
|
51473
51734
|
* @internal
|
|
51474
51735
|
*/
|
|
51475
51736
|
var GSGuitarMulti1 = class extends GSDriveDelayMulti {
|
|
@@ -51644,10 +51905,10 @@ var GSDriveEQMulti = class extends GSMulti {
|
|
|
51644
51905
|
};
|
|
51645
51906
|
/**
|
|
51646
51907
|
* GTR Multi 2 (`04 01`): `L+R -> compressor -> overdrive/distortion -> amp simulator -> 3-band EQ -> chorus/flanger`.
|
|
51647
|
-
* Parameters: 1-4 compressor sustain, attack
|
|
51648
|
-
* switch,
|
|
51649
|
-
*
|
|
51650
|
-
* source 2 the chorus level.
|
|
51908
|
+
* Parameters: 1-4 compressor sustain, attack (the manuals swap these two names, see `GSCompressor`), level and
|
|
51909
|
+
* switch, 5 drive type (OD, DS), 6 drive, 7 amp type, 8 amp switch, 9 drive switch, 10 low gain, 11-13 mid-frequency,
|
|
51910
|
+
* Q and gain, 14 high gain, 15 chorus or flanger, 16 rate (Rate2 column), 17 depth, 18 flanger feedback,
|
|
51911
|
+
* 19 chorus level, 20 level. EFX control source 1 moves the drive, source 2 the chorus level.
|
|
51651
51912
|
* @internal
|
|
51652
51913
|
*/
|
|
51653
51914
|
var GSGuitarMulti2 = class extends GSDriveEQMulti {
|
|
@@ -51755,10 +52016,10 @@ var BassAmpSimulator = class {
|
|
|
51755
52016
|
/**
|
|
51756
52017
|
* Bass Multi (`04 05`): `L+R -> compressor -> bass overdrive/distortion -> bass amp simulator -> 3-band EQ ->
|
|
51757
52018
|
* chorus/flanger`.
|
|
51758
|
-
* Parameters: 1-4 compressor sustain, attack
|
|
51759
|
-
*
|
|
51760
|
-
* 16 rate (Rate2 column), 17 depth, 18 flanger feedback,
|
|
51761
|
-
* drive, source 2 the chorus level.
|
|
52019
|
+
* Parameters: 1-4 compressor sustain, attack (the manuals swap these two names, see `GSCompressor`), level and
|
|
52020
|
+
* switch, 5 drive type (OD, DS), 6 drive, 7 amp type (0-2), 8 amp switch, 9 drive switch, 10 low gain, 11-13 mid- * frequency,
|
|
52021
|
+
* Q and gain, 14 high gain, 15 chorus or flanger, 16 rate (Rate2 column), 17 depth, 18 flanger feedback,
|
|
52022
|
+
* 19 chorus level, 20 level. EFX control source 1 moves the drive, source 2 the chorus level.
|
|
51762
52023
|
* @internal
|
|
51763
52024
|
*/
|
|
51764
52025
|
var GSBassMulti = class extends GSDriveEQMulti {
|
|
@@ -51812,10 +52073,10 @@ var GSBassMulti = class extends GSDriveEQMulti {
|
|
|
51812
52073
|
const LINEAR_INPUT = 127 / 128;
|
|
51813
52074
|
/**
|
|
51814
52075
|
* Clean GTR Multi 1 (`04 03`): `L+R -> compressor -> 3-band EQ -> chorus/flanger -> delay`.
|
|
51815
|
-
* Parameters: 1-4 compressor sustain, attack
|
|
51816
|
-
*
|
|
51817
|
-
*
|
|
51818
|
-
* source 2 the delay level.
|
|
52076
|
+
* Parameters: 1-4 compressor sustain, attack (the manuals swap these two names, see `GSCompressor`), level and
|
|
52077
|
+
* switch, 5 low gain, 6-8 mid-frequency, Q and gain, 9 high gain, 10 chorus or flanger, 11 rate (Rate2 column),
|
|
52078
|
+
* 12 depth, 13 flanger feedback, 14 chorus level, 15 delay time (Dly4 column), 16 feedback, 17 HF damp, 18 delay
|
|
52079
|
+
* level, 20 level. EFX control source 1 moves the chorus level, source 2 the delay level.
|
|
51819
52080
|
* @internal
|
|
51820
52081
|
*/
|
|
51821
52082
|
var GSCleanGuitarMulti1 = class extends GSMulti {
|
|
@@ -51889,7 +52150,7 @@ var GSCleanGuitarMulti1 = class extends GSMulti {
|
|
|
51889
52150
|
* Clean GTR Multi 2 (`04 04`): `L+R -> wah -> 3-band EQ -> chorus/flanger -> delay`. The wah is the Auto Wah's without
|
|
51890
52151
|
* the envelope follower, its LFO raises the cutoff (polarity up).
|
|
51891
52152
|
* Parameters: 1 wah filter type (LPF, BPF), 2 manual, 3 peak, 4 rate (Rate1 column), 5 depth, 6 wah switch (SC-VA's
|
|
51892
|
-
* code calls it the EQ switch, but it bypasses the wah), 7 low gain, 8-10 mid
|
|
52153
|
+
* code calls it the EQ switch, but it bypasses the wah), 7 low gain, 8-10 mid-frequency, Q and gain, 11 high gain,
|
|
51893
52154
|
* 12 chorus or flanger, 13 rate (Rate2 column), 14 depth, 15 flanger feedback, 16 chorus level, 17 delay time (Dly4
|
|
51894
52155
|
* column), 18 feedback, 19 delay level, 20 level. EFX control source 1 moves the wah manual, source 2 the delay
|
|
51895
52156
|
* level.
|
|
@@ -52107,9 +52368,11 @@ const DEFAULT_CONTROL_DEPTH = 64;
|
|
|
52107
52368
|
/**
|
|
52108
52369
|
* The offset an EFX control source adds to its parameter, like Sound Canvas VA computes it:
|
|
52109
52370
|
* `(depth - 64) * value / 63`, truncated and stored in a signed byte. Depth 0 counts as 1 (both are -100%).
|
|
52371
|
+
* The byte wraps without a clamp, like in SCCore.dll: pitch bend fully down (-128) at depth 0 or 1 gives +128,
|
|
52372
|
+
* which is stored as -128, so the offset flips sign there.
|
|
52110
52373
|
* @param depth The control depth (0-127).
|
|
52111
52374
|
* @param value The signed controller value.
|
|
52112
|
-
* @returns The signed offset.
|
|
52375
|
+
* @returns The signed offset (-128 to 127).
|
|
52113
52376
|
*/
|
|
52114
52377
|
function controlOffset(depth, value) {
|
|
52115
52378
|
const amount = (depth === 0 ? 1 : depth) - 64;
|
|
@@ -52167,7 +52430,7 @@ var DefaultGSInsertion = class {
|
|
|
52167
52430
|
* Constructs a new default GS insertion block.
|
|
52168
52431
|
* @param sampleRate The sample rate, in Hertz.
|
|
52169
52432
|
* @param maxBufferSize The maximum buffer size the synthesizer can render at once.
|
|
52170
|
-
*
|
|
52433
|
+
* Processing more samples than this is not supported and produces undefined output.
|
|
52171
52434
|
*/
|
|
52172
52435
|
constructor(sampleRate, maxBufferSize) {
|
|
52173
52436
|
const memory = new DelayMemory();
|
|
@@ -52532,8 +52795,9 @@ var SoundBankManager = class {
|
|
|
52532
52795
|
const bankOffset = s.bankOffset;
|
|
52533
52796
|
for (const p of bank.presets) {
|
|
52534
52797
|
const selectablePreset = new SoundBankManagerPreset(p, bankOffset);
|
|
52535
|
-
|
|
52536
|
-
|
|
52798
|
+
const key = selectablePreset.toMIDIString();
|
|
52799
|
+
if (!addedPresets.has(key)) {
|
|
52800
|
+
addedPresets.add(key);
|
|
52537
52801
|
presetList.push(selectablePreset);
|
|
52538
52802
|
}
|
|
52539
52803
|
}
|
|
@@ -52624,12 +52888,13 @@ for (let pan = MIN_PAN; pan <= MAX_PAN; pan++) {
|
|
|
52624
52888
|
}
|
|
52625
52889
|
/**
|
|
52626
52890
|
* Computes the LFO value for the given phase.
|
|
52627
|
-
*
|
|
52891
|
+
* All waveforms are at zero and rising at the default start phase of 0.25.
|
|
52628
52892
|
* @param phase the LFO phase, from 0 to 1.
|
|
52629
|
-
* @param waveform 0 for triangle, 1 for sawtooth.
|
|
52893
|
+
* @param waveform 0 for triangle, 1 for sawtooth, 2 for sine.
|
|
52630
52894
|
* @returns the LFO value, from -1 to 1.
|
|
52631
52895
|
*/
|
|
52632
52896
|
function getLfoValue(phase, waveform) {
|
|
52897
|
+
if (waveform === 2) return Math.sin(TWO_PI * (phase - .25));
|
|
52633
52898
|
if (waveform === 1) {
|
|
52634
52899
|
const p = phase + .25;
|
|
52635
52900
|
return 2 * (p >= 1 ? p - 1 : p) - 1;
|
|
@@ -52637,10 +52902,11 @@ function getLfoValue(phase, waveform) {
|
|
|
52637
52902
|
return 1 - 4 * Math.abs(phase - .5);
|
|
52638
52903
|
}
|
|
52639
52904
|
/**
|
|
52640
|
-
* Renders a voice
|
|
52905
|
+
* Renders a voice into one of the channel's voice send buffers,
|
|
52906
|
+
* which are later mixed into the stereo output and the effect sends.
|
|
52641
52907
|
* @param voice the voice to render
|
|
52642
52908
|
* @param timeNow current time in seconds
|
|
52643
|
-
* @param sampleCount the
|
|
52909
|
+
* @param sampleCount the number of samples to render (the buffers are 0-based, so no offset is needed)
|
|
52644
52910
|
*/
|
|
52645
52911
|
function renderVoice(voice, timeNow, sampleCount) {
|
|
52646
52912
|
if (timeNow < voice.startTime) {
|
|
@@ -52902,9 +53168,9 @@ var VoiceSendBuffer = class {
|
|
|
52902
53168
|
};
|
|
52903
53169
|
/**
|
|
52904
53170
|
* Mixes a voice send buffer into the channel output and the effect sends, then clears it.
|
|
52905
|
-
* @param channel
|
|
52906
|
-
* @param buffer
|
|
52907
|
-
* @param sampleCount
|
|
53171
|
+
* @param channel The channel that owns the buffer.
|
|
53172
|
+
* @param buffer The voice send buffer to mix.
|
|
53173
|
+
* @param sampleCount The number of samples to mix.
|
|
52908
53174
|
*/
|
|
52909
53175
|
function mixVoiceSendBuffer(channel, buffer, sampleCount) {
|
|
52910
53176
|
const core = channel.synthCore;
|
|
@@ -53203,7 +53469,7 @@ function handleAWE32NRPN(paramLSB, dataValue) {
|
|
|
53203
53469
|
}
|
|
53204
53470
|
// src/synthesizer/audio_engine/channel/data_entry.ts
|
|
53205
53471
|
/**
|
|
53206
|
-
* Executes a data entry
|
|
53472
|
+
* Executes a data entry change for the current channel.
|
|
53207
53473
|
*/
|
|
53208
53474
|
function dataEntry() {
|
|
53209
53475
|
const dataValue = this._midiControllers[MIDIControllers.dataEntryMSB];
|
|
@@ -53342,7 +53608,7 @@ function dataEntry() {
|
|
|
53342
53608
|
break;
|
|
53343
53609
|
case NonRegisteredParameterTypesMSB.drumVariation:
|
|
53344
53610
|
this.drumParams[parameterFine].variationSend = dataCoarse;
|
|
53345
|
-
SpessaLog.coolInfo(`Drum ${parameterFine} Variation Send for ${this.channel}`,
|
|
53611
|
+
SpessaLog.coolInfo(`Drum ${parameterFine} Variation Send for ${this.channel}`, dataCoarse, "");
|
|
53346
53612
|
break;
|
|
53347
53613
|
case NonRegisteredParameterTypesMSB.awe32:
|
|
53348
53614
|
handleAWE32NRPN.call(this, parameterFine, dataValue);
|
|
@@ -53675,7 +53941,7 @@ function noteOn(midiNote, velocity, emit = true) {
|
|
|
53675
53941
|
// src/synthesizer/audio_engine/channel/note_off.ts
|
|
53676
53942
|
/**
|
|
53677
53943
|
* Releases a note by its MIDI note number.
|
|
53678
|
-
* If the
|
|
53944
|
+
* If the synthesizer is in high performance mode and the channel is not a drum channel,
|
|
53679
53945
|
* or the drum channel has an rxNoteOff enabled,
|
|
53680
53946
|
* it kills the note instead of releasing it.
|
|
53681
53947
|
* @param midiNote The MIDI note number to release (0-127).
|
|
@@ -53852,7 +54118,7 @@ var DynamicModulatorManager = class {
|
|
|
53852
54118
|
case 5:
|
|
53853
54119
|
this.setModulator(source, isCC, GeneratorTypes.vibLfoToVolume, normalizedNotCentered * 200, bipolar, false, true);
|
|
53854
54120
|
this.setModulator(source, isCC, GeneratorTypes.initialAttenuation, normalizedNotCentered * 200, bipolar, false, true);
|
|
53855
|
-
SpessaLog.xgInfo(`Channel ${this.channel} ${sourceName} LFO amplitude depth control`, normalizedNotCentered * 200, "
|
|
54121
|
+
SpessaLog.xgInfo(`Channel ${this.channel} ${sourceName} LFO amplitude depth control`, normalizedNotCentered * 200, "cB");
|
|
53856
54122
|
}
|
|
53857
54123
|
}
|
|
53858
54124
|
setupReceiverGS(addr3, data, source, isCC, sourceName, bipolar = false) {
|
|
@@ -54259,6 +54525,7 @@ var MIDIChannel = class {
|
|
|
54259
54525
|
/**
|
|
54260
54526
|
* If true, this skips sends as they all go through insertion.
|
|
54261
54527
|
* Set by {@link SpessaSynthProcessor}.
|
|
54528
|
+
* @internal
|
|
54262
54529
|
*/
|
|
54263
54530
|
xgInsertionAssigned = false;
|
|
54264
54531
|
/**
|
|
@@ -54333,8 +54600,8 @@ var MIDIChannel = class {
|
|
|
54333
54600
|
*/
|
|
54334
54601
|
resetRP15 = resetRP15.bind(this);
|
|
54335
54602
|
/**
|
|
54336
|
-
* Executes a data entry
|
|
54337
|
-
*
|
|
54603
|
+
* Executes a data entry (MSB or LSB) change for the current channel.
|
|
54604
|
+
* The value is read from the controller table and applied to the selected RPN or NRPN.
|
|
54338
54605
|
* @internal
|
|
54339
54606
|
*/
|
|
54340
54607
|
dataEntry = dataEntry.bind(this);
|
|
@@ -54589,7 +54856,7 @@ var MIDIChannel = class {
|
|
|
54589
54856
|
noteOn(midiNote, velocity, emit = true) {}
|
|
54590
54857
|
/**
|
|
54591
54858
|
* Releases a note by its MIDI note number.
|
|
54592
|
-
* If the
|
|
54859
|
+
* If the synthesizer is in high performance mode and the channel is not a drum channel,
|
|
54593
54860
|
* or the drum channel has an rxNoteOff enabled,
|
|
54594
54861
|
* it kills the note instead of releasing it.
|
|
54595
54862
|
* @param midiNote The MIDI note number to release (0-127).
|
|
@@ -54618,14 +54885,16 @@ var MIDIChannel = class {
|
|
|
54618
54885
|
/**
|
|
54619
54886
|
* Reset this channel to its default state.
|
|
54620
54887
|
* Except for the locked controllers.
|
|
54888
|
+
* @param sendEvent If controller change events should be emitted for the reset controllers.
|
|
54621
54889
|
* @internal
|
|
54622
54890
|
*/
|
|
54623
54891
|
reset(sendEvent = true) {}
|
|
54624
54892
|
/**
|
|
54625
|
-
* Renders a voice
|
|
54893
|
+
* Renders a voice into one of the channel's voice send buffers,
|
|
54894
|
+
* which are later mixed into the stereo output and the effect sends.
|
|
54626
54895
|
* @param voice the voice to render
|
|
54627
54896
|
* @param timeNow current time in seconds
|
|
54628
|
-
* @param sampleCount the
|
|
54897
|
+
* @param sampleCount the number of samples to render (the buffers are 0-based, so no offset is needed)
|
|
54629
54898
|
* @internal
|
|
54630
54899
|
*/
|
|
54631
54900
|
renderVoice(voice, timeNow, sampleCount) {}
|
|
@@ -55207,7 +55476,7 @@ var LowpassFilter = class LowpassFilter {
|
|
|
55207
55476
|
const qCb = this.resonanceCb;
|
|
55208
55477
|
let cutoffHz = absCentsToHz(cutoffCents);
|
|
55209
55478
|
cutoffHz = Math.min(cutoffHz, this.maxCutoff);
|
|
55210
|
-
const resonanceGain = cbAttenuationToGain(-(qCb -
|
|
55479
|
+
const resonanceGain = cbAttenuationToGain(-(qCb - 30.1));
|
|
55211
55480
|
const qGain = 1 / Math.sqrt(cbAttenuationToGain(-qCb));
|
|
55212
55481
|
const w = 2 * Math.PI * cutoffHz / this.sampleRate;
|
|
55213
55482
|
const cosw = Math.cos(w);
|
|
@@ -55254,7 +55523,7 @@ var VolumeEnvelope = class {
|
|
|
55254
55523
|
/**
|
|
55255
55524
|
* The sample count between updates of the volume envelope.
|
|
55256
55525
|
* Since the volume envelope calculation runs once per rendering quantum,
|
|
55257
|
-
* this effectively the buffer size.
|
|
55526
|
+
* this is effectively the buffer size.
|
|
55258
55527
|
*/
|
|
55259
55528
|
updateInterval;
|
|
55260
55529
|
/**
|
|
@@ -55262,11 +55531,11 @@ var VolumeEnvelope = class {
|
|
|
55262
55531
|
*/
|
|
55263
55532
|
sampleTime = 0;
|
|
55264
55533
|
/**
|
|
55265
|
-
* The
|
|
55534
|
+
* The cB attenuation of the envelope when it entered the release stage.
|
|
55266
55535
|
*/
|
|
55267
55536
|
releaseStartCb = CB_SILENCE;
|
|
55268
55537
|
/**
|
|
55269
|
-
* The time in samples relative to the start of the envelope.
|
|
55538
|
+
* The time in samples when the release stage started, relative to the start of the envelope.
|
|
55270
55539
|
*/
|
|
55271
55540
|
releaseStartTimeSamples = 0;
|
|
55272
55541
|
/**
|
|
@@ -55405,10 +55674,10 @@ var VolumeEnvelope = class {
|
|
|
55405
55674
|
}
|
|
55406
55675
|
/**
|
|
55407
55676
|
* Calculates the gain value for the last sample in the block and writes it to `outputGain`.
|
|
55408
|
-
* Essentially we use approach of
|
|
55409
|
-
* @param sampleCount the amount of samples to
|
|
55677
|
+
* Essentially we use approach of 96 dB (CB_SILENCE) is silence, 0dB is peak.
|
|
55678
|
+
* @param sampleCount the amount of samples in the block to advance the envelope by.
|
|
55410
55679
|
* @param gainTarget the gain to apply.
|
|
55411
|
-
* @returns if the voice has finished.
|
|
55680
|
+
* @returns if the voice is still active (false once the envelope has finished).
|
|
55412
55681
|
*/
|
|
55413
55682
|
process(sampleCount, gainTarget) {
|
|
55414
55683
|
const { releaseStartTimeSamples, releaseStartCb, releaseDuration, delayEnd, attackEnd, attackDuration, holdEnd, decayEnd, decayDuration, sustainCb, decay2End, decay2Duration, sustain2Cb } = this;
|
|
@@ -55566,7 +55835,7 @@ var ModulationEnvelope = class {
|
|
|
55566
55835
|
* Calculates the current modulation envelope value for the given time and voice.
|
|
55567
55836
|
* @param voice the voice we are working on.
|
|
55568
55837
|
* @param currentTime in seconds.
|
|
55569
|
-
* @returns
|
|
55838
|
+
* @returns mod env value, from 0 to 1.
|
|
55570
55839
|
*/
|
|
55571
55840
|
process(voice, currentTime) {
|
|
55572
55841
|
if (this.enteredRelease) {
|
|
@@ -55839,6 +56108,217 @@ var HermiteOscillator = class extends WavetableOscillator {
|
|
|
55839
56108
|
return true;
|
|
55840
56109
|
}
|
|
55841
56110
|
};
|
|
56111
|
+
// src/synthesizer/audio_engine/voice/wavetable_oscillator/sinc.ts
|
|
56112
|
+
/**
|
|
56113
|
+
* Kernel resolution, the amount of table points per sample of distance.
|
|
56114
|
+
* The table is linearly interpolated, which keeps the error around -120 dB.
|
|
56115
|
+
*/
|
|
56116
|
+
const OVERSAMPLING = 256;
|
|
56117
|
+
/**
|
|
56118
|
+
* The highest playback step the anti-aliased kernel gets stretched to.
|
|
56119
|
+
* The tap count grows with the step, so this keeps extreme pitch ups from becoming too slow.
|
|
56120
|
+
* Above it, the content between the new Nyquist frequency and 1/16 of the old one folds back.
|
|
56121
|
+
*/
|
|
56122
|
+
const MAX_STRETCH = 16;
|
|
56123
|
+
/**
|
|
56124
|
+
* Zeroth order modified Bessel function of the first kind, for the Kaiser window.
|
|
56125
|
+
* @param x
|
|
56126
|
+
*/
|
|
56127
|
+
function besselI0(x) {
|
|
56128
|
+
const q = x * x / 4;
|
|
56129
|
+
let sum = 1;
|
|
56130
|
+
let term = 1;
|
|
56131
|
+
for (let k = 1; k < 100; k++) {
|
|
56132
|
+
term *= q / (k * k);
|
|
56133
|
+
sum += term;
|
|
56134
|
+
if (term < sum * 1e-17) break;
|
|
56135
|
+
}
|
|
56136
|
+
return sum;
|
|
56137
|
+
}
|
|
56138
|
+
/**
|
|
56139
|
+
* Builds a Kaiser windowed sinc kernel.
|
|
56140
|
+
* @param halfWidth Half of the kernel width, in samples. The kernel uses `2 * halfWidth` taps.
|
|
56141
|
+
* @param bandwidth The cutoff (-6 dB point) of the kernel, relative to the Nyquist frequency.
|
|
56142
|
+
* @param beta The Kaiser window beta, controls the stopband attenuation.
|
|
56143
|
+
*/
|
|
56144
|
+
function buildKernel(halfWidth, bandwidth, beta) {
|
|
56145
|
+
const windowNorm = besselI0(beta);
|
|
56146
|
+
const kernel = (x) => {
|
|
56147
|
+
const w = x / halfWidth;
|
|
56148
|
+
if (w <= -1 || w >= 1) return 0;
|
|
56149
|
+
const window = besselI0(beta * Math.sqrt(1 - w * w)) / windowNorm;
|
|
56150
|
+
const u = Math.PI * bandwidth * x;
|
|
56151
|
+
return bandwidth * (u === 0 ? 1 : Math.sin(u) / u) * window;
|
|
56152
|
+
};
|
|
56153
|
+
const taps = halfWidth * 2;
|
|
56154
|
+
const rows = new Float64Array(taps * 257);
|
|
56155
|
+
for (let q = 0; q <= OVERSAMPLING; q++) {
|
|
56156
|
+
const row = q * taps;
|
|
56157
|
+
let sum = 0;
|
|
56158
|
+
for (let k = 0; k < taps; k++) {
|
|
56159
|
+
const v = kernel(k + 1 - halfWidth - q / OVERSAMPLING);
|
|
56160
|
+
rows[row + k] = v;
|
|
56161
|
+
sum += v;
|
|
56162
|
+
}
|
|
56163
|
+
for (let k = 0; k < taps; k++) rows[row + k] /= sum;
|
|
56164
|
+
}
|
|
56165
|
+
const phases = new Float32Array(taps * OVERSAMPLING);
|
|
56166
|
+
const phaseDeltas = new Float32Array(taps * OVERSAMPLING);
|
|
56167
|
+
for (let i = 0; i < phases.length; i++) {
|
|
56168
|
+
phases[i] = rows[i];
|
|
56169
|
+
phaseDeltas[i] = rows[i + taps] - rows[i];
|
|
56170
|
+
}
|
|
56171
|
+
const tableLength = halfWidth * 2 * OVERSAMPLING + 2;
|
|
56172
|
+
const table = new Float32Array(tableLength);
|
|
56173
|
+
const tableDeltas = new Float32Array(tableLength);
|
|
56174
|
+
for (let i = 0; i < tableLength; i++) table[i] = kernel(i / OVERSAMPLING - halfWidth);
|
|
56175
|
+
for (let i = 0; i < tableLength - 1; i++) tableDeltas[i] = table[i + 1] - table[i];
|
|
56176
|
+
return {
|
|
56177
|
+
halfWidth,
|
|
56178
|
+
phases,
|
|
56179
|
+
phaseDeltas,
|
|
56180
|
+
table,
|
|
56181
|
+
tableDeltas
|
|
56182
|
+
};
|
|
56183
|
+
}
|
|
56184
|
+
/**
|
|
56185
|
+
* 32 points, meant for real time playback.
|
|
56186
|
+
*/
|
|
56187
|
+
const KERNEL_FAST = buildKernel(16, .92, 9);
|
|
56188
|
+
/**
|
|
56189
|
+
* 128 points, for offline rendering.
|
|
56190
|
+
*/
|
|
56191
|
+
const KERNEL_BEST = buildKernel(64, .975, 12);
|
|
56192
|
+
var SincOscillator = class extends WavetableOscillator {
|
|
56193
|
+
kernel;
|
|
56194
|
+
/**
|
|
56195
|
+
* If the kernel gets stretched when the playback step is above 1.
|
|
56196
|
+
* This lowers the cutoff to the new Nyquist frequency so the sample content above it does not alias.
|
|
56197
|
+
*/
|
|
56198
|
+
antiAlias;
|
|
56199
|
+
/**
|
|
56200
|
+
* The points around the cursor, gathered here when they need wrapping around the loop
|
|
56201
|
+
* or are outside the sample.
|
|
56202
|
+
*/
|
|
56203
|
+
scratch;
|
|
56204
|
+
/**
|
|
56205
|
+
* Creates a new windowed sinc oscillator.
|
|
56206
|
+
* @param best If true, uses the larger, anti-aliased kernel. It is too slow for real time playback.
|
|
56207
|
+
*/
|
|
56208
|
+
constructor(best) {
|
|
56209
|
+
super();
|
|
56210
|
+
this.kernel = best ? KERNEL_BEST : KERNEL_FAST;
|
|
56211
|
+
this.antiAlias = best;
|
|
56212
|
+
this.scratch = new Float32Array(best ? Math.ceil(this.kernel.halfWidth * MAX_STRETCH) * 2 + 2 : this.kernel.halfWidth * 2);
|
|
56213
|
+
}
|
|
56214
|
+
process(sampleCount, tuningRatio, outputBuffer) {
|
|
56215
|
+
const step = tuningRatio * this.playbackStep;
|
|
56216
|
+
const sampleData = this.sampleData;
|
|
56217
|
+
const scratch = this.scratch;
|
|
56218
|
+
const { loopStart, loopEnd, loopLength, end, isLooping } = this;
|
|
56219
|
+
const { halfWidth } = this.kernel;
|
|
56220
|
+
let cursor = this.cursor;
|
|
56221
|
+
let hasLooped = this.hasLooped;
|
|
56222
|
+
const upperLimit = isLooping ? loopEnd : end;
|
|
56223
|
+
const stretch = this.antiAlias && step > 1 ? Math.min(step, MAX_STRETCH) : 1;
|
|
56224
|
+
if (stretch === 1) {
|
|
56225
|
+
const { phases, phaseDeltas } = this.kernel;
|
|
56226
|
+
const taps = halfWidth * 2;
|
|
56227
|
+
for (let i = 0; i < sampleCount; i++) {
|
|
56228
|
+
if (isLooping) {
|
|
56229
|
+
if (cursor >= loopEnd) {
|
|
56230
|
+
hasLooped = true;
|
|
56231
|
+
while (cursor >= loopEnd) cursor -= loopLength;
|
|
56232
|
+
}
|
|
56233
|
+
} else if (cursor + 1 >= end) {
|
|
56234
|
+
outputBuffer.fill(0, i, sampleCount);
|
|
56235
|
+
this.cursor = cursor;
|
|
56236
|
+
return false;
|
|
56237
|
+
}
|
|
56238
|
+
const y0 = cursor | 0;
|
|
56239
|
+
let first = y0 + 1 - halfWidth;
|
|
56240
|
+
let source = sampleData;
|
|
56241
|
+
if (first < (isLooping && hasLooped ? loopStart : 0) || first + taps > upperLimit) {
|
|
56242
|
+
this.gather(first, taps, hasLooped);
|
|
56243
|
+
source = scratch;
|
|
56244
|
+
first = 0;
|
|
56245
|
+
}
|
|
56246
|
+
const phase = (cursor - y0) * OVERSAMPLING;
|
|
56247
|
+
const q = phase | 0;
|
|
56248
|
+
let row = q * taps;
|
|
56249
|
+
const fraction = phase - q;
|
|
56250
|
+
const rowEnd = row + taps;
|
|
56251
|
+
let out = 0;
|
|
56252
|
+
for (; row < rowEnd; row++) out += source[first++] * (phases[row] + phaseDeltas[row] * fraction);
|
|
56253
|
+
outputBuffer[i] = out;
|
|
56254
|
+
cursor += step;
|
|
56255
|
+
}
|
|
56256
|
+
} else {
|
|
56257
|
+
const { table, tableDeltas } = this.kernel;
|
|
56258
|
+
const reach = halfWidth * stretch;
|
|
56259
|
+
const tableStep = OVERSAMPLING / stretch;
|
|
56260
|
+
const center = halfWidth * OVERSAMPLING;
|
|
56261
|
+
const gain = 1 / stretch;
|
|
56262
|
+
for (let i = 0; i < sampleCount; i++) {
|
|
56263
|
+
if (isLooping) {
|
|
56264
|
+
if (cursor >= loopEnd) {
|
|
56265
|
+
hasLooped = true;
|
|
56266
|
+
while (cursor >= loopEnd) cursor -= loopLength;
|
|
56267
|
+
}
|
|
56268
|
+
} else if (cursor + 1 >= end) {
|
|
56269
|
+
outputBuffer.fill(0, i, sampleCount);
|
|
56270
|
+
this.cursor = cursor;
|
|
56271
|
+
return false;
|
|
56272
|
+
}
|
|
56273
|
+
let first = Math.floor(cursor - reach) + 1;
|
|
56274
|
+
const taps = Math.floor(cursor + reach) - first + 1;
|
|
56275
|
+
let position = (first - cursor) * tableStep + center;
|
|
56276
|
+
let source = sampleData;
|
|
56277
|
+
if (first < (isLooping && hasLooped ? loopStart : 0) || first + taps > upperLimit) {
|
|
56278
|
+
this.gather(first, taps, hasLooped);
|
|
56279
|
+
source = scratch;
|
|
56280
|
+
first = 0;
|
|
56281
|
+
}
|
|
56282
|
+
let out = 0;
|
|
56283
|
+
const last = first + taps;
|
|
56284
|
+
for (; first < last; first++) {
|
|
56285
|
+
const index = position | 0;
|
|
56286
|
+
out += source[first] * (table[index] + tableDeltas[index] * (position - index));
|
|
56287
|
+
position += tableStep;
|
|
56288
|
+
}
|
|
56289
|
+
outputBuffer[i] = out * gain;
|
|
56290
|
+
cursor += step;
|
|
56291
|
+
}
|
|
56292
|
+
}
|
|
56293
|
+
this.cursor = cursor;
|
|
56294
|
+
this.hasLooped = hasLooped;
|
|
56295
|
+
return true;
|
|
56296
|
+
}
|
|
56297
|
+
/**
|
|
56298
|
+
* Copies the points into the scratch buffer, wrapping them around the loop.
|
|
56299
|
+
* Points outside the sample are silent.
|
|
56300
|
+
* @param first The index of the first point.
|
|
56301
|
+
* @param count The amount of points.
|
|
56302
|
+
* @param hasLooped If the cursor has already looped.
|
|
56303
|
+
*/
|
|
56304
|
+
gather(first, count, hasLooped) {
|
|
56305
|
+
const sampleData = this.sampleData;
|
|
56306
|
+
const scratch = this.scratch;
|
|
56307
|
+
const { loopStart, loopEnd, loopLength, end } = this;
|
|
56308
|
+
if (this.isLooping) {
|
|
56309
|
+
let index = first;
|
|
56310
|
+
if (index >= loopEnd) index = loopStart + (index - loopStart) % loopLength;
|
|
56311
|
+
else if (index < loopStart && hasLooped) index = loopEnd - 1 - (loopStart - 1 - index) % loopLength;
|
|
56312
|
+
for (let k = 0; k < count; k++) {
|
|
56313
|
+
scratch[k] = index < 0 ? 0 : sampleData[index];
|
|
56314
|
+
if (++index >= loopEnd) index = loopStart;
|
|
56315
|
+
}
|
|
56316
|
+
} else for (let k = 0; k < count; k++) {
|
|
56317
|
+
const index = first + k;
|
|
56318
|
+
scratch[k] = index >= 0 && index < end ? sampleData[index] : 0;
|
|
56319
|
+
}
|
|
56320
|
+
}
|
|
56321
|
+
};
|
|
55842
56322
|
// src/synthesizer/audio_engine/voice/voice.ts
|
|
55843
56323
|
/**
|
|
55844
56324
|
* Voice.ts
|
|
@@ -55863,7 +56343,9 @@ var Voice = class {
|
|
|
55863
56343
|
oscillators = [
|
|
55864
56344
|
new LinearOscillator(),
|
|
55865
56345
|
new NearestOscillator(),
|
|
55866
|
-
new HermiteOscillator()
|
|
56346
|
+
new HermiteOscillator(),
|
|
56347
|
+
new SincOscillator(false),
|
|
56348
|
+
new SincOscillator(true)
|
|
55867
56349
|
];
|
|
55868
56350
|
/**
|
|
55869
56351
|
* The oscillator currently used by this voice.
|
|
@@ -55950,7 +56432,7 @@ var Voice = class {
|
|
|
55950
56432
|
* This is the effective MIDI note number,
|
|
55951
56433
|
* used to calculate scale tuning and envelope times,
|
|
55952
56434
|
* and can be overridden by generators.
|
|
55953
|
-
* It is also used
|
|
56435
|
+
* It is also used for the octave tuning and as the portamento target.
|
|
55954
56436
|
*/
|
|
55955
56437
|
targetKey = 0;
|
|
55956
56438
|
/**
|
|
@@ -56043,7 +56525,9 @@ var Voice = class {
|
|
|
56043
56525
|
this.filter = new LowpassFilter(sampleRate);
|
|
56044
56526
|
}
|
|
56045
56527
|
/**
|
|
56046
|
-
* Releases the voice as exclusiveClass.
|
|
56528
|
+
* Releases the voice as exclusiveClass, with a nearly instant release time.
|
|
56529
|
+
* @param currentTime the current time in seconds.
|
|
56530
|
+
* @param minExclusiveLength minimum note length in seconds.
|
|
56047
56531
|
*/
|
|
56048
56532
|
exclusiveRelease(currentTime, minExclusiveLength = MIN_EXCLUSIVE_LENGTH) {
|
|
56049
56533
|
this.overrideReleaseVolEnv = EXCLUSIVE_CUTOFF_TIME;
|
|
@@ -56051,8 +56535,9 @@ var Voice = class {
|
|
|
56051
56535
|
this.releaseVoice(currentTime, minExclusiveLength);
|
|
56052
56536
|
}
|
|
56053
56537
|
/**
|
|
56054
|
-
*
|
|
56055
|
-
*
|
|
56538
|
+
* Releases the voice. The release phase starts on the first render at or after the release time,
|
|
56539
|
+
* which is pushed back if the note would be shorter than the minimum length.
|
|
56540
|
+
* @param currentTime the current time in seconds.
|
|
56056
56541
|
* @param minNoteLength minimum note length in seconds
|
|
56057
56542
|
*/
|
|
56058
56543
|
releaseVoice(currentTime, minNoteLength = MIN_NOTE_LENGTH) {
|
|
@@ -56190,7 +56675,7 @@ function lockMIDIParameterInternal(parameter, isLocked) {
|
|
|
56190
56675
|
* @param byte1 The first byte (midi note).
|
|
56191
56676
|
* @param byte2 The second byte (most significant bits).
|
|
56192
56677
|
* @param byte3 The third byte (the least significant bits).
|
|
56193
|
-
* @
|
|
56678
|
+
* @returns The MIDI note with the fraction added (in semitones), or -1 for no change.
|
|
56194
56679
|
*/
|
|
56195
56680
|
function getTuning(byte1, byte2, byte3) {
|
|
56196
56681
|
const midiNote = byte1;
|
|
@@ -56354,8 +56839,8 @@ function universalSystemExclusive(syx, channelOffset = 0) {
|
|
|
56354
56839
|
const program = syx[currentMessageIndex++];
|
|
56355
56840
|
const tuningName = readBinaryString(syx, 16, currentMessageIndex);
|
|
56356
56841
|
currentMessageIndex += 16;
|
|
56357
|
-
if (syx.length <
|
|
56358
|
-
SpessaLog.warn(`The Bulk Tuning Dump is too short! (${syx.length} bytes, at least
|
|
56842
|
+
if (syx.length < 405) {
|
|
56843
|
+
SpessaLog.warn(`The Bulk Tuning Dump is too short! (${syx.length} bytes, at least 405 are expected)`);
|
|
56359
56844
|
return;
|
|
56360
56845
|
}
|
|
56361
56846
|
for (let midiNote = 0; midiNote < 128; midiNote++) this.tunings[program * 128 + midiNote] = getTuning(syx[currentMessageIndex++], syx[currentMessageIndex++], syx[currentMessageIndex++]);
|
|
@@ -57799,8 +58284,8 @@ function yamahaSystemExclusive(syx, channelOffset = 0) {
|
|
|
57799
58284
|
for (const ch of this.midiChannels) {
|
|
57800
58285
|
if (ch.midiParameters.drumMap !== setupNumber) continue;
|
|
57801
58286
|
ch.drumParams[drumKey].pitchFine = pitch;
|
|
57802
|
-
SpessaLog.xgInfo(`Drum Pitch Fine for key ${drumKey}`, pitch, "semitones");
|
|
57803
58287
|
}
|
|
58288
|
+
SpessaLog.xgInfo(`Drum Pitch Fine for key ${drumKey}`, pitch, "cents");
|
|
57804
58289
|
break;
|
|
57805
58290
|
}
|
|
57806
58291
|
case 2:
|
|
@@ -57933,7 +58418,7 @@ function applyPresetZones(indexes, presetGens, presetMods, instruments, presets)
|
|
|
57933
58418
|
}
|
|
57934
58419
|
// src/soundbank/soundfont/read/presets.ts
|
|
57935
58420
|
/**
|
|
57936
|
-
* Parses soundfont presets
|
|
58421
|
+
* Parses soundfont presets
|
|
57937
58422
|
*/
|
|
57938
58423
|
var SoundFontPreset = class extends BasicPreset {
|
|
57939
58424
|
zoneStartIndex;
|
|
@@ -58030,7 +58515,7 @@ function readInstruments(instrumentChunk, keepTerminal = false) {
|
|
|
58030
58515
|
}
|
|
58031
58516
|
// src/soundbank/soundfont/read/modulators.ts
|
|
58032
58517
|
/**
|
|
58033
|
-
* Reads the
|
|
58518
|
+
* Reads the modulators
|
|
58034
58519
|
* @param modulatorChunk pmod, imod or DMOD
|
|
58035
58520
|
* @param allowSFEGenerators if destinations above endOper are allowed (SFE banks)
|
|
58036
58521
|
*/
|
|
@@ -58379,7 +58864,11 @@ function applySnapshot(snapshot) {
|
|
|
58379
58864
|
this.xgReverbBlock.applySnapshot(snapshot.xgReverbBlock);
|
|
58380
58865
|
this.xgChorusBlock.applySnapshot(snapshot.xgChorusBlock);
|
|
58381
58866
|
this.xgVariationBlock.applySnapshot(snapshot.xgVariationBlock);
|
|
58382
|
-
for (let i = 0; i < this.xgInsertionBlocks.length; i++)
|
|
58867
|
+
for (let i = 0; i < this.xgInsertionBlocks.length; i++) {
|
|
58868
|
+
const insertion = this.xgInsertionBlocks[i];
|
|
58869
|
+
if (i < snapshot.xgInsertionBlocks.length) insertion.applySnapshot(snapshot.xgInsertionBlocks[i]);
|
|
58870
|
+
else insertion.reset();
|
|
58871
|
+
}
|
|
58383
58872
|
for (let drumSet = 0; drumSet < snapshot.userDrumSets.length; drumSet++) {
|
|
58384
58873
|
const userDrumSet = snapshot.userDrumSets[drumSet];
|
|
58385
58874
|
for (let midiNote = 0; midiNote < userDrumSet.length; midiNote++) DrumParameterUtils.copyIntoUser(userDrumSet[midiNote], this.soundBankManager.userDrumSets[drumSet].keyParams[midiNote]);
|
|
@@ -58411,7 +58900,8 @@ function getSynthesizerSnapshot() {
|
|
|
58411
58900
|
/**
|
|
58412
58901
|
* Keeps the decoded sample data under a size budget by releasing the least recently used samples.
|
|
58413
58902
|
* A released sample decodes again the next time it plays.
|
|
58414
|
-
* Samples that cannot decode again (replaced float data) are never released
|
|
58903
|
+
* Samples that cannot decode again (replaced float data) are never released
|
|
58904
|
+
* and are not tracked, so they do not count towards the budget.
|
|
58415
58905
|
* @internal
|
|
58416
58906
|
*/
|
|
58417
58907
|
var DecodedSampleCache = class {
|
|
@@ -58424,6 +58914,7 @@ var DecodedSampleCache = class {
|
|
|
58424
58914
|
*/
|
|
58425
58915
|
totalBytes = 0;
|
|
58426
58916
|
entries = /* @__PURE__ */ new Map();
|
|
58917
|
+
unreleasable = /* @__PURE__ */ new WeakSet();
|
|
58427
58918
|
voiceCache;
|
|
58428
58919
|
/**
|
|
58429
58920
|
* @param budget The budget in bytes.
|
|
@@ -58444,7 +58935,11 @@ var DecodedSampleCache = class {
|
|
|
58444
58935
|
touch(sample, cacheKey) {
|
|
58445
58936
|
let entry = this.entries.get(sample);
|
|
58446
58937
|
if (entry) this.entries.delete(sample);
|
|
58447
|
-
|
|
58938
|
+
if (this.unreleasable.has(sample) || sample.storedForm === "float32" && !(sample instanceof DLSSample)) {
|
|
58939
|
+
if (entry) this.totalBytes -= entry.bytes;
|
|
58940
|
+
return;
|
|
58941
|
+
}
|
|
58942
|
+
if (!entry) {
|
|
58448
58943
|
entry = {
|
|
58449
58944
|
bytes: sample.getAudioData().byteLength,
|
|
58450
58945
|
cacheKeys: /* @__PURE__ */ new Set()
|
|
@@ -58461,7 +58956,10 @@ var DecodedSampleCache = class {
|
|
|
58461
58956
|
if (sample === keep) continue;
|
|
58462
58957
|
this.entries.delete(sample);
|
|
58463
58958
|
this.totalBytes -= entry.bytes;
|
|
58464
|
-
sample.releaseDecoded()
|
|
58959
|
+
if (!sample.releaseDecoded()) {
|
|
58960
|
+
this.unreleasable.add(sample);
|
|
58961
|
+
continue;
|
|
58962
|
+
}
|
|
58465
58963
|
for (const key of entry.cacheKeys) this.voiceCache.delete(key);
|
|
58466
58964
|
}
|
|
58467
58965
|
}
|
|
@@ -58596,7 +59094,7 @@ var SpessaSynthProcessor = class {
|
|
|
58596
59094
|
*/
|
|
58597
59095
|
sharedVoiceSendBuffer;
|
|
58598
59096
|
/**
|
|
58599
|
-
* The insertion processor's left input buffer.
|
|
59097
|
+
* The GS insertion processor's left input buffer.
|
|
58600
59098
|
* @internal
|
|
58601
59099
|
*/
|
|
58602
59100
|
insertionInputL;
|
|
@@ -58801,11 +59299,6 @@ var SpessaSynthProcessor = class {
|
|
|
58801
59299
|
*/
|
|
58802
59300
|
cachedVoices = /* @__PURE__ */ new Map();
|
|
58803
59301
|
/**
|
|
58804
|
-
* Keeps the decoded sample data under the budget from the options.
|
|
58805
|
-
* @internal
|
|
58806
|
-
*/
|
|
58807
|
-
decodedSamples;
|
|
58808
|
-
/**
|
|
58809
59302
|
* Insertion is not used outside SC-88Pro+ MIDIs, this is an optimization.
|
|
58810
59303
|
* @internal
|
|
58811
59304
|
*/
|
|
@@ -58828,6 +59321,11 @@ var SpessaSynthProcessor = class {
|
|
|
58828
59321
|
*/
|
|
58829
59322
|
gsInsertionBlock;
|
|
58830
59323
|
/**
|
|
59324
|
+
* Keeps the decoded sample data under the budget from the options.
|
|
59325
|
+
* @internal
|
|
59326
|
+
*/
|
|
59327
|
+
decodedSamples;
|
|
59328
|
+
/**
|
|
58831
59329
|
* The synthesizer's GS reverb processor.
|
|
58832
59330
|
*
|
|
58833
59331
|
* Used when {@link GlobalMIDIParameter.system} is `gm` `gm2` or `gs`.
|
|
@@ -58856,7 +59354,7 @@ var SpessaSynthProcessor = class {
|
|
|
58856
59354
|
*/
|
|
58857
59355
|
xgReverbBlock;
|
|
58858
59356
|
/**
|
|
58859
|
-
* The synthesizer's XG
|
|
59357
|
+
* The synthesizer's XG chorus block.
|
|
58860
59358
|
*
|
|
58861
59359
|
* Used when {@link GlobalMIDIParameter.system} is `xg`.
|
|
58862
59360
|
* @internal
|
|
@@ -58978,6 +59476,13 @@ var SpessaSynthProcessor = class {
|
|
|
58978
59476
|
return this._voiceCount;
|
|
58979
59477
|
}
|
|
58980
59478
|
/**
|
|
59479
|
+
* The decoded sample data kept under {@link SynthProcessorOptions.decodedSampleBudget}, in bytes.
|
|
59480
|
+
* It is only counted when a budget is set.
|
|
59481
|
+
*/
|
|
59482
|
+
get decodedSampleBytes() {
|
|
59483
|
+
return this.decodedSamples.totalBytes;
|
|
59484
|
+
}
|
|
59485
|
+
/**
|
|
58981
59486
|
* Locks or unlocks a given {@link GlobalMIDIParameter}.
|
|
58982
59487
|
* This prevents any changes to it until it's unlocked.
|
|
58983
59488
|
* @param parameter The Global MIDI Parameter to lock.
|
|
@@ -59010,7 +59515,7 @@ var SpessaSynthProcessor = class {
|
|
|
59010
59515
|
* > Refer to the [MIDI Implementation](../../docs/extra/midi-implementation.md)
|
|
59011
59516
|
* > for the list of supported System Exclusives.
|
|
59012
59517
|
*
|
|
59013
|
-
* @param syx The system exclusive message as an array of bytes.
|
|
59518
|
+
* @param syx The system exclusive message as an array of bytes, without the starting `F0` byte.
|
|
59014
59519
|
* @param channelOffset The channel offset for the message as they usually can only address the first 16 channels.
|
|
59015
59520
|
* For example, to send a system exclusive on channel 16,
|
|
59016
59521
|
* send a system exclusive for channel 0 and set an offset of 16.
|
|
@@ -59232,11 +59737,11 @@ var SpessaSynthProcessor = class {
|
|
|
59232
59737
|
for (const message of messages) this.processMessage(message, channelOffset, options);
|
|
59233
59738
|
}
|
|
59234
59739
|
/**
|
|
59235
|
-
*
|
|
59740
|
+
* Destroy the synthesizer processor, clearing all channels and voices.
|
|
59236
59741
|
*
|
|
59237
|
-
*
|
|
59238
|
-
*
|
|
59239
|
-
*
|
|
59742
|
+
* > **Warning**
|
|
59743
|
+
* >
|
|
59744
|
+
* > This is irreversible, so use with caution.
|
|
59240
59745
|
*/
|
|
59241
59746
|
destroy() {
|
|
59242
59747
|
this.voices.length = 0;
|
|
@@ -59264,6 +59769,9 @@ var SpessaSynthProcessor = class {
|
|
|
59264
59769
|
* >
|
|
59265
59770
|
* > This method overrides the existing System Parameters with the ones from the snapshot.
|
|
59266
59771
|
*
|
|
59772
|
+
* If this processor has more XG insertion blocks than the snapshot,
|
|
59773
|
+
* the blocks without snapshot data are reset to their defaults.
|
|
59774
|
+
*
|
|
59267
59775
|
* @param snapshot The snapshot to apply.
|
|
59268
59776
|
*/
|
|
59269
59777
|
applySnapshot(snapshot) {
|
|
@@ -59573,10 +60081,11 @@ var SpessaSynthProcessor = class {
|
|
|
59573
60081
|
const channel = this.midiChannels[this.xgVariationBlock.partNumber];
|
|
59574
60082
|
if (channel) this.processXGInsertion(this.xgVariationBlock, channel, sampleCount);
|
|
59575
60083
|
}
|
|
60084
|
+
const { reverbGain, chorusGain } = this.systemParameters;
|
|
59576
60085
|
for (const ch of this.midiChannels) {
|
|
59577
60086
|
if (!ch.xgInsertionAssigned) continue;
|
|
59578
|
-
const revSend = (ch.midiControllers[MIDIControllers.reverbDepth] >> 7) / 127;
|
|
59579
|
-
const choSend = (ch.midiControllers[MIDIControllers.chorusDepth] >> 7) / 127;
|
|
60087
|
+
const revSend = reverbGain * (ch.midiControllers[MIDIControllers.reverbDepth] >> 7) / 127;
|
|
60088
|
+
const choSend = chorusGain * (ch.midiControllers[MIDIControllers.chorusDepth] >> 7) / 127;
|
|
59580
60089
|
if (revSend <= 0 && choSend <= 0) continue;
|
|
59581
60090
|
const { outputLeft, outputRight } = ch;
|
|
59582
60091
|
for (let i = 0; i < sampleCount; i++) {
|
|
@@ -59692,13 +60201,6 @@ var SpessaSynthProcessor = class {
|
|
|
59692
60201
|
this.soundBankManager.refresh();
|
|
59693
60202
|
}
|
|
59694
60203
|
/**
|
|
59695
|
-
* The decoded sample data kept under {@link SynthProcessorOptions.decodedSampleBudget}, in bytes.
|
|
59696
|
-
* It is only counted when a budget is set.
|
|
59697
|
-
*/
|
|
59698
|
-
get decodedSampleBytes() {
|
|
59699
|
-
return this.decodedSamples.totalBytes;
|
|
59700
|
-
}
|
|
59701
|
-
/**
|
|
59702
60204
|
* Clears the synthesizer's voice cache.
|
|
59703
60205
|
* This can be used to hear the changes after editing a {@link BasicSoundBank}
|
|
59704
60206
|
*/
|
|
@@ -59730,7 +60232,7 @@ var SpessaSynthProcessor = class {
|
|
|
59730
60232
|
*/
|
|
59731
60233
|
purgeCachedPatch(patch) {
|
|
59732
60234
|
const patchKey = this.getCachedVoiceIndex(patch, 0, 0);
|
|
59733
|
-
for (const key of this.cachedVoices.keys()) if (key %
|
|
60235
|
+
for (const key of this.cachedVoices.keys()) if (key % 4194304 === patchKey) this.cachedVoices.delete(key);
|
|
59734
60236
|
}
|
|
59735
60237
|
/**
|
|
59736
60238
|
* Sets a global MIDI parameter of the synthesizer.
|
|
@@ -60210,14 +60712,8 @@ var SpessaSynthProcessor = class {
|
|
|
60210
60712
|
}, "gs");
|
|
60211
60713
|
}
|
|
60212
60714
|
getCachedVoiceIndex(patch, midiNote, velocity) {
|
|
60213
|
-
|
|
60214
|
-
|
|
60215
|
-
const { isGMGSDrum, program } = patch;
|
|
60216
|
-
if (isGMGSDrum) {
|
|
60217
|
-
bankMSB = 128;
|
|
60218
|
-
bankLSB = 0;
|
|
60219
|
-
}
|
|
60220
|
-
return bankMSB + bankLSB * 129 + program * 16512 + 2113536 * midiNote + 270532608 * velocity;
|
|
60715
|
+
const { isGMGSDrum, program, bankLSB } = patch;
|
|
60716
|
+
return (isGMGSDrum ? 128 + patch.bankMSB : patch.bankMSB) + bankLSB * 256 + program * 32768 + 4194304 * midiNote + 536870912 * velocity;
|
|
60221
60717
|
}
|
|
60222
60718
|
};
|
|
60223
60719
|
export { BasicInstrument, BasicInstrumentZone, BasicLinkedZone, BasicMIDI, BasicPreset, BasicPresetZone, BasicSample, BasicSoundBank, BasicZone, DEFAULT_CHANNEL_MIDI_PARAMETERS, DEFAULT_CHANNEL_SYSTEM_PARAMETERS, DEFAULT_DRUM_REVERB, DEFAULT_GLOBAL_MIDI_PARAMETERS, DEFAULT_GLOBAL_SYSTEM_PARAMETERS, DEFAULT_MIDI_CONTROLLERS, DEFAULT_SOUND_BANK_CLONE_OPTIONS, DEFAULT_SOUND_BANK_LOAD_OPTIONS, DEFAULT_SYNTH_MODE, DefaultGSChorus, DefaultGSDelay, DefaultGSInsertion, DefaultGSReverb, DefaultXGChorus, DefaultXGEffect, DefaultXGInsertion, DefaultXGReverb, DefaultXGSystemEffect, DefaultXGVariation, DrumParameterUtils, EmptySample, GENERATORS_AMOUNT, GSInsertionControlSources, Generator, GeneratorLimits, GeneratorTypes, INSTRUMENT_ONLY_GENERATORS, IndexedByteArray, InterpolationTypes, MAX_GENERATOR, MIDIBuilder, MIDIChannel, MIDIControllers, MIDIMessage, MIDIMessageTypes, MIDIPatchTools, MIDITrack, MIDIUtils, MIDI_DRUM_CHANNEL, Modulator, ModulatorControllerSources, ModulatorCurveTypes, ModulatorSource, ModulatorTransformTypes, NonRegisteredParameterTypesLSB, NonRegisteredParameterTypesMSB, RegisteredParameterTypes, SPESSASYNTH_GAIN_FACTOR, SPESSA_BUFSIZE, SampleTypes, SoundBankCloner, SoundBankLoader, SoundBankManager, SpessaLog, SpessaSynthCoreUtils, SpessaSynthProcessor, SpessaSynthSequencer, VOICE_CAP, audioToWav };
|