mirror of
https://github.com/koel/koel
synced 2024-12-21 01:53:11 +00:00
101 lines
2.8 KiB
TypeScript
101 lines
2.8 KiB
TypeScript
import { equalizerStore } from '@/stores'
|
|
import { frequencies } from '@/config'
|
|
import { dbToGain } from '@/utils'
|
|
|
|
interface Band {
|
|
label: string
|
|
filter: BiquadFilterNode
|
|
db: number
|
|
}
|
|
|
|
export const audioService = {
|
|
unlocked: false,
|
|
|
|
context: null as unknown as AudioContext,
|
|
source: null as unknown as MediaElementAudioSourceNode,
|
|
element: null as unknown as HTMLMediaElement,
|
|
preampGainNode: null as unknown as GainNode,
|
|
analyzer: null as unknown as AnalyserNode,
|
|
|
|
bands: [] as Band[],
|
|
|
|
init (mediaElement: HTMLMediaElement) {
|
|
this.element = mediaElement
|
|
|
|
this.context = new AudioContext()
|
|
this.preampGainNode = this.context.createGain()
|
|
this.source = this.context.createMediaElementSource(this.element)
|
|
this.analyzer = this.context.createAnalyser()
|
|
|
|
this.source.connect(this.preampGainNode)
|
|
|
|
const config = equalizerStore.getConfig()
|
|
|
|
this.changePreampGain(config.preamp)
|
|
|
|
let prevFilter: BiquadFilterNode
|
|
|
|
// 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]
|
|
})
|
|
})
|
|
|
|
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)
|
|
|
|
this.unlockAudioContext()
|
|
},
|
|
|
|
changePreampGain (db: number) {
|
|
this.preampGainNode.gain.value = dbToGain(db)
|
|
},
|
|
|
|
changeFilterGain (node: BiquadFilterNode, db: number) {
|
|
this.bands.find(({ filter }) => 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
|
|
})
|
|
})
|
|
}
|
|
}
|