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.
Files changed (43) hide show
  1. package/@types/soundfont/basic_soundfont/basic_sample.d.ts +2 -2
  2. package/@types/soundfont/basic_soundfont/basic_zone.d.ts +12 -12
  3. package/@types/soundfont/basic_soundfont/basic_zones.d.ts +4 -0
  4. package/@types/soundfont/basic_soundfont/riff_chunk.d.ts +6 -0
  5. package/@types/soundfont/dls/articulator_converter.d.ts +10 -0
  6. package/@types/soundfont/dls/dls_destinations.d.ts +29 -0
  7. package/@types/soundfont/dls/dls_preset.d.ts +7 -5
  8. package/@types/soundfont/dls/dls_sample.d.ts +18 -0
  9. package/@types/soundfont/dls/dls_soundfont.d.ts +9 -2
  10. package/@types/soundfont/dls/dls_sources.d.ts +22 -0
  11. package/@types/soundfont/dls/dls_zone.d.ts +22 -0
  12. package/@types/soundfont/dls/read_articulation.d.ts +12 -0
  13. package/@types/soundfont/dls/read_instrument_list.d.ts +2 -2
  14. package/@types/soundfont/dls/read_lart.d.ts +7 -0
  15. package/@types/soundfont/dls/read_region.d.ts +7 -0
  16. package/@types/soundfont/dls/read_samples.d.ts +5 -0
  17. package/@types/soundfont/read_sf2/generators.d.ts +18 -5
  18. package/@types/soundfont/read_sf2/modulators.d.ts +1 -0
  19. package/README.md +10 -0
  20. package/midi_parser/midi_loader.js +30 -3
  21. package/package.json +1 -1
  22. package/soundfont/README.md +6 -2
  23. package/soundfont/basic_soundfont/basic_sample.js +3 -3
  24. package/soundfont/basic_soundfont/basic_zone.js +28 -28
  25. package/soundfont/basic_soundfont/basic_zones.js +15 -19
  26. package/soundfont/basic_soundfont/riff_chunk.js +18 -2
  27. package/soundfont/dls/articulator_converter.js +311 -0
  28. package/soundfont/dls/dls_destinations.js +38 -0
  29. package/soundfont/dls/dls_preset.js +15 -8
  30. package/soundfont/dls/dls_sample.js +58 -0
  31. package/soundfont/dls/dls_soundfont.js +68 -11
  32. package/soundfont/dls/dls_sources.js +26 -0
  33. package/soundfont/dls/dls_zone.js +75 -0
  34. package/soundfont/dls/read_articulation.js +327 -0
  35. package/soundfont/dls/read_instrument.js +82 -4
  36. package/soundfont/dls/read_instrument_list.js +6 -6
  37. package/soundfont/dls/read_lart.js +35 -0
  38. package/soundfont/dls/read_region.js +129 -0
  39. package/soundfont/dls/read_samples.js +174 -0
  40. package/soundfont/read_sf2/generators.js +41 -6
  41. package/soundfont/read_sf2/modulators.js +3 -3
  42. package/synthetizer/worklet_processor.min.js +10 -8
  43. 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
- export class Generator{
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 Generator(generatorChunk.chunkData));
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