@yume-chan/pcm-player 0.0.20 → 0.0.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.
package/worker/worker.ts CHANGED
@@ -1,80 +1,192 @@
1
+ const INPUT_HOP_SIZE = 3000;
2
+ const SCALE = 0.9;
3
+ const OUTPUT_HOP_SIZE = (INPUT_HOP_SIZE * SCALE) | 0;
4
+
5
+ const WINDOW_SIZE = 6000;
6
+ const WINDOW_WEIGHT_TABLE = new Float32Array(WINDOW_SIZE);
7
+ for (let i = 0; i < WINDOW_SIZE / 2; i += 1) {
8
+ const value = Math.sin((i / WINDOW_SIZE) * Math.PI);
9
+ WINDOW_WEIGHT_TABLE[i] = value;
10
+ WINDOW_WEIGHT_TABLE[WINDOW_SIZE - i - 1] = value;
11
+ }
12
+
1
13
  abstract class SourceProcessor<T>
2
14
  extends AudioWorkletProcessor
3
15
  implements AudioWorkletProcessorImpl
4
16
  {
5
- private _sources: T[] = [];
6
- private _sourceSampleCount = 0;
17
+ channelCount: number;
18
+ #readBuffer: Float32Array;
19
+
20
+ #chunks: T[] = [];
21
+ #chunkSampleCounts: number[] = [];
22
+ #totalSampleCount = 0;
23
+
24
+ #starting = true;
25
+ #speedUp = false;
26
+ #readOffset = 0;
27
+ #inputOffset = 0;
28
+ #outputOffset = 0;
7
29
 
8
- public constructor() {
30
+ constructor(options: { outputChannelCount?: number[] }) {
9
31
  super();
32
+
33
+ this.channelCount = options.outputChannelCount![0]!;
34
+ this.#readBuffer = new Float32Array(this.channelCount);
35
+
10
36
  this.port.onmessage = (event) => {
37
+ while (this.#totalSampleCount > 0.35 * 48000) {
38
+ this.#chunks.shift();
39
+ const count = this.#chunkSampleCounts.shift()!;
40
+ this.#totalSampleCount -= count;
41
+ }
42
+
11
43
  const data = event.data as ArrayBuffer[];
12
44
  const [source, length] = this.createSource(data);
13
- this._sources.push(source);
14
- this._sourceSampleCount += length;
45
+ this.#chunks.push(source);
46
+ this.#chunkSampleCounts.push(length);
47
+ this.#totalSampleCount += length;
48
+
49
+ if (!this.#speedUp && this.#totalSampleCount > 0.25 * 48000) {
50
+ this.#speedUp = true;
51
+ this.#readOffset = 0;
52
+ this.#inputOffset = 0;
53
+ this.#outputOffset = 0;
54
+ }
15
55
  };
16
56
  }
17
57
 
18
58
  protected abstract createSource(data: ArrayBuffer[]): [T, number];
19
59
 
20
- process(_inputs: Float32Array[][], outputs: Float32Array[][]) {
21
- const outputLeft = outputs[0]![0]!;
22
- const outputRight = outputs[0]![1]!;
23
- const outputLength = outputLeft.length;
24
- let outputIndex = 0;
60
+ process(_inputs: Float32Array[][], [outputs]: Float32Array[][]) {
61
+ if (this.#starting) {
62
+ if (this.#totalSampleCount < 0.1 * 48000) {
63
+ return true;
64
+ } else {
65
+ this.#starting = false;
66
+ }
67
+ }
25
68
 
26
- // Resample source catch up with output
27
- // TODO: should we limit the minimum and maximum speed?
28
- // TODO: this simple resample method changes pitch
29
- const sourceIndexStep = this._sourceSampleCount > 48000 ? 1.02 : 1;
30
- let sourceIndex = 0;
69
+ if (this.#speedUp && this.#totalSampleCount < 0.15 * 48000) {
70
+ this.#speedUp = false;
71
+ this.#starting = true;
72
+ }
73
+
74
+ const outputLength = outputs![0]!.length;
31
75
 
32
- while (this._sources.length > 0 && outputIndex < outputLength) {
33
- const beginSourceIndex = sourceIndex | 0;
76
+ if (this.#speedUp) {
77
+ for (let i = 0; i < outputLength; i += 1) {
78
+ let totalWeight = 0;
34
79
 
35
- let source: T | undefined = this._sources[0];
36
- [source, sourceIndex, outputIndex] = this.copyChunk(
37
- sourceIndex,
38
- sourceIndexStep,
80
+ const firstWindow = Math.max(
81
+ 0,
82
+ Math.floor(
83
+ (this.#outputOffset - WINDOW_SIZE) / OUTPUT_HOP_SIZE,
84
+ ) + 1,
85
+ );
86
+
87
+ let inWindowIndex =
88
+ this.#outputOffset - firstWindow * OUTPUT_HOP_SIZE;
89
+ let inputIndex = firstWindow * INPUT_HOP_SIZE + inWindowIndex;
90
+
91
+ while (inputIndex > 0 && inWindowIndex >= 0) {
92
+ this.#read(inputIndex - this.#readOffset);
93
+ const weight = WINDOW_WEIGHT_TABLE[inWindowIndex]!;
94
+ for (let j = 0; j < this.channelCount; j += 1) {
95
+ outputs![j]![i] += this.#readBuffer[j]! * weight;
96
+ }
97
+ totalWeight += weight;
98
+
99
+ inputIndex += INPUT_HOP_SIZE - OUTPUT_HOP_SIZE;
100
+ inWindowIndex -= OUTPUT_HOP_SIZE;
101
+ }
102
+
103
+ if (totalWeight > 0) {
104
+ for (let j = 0; j < this.channelCount; j += 1) {
105
+ outputs![j]![i] /= totalWeight;
106
+ }
107
+ }
108
+
109
+ this.#outputOffset += 1;
110
+ if (firstWindow > 0) {
111
+ this.#outputOffset -= OUTPUT_HOP_SIZE;
112
+ this.#readOffset -= INPUT_HOP_SIZE;
113
+ this.#inputOffset += (1 - SCALE) * INPUT_HOP_SIZE;
114
+ }
115
+ }
116
+
117
+ this.#inputOffset += outputLength;
118
+ const firstChunkSampleCount = this.#chunkSampleCounts[0]!;
119
+ if (
120
+ firstChunkSampleCount !== undefined &&
121
+ this.#inputOffset >= firstChunkSampleCount
122
+ ) {
123
+ this.#chunks.shift();
124
+ this.#chunkSampleCounts.shift();
125
+ this.#totalSampleCount -= firstChunkSampleCount;
126
+ this.#readOffset += firstChunkSampleCount;
127
+ this.#inputOffset -= firstChunkSampleCount;
128
+ }
129
+ } else {
130
+ this.#copyChunks(outputs!);
131
+ }
132
+
133
+ return true;
134
+ }
135
+
136
+ #copyChunks(outputs: Float32Array[]) {
137
+ let outputIndex = 0;
138
+ const outputLength = outputs[0]!.length;
139
+
140
+ while (this.#chunks.length > 0 && outputIndex < outputLength) {
141
+ let source: T | undefined = this.#chunks[0];
142
+ let consumedSampleCount = 0;
143
+ [source, consumedSampleCount, outputIndex] = this.copyChunk(
39
144
  source!,
40
- outputLeft,
41
- outputRight,
145
+ outputs,
42
146
  outputLength,
43
- outputIndex
147
+ outputIndex,
44
148
  );
45
149
 
46
- const consumedSampleCount = (sourceIndex | 0) - beginSourceIndex;
47
- this._sourceSampleCount -= consumedSampleCount;
48
- sourceIndex -= consumedSampleCount;
150
+ this.#totalSampleCount -= consumedSampleCount;
49
151
 
50
152
  if (source) {
51
153
  // Output full
52
- this._sources[0] = source;
53
- return true;
154
+ this.#chunks[0] = source;
155
+ this.#chunkSampleCounts[0]! -= consumedSampleCount;
156
+ return;
54
157
  }
55
158
 
56
- this._sources.shift();
159
+ this.#chunks.shift();
160
+ this.#chunkSampleCounts.shift();
57
161
  }
162
+ }
58
163
 
59
- if (outputIndex < outputLength) {
60
- console.log(
61
- `[Audio] Buffer underflow, inserting silence: ${
62
- outputLength - outputIndex
63
- } samples`
64
- );
164
+ #read(offset: number) {
165
+ for (let i = 0; i < this.#chunks.length; i += 1) {
166
+ const length = this.#chunkSampleCounts[i]!;
167
+
168
+ if (offset < length) {
169
+ this.read(this.#chunks[i]!, offset, this.#readBuffer);
170
+ return;
171
+ }
172
+
173
+ offset -= length;
65
174
  }
66
175
 
67
- return true;
176
+ this.#readBuffer.fill(0);
68
177
  }
69
178
 
179
+ protected abstract read(
180
+ source: T,
181
+ offset: number,
182
+ target: Float32Array,
183
+ ): void;
184
+
70
185
  protected abstract copyChunk(
71
- sourceIndex: number,
72
- sourceIndexStep: number,
73
186
  source: T,
74
- outputLeft: Float32Array,
75
- outputRight: Float32Array,
187
+ outputs: Float32Array[],
76
188
  outputLength: number,
77
- outputIndex: number
189
+ outputIndex: number,
78
190
  ): [source: T | undefined, sourceIndex: number, outputIndex: number];
79
191
  }
80
192
 
@@ -84,135 +196,151 @@ class Int16SourceProcessor
84
196
  {
85
197
  protected override createSource(data: ArrayBuffer[]): [Int16Array, number] {
86
198
  const source = new Int16Array(data[0]!);
87
- return [source, source.length / 2];
199
+ return [source, source.length / this.channelCount];
200
+ }
201
+
202
+ protected override read(
203
+ source: Int16Array,
204
+ offset: number,
205
+ target: Float32Array,
206
+ ) {
207
+ const sourceOffset = offset * this.channelCount;
208
+ for (let i = 0; i < this.channelCount; i += 1) {
209
+ target[i] = source[sourceOffset + i]! / 0x8000;
210
+ }
88
211
  }
89
212
 
90
213
  protected override copyChunk(
91
- sourceIndex: number,
92
- sourceIndexStep: number,
93
214
  source: Int16Array,
94
- outputLeft: Float32Array,
95
- outputRight: Float32Array,
215
+ outputs: Float32Array[],
96
216
  outputLength: number,
97
- outputIndex: number
217
+ outputIndex: number,
98
218
  ): [
99
219
  source: Int16Array | undefined,
100
220
  sourceIndex: number,
101
- outputIndex: number
221
+ outputIndex: number,
102
222
  ] {
103
223
  const sourceLength = source.length;
104
- let sourceSampleIndex = sourceIndex << 1;
105
-
106
- while (sourceSampleIndex < sourceLength) {
107
- outputLeft[outputIndex] = source[sourceSampleIndex]! / 0x8000;
108
- outputRight[outputIndex] = source[sourceSampleIndex + 1]! / 0x8000;
224
+ let sourceIndex = 0;
109
225
 
110
- sourceIndex += sourceIndexStep;
111
- sourceSampleIndex = sourceIndex << 1;
226
+ while (sourceIndex < sourceLength) {
227
+ for (let i = 0; i < this.channelCount; i += 1) {
228
+ outputs[i]![outputIndex] = source[sourceIndex]! / 0x8000;
229
+ sourceIndex += 1;
230
+ }
112
231
  outputIndex += 1;
113
232
 
114
233
  if (outputIndex === outputLength) {
115
234
  return [
116
- sourceSampleIndex < sourceLength
117
- ? source.subarray(sourceSampleIndex)
235
+ sourceIndex < sourceLength
236
+ ? source.subarray(sourceIndex)
118
237
  : undefined,
119
- sourceIndex,
238
+ sourceIndex / this.channelCount,
120
239
  outputIndex,
121
240
  ];
122
241
  }
123
242
  }
124
243
 
125
- return [undefined, sourceIndex, outputIndex];
244
+ return [undefined, sourceIndex / this.channelCount, outputIndex];
126
245
  }
127
246
  }
128
247
 
129
248
  class Float32SourceProcessor extends SourceProcessor<Float32Array> {
130
249
  protected override createSource(
131
- data: ArrayBuffer[]
250
+ data: ArrayBuffer[],
132
251
  ): [Float32Array, number] {
133
252
  const source = new Float32Array(data[0]!);
134
- return [source, source.length / 2];
253
+ return [source, source.length / this.channelCount];
254
+ }
255
+
256
+ protected override read(
257
+ source: Float32Array,
258
+ offset: number,
259
+ target: Float32Array,
260
+ ) {
261
+ const sourceOffset = offset * this.channelCount;
262
+ for (let i = 0; i < this.channelCount; i += 1) {
263
+ target[i] = source[sourceOffset + i]!;
264
+ }
135
265
  }
136
266
 
137
267
  protected override copyChunk(
138
- sourceIndex: number,
139
- sourceIndexStep: number,
140
268
  source: Float32Array,
141
- outputLeft: Float32Array,
142
- outputRight: Float32Array,
269
+ outputs: Float32Array[],
143
270
  outputLength: number,
144
- outputIndex: number
271
+ outputIndex: number,
145
272
  ): [
146
273
  source: Float32Array | undefined,
147
274
  sourceIndex: number,
148
- outputIndex: number
275
+ outputIndex: number,
149
276
  ] {
150
277
  const sourceLength = source.length;
151
- let sourceSampleIndex = sourceIndex << 1;
152
-
153
- while (sourceSampleIndex < sourceLength) {
154
- outputLeft[outputIndex] = source[sourceSampleIndex]!;
155
- outputRight[outputIndex] = source[sourceSampleIndex + 1]!;
278
+ let sourceIndex = 0;
156
279
 
157
- sourceIndex += sourceIndexStep;
158
- sourceSampleIndex = sourceIndex << 1;
280
+ while (sourceIndex < sourceLength) {
281
+ for (let i = 0; i < this.channelCount; i += 1) {
282
+ outputs[i]![outputIndex] = source[sourceIndex]!;
283
+ sourceIndex += 1;
284
+ }
159
285
  outputIndex += 1;
160
286
 
161
287
  if (outputIndex === outputLength) {
162
288
  return [
163
- sourceSampleIndex < sourceLength
164
- ? source.subarray(sourceSampleIndex)
289
+ sourceIndex < sourceLength
290
+ ? source.subarray(sourceIndex)
165
291
  : undefined,
166
- sourceIndex,
292
+ sourceIndex / this.channelCount,
167
293
  outputIndex,
168
294
  ];
169
295
  }
170
296
  }
171
297
 
172
- return [undefined, sourceIndex, outputIndex];
298
+ return [undefined, sourceIndex / this.channelCount, outputIndex];
173
299
  }
174
300
  }
175
301
 
176
302
  class Float32PlanerSourceProcessor extends SourceProcessor<Float32Array[]> {
177
303
  protected override createSource(
178
- data: ArrayBuffer[]
304
+ data: ArrayBuffer[],
179
305
  ): [Float32Array[], number] {
180
306
  const source = data.map((channel) => new Float32Array(channel));
181
307
  return [source, source[0]!.length];
182
308
  }
183
309
 
310
+ protected override read(
311
+ source: Float32Array[],
312
+ offset: number,
313
+ target: Float32Array,
314
+ ) {
315
+ for (let i = 0; i < target.length; i += 1) {
316
+ target[i] = source[i]![offset]!;
317
+ }
318
+ }
319
+
184
320
  protected override copyChunk(
185
- sourceIndex: number,
186
- sourceIndexStep: number,
187
321
  source: Float32Array[],
188
- outputLeft: Float32Array,
189
- outputRight: Float32Array,
322
+ outputs: Float32Array[],
190
323
  outputLength: number,
191
- outputIndex: number
324
+ outputIndex: number,
192
325
  ): [
193
326
  source: Float32Array[] | undefined,
194
327
  sourceIndex: number,
195
- outputIndex: number
328
+ outputIndex: number,
196
329
  ] {
197
- const sourceLeft = source[0]!;
198
- const sourceRight = source[1]!;
199
- const sourceLength = sourceLeft.length;
200
- let sourceSampleIndex = sourceIndex | 0;
201
-
202
- while (sourceSampleIndex < sourceLength) {
203
- outputLeft[outputIndex] = sourceLeft[sourceSampleIndex]!;
204
- outputRight[outputIndex] = sourceRight[sourceSampleIndex]!;
330
+ const sourceLength = source[0]!.length;
331
+ let sourceIndex = 0;
205
332
 
206
- sourceIndex += sourceIndexStep;
207
- sourceSampleIndex = sourceIndex | 0;
333
+ while (sourceIndex < sourceLength) {
334
+ for (let i = 0; i < this.channelCount; i += 1) {
335
+ outputs[i]![outputIndex] = source[i]![sourceIndex]!;
336
+ }
337
+ sourceIndex += 1;
208
338
  outputIndex += 1;
209
339
 
210
340
  if (outputIndex === outputLength) {
211
341
  return [
212
- sourceSampleIndex < sourceLength
213
- ? source.map((channel) =>
214
- channel.subarray(sourceSampleIndex)
215
- )
342
+ sourceIndex < sourceLength
343
+ ? source.map((channel) => channel.subarray(sourceIndex))
216
344
  : undefined,
217
345
  sourceIndex,
218
346
  outputIndex,
@@ -228,5 +356,5 @@ registerProcessor("int16-source-processor", Int16SourceProcessor);
228
356
  registerProcessor("float32-source-processor", Float32SourceProcessor);
229
357
  registerProcessor(
230
358
  "float32-planer-source-processor",
231
- Float32PlanerSourceProcessor
359
+ Float32PlanerSourceProcessor,
232
360
  );