Skip to content

Commit 062a67e

Browse files
committed
implemented and tested fast sampler
1 parent 59982cd commit 062a67e

4 files changed

Lines changed: 84 additions & 6 deletions

File tree

src/composition/composition.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -438,8 +438,6 @@ export class Composition extends EventEmitterMixin<CompositionEvents, typeof Ser
438438
output.getChannelData(i).set(outputData);
439439
}
440440
} catch (_) { }
441-
442-
clip.source.audioBuffer = undefined;
443441
}
444442

445443
return output;

src/sources/audio.ts

Lines changed: 60 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,29 +9,48 @@ import { Source } from './source';
99

1010
import type { ClipType } from '../clips';
1111
import type { ArgumentTypes } from '../types';
12+
import type { FastSamplerOptions } from './audio.types';
1213

1314
export class AudioSource extends Source {
15+
private decoding = false;
16+
1417
public readonly type: ClipType = 'audio';
1518
public audioBuffer?: AudioBuffer;
1619

1720
public async decode(
1821
numberOfChannels: number = 2,
1922
sampleRate: number = 48000,
23+
cache = false,
2024
): Promise<AudioBuffer> {
25+
// make sure audio is not decoded multiple times
26+
if (this.decoding && cache) {
27+
await new Promise(this.resolve('update'));
28+
29+
if (this.audioBuffer) {
30+
return this.audioBuffer;
31+
}
32+
}
33+
34+
this.decoding = true;
2135
const buffer = await this.arrayBuffer();
2236

2337
const ctx = new OfflineAudioContext(numberOfChannels, 1, sampleRate);
2438

25-
this.audioBuffer = await ctx.decodeAudioData(buffer);
26-
this.duration.seconds = this.audioBuffer.duration;
39+
const audioBuffer = await ctx.decodeAudioData(buffer);
40+
this.duration.seconds = audioBuffer.duration;
41+
if (cache) this.audioBuffer = audioBuffer;
2742

43+
this.decoding = false;
2844
this.trigger('update', undefined);
2945

30-
return this.audioBuffer;
46+
return audioBuffer;
3147
}
3248

49+
/**
50+
* @deprecated Use fastsampler instead.
51+
*/
3352
public async samples(numberOfSampes = 60, windowSize = 50, min = 0): Promise<number[]> {
34-
const buffer = this.audioBuffer ?? (await this.decode(1, 16e3));
53+
const buffer = this.audioBuffer ?? (await this.decode(1, 3000, true));
3554

3655
const window = Math.round(buffer.sampleRate / windowSize);
3756
const length = buffer.sampleRate * buffer.duration - window;
@@ -50,6 +69,43 @@ export class AudioSource extends Source {
5069
return res.map((v) => Math.round((v / Math.max(...res)) * (100 - min)) + min);
5170
}
5271

72+
/**
73+
* Fast sampler that uses a window size to calculate the max value of the samples in the window.
74+
* @param options - Sampling options.
75+
* @returns An array of the max values of the samples in the window.
76+
*/
77+
public async fastsampler({ length = 60, start = 0, stop, logarithmic = false }: FastSamplerOptions): Promise<Float32Array> {
78+
if (typeof start === 'object') start = start.millis;
79+
if (typeof stop === 'object') stop = stop.millis;
80+
81+
const sampleRate = 3000;
82+
const audioBuffer = this.audioBuffer ?? (await this.decode(1, sampleRate, true));
83+
const channelData = audioBuffer.getChannelData(0);
84+
85+
const firstSample = Math.floor(Math.max(start * sampleRate / 1000, 0));
86+
const lastSample = stop
87+
? Math.floor(Math.min(stop * sampleRate / 1000, audioBuffer.length))
88+
: audioBuffer.length;
89+
90+
const windowSize = Math.floor((lastSample - firstSample) / length);
91+
const result = new Float32Array(length);
92+
93+
for (let i = 0; i < length; i++) {
94+
const start = firstSample + i * windowSize;
95+
const end = start + windowSize;
96+
let min = Infinity;
97+
let max = -Infinity;
98+
99+
for (let j = start; j < end; j++) {
100+
const sample = channelData[j];
101+
if (sample < min) min = sample;
102+
if (sample > max) max = sample;
103+
}
104+
result[i] = logarithmic ? Math.log2(1 + max) : max;
105+
}
106+
return result;
107+
}
108+
53109
public async thumbnail(...args: ArgumentTypes<this['samples']>): Promise<HTMLElement> {
54110
const samples = await this.samples(...args);
55111

src/sources/audio.types.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import type { Timestamp } from '../models';
2+
3+
/**
4+
* Fast sampler options.
5+
*/
6+
export type FastSamplerOptions = {
7+
/**
8+
* The number of samples to return.
9+
*/
10+
length?: number;
11+
/**
12+
* The start time in **milliseconds** relative to the beginning of the clip.
13+
*/
14+
start?: Timestamp | number;
15+
/**
16+
* The stop time in **milliseconds** relative to the beginning of the clip.
17+
*/
18+
stop?: Timestamp | number;
19+
/**
20+
* Whether to use a logarithmic scale.
21+
*/
22+
logarithmic?: boolean;
23+
};

src/sources/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@
88
export * from './source';
99
export * from './html';
1010
export * from './audio';
11+
export * from './audio.types';
1112
export * from './image';
1213
export * from './video';

0 commit comments

Comments
 (0)