koel/resources/assets/js/services/audioService.ts

102 lines
2.8 KiB
TypeScript
Raw Normal View History

2022-11-02 19:25:22 +00:00
import { equalizerStore } from '@/stores'
import { frequencies } from '@/config'
import { dbToGain } from '@/utils'
interface Band {
label: string
filter: BiquadFilterNode
db: number
}
2022-04-24 08:50:45 +00:00
export const audioService = {
unlocked: false,
2022-11-02 19:25:22 +00:00
2022-05-14 18:49:45 +00:00
context: null as unknown as AudioContext,
source: null as unknown as MediaElementAudioSourceNode,
element: null as unknown as HTMLMediaElement,
2022-11-02 19:25:22 +00:00
preampGainNode: null as unknown as GainNode,
2022-11-06 17:09:06 +00:00
analyzer: null as unknown as AnalyserNode,
2022-11-02 19:25:22 +00:00
bands: [] as Band[],
init (mediaElement: HTMLMediaElement) {
this.element = mediaElement
2022-04-15 14:24:30 +00:00
this.context = new AudioContext()
2022-11-02 19:25:22 +00:00
this.preampGainNode = this.context.createGain()
this.source = this.context.createMediaElementSource(this.element)
2022-11-06 17:09:06 +00:00
this.analyzer = this.context.createAnalyser()
2022-11-07 14:23:59 +00:00
this.source.connect(this.preampGainNode)
2022-11-02 19:25:22 +00:00
const config = equalizerStore.getConfig()
this.changePreampGain(config.preamp)
let prevFilter: BiquadFilterNode
2022-04-15 14:24:30 +00:00
2022-11-02 19:25:22 +00:00
// Create 10 bands with the frequencies similar to those of Winamp and connect them together.
frequencies.forEach((frequency, i) => {
const filter = this.context.createBiquadFilter()
if (i === 0) {
filter.type = 'lowshelf'
} else if (i === frequencies.length - 1) {
filter.type = 'highshelf'
} else {
filter.type = 'peaking'
}
filter.Q.setTargetAtTime(1, this.context.currentTime, 0.01)
filter.frequency.setTargetAtTime(frequency, this.context.currentTime, 0.01)
filter.gain.value = dbToGain(config.gains[i])
prevFilter ? prevFilter.connect(filter) : this.preampGainNode.connect(filter)
prevFilter = filter
this.bands.push({
filter,
label: String(frequency).replace('000', 'K'),
db: config.gains[i]
})
})
2022-11-07 14:23:59 +00:00
prevFilter!.connect(this.analyzer)
// connect the analyzer node last, so that changes to the equalizer affect the visualizer as well
this.analyzer.connect(this.context.destination)
2022-11-02 19:25:22 +00:00
this.unlockAudioContext()
2022-04-15 14:24:30 +00:00
},
2022-11-02 19:25:22 +00:00
changePreampGain (db: number) {
this.preampGainNode.gain.value = dbToGain(db)
2022-04-15 14:24:30 +00:00
},
2022-11-02 19:25:22 +00:00
changeFilterGain (node: BiquadFilterNode, db: number) {
this.bands.find(band => band.filter === node)!.db = db
node.gain.value = dbToGain(db)
},
/**
* Attempt to unlock the audio context on mobile devices by creating and playing a silent buffer upon the
* first user interaction.
*/
unlockAudioContext () {
['touchend', 'touchstart', 'click'].forEach(event => {
document.addEventListener(event, () => {
if (this.unlocked) return
const source = this.context.createBufferSource()
source.buffer = this.context.createBuffer(1, 1, 22050)
source.connect(this.context.destination)
source.start(0)
this.unlocked = true
}, {
once: true
})
})
2022-04-15 14:24:30 +00:00
}
}