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