98 lines
2.4 KiB
TypeScript
98 lines
2.4 KiB
TypeScript
class Sampler {
|
|
public audioContext: AudioContext = new AudioContext();
|
|
public gainNode: GainNode = new GainNode(this.audioContext);
|
|
|
|
public audioBuffer: AudioBuffer | null = null;
|
|
public audioBufferReversed: AudioBuffer | null = null;
|
|
public audioSource: AudioBufferSourceNode | null = null;
|
|
|
|
public duration: number = 0;
|
|
public isReversed: boolean = false;
|
|
|
|
constructor() {
|
|
this.gainNode.connect(this.audioContext.destination);
|
|
}
|
|
|
|
async getAudioBuffer(audioUrl: string) {
|
|
const response = await fetch(audioUrl);
|
|
const arrayBuffer = await response.arrayBuffer();
|
|
|
|
const audioBuffer = await this.audioContext.decodeAudioData(arrayBuffer);
|
|
|
|
return audioBuffer;
|
|
}
|
|
|
|
async loadTrack(audioUrl: string) {
|
|
this.audioBuffer = await this.getAudioBuffer(audioUrl);
|
|
this.audioBufferReversed = this.getReversedAudioBuffer(this.audioBuffer);
|
|
|
|
this.duration = this.audioBuffer.duration;
|
|
}
|
|
|
|
getReversedAudioBuffer(audioBuffer: AudioBuffer) {
|
|
const bufferArray = audioBuffer.getChannelData(0).slice().reverse();
|
|
|
|
const audioBufferReversed = this.audioContext.createBuffer(
|
|
1,
|
|
audioBuffer.length,
|
|
audioBuffer.sampleRate,
|
|
);
|
|
|
|
audioBufferReversed.getChannelData(0).set(bufferArray);
|
|
|
|
return audioBufferReversed;
|
|
}
|
|
|
|
changeDirection(isReversed: boolean, secondsPlayed: number) {
|
|
this.isReversed = isReversed;
|
|
this.play(secondsPlayed);
|
|
}
|
|
|
|
play(offset = 0) {
|
|
this.pause();
|
|
|
|
const buffer = this.isReversed
|
|
? this.audioBufferReversed
|
|
: this.audioBuffer;
|
|
|
|
const cueTime = this.isReversed ? this.duration - offset : offset;
|
|
|
|
this.audioSource = this.audioContext.createBufferSource();
|
|
this.audioSource.buffer = buffer;
|
|
this.audioSource.loop = false;
|
|
|
|
this.audioSource.connect(this.gainNode);
|
|
|
|
this.audioSource.start(0, cueTime);
|
|
}
|
|
|
|
updateSpeed(speed: number, isReversed: boolean, secondsPlayed: number) {
|
|
if (!this.audioSource) {
|
|
return;
|
|
}
|
|
|
|
if (isReversed !== this.isReversed) {
|
|
this.changeDirection(isReversed, secondsPlayed);
|
|
}
|
|
|
|
const { currentTime } = this.audioContext;
|
|
const speedAbsolute = Math.abs(speed);
|
|
|
|
this.audioSource.playbackRate.cancelScheduledValues(currentTime);
|
|
this.audioSource.playbackRate.linearRampToValueAtTime(
|
|
Math.max(0.001, speedAbsolute),
|
|
currentTime,
|
|
);
|
|
}
|
|
|
|
pause() {
|
|
if (!this.audioSource) {
|
|
return;
|
|
}
|
|
|
|
this.audioSource.stop();
|
|
}
|
|
}
|
|
|
|
export default Sampler;
|