spessasynth_lib 3.23.0 → 3.23.3
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/@types/soundfont/basic_soundfont/basic_soundfont.d.ts +1 -1
- package/@types/soundfont/basic_soundfont/write_dls/rgn2.d.ts +2 -1
- package/@types/soundfont/dls/dls_sample.d.ts +1 -1
- package/@types/soundfont/read_sf2/samples.d.ts +2 -2
- package/@types/synthetizer/key_modifier_manager.d.ts +14 -0
- package/@types/synthetizer/worklet_system/worklet_utilities/controller_tables.d.ts +2 -2
- package/@types/utils/indexed_array.d.ts +1 -1
- package/README.md +2 -1
- package/package.json +1 -1
- package/soundfont/basic_soundfont/basic_zone.js +1 -1
- package/soundfont/basic_soundfont/write_dls/ins.js +1 -1
- package/soundfont/basic_soundfont/write_dls/rgn2.js +17 -2
- package/soundfont/basic_soundfont/write_dls/wave.js +22 -8
- package/soundfont/basic_soundfont/write_dls/write_dls.js +1 -1
- package/soundfont/basic_soundfont/write_dls/wsmp.js +12 -8
- package/synthetizer/key_modifier_manager.js +30 -1
- package/synthetizer/worklet_processor.min.js +9 -9
- package/synthetizer/worklet_system/main_processor.js +2 -2
- package/synthetizer/worklet_system/worklet_methods/note_on.js +1 -1
- package/synthetizer/worklet_system/worklet_methods/worklet_key_modifier.js +3 -3
- package/synthetizer/worklet_system/worklet_utilities/volume_envelope.js +20 -13
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @param zone {BasicInstrumentZone}
|
|
3
|
+
* @param globalZone {BasicInstrumentZone}
|
|
3
4
|
* @this {BasicSoundFont}
|
|
4
5
|
* @returns {IndexedByteArray}
|
|
5
6
|
*/
|
|
6
|
-
export function writeDLSRegion(this: BasicSoundFont, zone: BasicInstrumentZone): IndexedByteArray;
|
|
7
|
+
export function writeDLSRegion(this: BasicSoundFont, zone: BasicInstrumentZone, globalZone: BasicInstrumentZone): IndexedByteArray;
|
|
7
8
|
import { IndexedByteArray } from "../../../utils/indexed_array.js";
|
|
@@ -30,8 +30,8 @@ export class LoadedSample extends BasicSample {
|
|
|
30
30
|
isSampleLoaded: boolean;
|
|
31
31
|
sampleID: number;
|
|
32
32
|
sampleLength: number;
|
|
33
|
-
sampleDataArray: Float32Array | IndexedByteArray;
|
|
34
|
-
sampleData: Float32Array
|
|
33
|
+
sampleDataArray: Float32Array<ArrayBuffer> | IndexedByteArray;
|
|
34
|
+
sampleData: Float32Array<ArrayBuffer>;
|
|
35
35
|
isDataRaw: boolean;
|
|
36
36
|
/**
|
|
37
37
|
* Get raw data, whether it's compressed or not as we simply write it to the file
|
|
@@ -4,6 +4,12 @@ export class KeyModifierManager {
|
|
|
4
4
|
*/
|
|
5
5
|
constructor(synth: Synthetizer);
|
|
6
6
|
synth: Synthetizer;
|
|
7
|
+
/**
|
|
8
|
+
* The velocity override mappings for MIDI keys
|
|
9
|
+
* @type {KeyModifier[][]}
|
|
10
|
+
* @private
|
|
11
|
+
*/
|
|
12
|
+
private _keyModifiers;
|
|
7
13
|
/**
|
|
8
14
|
* @private
|
|
9
15
|
* @param type {workletKeyModifierMessageType}
|
|
@@ -29,6 +35,13 @@ export class KeyModifierManager {
|
|
|
29
35
|
program: number;
|
|
30
36
|
} | undefined;
|
|
31
37
|
}): void;
|
|
38
|
+
/**
|
|
39
|
+
* Gets a key modifier
|
|
40
|
+
* @param channel {number} the channel affected. Usually 0-15
|
|
41
|
+
* @param midiNote {number} the MIDI note to change. 0-127
|
|
42
|
+
* @returns {KeyModifier|undefined}
|
|
43
|
+
*/
|
|
44
|
+
getModifier(channel: number, midiNote: number): KeyModifier | undefined;
|
|
32
45
|
/**
|
|
33
46
|
* Deletes a key modifier
|
|
34
47
|
* @param channel {number} the channel affected. Usually 0-15
|
|
@@ -40,3 +53,4 @@ export class KeyModifierManager {
|
|
|
40
53
|
*/
|
|
41
54
|
clearModifiers(): void;
|
|
42
55
|
}
|
|
56
|
+
import { KeyModifier } from "./worklet_system/worklet_methods/worklet_key_modifier.js";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export const NON_CC_INDEX_OFFSET: 128;
|
|
2
2
|
export const CONTROLLER_TABLE_SIZE: 147;
|
|
3
|
-
export const resetArray: Int16Array
|
|
3
|
+
export const resetArray: Int16Array<ArrayBuffer>;
|
|
4
4
|
export function setResetValue(i: any, v: any): number;
|
|
5
5
|
export namespace customControllers {
|
|
6
6
|
let channelTuning: number;
|
|
@@ -10,7 +10,7 @@ export namespace customControllers {
|
|
|
10
10
|
let channelTuningSemitones: number;
|
|
11
11
|
}
|
|
12
12
|
export const CUSTOM_CONTROLLER_TABLE_SIZE: number;
|
|
13
|
-
export const customResetArray: Float32Array
|
|
13
|
+
export const customResetArray: Float32Array<ArrayBuffer>;
|
|
14
14
|
export type dataEntryStates = number;
|
|
15
15
|
export namespace dataEntryStates {
|
|
16
16
|
let Idle: number;
|
|
@@ -7,7 +7,7 @@ export function combineArrays(arrs: (IndexedByteArray | Uint8Array)[]): IndexedB
|
|
|
7
7
|
* indexed_array.js
|
|
8
8
|
* purpose: exteds Uint8Array with a currentIndex property
|
|
9
9
|
*/
|
|
10
|
-
export class IndexedByteArray extends Uint8Array {
|
|
10
|
+
export class IndexedByteArray extends Uint8Array<ArrayBuffer> {
|
|
11
11
|
/**
|
|
12
12
|
* Creates a new instance of an Uint8Array with a currentIndex property
|
|
13
13
|
* @param args {any} same as for Uint8Array
|
package/README.md
CHANGED
|
@@ -105,7 +105,7 @@ document.getElementById("button").onclick = async () =>
|
|
|
105
105
|
- **Variable compression quality:** You choose between file size and quality!
|
|
106
106
|
- **Compression preserving:** Avoid decompressing and recompressing uncompressed samples for minimal quality loss!
|
|
107
107
|
|
|
108
|
-
#### Read and
|
|
108
|
+
#### Read and write DLS Level 1 or 2 files
|
|
109
109
|
- Read DLS (DownLoadable Sounds) files as SF2 files!
|
|
110
110
|
- **Works like a normal soundfont:** *Saving it as sf2 is still [just one function!](https://github.com/spessasus/SpessaSynth/wiki/SoundFont2-Class#write)*
|
|
111
111
|
- Converts articulators to both **modulators** and **generators**!
|
|
@@ -113,6 +113,7 @@ document.getElementById("button").onclick = async () =>
|
|
|
113
113
|
- **Covers special generator cases:** *such as modLfoToPitch*!
|
|
114
114
|
- **Correct volume:** *looking at you, Viena and gm.sf2!*
|
|
115
115
|
- Support built right into the synthesizer!
|
|
116
|
+
- **Convert SF2 to DLS:** [with limitations](https://github.com/spessasus/SpessaSynth/wiki/DLS-Conversion-Problem);
|
|
116
117
|
|
|
117
118
|
### Export MIDI as WAV
|
|
118
119
|
- Save the MIDI file as WAV audio!
|
package/package.json
CHANGED
|
@@ -58,7 +58,7 @@ export class BasicZone
|
|
|
58
58
|
*/
|
|
59
59
|
getGeneratorValue(generatorType, notFoundValue)
|
|
60
60
|
{
|
|
61
|
-
return this.generators.find(g => g.generatorType === generatorType)?.generatorValue
|
|
61
|
+
return this.generators.find(g => g.generatorType === generatorType)?.generatorValue ?? notFoundValue;
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
64
|
|
|
@@ -7,10 +7,11 @@ import { writeArticulator } from "./art2.js";
|
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* @param zone {BasicInstrumentZone}
|
|
10
|
+
* @param globalZone {BasicInstrumentZone}
|
|
10
11
|
* @this {BasicSoundFont}
|
|
11
12
|
* @returns {IndexedByteArray}
|
|
12
13
|
*/
|
|
13
|
-
export function writeDLSRegion(zone)
|
|
14
|
+
export function writeDLSRegion(zone, globalZone)
|
|
14
15
|
{
|
|
15
16
|
// region header
|
|
16
17
|
const rgnhData = new IndexedByteArray(14);
|
|
@@ -32,10 +33,24 @@ export function writeDLSRegion(zone)
|
|
|
32
33
|
rgnhData
|
|
33
34
|
);
|
|
34
35
|
|
|
36
|
+
let rootKey = zone.getGeneratorValue(generatorTypes.overridingRootKey, zone.sample.samplePitch);
|
|
37
|
+
|
|
38
|
+
// a lot of soundfonts like to set scaletuning to 0 in drums and keep the key at 60
|
|
39
|
+
// since we implement scaletuning via a dls articulator and fluid doesn't support these,
|
|
40
|
+
// change the root key here
|
|
41
|
+
const scaleTuning = zone.getGeneratorValue(
|
|
42
|
+
generatorTypes.scaleTuning,
|
|
43
|
+
globalZone.getGeneratorValue(generatorTypes.scaleTuning, 100)
|
|
44
|
+
);
|
|
45
|
+
if (scaleTuning === 0 && zone.keyRange.max - zone.keyRange.min === 0)
|
|
46
|
+
{
|
|
47
|
+
rootKey = zone.keyRange.min;
|
|
48
|
+
}
|
|
49
|
+
|
|
35
50
|
// wavesample (Wsmp)
|
|
36
51
|
const wsmp = writeWavesample(
|
|
37
52
|
zone.sample,
|
|
38
|
-
|
|
53
|
+
rootKey,
|
|
39
54
|
zone.getGeneratorValue(
|
|
40
55
|
generatorTypes.fineTune,
|
|
41
56
|
0
|
|
@@ -32,17 +32,31 @@ export function writeDLSSample(sample)
|
|
|
32
32
|
sample.sampleLoopEndIndex,
|
|
33
33
|
1
|
|
34
34
|
);
|
|
35
|
-
|
|
36
35
|
const audio = sample.getAudioData();
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
let data;
|
|
37
|
+
// if sample is compressed, getRawData cannot be used
|
|
38
|
+
if (sample.isCompressed)
|
|
39
39
|
{
|
|
40
|
-
data16
|
|
40
|
+
const data16 = new Int16Array(audio.length);
|
|
41
|
+
|
|
42
|
+
for (let i = 0; i < audio.length; i++)
|
|
43
|
+
{
|
|
44
|
+
data16[i] = audio[i] * 32768;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
data = writeRIFFOddSize(
|
|
49
|
+
"data",
|
|
50
|
+
new IndexedByteArray(data16.buffer)
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
else
|
|
54
|
+
{
|
|
55
|
+
data = writeRIFFOddSize(
|
|
56
|
+
"data",
|
|
57
|
+
sample.getRawData()
|
|
58
|
+
);
|
|
41
59
|
}
|
|
42
|
-
const data = writeRIFFOddSize(
|
|
43
|
-
"data",
|
|
44
|
-
new IndexedByteArray(data16.buffer)
|
|
45
|
-
);
|
|
46
60
|
|
|
47
61
|
const inam = writeRIFFOddSize(
|
|
48
62
|
"INAM",
|
|
@@ -47,7 +47,7 @@ export function writeDLS()
|
|
|
47
47
|
SpessaSynthGroupEnd();
|
|
48
48
|
|
|
49
49
|
// write ptbl
|
|
50
|
-
const ptblData = new IndexedByteArray(8 +
|
|
50
|
+
const ptblData = new IndexedByteArray(8 + 4 * ptblOffsets.length);
|
|
51
51
|
writeDword(ptblData, 8);
|
|
52
52
|
writeDword(ptblData, ptblOffsets.length);
|
|
53
53
|
for (const offset of ptblOffsets)
|
|
@@ -2,6 +2,8 @@ import { writeDword, writeWord } from "../../../utils/byte_functions/little_endi
|
|
|
2
2
|
import { IndexedByteArray } from "../../../utils/indexed_array.js";
|
|
3
3
|
import { writeRIFFOddSize } from "../riff_chunk.js";
|
|
4
4
|
|
|
5
|
+
const WSMP_SIZE = 20;
|
|
6
|
+
|
|
5
7
|
/**
|
|
6
8
|
* @param sample {BasicSample}
|
|
7
9
|
* @param rootKey {number}
|
|
@@ -21,9 +23,9 @@ export function writeWavesample(
|
|
|
21
23
|
loopEnd,
|
|
22
24
|
loopingMode)
|
|
23
25
|
{
|
|
24
|
-
|
|
25
|
-
const wsmpData = new IndexedByteArray(
|
|
26
|
-
writeDword(wsmpData,
|
|
26
|
+
let loopCount = loopingMode === 0 ? 0 : 1;
|
|
27
|
+
const wsmpData = new IndexedByteArray(WSMP_SIZE + loopCount * 16);
|
|
28
|
+
writeDword(wsmpData, WSMP_SIZE); // cbSize
|
|
27
29
|
// usUnityNote (apply root pitch here)
|
|
28
30
|
writeWord(wsmpData, rootKey);
|
|
29
31
|
// sFineTune
|
|
@@ -39,7 +41,6 @@ export function writeWavesample(
|
|
|
39
41
|
writeDword(wsmpData, 0);
|
|
40
42
|
|
|
41
43
|
const loopSize = loopEnd - loopStart;
|
|
42
|
-
let loopCount = 1;
|
|
43
44
|
let ulLoopType = 0;
|
|
44
45
|
switch (loopingMode)
|
|
45
46
|
{
|
|
@@ -63,10 +64,13 @@ export function writeWavesample(
|
|
|
63
64
|
|
|
64
65
|
// cSampleLoops
|
|
65
66
|
writeDword(wsmpData, loopCount);
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
67
|
+
if (loopCount === 1)
|
|
68
|
+
{
|
|
69
|
+
writeDword(wsmpData, 16); // cbSize
|
|
70
|
+
writeDword(wsmpData, ulLoopType);
|
|
71
|
+
writeDword(wsmpData, loopStart);
|
|
72
|
+
writeDword(wsmpData, loopSize);
|
|
73
|
+
}
|
|
70
74
|
return writeRIFFOddSize(
|
|
71
75
|
"wsmp",
|
|
72
76
|
wsmpData
|
|
@@ -9,6 +9,12 @@ export class KeyModifierManager
|
|
|
9
9
|
constructor(synth)
|
|
10
10
|
{
|
|
11
11
|
this.synth = synth;
|
|
12
|
+
/**
|
|
13
|
+
* The velocity override mappings for MIDI keys
|
|
14
|
+
* @type {KeyModifier[][]}
|
|
15
|
+
* @private
|
|
16
|
+
*/
|
|
17
|
+
this._keyModifiers = [];
|
|
12
18
|
}
|
|
13
19
|
|
|
14
20
|
/**
|
|
@@ -44,12 +50,29 @@ export class KeyModifierManager
|
|
|
44
50
|
const velocity = options?.velocity ?? -1;
|
|
45
51
|
const program = options?.patch?.program ?? -1;
|
|
46
52
|
const bank = options?.patch?.bank ?? -1;
|
|
53
|
+
const mod = new KeyModifier(velocity, bank, program);
|
|
54
|
+
if (this._keyModifiers[channel] === undefined)
|
|
55
|
+
{
|
|
56
|
+
this._keyModifiers[channel] = [];
|
|
57
|
+
}
|
|
58
|
+
this._keyModifiers[channel][midiNote] = mod;
|
|
47
59
|
this._sendToWorklet(
|
|
48
60
|
workletKeyModifierMessageType.addMapping,
|
|
49
|
-
[channel, midiNote,
|
|
61
|
+
[channel, midiNote, mod]
|
|
50
62
|
);
|
|
51
63
|
}
|
|
52
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Gets a key modifier
|
|
67
|
+
* @param channel {number} the channel affected. Usually 0-15
|
|
68
|
+
* @param midiNote {number} the MIDI note to change. 0-127
|
|
69
|
+
* @returns {KeyModifier|undefined}
|
|
70
|
+
*/
|
|
71
|
+
getModifier(channel, midiNote)
|
|
72
|
+
{
|
|
73
|
+
return this._keyModifiers?.[channel]?.[midiNote];
|
|
74
|
+
}
|
|
75
|
+
|
|
53
76
|
/**
|
|
54
77
|
* Deletes a key modifier
|
|
55
78
|
* @param channel {number} the channel affected. Usually 0-15
|
|
@@ -61,6 +84,11 @@ export class KeyModifierManager
|
|
|
61
84
|
workletKeyModifierMessageType.deleteMapping,
|
|
62
85
|
[channel, midiNote]
|
|
63
86
|
);
|
|
87
|
+
if (this._keyModifiers[channel]?.[midiNote] === undefined)
|
|
88
|
+
{
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
this._keyModifiers[channel][midiNote] = undefined;
|
|
64
92
|
}
|
|
65
93
|
|
|
66
94
|
/**
|
|
@@ -69,5 +97,6 @@ export class KeyModifierManager
|
|
|
69
97
|
clearModifiers()
|
|
70
98
|
{
|
|
71
99
|
this._sendToWorklet(workletKeyModifierMessageType.clearMappings, undefined);
|
|
100
|
+
this._keyModifiers = [];
|
|
72
101
|
}
|
|
73
102
|
}
|