spessasynth_core 3.26.20 → 3.26.22

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.
@@ -13,18 +13,17 @@ import { writeLittleEndian } from "./byte_functions/little_endian.js";
13
13
 
14
14
  /**
15
15
  *
16
- * @param audioData {{leftChannel: Float32Array, rightChannel: Float32Array, sampleRate: number}}
16
+ * @param audioData {Float32Array[]} channels
17
+ * @param sampleRate {number}
17
18
  * @param normalizeAudio {boolean} find the max sample point and set it to 1, and scale others with it
18
19
  * @param metadata {WaveMetadata}
19
20
  * @param loop {{start: number, end: number}} loop start and end points in seconds. Undefined if no loop
20
21
  * @returns {ArrayBuffer}
21
22
  */
22
- export function audioToWav(audioData, normalizeAudio = true, metadata = {}, loop = undefined)
23
+ export function audioToWav(audioData, sampleRate, normalizeAudio = true, metadata = {}, loop = undefined)
23
24
  {
24
- const channel1Data = audioData.leftChannel;
25
- const channel2Data = audioData.rightChannel;
26
- const length = channel1Data.length;
27
- const sampleRate = audioData.sampleRate;
25
+ const length = audioData[0].length;
26
+ const numChannels = audioData.length;
28
27
 
29
28
  const bytesPerSample = 2; // 16-bit PCM
30
29
 
@@ -100,7 +99,7 @@ export function audioToWav(audioData, normalizeAudio = true, metadata = {}, loop
100
99
 
101
100
  // Prepare the header
102
101
  const headerSize = 44;
103
- const dataSize = length * 2 * bytesPerSample; // 2 channels, 16-bit per channel
102
+ const dataSize = length * numChannels * bytesPerSample; // 16-bit per channel
104
103
  const fileSize = headerSize + dataSize + infoChunk.length + cueChunk.length - 8; // total file size minus the first 8 bytes
105
104
  const header = new Uint8Array(headerSize);
106
105
 
@@ -120,20 +119,20 @@ export function audioToWav(audioData, normalizeAudio = true, metadata = {}, loop
120
119
  // audio format (PCM)
121
120
  header.set([1, 0], 20);
122
121
  // number of channels (2)
123
- header.set([2, 0], 22);
122
+ header.set([numChannels & 255, numChannels >> 8], 22);
124
123
  // sample rate
125
124
  header.set(
126
125
  new Uint8Array([sampleRate & 0xff, (sampleRate >> 8) & 0xff, (sampleRate >> 16) & 0xff, (sampleRate >> 24) & 0xff]),
127
126
  24
128
127
  );
129
128
  // byte rate (sample rate * block align)
130
- const byteRate = sampleRate * 2 * bytesPerSample; // 2 channels, 16-bit per channel
129
+ const byteRate = sampleRate * numChannels * bytesPerSample; // 16-bit per channel
131
130
  header.set(
132
131
  new Uint8Array([byteRate & 0xff, (byteRate >> 8) & 0xff, (byteRate >> 16) & 0xff, (byteRate >> 24) & 0xff]),
133
132
  28
134
133
  );
135
134
  // block align (channels * bytes per sample)
136
- header.set([4, 0], 32); // 2 channels * 16-bit per channel / 8
135
+ header.set([numChannels * bytesPerSample, 0], 32); // n channels * 16-bit per channel / 8
137
136
  // bits per sample
138
137
  header.set([16, 0], 34); // 16-bit
139
138
 
@@ -154,21 +153,37 @@ export function audioToWav(audioData, normalizeAudio = true, metadata = {}, loop
154
153
  if (normalizeAudio)
155
154
  {
156
155
  // find min and max values to prevent clipping when converting to 16 bits
157
- const maxAbsValue = channel1Data.map((v, i) => Math.max(Math.abs(v), Math.abs(channel2Data[i])))
158
- .reduce((a, b) => Math.max(a, b));
156
+ const numSamples = audioData[0].length;
157
+
158
+ let maxAbsValue = 0;
159
+
160
+ for (let ch = 0; ch < numChannels; ch++)
161
+ {
162
+ const data = audioData[ch];
163
+ for (let i = 0; i < numSamples; i++)
164
+ {
165
+ const sample = Math.abs(data[i]);
166
+ if (sample > maxAbsValue)
167
+ {
168
+ maxAbsValue = sample;
169
+ }
170
+ }
171
+ }
172
+
159
173
  multiplier = maxAbsValue > 0 ? (32767 / maxAbsValue) : 1;
160
174
  }
161
175
  for (let i = 0; i < length; i++)
162
176
  {
163
177
  // interleave both channels
164
- const sample1 = Math.min(32767, Math.max(-32768, channel1Data[i] * multiplier));
165
- const sample2 = Math.min(32767, Math.max(-32768, channel2Data[i] * multiplier));
178
+ audioData.forEach(d =>
179
+ {
180
+ const sample = Math.min(32767, Math.max(-32768, d[i] * multiplier));
181
+ // convert to 16-bit
182
+ wavData[offset++] = sample & 0xff;
183
+ wavData[offset++] = (sample >> 8) & 0xff;
184
+
185
+ });
166
186
 
167
- // convert to 16-bit
168
- wavData[offset++] = sample1 & 0xff;
169
- wavData[offset++] = (sample1 >> 8) & 0xff;
170
- wavData[offset++] = sample2 & 0xff;
171
- wavData[offset++] = (sample2 >> 8) & 0xff;
172
187
  }
173
188
 
174
189
  if (infoOn)
@@ -1,223 +0,0 @@
1
- import { readLittleEndian } from "../../utils/byte_functions/little_endian.js";
2
- import { IndexedByteArray } from "../../utils/indexed_array.js";
3
- import { RiffChunk } from "../basic_soundfont/riff_chunk.js";
4
- import { BasicPresetZone } from "../basic_soundfont/basic_preset_zone.js";
5
- import { Generator } from "../basic_soundfont/generator.js";
6
- import { Modulator } from "../basic_soundfont/modulator.js";
7
- import { generatorTypes } from "../basic_soundfont/generator_types.js";
8
- import { BasicInstrumentZone } from "../basic_soundfont/basic_instrument_zone.js";
9
-
10
- /**
11
- * zones.js
12
- * purpose: reads instrumend and preset zones from soundfont and gets their respective samples and generators and modulators
13
- */
14
-
15
- export class InstrumentZone extends BasicInstrumentZone
16
- {
17
- /**
18
- * Creates a zone (instrument)
19
- * @param dataArray {IndexedByteArray}
20
- */
21
- constructor(dataArray)
22
- {
23
- super();
24
- this.generatorZoneStartIndex = readLittleEndian(dataArray, 2);
25
- this.modulatorZoneStartIndex = readLittleEndian(dataArray, 2);
26
- this.modulatorZoneSize = 0;
27
- this.generatorZoneSize = 0;
28
- }
29
-
30
- setZoneSize(modulatorZoneSize, generatorZoneSize)
31
- {
32
- this.modulatorZoneSize = modulatorZoneSize;
33
- this.generatorZoneSize = generatorZoneSize;
34
- }
35
-
36
- /**
37
- * grab the generators
38
- * @param generators {Generator[]}
39
- */
40
- getGenerators(generators)
41
- {
42
- for (let i = this.generatorZoneStartIndex; i < this.generatorZoneStartIndex + this.generatorZoneSize; i++)
43
- {
44
- const g = generators[i];
45
- if (!g)
46
- {
47
- throw new Error("Missing generator in instrument zone! The file may corrupted.");
48
- }
49
- this.addGenerators(g);
50
- }
51
- }
52
-
53
- /**
54
- * grab the modulators
55
- * @param modulators {Modulator[]}
56
- */
57
- getModulators(modulators)
58
- {
59
- for (let i = this.modulatorZoneStartIndex; i < this.modulatorZoneStartIndex + this.modulatorZoneSize; i++)
60
- {
61
- const m = modulators[i];
62
- if (!m)
63
- {
64
- throw new Error("Missing modulator in instrument zone! The file may corrupted.");
65
- }
66
- this.addModulators(m);
67
- }
68
- }
69
-
70
- /**
71
- * Loads the zone's sample
72
- * @param samples {BasicSample[]}
73
- */
74
- getSample(samples)
75
- {
76
- let sampleID = this.generators.find(g => g.generatorType === generatorTypes.sampleID);
77
- if (sampleID)
78
- {
79
- this.setSample(samples[sampleID.generatorValue]);
80
- }
81
- }
82
- }
83
-
84
- /**
85
- * Reads the given instrument zone read
86
- * @param zonesChunk {RiffChunk}
87
- * @param instrumentGenerators {Generator[]}
88
- * @param instrumentModulators {Modulator[]}
89
- * @param instrumentSamples {BasicSample[]}
90
- * @returns {InstrumentZone[]}
91
- */
92
- export function readInstrumentZones(zonesChunk, instrumentGenerators, instrumentModulators, instrumentSamples)
93
- {
94
- /**
95
- * @type {InstrumentZone[]}
96
- */
97
- let zones = [];
98
- while (zonesChunk.chunkData.length > zonesChunk.chunkData.currentIndex)
99
- {
100
- let zone = new InstrumentZone(zonesChunk.chunkData);
101
- if (zones.length > 0)
102
- {
103
- let modulatorZoneSize = zone.modulatorZoneStartIndex - zones[zones.length - 1].modulatorZoneStartIndex;
104
- let generatorZoneSize = zone.generatorZoneStartIndex - zones[zones.length - 1].generatorZoneStartIndex;
105
- zones[zones.length - 1].setZoneSize(modulatorZoneSize, generatorZoneSize);
106
- zones[zones.length - 1].getGenerators(instrumentGenerators);
107
- zones[zones.length - 1].getModulators(instrumentModulators);
108
- zones[zones.length - 1].getSample(instrumentSamples);
109
- }
110
- zones.push(zone);
111
- }
112
- if (zones.length > 1)
113
- {
114
- // remove terminal
115
- zones.pop();
116
- }
117
- return zones;
118
- }
119
-
120
- export class PresetZone extends BasicPresetZone
121
- {
122
- /**
123
- * Creates a zone (preset)
124
- * @param dataArray {IndexedByteArray}
125
- */
126
- constructor(dataArray)
127
- {
128
- super();
129
- this.generatorZoneStartIndex = readLittleEndian(dataArray, 2);
130
- this.modulatorZoneStartIndex = readLittleEndian(dataArray, 2);
131
- this.modulatorZoneSize = 0;
132
- this.generatorZoneSize = 0;
133
- }
134
-
135
- setZoneSize(modulatorZoneSize, generatorZoneSize)
136
- {
137
- this.modulatorZoneSize = modulatorZoneSize;
138
- this.generatorZoneSize = generatorZoneSize;
139
- }
140
-
141
- /**
142
- * grab the generators
143
- * @param generators {Generator[]}
144
- */
145
- getGenerators(generators)
146
- {
147
- for (let i = this.generatorZoneStartIndex; i < this.generatorZoneStartIndex + this.generatorZoneSize; i++)
148
- {
149
- const g = generators[i];
150
- if (!g)
151
- {
152
- throw new Error("Missing generator in preset zone! The file may corrupted.");
153
- }
154
- this.addGenerators(g);
155
- }
156
- }
157
-
158
- /**
159
- * grab the modulators
160
- * @param modulators {Modulator[]}
161
- */
162
- getModulators(modulators)
163
- {
164
- for (let i = this.modulatorZoneStartIndex; i < this.modulatorZoneStartIndex + this.modulatorZoneSize; i++)
165
- {
166
- const m = modulators[i];
167
- if (!m)
168
- {
169
- throw new Error("Missing modulator in preset zone! The file may corrupted.");
170
- }
171
- this.addModulators(m);
172
- }
173
- }
174
-
175
- /**
176
- * grab the instrument
177
- * @param instruments {BasicInstrument[]}
178
- */
179
- getInstrument(instruments)
180
- {
181
- let instrumentID = this.generators.find(g => g.generatorType === generatorTypes.instrument);
182
- if (instrumentID)
183
- {
184
- this.setInstrument(instruments[instrumentID.generatorValue]);
185
- }
186
- }
187
- }
188
-
189
- /**
190
- * Reads the given preset zone read
191
- * @param zonesChunk {RiffChunk}
192
- * @param presetGenerators {Generator[]}
193
- * @param instruments {BasicInstrument[]}
194
- * @param presetModulators {Modulator[]}
195
- * @returns {PresetZone[]}
196
- */
197
- export function readPresetZones(zonesChunk, presetGenerators, presetModulators, instruments)
198
- {
199
- /**
200
- * @type {PresetZone[]}
201
- */
202
- let zones = [];
203
- while (zonesChunk.chunkData.length > zonesChunk.chunkData.currentIndex)
204
- {
205
- let zone = new PresetZone(zonesChunk.chunkData);
206
- if (zones.length > 0)
207
- {
208
- let modulatorZoneSize = zone.modulatorZoneStartIndex - zones[zones.length - 1].modulatorZoneStartIndex;
209
- let generatorZoneSize = zone.generatorZoneStartIndex - zones[zones.length - 1].generatorZoneStartIndex;
210
- zones[zones.length - 1].setZoneSize(modulatorZoneSize, generatorZoneSize);
211
- zones[zones.length - 1].getGenerators(presetGenerators);
212
- zones[zones.length - 1].getModulators(presetModulators);
213
- zones[zones.length - 1].getInstrument(instruments);
214
- }
215
- zones.push(zone);
216
- }
217
- if (zones.length > 1)
218
- {
219
- // remove terminal
220
- zones.pop();
221
- }
222
- return zones;
223
- }