spessasynth_lib 3.17.0 → 3.20.0
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_sample.d.ts +2 -2
- package/@types/soundfont/basic_soundfont/basic_zone.d.ts +12 -12
- package/@types/soundfont/basic_soundfont/basic_zones.d.ts +4 -0
- package/@types/soundfont/basic_soundfont/riff_chunk.d.ts +6 -0
- package/@types/soundfont/dls/articulator_converter.d.ts +10 -0
- package/@types/soundfont/dls/dls_destinations.d.ts +29 -0
- package/@types/soundfont/dls/dls_preset.d.ts +7 -5
- package/@types/soundfont/dls/dls_sample.d.ts +18 -0
- package/@types/soundfont/dls/dls_soundfont.d.ts +9 -2
- package/@types/soundfont/dls/dls_sources.d.ts +22 -0
- package/@types/soundfont/dls/dls_zone.d.ts +22 -0
- package/@types/soundfont/dls/read_articulation.d.ts +12 -0
- package/@types/soundfont/dls/read_instrument_list.d.ts +2 -2
- package/@types/soundfont/dls/read_lart.d.ts +7 -0
- package/@types/soundfont/dls/read_region.d.ts +7 -0
- package/@types/soundfont/dls/read_samples.d.ts +5 -0
- package/@types/soundfont/read_sf2/generators.d.ts +18 -5
- package/@types/soundfont/read_sf2/modulators.d.ts +1 -0
- package/README.md +10 -0
- package/midi_parser/midi_loader.js +30 -3
- package/package.json +1 -1
- package/soundfont/README.md +6 -2
- package/soundfont/basic_soundfont/basic_sample.js +3 -3
- package/soundfont/basic_soundfont/basic_zone.js +28 -28
- package/soundfont/basic_soundfont/basic_zones.js +15 -19
- package/soundfont/basic_soundfont/riff_chunk.js +18 -2
- package/soundfont/dls/articulator_converter.js +311 -0
- package/soundfont/dls/dls_destinations.js +38 -0
- package/soundfont/dls/dls_preset.js +15 -8
- package/soundfont/dls/dls_sample.js +58 -0
- package/soundfont/dls/dls_soundfont.js +68 -11
- package/soundfont/dls/dls_sources.js +26 -0
- package/soundfont/dls/dls_zone.js +75 -0
- package/soundfont/dls/read_articulation.js +327 -0
- package/soundfont/dls/read_instrument.js +82 -4
- package/soundfont/dls/read_instrument_list.js +6 -6
- package/soundfont/dls/read_lart.js +35 -0
- package/soundfont/dls/read_region.js +129 -0
- package/soundfont/dls/read_samples.js +174 -0
- package/soundfont/read_sf2/generators.js +41 -6
- package/soundfont/read_sf2/modulators.js +3 -3
- package/synthetizer/worklet_processor.min.js +10 -8
- package/utils/buffer_to_wav.js +5 -26
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { findRIFFListType, readRIFFChunk } from '../basic_soundfont/riff_chunk.js'
|
|
2
|
+
import { readBytesAsString } from '../../utils/byte_functions/string.js'
|
|
3
|
+
import {
|
|
4
|
+
SpessaSynthGroupCollapsed,
|
|
5
|
+
SpessaSynthGroupEnd,
|
|
6
|
+
SpessaSynthInfo, SpessaSynthWarn,
|
|
7
|
+
} from '../../utils/loggin.js'
|
|
8
|
+
import { consoleColors } from '../../utils/other.js'
|
|
9
|
+
import { readLittleEndian, signedInt16 } from '../../utils/byte_functions/little_endian.js'
|
|
10
|
+
import { DLSSample } from './dls_sample.js'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @this {DLSSoundFont}
|
|
14
|
+
* @param waveListChunk {RiffChunk}
|
|
15
|
+
*/
|
|
16
|
+
export function readDLSSamples(waveListChunk)
|
|
17
|
+
{
|
|
18
|
+
SpessaSynthGroupCollapsed("%cLoading Wave samples...",
|
|
19
|
+
consoleColors.recognized);
|
|
20
|
+
let sampleID = 0;
|
|
21
|
+
while(waveListChunk.chunkData.currentIndex < waveListChunk.chunkData.length)
|
|
22
|
+
{
|
|
23
|
+
const waveChunk = readRIFFChunk(waveListChunk.chunkData);
|
|
24
|
+
this.verifyHeader(waveChunk, "LIST");
|
|
25
|
+
this.verifyText(readBytesAsString(waveChunk.chunkData, 4), "wave");
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @type {RiffChunk[]}
|
|
29
|
+
*/
|
|
30
|
+
const waveChunks = [];
|
|
31
|
+
while(waveChunk.chunkData.currentIndex < waveChunk.chunkData.length)
|
|
32
|
+
{
|
|
33
|
+
waveChunks.push(readRIFFChunk(waveChunk.chunkData));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const fmtChunk = waveChunks.find(c => c.header === "fmt ");
|
|
37
|
+
if(!fmtChunk)
|
|
38
|
+
{
|
|
39
|
+
throw new Error("No fmt chunk in the wave file!");
|
|
40
|
+
}
|
|
41
|
+
const waveFormat = readLittleEndian(fmtChunk.chunkData, 2);
|
|
42
|
+
if(waveFormat !== 1)
|
|
43
|
+
{
|
|
44
|
+
throw new Error("Only PCM format in WAVE is supported.");
|
|
45
|
+
}
|
|
46
|
+
const channelsAmount = readLittleEndian(fmtChunk.chunkData, 2);
|
|
47
|
+
if(channelsAmount !== 1)
|
|
48
|
+
{
|
|
49
|
+
throw new Error("Only mono samples are supported.");
|
|
50
|
+
}
|
|
51
|
+
const sampleRate = readLittleEndian(fmtChunk.chunkData, 4);
|
|
52
|
+
// skip avg bytes
|
|
53
|
+
readLittleEndian(fmtChunk.chunkData, 4);
|
|
54
|
+
// blockAlign
|
|
55
|
+
readLittleEndian(fmtChunk.chunkData, 2);
|
|
56
|
+
// it's bits per sample because one channel
|
|
57
|
+
const wBitsPerSample = readLittleEndian(fmtChunk.chunkData, 2);
|
|
58
|
+
const bytesPerSample = wBitsPerSample / 8;
|
|
59
|
+
|
|
60
|
+
const maxSampleValue = Math.pow(2, bytesPerSample * 8 - 1); // Max value for the sample
|
|
61
|
+
const maxUnsigned = Math.pow(2, bytesPerSample * 8);
|
|
62
|
+
|
|
63
|
+
let normalizationFactor;
|
|
64
|
+
let isUnsigned = false;
|
|
65
|
+
|
|
66
|
+
if (wBitsPerSample === 8)
|
|
67
|
+
{
|
|
68
|
+
normalizationFactor = 255; // For 8-bit normalize from 0-255
|
|
69
|
+
isUnsigned = true;
|
|
70
|
+
}
|
|
71
|
+
else
|
|
72
|
+
{
|
|
73
|
+
normalizationFactor = maxSampleValue; // For 16-bit normalize from -32768 to 32767
|
|
74
|
+
}
|
|
75
|
+
// read the data
|
|
76
|
+
const dataChunk = waveChunks.find(c => c.header === "data");
|
|
77
|
+
if(!dataChunk)
|
|
78
|
+
{
|
|
79
|
+
throw new Error("No data chunk in the wave chunk!");
|
|
80
|
+
}
|
|
81
|
+
const sampleLength = dataChunk.size / bytesPerSample;
|
|
82
|
+
const sampleData = new Float32Array(sampleLength);
|
|
83
|
+
for (let i = 0; i < sampleData.length; i++)
|
|
84
|
+
{
|
|
85
|
+
// read
|
|
86
|
+
let sample = readLittleEndian(dataChunk.chunkData, bytesPerSample);
|
|
87
|
+
// turn into signed
|
|
88
|
+
if (isUnsigned)
|
|
89
|
+
{
|
|
90
|
+
// normalize unsigned 8-bit sample
|
|
91
|
+
sampleData[i] = (sample / normalizationFactor) - 0.5;
|
|
92
|
+
}
|
|
93
|
+
else
|
|
94
|
+
{
|
|
95
|
+
// normalize signed 16-bit sample
|
|
96
|
+
if (sample >= maxSampleValue)
|
|
97
|
+
{
|
|
98
|
+
sample -= maxUnsigned;
|
|
99
|
+
}
|
|
100
|
+
sampleData[i] = sample / normalizationFactor;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// sane defaults
|
|
105
|
+
let sampleKey = 60;
|
|
106
|
+
let samplePitch = 0;
|
|
107
|
+
let sampleLoopStart = 0;
|
|
108
|
+
let sampleLoopEnd = sampleData.length - 1;
|
|
109
|
+
|
|
110
|
+
// read wsmp
|
|
111
|
+
const wsmpChunk = waveChunks.find(c => c.header === "wsmp")
|
|
112
|
+
if(wsmpChunk)
|
|
113
|
+
{
|
|
114
|
+
// skip cbsize
|
|
115
|
+
readLittleEndian(wsmpChunk.chunkData, 4);
|
|
116
|
+
sampleKey = readLittleEndian(wsmpChunk.chunkData, 2);
|
|
117
|
+
// section 1.14.2: Each relative pitch unit represents 1/65536 cents.
|
|
118
|
+
// but that doesn't seem to be true for this one: it's just cents.
|
|
119
|
+
samplePitch = signedInt16(
|
|
120
|
+
wsmpChunk.chunkData[wsmpChunk.chunkData.currentIndex++],
|
|
121
|
+
wsmpChunk.chunkData[wsmpChunk.chunkData.currentIndex++]
|
|
122
|
+
);
|
|
123
|
+
// gain is handled in regions as initialAttenuation
|
|
124
|
+
readLittleEndian(wsmpChunk.chunkData, 4);
|
|
125
|
+
// no idea about ful options
|
|
126
|
+
readLittleEndian(wsmpChunk.chunkData, 4);
|
|
127
|
+
const loopsAmount = readLittleEndian(wsmpChunk.chunkData, 4);
|
|
128
|
+
if(loopsAmount === 1)
|
|
129
|
+
{
|
|
130
|
+
// skip size and type
|
|
131
|
+
readLittleEndian(wsmpChunk.chunkData, 8);
|
|
132
|
+
sampleLoopStart = readLittleEndian(wsmpChunk.chunkData, 4);
|
|
133
|
+
const loopSize = readLittleEndian(wsmpChunk.chunkData, 4);
|
|
134
|
+
sampleLoopEnd = sampleLoopStart + loopSize;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
else
|
|
138
|
+
{
|
|
139
|
+
SpessaSynthWarn("No wsmp chunk in wave... using sane defaults.")
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// read sample name
|
|
143
|
+
const waveInfo = findRIFFListType(waveChunks, "INFO");
|
|
144
|
+
let sampleName = `Unnamed ${sampleID}`;
|
|
145
|
+
if(waveInfo)
|
|
146
|
+
{
|
|
147
|
+
let infoChunk = readRIFFChunk(waveInfo.chunkData);
|
|
148
|
+
while(infoChunk.header !== "INAM" && waveInfo.chunkData.currentIndex < waveInfo.chunkData.length)
|
|
149
|
+
{
|
|
150
|
+
infoChunk = readRIFFChunk(waveInfo.chunkData);
|
|
151
|
+
}
|
|
152
|
+
if(infoChunk.header === "INAM")
|
|
153
|
+
{
|
|
154
|
+
sampleName = readBytesAsString(infoChunk.chunkData, infoChunk.size).trim();
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
this.samples.push(new DLSSample(
|
|
159
|
+
sampleName,
|
|
160
|
+
sampleRate,
|
|
161
|
+
sampleKey,
|
|
162
|
+
samplePitch,
|
|
163
|
+
sampleLoopStart,
|
|
164
|
+
sampleLength,
|
|
165
|
+
sampleData
|
|
166
|
+
));
|
|
167
|
+
|
|
168
|
+
sampleID++;
|
|
169
|
+
SpessaSynthInfo(`%cLoaded sample %c${sampleName}`,
|
|
170
|
+
consoleColors.info,
|
|
171
|
+
consoleColors.recognized);
|
|
172
|
+
}
|
|
173
|
+
SpessaSynthGroupEnd();
|
|
174
|
+
}
|
|
@@ -149,6 +149,39 @@ generatorLimits[generatorTypes.exclusiveClass] = {min: 0, max: 99999, def: 0};
|
|
|
149
149
|
generatorLimits[generatorTypes.overridingRootKey] = {min: 0-1, max: 127, def: -1};
|
|
150
150
|
|
|
151
151
|
|
|
152
|
+
export class Generator
|
|
153
|
+
{
|
|
154
|
+
/**
|
|
155
|
+
* Constructs a new generator
|
|
156
|
+
* @param type {generatorTypes|number}
|
|
157
|
+
* @param value {number}
|
|
158
|
+
*/
|
|
159
|
+
constructor(type = generatorTypes.INVALID, value = 0)
|
|
160
|
+
{
|
|
161
|
+
this.generatorType = type;
|
|
162
|
+
if(value === undefined)
|
|
163
|
+
{
|
|
164
|
+
throw new Error("No value provided.");
|
|
165
|
+
}
|
|
166
|
+
const lim = generatorLimits[type];
|
|
167
|
+
this.generatorValue = Math.round(value);
|
|
168
|
+
if(lim !== undefined)
|
|
169
|
+
{
|
|
170
|
+
this.generatorValue = Math.max(lim.min, Math.min(lim.max, this.generatorValue));
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* The generator's enum number
|
|
175
|
+
* @type {generatorTypes|number}
|
|
176
|
+
*/
|
|
177
|
+
generatorType = generatorTypes.INVALID;
|
|
178
|
+
/**
|
|
179
|
+
* The generator's 16-bit value
|
|
180
|
+
* @type {number}
|
|
181
|
+
*/
|
|
182
|
+
generatorValue = 0;
|
|
183
|
+
}
|
|
184
|
+
|
|
152
185
|
/**
|
|
153
186
|
* @param generatorType {number}
|
|
154
187
|
* @param presetGens {Generator[]}
|
|
@@ -173,19 +206,21 @@ export function addAndClampGenerator(generatorType, presetGens, instrumentGens)
|
|
|
173
206
|
return Math.max(limits.min, Math.min(limits.max, instruValue + presetValue));
|
|
174
207
|
}
|
|
175
208
|
|
|
176
|
-
|
|
177
|
-
|
|
209
|
+
export class ReadGenerator extends Generator
|
|
210
|
+
{
|
|
178
211
|
/**
|
|
179
212
|
* Creates a generator
|
|
180
213
|
* @param dataArray {IndexedByteArray}
|
|
181
214
|
*/
|
|
182
|
-
constructor(dataArray)
|
|
215
|
+
constructor(dataArray)
|
|
216
|
+
{
|
|
217
|
+
super();
|
|
183
218
|
// 4 bytes:
|
|
184
219
|
// type, type, type, value
|
|
185
220
|
const i = dataArray.currentIndex;
|
|
186
221
|
/**
|
|
187
|
-
* @type {generatorTypes}
|
|
188
|
-
|
|
222
|
+
* @type {generatorTypes|number}
|
|
223
|
+
*/
|
|
189
224
|
this.generatorType = (dataArray[i + 1] << 8) | dataArray[i];
|
|
190
225
|
this.generatorValue = signedInt16(dataArray[i + 2], dataArray[i + 3]);
|
|
191
226
|
dataArray.currentIndex += 4;
|
|
@@ -202,7 +237,7 @@ export function readGenerators(generatorChunk)
|
|
|
202
237
|
let gens = [];
|
|
203
238
|
while(generatorChunk.chunkData.length > generatorChunk.chunkData.currentIndex)
|
|
204
239
|
{
|
|
205
|
-
gens.push(new
|
|
240
|
+
gens.push(new ReadGenerator(generatorChunk.chunkData));
|
|
206
241
|
}
|
|
207
242
|
if(gens.length > 1)
|
|
208
243
|
{
|
|
@@ -15,7 +15,8 @@ export const modulatorSources = {
|
|
|
15
15
|
channelPressure: 13,
|
|
16
16
|
pitchWheel: 14,
|
|
17
17
|
pitchWheelRange: 16,
|
|
18
|
-
link: 127
|
|
18
|
+
link: 127,
|
|
19
|
+
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
export const modulatorCurveTypes = {
|
|
@@ -142,7 +143,7 @@ export class Modulator{
|
|
|
142
143
|
}
|
|
143
144
|
}
|
|
144
145
|
|
|
145
|
-
function getModSourceEnum(curveType, polarity, direction, isCC, index)
|
|
146
|
+
export function getModSourceEnum(curveType, polarity, direction, isCC, index)
|
|
146
147
|
{
|
|
147
148
|
return (curveType << 10) | (polarity << 9) | (direction << 8) | (isCC << 7) | index;
|
|
148
149
|
}
|
|
@@ -189,7 +190,6 @@ export const defaultModulators = [
|
|
|
189
190
|
}),
|
|
190
191
|
|
|
191
192
|
// reverb effects to send
|
|
192
|
-
// 1000 to align with the reverbSend (overriding it works anyways)
|
|
193
193
|
new Modulator({srcEnum: 0x00DB, dest: generatorTypes.reverbEffectsSend, amt: 200, secSrcEnum: 0x0, transform: 0}),
|
|
194
194
|
|
|
195
195
|
// chorus effects to send
|