spessasynth_core 3.26.34 → 3.26.36
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/package.json +1 -1
- package/src/soundfont/basic_soundfont/basic_instrument.js +4 -2
- package/src/soundfont/basic_soundfont/basic_sample.js +12 -13
- package/src/soundfont/basic_soundfont/basic_soundbank.js +7 -6
- package/src/soundfont/basic_soundfont/write_dls/write_dls.js +18 -2
- package/src/soundfont/basic_soundfont/write_dls/wvpl.js +10 -5
- package/src/soundfont/basic_soundfont/write_sf2/sdta.js +33 -12
- package/src/soundfont/basic_soundfont/write_sf2/write.js +33 -13
- package/src/soundfont/dls/dls_sample.js +3 -2
- package/src/soundfont/read_sf2/samples.js +12 -20
- package/src/synthetizer/audio_engine/engine_components/midi_audio_channel.js +2 -3
- package/src/synthetizer/audio_engine/engine_components/voice.js +8 -2
- package/src/synthetizer/audio_engine/engine_methods/controller_control/reset_controllers.js +8 -4
- package/src/synthetizer/audio_engine/engine_methods/note_on.js +8 -1
- package/src/synthetizer/audio_engine/engine_methods/program_change.js +10 -1
- package/src/synthetizer/audio_engine/engine_methods/render_voice.js +1 -1
package/package.json
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { BasicGlobalZone } from "./basic_global_zone.js";
|
|
2
2
|
import { BasicInstrumentZone } from "./basic_instrument_zone.js";
|
|
3
|
+
import { SpessaSynthWarn } from "../../utils/loggin.js";
|
|
3
4
|
|
|
4
5
|
export class BasicInstrument
|
|
5
6
|
{
|
|
@@ -64,7 +65,8 @@ export class BasicInstrument
|
|
|
64
65
|
const index = this.linkedPresets.indexOf(preset);
|
|
65
66
|
if (index < 0)
|
|
66
67
|
{
|
|
67
|
-
|
|
68
|
+
SpessaSynthWarn(`Cannot unlink ${preset.presetName} from ${this.instrumentName}: not linked.`);
|
|
69
|
+
return;
|
|
68
70
|
}
|
|
69
71
|
this.linkedPresets.splice(index, 1);
|
|
70
72
|
this.instrumentZones.forEach(z => z.useCount--);
|
|
@@ -87,7 +89,7 @@ export class BasicInstrument
|
|
|
87
89
|
{
|
|
88
90
|
if (this.useCount > 0)
|
|
89
91
|
{
|
|
90
|
-
throw new Error(`Cannot delete an instrument that
|
|
92
|
+
throw new Error(`Cannot delete an instrument that is used by: ${this.linkedPresets.map(p => p.presetName)}.`);
|
|
91
93
|
}
|
|
92
94
|
this.instrumentZones.forEach(z => z.deleteZone());
|
|
93
95
|
this.instrumentZones.length = 0;
|
|
@@ -20,11 +20,10 @@ export const sampleTypes = {
|
|
|
20
20
|
};
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
|
-
* @typedef {function}
|
|
24
|
-
* @
|
|
23
|
+
* @typedef {function} SampleEncodingFunction
|
|
24
|
+
* @async
|
|
25
|
+
* @param audioData {Float32Array}
|
|
25
26
|
* @param sampleRate {number}
|
|
26
|
-
* @param channels {number}
|
|
27
|
-
* @param quality {number} -0.1 to 1
|
|
28
27
|
* @returns {Uint8Array}
|
|
29
28
|
*/
|
|
30
29
|
|
|
@@ -106,7 +105,7 @@ export class BasicSample
|
|
|
106
105
|
* Indicates if the data was overriden, so it cannot be copied back unchanged
|
|
107
106
|
* @type {boolean}
|
|
108
107
|
*/
|
|
109
|
-
dataOverriden =
|
|
108
|
+
dataOverriden = true;
|
|
110
109
|
|
|
111
110
|
/**
|
|
112
111
|
* The basic representation of a sample
|
|
@@ -163,7 +162,7 @@ export class BasicSample
|
|
|
163
162
|
* @return {Uint8Array} either s16 or vorbis data
|
|
164
163
|
* @virtual
|
|
165
164
|
*/
|
|
166
|
-
getRawData(allowVorbis
|
|
165
|
+
getRawData(allowVorbis)
|
|
167
166
|
{
|
|
168
167
|
if (this.isCompressed && allowVorbis && !this.dataOverriden)
|
|
169
168
|
{
|
|
@@ -190,10 +189,9 @@ export class BasicSample
|
|
|
190
189
|
}
|
|
191
190
|
|
|
192
191
|
/**
|
|
193
|
-
* @param
|
|
194
|
-
* @param encodeVorbis {EncodeVorbisFunction}
|
|
192
|
+
* @param encodeVorbis {SampleEncodingFunction}
|
|
195
193
|
*/
|
|
196
|
-
compressSample(
|
|
194
|
+
async compressSample(encodeVorbis)
|
|
197
195
|
{
|
|
198
196
|
// no need to compress
|
|
199
197
|
if (this.isCompressed)
|
|
@@ -210,12 +208,12 @@ export class BasicSample
|
|
|
210
208
|
this.resampleData(RESAMPLE_RATE);
|
|
211
209
|
audioData = this.getAudioData();
|
|
212
210
|
}
|
|
213
|
-
const compressed = encodeVorbis(
|
|
211
|
+
const compressed = await encodeVorbis(audioData, this.sampleRate);
|
|
214
212
|
this.setCompressedData(compressed);
|
|
215
213
|
}
|
|
216
214
|
catch (e)
|
|
217
215
|
{
|
|
218
|
-
SpessaSynthWarn(`Failed to compress ${this.sampleName}. Leaving as uncompressed
|
|
216
|
+
SpessaSynthWarn(`Failed to compress ${this.sampleName}. Leaving as uncompressed!`, e);
|
|
219
217
|
delete this.compressedData;
|
|
220
218
|
// flag as uncompressed
|
|
221
219
|
this.isCompressed = false;
|
|
@@ -251,7 +249,7 @@ export class BasicSample
|
|
|
251
249
|
{
|
|
252
250
|
if (this.useCount > 0)
|
|
253
251
|
{
|
|
254
|
-
throw new Error(`Cannot delete sample
|
|
252
|
+
throw new Error(`Cannot delete sample used by: ${this.linkedInstruments.map(i => i.instrumentName)}.`);
|
|
255
253
|
}
|
|
256
254
|
this.unlinkSample();
|
|
257
255
|
}
|
|
@@ -312,7 +310,8 @@ export class BasicSample
|
|
|
312
310
|
const index = this.linkedInstruments.indexOf(instrument);
|
|
313
311
|
if (index < 0)
|
|
314
312
|
{
|
|
315
|
-
|
|
313
|
+
SpessaSynthWarn(`Cannot unlink ${instrument.instrumentName} from ${this.sampleName}: not linked.`);
|
|
314
|
+
return;
|
|
316
315
|
}
|
|
317
316
|
this.linkedInstruments.splice(index, 1);
|
|
318
317
|
}
|
|
@@ -148,9 +148,9 @@ class BasicSoundBank
|
|
|
148
148
|
|
|
149
149
|
/**
|
|
150
150
|
* Creates a simple soundfont with one saw wave preset.
|
|
151
|
-
* @returns {ArrayBufferLike}
|
|
151
|
+
* @returns {Promise<ArrayBufferLike>}
|
|
152
152
|
*/
|
|
153
|
-
static getDummySoundfontFile()
|
|
153
|
+
static async getDummySoundfontFile()
|
|
154
154
|
{
|
|
155
155
|
const font = new BasicSoundBank();
|
|
156
156
|
const sample = new BasicSample(
|
|
@@ -162,11 +162,12 @@ class BasicSoundBank
|
|
|
162
162
|
0,
|
|
163
163
|
127
|
|
164
164
|
);
|
|
165
|
-
|
|
165
|
+
const sampleData = new Float32Array(128);
|
|
166
166
|
for (let i = 0; i < 128; i++)
|
|
167
167
|
{
|
|
168
|
-
|
|
168
|
+
sampleData[i] = (i / 128) * 2 - 1;
|
|
169
169
|
}
|
|
170
|
+
sample.setAudioData(sampleData);
|
|
170
171
|
font.addSamples(sample);
|
|
171
172
|
|
|
172
173
|
const gZone = new BasicGlobalZone();
|
|
@@ -201,7 +202,8 @@ class BasicSoundBank
|
|
|
201
202
|
font.soundFontInfo["isng"] = "E-mu 10K2";
|
|
202
203
|
font.soundFontInfo["INAM"] = "Dummy";
|
|
203
204
|
font.flush();
|
|
204
|
-
|
|
205
|
+
const f = await font.write();
|
|
206
|
+
return f.buffer;
|
|
205
207
|
}
|
|
206
208
|
|
|
207
209
|
/**
|
|
@@ -252,7 +254,6 @@ class BasicSoundBank
|
|
|
252
254
|
if (sample.isCompressed)
|
|
253
255
|
{
|
|
254
256
|
newSample.setCompressedData(sample.compressedData.slice());
|
|
255
|
-
console.log(sample.compressedData.length);
|
|
256
257
|
}
|
|
257
258
|
else
|
|
258
259
|
{
|
|
@@ -6,14 +6,30 @@ import { getStringBytes } from "../../../utils/byte_functions/string.js";
|
|
|
6
6
|
import { writeWavePool } from "./wvpl.js";
|
|
7
7
|
import { SpessaSynthGroupCollapsed, SpessaSynthGroupEnd, SpessaSynthInfo } from "../../../utils/loggin.js";
|
|
8
8
|
import { consoleColors } from "../../../utils/other.js";
|
|
9
|
+
import { fillWithDefaults } from "../../../utils/fill_with_defaults.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @typedef {Object} DLSWriteOptions
|
|
13
|
+
* @property {ProgressFunction|undefined} progressFunction - a function to show progress for writing large banks. It can be undefined.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @type {DLSWriteOptions}
|
|
19
|
+
*/
|
|
20
|
+
const DEFAULT_DLS_OPTIONS = {
|
|
21
|
+
progressFunction: undefined
|
|
22
|
+
};
|
|
9
23
|
|
|
10
24
|
/**
|
|
11
25
|
* Write the soundfont as a .dls file. Experimental
|
|
12
26
|
* @this {BasicSoundBank}
|
|
27
|
+
* @param {DLSWriteOptions|undefined} options - options for writing the file.
|
|
13
28
|
* @returns {Uint8Array}
|
|
14
29
|
*/
|
|
15
|
-
export function writeDLS()
|
|
30
|
+
export async function writeDLS(options = DEFAULT_DLS_OPTIONS)
|
|
16
31
|
{
|
|
32
|
+
options = fillWithDefaults(options, DEFAULT_DLS_OPTIONS);
|
|
17
33
|
SpessaSynthGroupCollapsed(
|
|
18
34
|
"%cSaving DLS...",
|
|
19
35
|
consoleColors.info
|
|
@@ -40,7 +56,7 @@ export function writeDLS()
|
|
|
40
56
|
"%cWriting WAVE samples...",
|
|
41
57
|
consoleColors.info
|
|
42
58
|
);
|
|
43
|
-
const wavepool = writeWavePool.
|
|
59
|
+
const wavepool = await writeWavePool.call(this, options.progressFunction);
|
|
44
60
|
const wvpl = wavepool.data;
|
|
45
61
|
const ptblOffsets = wavepool.indexes;
|
|
46
62
|
SpessaSynthInfo("%cSucceeded!", consoleColors.recognized);
|
|
@@ -3,22 +3,27 @@ import { writeRIFFChunkParts } from "../riff_chunk.js";
|
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* @this {BasicSoundBank}
|
|
6
|
-
* @
|
|
6
|
+
* @param {ProgressFunction|undefined} progressFunction
|
|
7
|
+
* @returns {Promise<{data: IndexedByteArray, indexes: number[] }>}
|
|
7
8
|
*/
|
|
8
|
-
export function writeWavePool()
|
|
9
|
+
export async function writeWavePool(progressFunction)
|
|
9
10
|
{
|
|
10
11
|
let currentIndex = 0;
|
|
11
12
|
const offsets = [];
|
|
12
13
|
/**
|
|
13
14
|
* @type {IndexedByteArray[]}
|
|
14
15
|
*/
|
|
15
|
-
const samples =
|
|
16
|
+
const samples = [];
|
|
17
|
+
let written = 0;
|
|
18
|
+
for (const s of this.samples)
|
|
16
19
|
{
|
|
17
20
|
const out = writeDLSSample(s);
|
|
21
|
+
await progressFunction?.(s.sampleName, written, this.samples.length);
|
|
18
22
|
offsets.push(currentIndex);
|
|
19
23
|
currentIndex += out.length;
|
|
20
|
-
|
|
21
|
-
|
|
24
|
+
samples.push(out);
|
|
25
|
+
written++;
|
|
26
|
+
}
|
|
22
27
|
return {
|
|
23
28
|
data: writeRIFFChunkParts(
|
|
24
29
|
"wvpl",
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { SpessaSynthInfo } from "../../../utils/loggin.js";
|
|
2
|
-
import { consoleColors } from "../../../utils/other.js";
|
|
3
1
|
import { IndexedByteArray } from "../../../utils/indexed_array.js";
|
|
4
2
|
import { writeStringAsBytes } from "../../../utils/byte_functions/string.js";
|
|
5
3
|
import { writeLittleEndian } from "../../../utils/byte_functions/little_endian.js";
|
|
4
|
+
import { SpessaSynthInfo } from "../../../utils/loggin.js";
|
|
5
|
+
import { consoleColors } from "../../../utils/other.js";
|
|
6
6
|
|
|
7
7
|
/*
|
|
8
8
|
Sdta structure:
|
|
@@ -26,25 +26,45 @@ const SDTA_TO_DATA_OFFSET =
|
|
|
26
26
|
* @param smplStartOffsets {number[]}
|
|
27
27
|
* @param smplEndOffsets {number[]}
|
|
28
28
|
* @param compress {boolean}
|
|
29
|
-
* @param
|
|
30
|
-
* @param vorbisFunc {
|
|
29
|
+
* @param decompress {boolean}
|
|
30
|
+
* @param vorbisFunc {SampleEncodingFunction}
|
|
31
|
+
* @param progressFunc {ProgressFunction|undefined}
|
|
31
32
|
* @returns {Uint8Array}
|
|
32
33
|
*/
|
|
33
|
-
export function getSDTA(smplStartOffsets,
|
|
34
|
+
export async function getSDTA(smplStartOffsets,
|
|
35
|
+
smplEndOffsets,
|
|
36
|
+
compress,
|
|
37
|
+
decompress,
|
|
38
|
+
vorbisFunc,
|
|
39
|
+
progressFunc
|
|
40
|
+
)
|
|
34
41
|
{
|
|
35
42
|
// write smpl: write int16 data of each sample linearly
|
|
36
43
|
// get size (calling getAudioData twice doesn't matter since it gets cached)
|
|
44
|
+
let writtenCount = 0;
|
|
37
45
|
let smplChunkSize = 0;
|
|
38
|
-
const sampleDatas =
|
|
46
|
+
const sampleDatas = [];
|
|
47
|
+
|
|
48
|
+
// linear async is faster here as the writing function usually uses a single wasm instance
|
|
49
|
+
for (const s of this.samples)
|
|
39
50
|
{
|
|
40
51
|
if (compress)
|
|
41
52
|
{
|
|
42
|
-
s.compressSample(
|
|
53
|
+
await s.compressSample(vorbisFunc);
|
|
54
|
+
}
|
|
55
|
+
if (decompress)
|
|
56
|
+
{
|
|
57
|
+
s.setAudioData(s.getAudioData());
|
|
43
58
|
}
|
|
59
|
+
|
|
44
60
|
// raw data: either copy s16le or encoded vorbis or encode manually if overridden
|
|
45
|
-
|
|
61
|
+
// use set timeout so the thread doesn't die
|
|
62
|
+
const r = s.getRawData(true);
|
|
63
|
+
writtenCount++;
|
|
64
|
+
progressFunc?.(s.sampleName, writtenCount, this.samples.length);
|
|
65
|
+
|
|
46
66
|
SpessaSynthInfo(
|
|
47
|
-
`%cEncoded sample %c${
|
|
67
|
+
`%cEncoded sample %c${writtenCount}. ${s.sampleName}%c of %c${this.samples.length}%c. Compressed: %c${s.isCompressed}%c.`,
|
|
48
68
|
consoleColors.info,
|
|
49
69
|
consoleColors.recognized,
|
|
50
70
|
consoleColors.info,
|
|
@@ -53,15 +73,16 @@ export function getSDTA(smplStartOffsets, smplEndOffsets, compress, quality, vor
|
|
|
53
73
|
s.isCompressed ? consoleColors.recognized : consoleColors.unrecognized,
|
|
54
74
|
consoleColors.info
|
|
55
75
|
);
|
|
76
|
+
|
|
56
77
|
/* 6.1 Sample Data Format in the smpl Sub-chunk
|
|
57
78
|
Each sample is followed by a minimum of forty-six zero
|
|
58
79
|
valued sample data points. These zero valued data points are necessary to guarantee that any reasonable upward pitch shift
|
|
59
80
|
using any reasonable interpolator can loop on zero data at the end of the sound.
|
|
60
81
|
This doesn't apply to sf3 tho
|
|
61
82
|
*/
|
|
62
|
-
smplChunkSize += r.length + (s.isCompressed ? 0 : 92);
|
|
63
|
-
|
|
64
|
-
}
|
|
83
|
+
smplChunkSize += r.length + (s.isCompressed ? 0 : 92);
|
|
84
|
+
sampleDatas.push(r);
|
|
85
|
+
}
|
|
65
86
|
|
|
66
87
|
if (smplChunkSize % 2 !== 0)
|
|
67
88
|
{
|
|
@@ -16,16 +16,25 @@ import { writeLittleEndian, writeWord } from "../../../utils/byte_functions/litt
|
|
|
16
16
|
import { SpessaSynthGroupCollapsed, SpessaSynthGroupEnd, SpessaSynthInfo } from "../../../utils/loggin.js";
|
|
17
17
|
import { MOD_BYTE_SIZE } from "../modulator.js";
|
|
18
18
|
import { fillWithDefaults } from "../../../utils/fill_with_defaults.js";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @typedef {function} ProgressFunction
|
|
22
|
+
* @param {string} sampleName - the written sample name.
|
|
23
|
+
* @param {number} sampleIndex - the sample's index.
|
|
24
|
+
* @param {number} sampleCount - the total sample count for progress displaying.
|
|
25
|
+
*/
|
|
26
|
+
|
|
19
27
|
/**
|
|
20
28
|
* @typedef {Object} SoundFont2WriteOptions
|
|
21
|
-
* @property {boolean|undefined} compress - if the soundfont should be compressed with
|
|
22
|
-
* @property {
|
|
23
|
-
*
|
|
24
|
-
*
|
|
29
|
+
* @property {boolean|undefined} compress - if the soundfont should be compressed with a given function.
|
|
30
|
+
* @property {SampleEncodingFunction|undefined} compressionFunction -
|
|
31
|
+
* the encode vorbis function. It can be undefined if not compressed.
|
|
32
|
+
* @property {ProgressFunction|undefined} progressFunction - a function to show progress for writing large banks. It can be undefined.
|
|
25
33
|
* @property {boolean|undefined} writeDefaultModulators - if the DMOD chunk should be written.
|
|
26
34
|
* Recommended.
|
|
27
35
|
* @property {boolean|undefined} writeExtendedLimits - if the xdta chunk should be written to allow virtually infinite parameters.
|
|
28
36
|
* Recommended.
|
|
37
|
+
* @property {boolean|undefined} decompress - if an sf3 bank should be decompressed back to sf2. Not recommended.
|
|
29
38
|
*/
|
|
30
39
|
|
|
31
40
|
|
|
@@ -43,8 +52,10 @@ const DEFAULT_WRITE_OPTIONS = {
|
|
|
43
52
|
compress: false,
|
|
44
53
|
compressionQuality: 0.5,
|
|
45
54
|
compressionFunction: undefined,
|
|
55
|
+
progressFunction: undefined,
|
|
46
56
|
writeDefaultModulators: true,
|
|
47
|
-
writeExtendedLimits: true
|
|
57
|
+
writeExtendedLimits: true,
|
|
58
|
+
decompress: false
|
|
48
59
|
};
|
|
49
60
|
|
|
50
61
|
/**
|
|
@@ -53,14 +64,18 @@ const DEFAULT_WRITE_OPTIONS = {
|
|
|
53
64
|
* @param {SoundFont2WriteOptions} options
|
|
54
65
|
* @returns {Uint8Array}
|
|
55
66
|
*/
|
|
56
|
-
export function write(options = DEFAULT_WRITE_OPTIONS)
|
|
67
|
+
export async function write(options = DEFAULT_WRITE_OPTIONS)
|
|
57
68
|
{
|
|
58
69
|
options = fillWithDefaults(options, DEFAULT_WRITE_OPTIONS);
|
|
59
|
-
if (options
|
|
70
|
+
if (options?.compress)
|
|
60
71
|
{
|
|
61
|
-
if (typeof options
|
|
72
|
+
if (typeof options?.compressionFunction !== "function")
|
|
62
73
|
{
|
|
63
|
-
throw new
|
|
74
|
+
throw new Error("No compression function supplied but compression enabled.");
|
|
75
|
+
}
|
|
76
|
+
if (options?.decompress)
|
|
77
|
+
{
|
|
78
|
+
throw new Error("Decompressed and compressed at the same time.");
|
|
64
79
|
}
|
|
65
80
|
}
|
|
66
81
|
SpessaSynthGroupCollapsed(
|
|
@@ -88,6 +103,10 @@ export function write(options = DEFAULT_WRITE_OPTIONS)
|
|
|
88
103
|
{
|
|
89
104
|
this.soundFontInfo["ifil"] = "3.0"; // set version to 3
|
|
90
105
|
}
|
|
106
|
+
if (options?.decompress)
|
|
107
|
+
{
|
|
108
|
+
this.soundFontInfo["ifil"] = "2.4"; // set version to 2.04
|
|
109
|
+
}
|
|
91
110
|
|
|
92
111
|
if (options?.writeDefaultModulators)
|
|
93
112
|
{
|
|
@@ -152,13 +171,14 @@ export function write(options = DEFAULT_WRITE_OPTIONS)
|
|
|
152
171
|
// write sdta
|
|
153
172
|
const smplStartOffsets = [];
|
|
154
173
|
const smplEndOffsets = [];
|
|
155
|
-
const sdtaChunk = getSDTA.call(
|
|
174
|
+
const sdtaChunk = await getSDTA.call(
|
|
156
175
|
this,
|
|
157
176
|
smplStartOffsets,
|
|
158
177
|
smplEndOffsets,
|
|
159
|
-
options
|
|
160
|
-
options
|
|
161
|
-
options
|
|
178
|
+
options.compress,
|
|
179
|
+
options.decompress,
|
|
180
|
+
options?.compressionFunction,
|
|
181
|
+
options?.progressFunction
|
|
162
182
|
);
|
|
163
183
|
|
|
164
184
|
SpessaSynthInfo(
|
|
@@ -175,6 +175,7 @@ export class DLSSample extends BasicSample
|
|
|
175
175
|
loopEnd
|
|
176
176
|
);
|
|
177
177
|
this.sampleDbAttenuation = sampleDbAttenuation;
|
|
178
|
+
this.dataOverriden = false;
|
|
178
179
|
/**
|
|
179
180
|
* @type {IndexedByteArray}
|
|
180
181
|
*/
|
|
@@ -221,11 +222,11 @@ export class DLSSample extends BasicSample
|
|
|
221
222
|
super.setAudioData(audioData);
|
|
222
223
|
}
|
|
223
224
|
|
|
224
|
-
getRawData(allowVorbis
|
|
225
|
+
getRawData(allowVorbis)
|
|
225
226
|
{
|
|
226
227
|
if (this.dataOverriden || this.isCompressed)
|
|
227
228
|
{
|
|
228
|
-
return super.getRawData();
|
|
229
|
+
return super.getRawData(allowVorbis);
|
|
229
230
|
}
|
|
230
231
|
if (this.wFormatTag === W_FORMAT_TAG.PCM && this.bytesPerSample === 2)
|
|
231
232
|
{
|
|
@@ -22,21 +22,10 @@ export class SoundFontSample extends BasicSample
|
|
|
22
22
|
linkedSampleIndex;
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
|
-
* The
|
|
25
|
+
* The sliced sample from the smpl chunk
|
|
26
26
|
* @type {Uint8Array}
|
|
27
27
|
*/
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Start index of the sample in the file byte array
|
|
32
|
-
* @type {number}
|
|
33
|
-
*/
|
|
34
|
-
s16leStart = 0;
|
|
35
|
-
/**
|
|
36
|
-
* End index of the sample in the file byte array
|
|
37
|
-
* @type {number}
|
|
38
|
-
*/
|
|
39
|
-
s16leEnd = 0;
|
|
28
|
+
s16leData;
|
|
40
29
|
|
|
41
30
|
/**
|
|
42
31
|
* Creates a sample
|
|
@@ -83,6 +72,7 @@ export class SoundFontSample extends BasicSample
|
|
|
83
72
|
sampleLoopStartIndex - (sampleStartIndex / 2),
|
|
84
73
|
sampleLoopEndIndex - (sampleStartIndex / 2)
|
|
85
74
|
);
|
|
75
|
+
this.dataOverriden = false;
|
|
86
76
|
this.isCompressed = compressed;
|
|
87
77
|
this.sampleName = sampleName;
|
|
88
78
|
// in bytes
|
|
@@ -116,13 +106,15 @@ export class SoundFontSample extends BasicSample
|
|
|
116
106
|
this.startByteOffset / 2,
|
|
117
107
|
this.endByteOffset / 2
|
|
118
108
|
);
|
|
109
|
+
this.dataOverriden = true;
|
|
119
110
|
}
|
|
120
111
|
else
|
|
121
112
|
{
|
|
122
113
|
// regular sf2 s16le
|
|
123
|
-
this.
|
|
124
|
-
|
|
125
|
-
|
|
114
|
+
this.s16leData = sampleDataArray.slice(
|
|
115
|
+
smplStart + this.startByteOffset,
|
|
116
|
+
smplStart + this.endByteOffset
|
|
117
|
+
);
|
|
126
118
|
}
|
|
127
119
|
|
|
128
120
|
}
|
|
@@ -189,7 +181,7 @@ export class SoundFontSample extends BasicSample
|
|
|
189
181
|
// read the sample data
|
|
190
182
|
let audioData = new Float32Array(byteLength / 2);
|
|
191
183
|
let convertedSigned16 = new Int16Array(
|
|
192
|
-
this.
|
|
184
|
+
this.s16leData.buffer
|
|
193
185
|
);
|
|
194
186
|
|
|
195
187
|
// convert to float
|
|
@@ -207,15 +199,15 @@ export class SoundFontSample extends BasicSample
|
|
|
207
199
|
* @param allowVorbis
|
|
208
200
|
* @returns {Uint8Array}
|
|
209
201
|
*/
|
|
210
|
-
getRawData(allowVorbis
|
|
202
|
+
getRawData(allowVorbis)
|
|
211
203
|
{
|
|
212
204
|
if (this.dataOverriden || this.compressedData)
|
|
213
205
|
{
|
|
214
206
|
// return vorbis or encode manually
|
|
215
|
-
return super.getRawData();
|
|
207
|
+
return super.getRawData(allowVorbis);
|
|
216
208
|
}
|
|
217
209
|
// copy the smpl directly
|
|
218
|
-
return this.
|
|
210
|
+
return this.s16leData;
|
|
219
211
|
}
|
|
220
212
|
}
|
|
221
213
|
|
|
@@ -166,7 +166,7 @@ class MidiAudioChannel
|
|
|
166
166
|
|
|
167
167
|
/**
|
|
168
168
|
* The preset currently assigned to the channel.
|
|
169
|
-
* @type {BasicPreset}
|
|
169
|
+
* @type {?BasicPreset}
|
|
170
170
|
*/
|
|
171
171
|
preset = undefined;
|
|
172
172
|
|
|
@@ -364,7 +364,6 @@ class MidiAudioChannel
|
|
|
364
364
|
{
|
|
365
365
|
return;
|
|
366
366
|
}
|
|
367
|
-
delete this.preset;
|
|
368
367
|
this.preset = preset;
|
|
369
368
|
}
|
|
370
369
|
|
|
@@ -464,7 +463,7 @@ class MidiAudioChannel
|
|
|
464
463
|
isDrum: this.drumChannel,
|
|
465
464
|
transposition: this.channelTransposeKeyShift + this.customControllers[customControllers.channelTransposeFine] / 100,
|
|
466
465
|
bank: this.sentBank,
|
|
467
|
-
program: this.preset
|
|
466
|
+
program: this.preset?.program
|
|
468
467
|
};
|
|
469
468
|
this.synth?.onChannelPropertyChange?.(data, this.channelNumber);
|
|
470
469
|
}
|
|
@@ -491,7 +491,14 @@ export function getVoices(channel, midiNote, velocity, realKey)
|
|
|
491
491
|
const overridePatch = this.keyModifierManager.hasOverridePatch(channel, midiNote);
|
|
492
492
|
|
|
493
493
|
let bank = channelObject.getBankSelect();
|
|
494
|
-
|
|
494
|
+
|
|
495
|
+
let preset = channelObject.preset;
|
|
496
|
+
if (!preset)
|
|
497
|
+
{
|
|
498
|
+
SpessaSynthWarn(`No preset for channel ${channel}!`);
|
|
499
|
+
return [];
|
|
500
|
+
}
|
|
501
|
+
let program = preset.program;
|
|
495
502
|
if (overridePatch)
|
|
496
503
|
{
|
|
497
504
|
const override = this.keyModifierManager.getPatch(channel, midiNote);
|
|
@@ -507,7 +514,6 @@ export function getVoices(channel, midiNote, velocity, realKey)
|
|
|
507
514
|
}
|
|
508
515
|
|
|
509
516
|
// not cached...
|
|
510
|
-
let preset = channelObject.preset;
|
|
511
517
|
if (overridePatch)
|
|
512
518
|
{
|
|
513
519
|
preset = this.getPreset(bank, program);
|
|
@@ -29,13 +29,17 @@ export function resetAllControllers(log = true)
|
|
|
29
29
|
this.setSystem(DEFAULT_SYNTH_MODE);
|
|
30
30
|
for (let channelNumber = 0; channelNumber < this.midiAudioChannels.length; channelNumber++)
|
|
31
31
|
{
|
|
32
|
-
this.midiAudioChannels[channelNumber].resetControllers();
|
|
33
|
-
|
|
34
32
|
/**
|
|
35
33
|
* @type {MidiAudioChannel}
|
|
36
34
|
**/
|
|
37
35
|
const ch = this.midiAudioChannels[channelNumber];
|
|
38
36
|
|
|
37
|
+
ch.resetControllers();
|
|
38
|
+
// safety net
|
|
39
|
+
if (!ch.preset)
|
|
40
|
+
{
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
39
43
|
// if preset is unlocked, switch to non-drums and call event
|
|
40
44
|
if (!ch.lockPreset)
|
|
41
45
|
{
|
|
@@ -67,11 +71,11 @@ export function resetAllControllers(log = true)
|
|
|
67
71
|
});
|
|
68
72
|
}
|
|
69
73
|
|
|
70
|
-
const presetBank = ch.preset
|
|
74
|
+
const presetBank = ch.preset?.bank;
|
|
71
75
|
// call program change
|
|
72
76
|
this.callEvent("programchange", {
|
|
73
77
|
channel: channelNumber,
|
|
74
|
-
program: ch.preset
|
|
78
|
+
program: ch.preset?.program,
|
|
75
79
|
bank: presetBank
|
|
76
80
|
});
|
|
77
81
|
|
|
@@ -4,6 +4,7 @@ import { customControllers } from "../engine_components/controller_tables.js";
|
|
|
4
4
|
import { Modulator } from "../../../soundfont/basic_soundfont/modulator.js";
|
|
5
5
|
import { GENERATOR_OVERRIDE_NO_CHANGE_VALUE } from "../../synth_constants.js";
|
|
6
6
|
import { generatorTypes } from "../../../soundfont/basic_soundfont/generator_types.js";
|
|
7
|
+
import { SpessaSynthWarn } from "../../../utils/loggin.js";
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* sends a "MIDI Note on message"
|
|
@@ -29,6 +30,12 @@ export function noteOn(midiNote, velocity)
|
|
|
29
30
|
return;
|
|
30
31
|
}
|
|
31
32
|
|
|
33
|
+
if (!this.preset)
|
|
34
|
+
{
|
|
35
|
+
SpessaSynthWarn(`No preset for channel ${this.channelNumber}!`);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
32
39
|
const realKey = midiNote + this.channelTransposeKeyShift + this.customControllers[customControllers.channelKeyShift];
|
|
33
40
|
let internalMidiNote = realKey;
|
|
34
41
|
|
|
@@ -36,7 +43,7 @@ export function noteOn(midiNote, velocity)
|
|
|
36
43
|
{
|
|
37
44
|
return;
|
|
38
45
|
}
|
|
39
|
-
const program = this.preset
|
|
46
|
+
const program = this.preset?.program;
|
|
40
47
|
const tune = this.synth.tunings[program]?.[realKey]?.midiNote;
|
|
41
48
|
if (tune >= 0)
|
|
42
49
|
{
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { SpessaSynthWarn } from "../../../utils/loggin.js";
|
|
2
|
+
import { BasicPreset } from "../../../soundfont/basic_soundfont/basic_preset.js";
|
|
3
|
+
|
|
1
4
|
/**
|
|
2
5
|
* executes a program change
|
|
3
6
|
* @param programNumber {number}
|
|
@@ -14,7 +17,13 @@ export function programChange(programNumber)
|
|
|
14
17
|
|
|
15
18
|
const isXG = this.isXGChannel;
|
|
16
19
|
const p = this.synth.soundfontManager.getPreset(bank, programNumber, isXG);
|
|
17
|
-
|
|
20
|
+
let preset = p.preset;
|
|
21
|
+
if (!preset)
|
|
22
|
+
{
|
|
23
|
+
SpessaSynthWarn("No presets! Using empty fallback.");
|
|
24
|
+
preset = new BasicPreset(this.synth.soundfontManager.soundfontList[0].soundfont);
|
|
25
|
+
preset.presetName = "SPESSA EMPTY FALLBACK PRESET";
|
|
26
|
+
}
|
|
18
27
|
this.setPreset(preset);
|
|
19
28
|
this.sentBank = Math.min(128, preset.bank + p.bankOffset);
|
|
20
29
|
this.synth.callEvent("programchange", {
|
|
@@ -66,7 +66,7 @@ export function renderVoice(
|
|
|
66
66
|
let semitones = voice.modulatedGenerators[generatorTypes.coarseTune]; // soundfont coarse tuning
|
|
67
67
|
|
|
68
68
|
// midi tuning standard
|
|
69
|
-
const tuning = this.synth.tunings[this.preset
|
|
69
|
+
const tuning = this.synth.tunings[this.preset?.program]?.[voice.realKey];
|
|
70
70
|
if (tuning !== undefined && tuning?.midiNote >= 0)
|
|
71
71
|
{
|
|
72
72
|
// override key
|