feat: equalizer overhaul (#1573)

This commit is contained in:
Phan An 2022-11-02 20:25:22 +01:00 committed by GitHub
parent b40ffa358a
commit e25430b3d8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 279 additions and 308 deletions

View file

@ -1,5 +1,5 @@
<template>
<dialog ref="dialog" class="text-primary bg-primary">
<dialog ref="dialog" class="text-primary bg-primary" @cancel.prevent>
<Component :is="modalNameToComponentMap[activeModalName]" v-if="activeModalName" @close="close"/>
</dialog>
</template>
@ -19,7 +19,8 @@ const modalNameToComponentMap: Record<string, ComponentPublicInstance> = {
'edit-song-form': defineAsyncComponent(() => import('@/components/song/EditSongForm.vue')),
'create-playlist-folder-form': defineAsyncComponent(() => import('@/components/playlist/CreatePlaylistFolderForm.vue')),
'edit-playlist-folder-form': defineAsyncComponent(() => import('@/components/playlist/EditPlaylistFolderForm.vue')),
'about-koel': defineAsyncComponent(() => import('@/components/meta/AboutKoelModal.vue'))
'about-koel': defineAsyncComponent(() => import('@/components/meta/AboutKoelModal.vue')),
'equalizer': defineAsyncComponent(() => import('@/components/ui/Equalizer.vue'))
}
type ModalName = keyof typeof modalNameToComponentMap
@ -71,7 +72,9 @@ eventBus.on({
MODAL_SHOW_EDIT_PLAYLIST_FOLDER_FORM: (folder: PlaylistFolder) => {
playlistFolderToEdit.value = folder
activeModalName.value = 'edit-playlist-folder-form'
}
},
MODAL_SHOW_EQUALIZER: () => (activeModalName.value = 'equalizer')
})
</script>

View file

@ -1,8 +1,6 @@
<template>
<div class="extra-controls" data-testid="other-controls">
<div v-koel-clickaway="closeEqualizer" class="wrapper">
<Equalizer v-if="useEqualizer" v-show="showEqualizer"/>
<div class="wrapper">
<button
v-if="song?.playback_state === 'Playing'"
v-koel-tooltip.top
@ -19,11 +17,10 @@
v-if="useEqualizer"
v-koel-tooltip.top
:class="{ active: showEqualizer }"
:title="`${ showEqualizer ? 'Hide' : 'Show'} equalizer`"
class="equalizer"
data-testid="toggle-equalizer-btn"
title="Show equalizer"
type="button"
@click.prevent="toggleEqualizer"
@click.prevent="showEqualizer"
>
<icon :icon="faSliders"/>
</button>
@ -39,15 +36,11 @@ import { ref } from 'vue'
import { eventBus, isAudioContextSupported as useEqualizer, requireInjection } from '@/utils'
import { CurrentSongKey } from '@/symbols'
import Equalizer from '@/components/ui/Equalizer.vue'
import Volume from '@/components/ui/Volume.vue'
const song = requireInjection(CurrentSongKey, ref(null))
const showEqualizer = ref(false)
const toggleEqualizer = () => (showEqualizer.value = !showEqualizer.value)
const closeEqualizer = () => (showEqualizer.value = false)
const showEqualizer = () => eventBus.emit('MODAL_SHOW_EQUALIZER')
const toggleVisualizer = () => eventBus.emit('TOGGLE_VISUALIZER')
</script>

View file

@ -2,8 +2,7 @@
exports[`renders 1`] = `
<div class="extra-controls" data-testid="other-controls" data-v-8bf5fe81="">
<div class="wrapper" data-v-8bf5fe81="">
<!--v-if--><button class="visualizer-btn" data-testid="toggle-visualizer-btn" title="Toggle the visualizer" type="button" data-v-8bf5fe81=""><br data-testid="icon" icon="[object Object]" data-v-8bf5fe81=""></button>
<div class="wrapper" data-v-8bf5fe81=""><button class="visualizer-btn" data-testid="toggle-visualizer-btn" title="Toggle the visualizer" type="button" data-v-8bf5fe81=""><br data-testid="icon" icon="[object Object]" data-v-8bf5fe81=""></button>
<!--v-if--><br data-testid="Volume" data-v-8bf5fe81="">
</div>
</div>

View file

@ -2,7 +2,6 @@ import { waitFor } from '@testing-library/vue'
import { expect, it } from 'vitest'
import UnitTestCase from '@/__tests__/UnitTestCase'
import { playbackService, volumeManager } from '@/services'
import { eventBus } from '@/utils'
import { preferenceStore } from '@/stores'
import Component from './index.vue'
@ -11,7 +10,6 @@ new class extends UnitTestCase {
it('initializes playback services', async () => {
const initPlaybackMock = this.mock(playbackService, 'init')
const initVolumeMock = this.mock(volumeManager, 'init')
const emitMock = this.mock(eventBus, 'emit')
this.render(Component)
preferenceStore.initialized.value = true
@ -19,7 +17,6 @@ new class extends UnitTestCase {
await waitFor(() => {
expect(initPlaybackMock).toHaveBeenCalled()
expect(initVolumeMock).toHaveBeenCalled()
expect(emitMock).toHaveBeenCalledWith('INIT_EQUALIZER')
})
})
}

View file

@ -41,8 +41,6 @@ const initPlaybackRelatedServices = async () => {
playbackService.init(plyrWrapper)
volumeManager.init(volumeInput)
isAudioContextSupported && audioService.init(playbackService.player.media)
eventBus.emit('INIT_EQUALIZER')
}
watch(preferenceStore.initialized, async initialized => {

View file

@ -1,72 +1,79 @@
<template>
<div id="equalizer" data-testid="equalizer" ref="root">
<div class="presets">
<form id="equalizer" ref="root" data-testid="equalizer" tabindex="0" @keydown.esc="close">
<header>
<label class="select-wrapper">
<select v-model="selectedPresetId" title="Select equalizer">
<option disabled value="-1">Preset</option>
<option v-for="preset in presets" :value="preset.id" :key="preset.id" v-once>{{ preset.name }}</option>
<option v-for="preset in presets" :key="preset.id" :value="preset.id">{{ preset.name }}</option>
</select>
<icon :icon="faCaretDown" class="arrow text-highlight" size="sm"/>
</label>
</div>
<div class="bands">
<span class="band preamp">
<span class="slider"></span>
<label>Preamp</label>
</span>
</header>
<span class="indicators">
<span>+20</span>
<span>0</span>
<span>-20</span>
</span>
<main>
<div class="bands">
<span class="band">
<span class="slider"/>
<label>Preamp</label>
</span>
<span class="band amp" v-for="band in bands" :key="band.label">
<span class="slider"></span>
<label>{{ band.label }}</label>
</span>
</div>
</div>
<span class="indicators">
<span>+20</span>
<span>0</span>
<span>-20</span>
</span>
<span v-for="band in bands" :key="band.label" class="band">
<span class="slider"/>
<label>{{ band.label }}</label>
</span>
</div>
</main>
<footer>
<Btn @click.prevent="close">Close</Btn>
</footer>
</form>
</template>
<script lang="ts" setup>
import noUiSlider from 'nouislider'
import { faCaretDown } from '@fortawesome/free-solid-svg-icons'
import { nextTick, onMounted, ref, watch } from 'vue'
import { eventBus } from '@/utils'
import { equalizerStore, preferenceStore as preferences } from '@/stores'
import { audioService as audioService } from '@/services'
import { onMounted, ref, watch } from 'vue'
import { equalizerStore } from '@/stores'
import { audioService } from '@/services'
import { equalizerPresets as presets } from '@/config'
interface Band {
label: string
filter: BiquadFilterNode
}
import Btn from '@/components/ui/Btn.vue'
let context!: AudioContext
let preampGainNode!: GainNode
const emit = defineEmits(['close'])
const bands = audioService.bands
const root = ref<HTMLElement>()
const bands = ref<Band[]>([])
const preampGainValue = ref(0)
const preampGain = ref(0)
const selectedPresetId = ref(-1)
const presets: EqualizerPreset[] = Object.assign([], equalizerStore.presets)
watch(preampGain, value => audioService.changePreampGain(value))
const changePreampGain = (dbValue: number) => {
preampGainValue.value = dbValue
preampGainNode.gain.setTargetAtTime(Math.pow(10, dbValue / 20), context.currentTime, 0.01)
}
watch(selectedPresetId, () => {
if (selectedPresetId.value !== -1) {
loadPreset(equalizerStore.getPresetById(selectedPresetId.value) || presets[0])
}
const changeFilterGain = (filter: BiquadFilterNode, value: number) => {
filter.gain.setTargetAtTime(value, context.currentTime, 0.01)
}
save()
})
const createSliders = () => {
const config = equalizerStore.get()!
const config = equalizerStore.getConfig()
root.value?.querySelectorAll<SliderElement>('.slider').forEach((el, i) => {
el.noUiSlider?.destroy()
selectedPresetId.value = config.id
preampGain.value = config.preamp
if (!root.value) {
throw new Error('Equalizer config or root element not found')
}
root.value.querySelectorAll<EqualizerBandElement>('.slider').forEach((el, i) => {
noUiSlider.create(el, {
connect: [false, true],
// the first element is the preamp. The rest are gains.
@ -76,116 +83,58 @@ const createSliders = () => {
direction: 'rtl'
})
if (!el.noUiSlider) {
throw new Error(`Failed to initialize slider on element ${i}`)
}
el.isPreamp = i === 0
el.noUiSlider.on('slide', (values, handle) => {
if (el.parentElement!.matches('.preamp')) {
changePreampGain(values[handle])
} else {
changeFilterGain(bands.value[i - 1].filter, values[handle])
}
})
const value = parseFloat(values[handle])
if (el.isPreamp) {
preampGain.value = value
} else {
audioService.changeFilterGain(bands[i - 1].filter, value)
}
el.noUiSlider.on('change', () => {
// User has customized the equalizer. No preset should be selected.
selectedPresetId.value = -1
save()
})
})
// Now we set this value to trigger the audio processing.
selectedPresetId.value = preferences.selectedPreset
}
const init = async () => {
const config = equalizerStore.get()!
context = audioService.getContext()
preampGainNode = context.createGain()
changePreampGain(config.preamp)
const source = audioService.getSource()
source.connect(preampGainNode)
let prevFilter: BiquadFilterNode
// Create 10 bands with the frequencies similar to those of Winamp and connect them together.
const frequencies = [60, 170, 310, 600, 1_000, 3_000, 6_000, 12_000, 14_000, 16_000]
frequencies.forEach((frequency, i) => {
const filter = context.createBiquadFilter()
if (i === 0) {
filter.type = 'lowshelf'
} else if (i === 9) {
filter.type = 'highshelf'
} else {
filter.type = 'peaking'
}
filter.gain.setTargetAtTime(0, context.currentTime, 0.01)
filter.Q.setTargetAtTime(1, context.currentTime, 0.01)
filter.frequency.setTargetAtTime(frequency, context.currentTime, 0.01)
prevFilter ? prevFilter.connect(filter) : preampGainNode.connect(filter)
prevFilter = filter
bands.value.push({
filter,
label: String(frequency).replace('000', 'K')
})
})
prevFilter!.connect(context.destination)
await nextTick()
createSliders()
}
const save = () => equalizerStore.set(preampGainValue.value, bands.value.map(band => band.filter.gain.value))
const loadPreset = (preset: EqualizerPreset) => {
root.value?.querySelectorAll<SliderElement>('.slider').forEach((el, i) => {
preampGain.value = preset.preamp
root.value?.querySelectorAll<EqualizerBandElement>('.slider').forEach((el, i) => {
if (!el.noUiSlider) {
throw new Error('Preset can only be loaded after sliders have been set up')
}
// We treat our preamp slider differently.
if (el.parentElement!.matches('.preamp')) {
changePreampGain(preset.preamp)
// Update the slider values into GUI.
if (el.isPreamp) {
el.noUiSlider.set(preset.preamp)
} else {
changeFilterGain(bands.value[i - 1].filter, preset.gains[i - 1])
// Update the slider values into GUI.
audioService.changeFilterGain(bands[i - 1].filter, preset.gains[i - 1])
el.noUiSlider.set(preset.gains[i - 1])
}
})
save()
}
watch(selectedPresetId, () => {
preferences.selectedPreset = selectedPresetId.value
selectedPresetId.value !== -1 && loadPreset(equalizerStore.getPresetById(selectedPresetId.value)!)
})
const save = () => equalizerStore.saveConfig(selectedPresetId.value, preampGain.value, bands.map(band => band.db))
const close = () => emit('close')
onMounted(() => eventBus.on('INIT_EQUALIZER', () => init()))
onMounted(() => createSliders())
</script>
<style lang="scss">
#equalizer {
user-select: none;
position: absolute;
bottom: var(--footer-height);
width: 100%;
background: var(--color-bg-primary);
display: flex;
flex-direction: column;
left: 0;
box-shadow: 0 0 50x 0 var(--color-bg-primary);
footer {
border-top: 1px solid rgba(255, 255, 255, .05);
}
label {
margin-top: 8px;
@ -193,22 +142,18 @@ onMounted(() => eventBus.on('INIT_EQUALIZER', () => init()))
text-align: left;
}
.presets {
padding: 8px 16px;
display: flex;
flex-wrap: wrap;
justify-content: space-between;
flex: 1;
align-content: center;
z-index: 1;
border-bottom: 1px solid rgba(255, 255, 255, .1);
header {
padding: 12px 16px;
.select-wrapper {
margin-top: 0;
position: relative;
margin-bottom: 0;
padding: 0 8px;
background: rgba(0, 0, 0, .2);
border-radius: 5px;
.arrow {
margin-left: -14px;
margin-left: -6px;
pointer-events: none;
}
}
@ -227,12 +172,11 @@ onMounted(() => eventBus.on('INIT_EQUALIZER', () => init()))
}
.bands {
padding: 16px;
z-index: 1;
left: 0;
padding: 14px 16px 12px;
border-radius: 4px;
background: rgba(0, 0, 0, .2);
display: flex;
justify-content: space-between;
align-items: flex-start;
label, .indicators {
font-size: .8rem;
@ -242,6 +186,7 @@ onMounted(() => eventBus.on('INIT_EQUALIZER', () => init()))
display: flex;
flex-direction: column;
align-items: center;
min-width: 24px;
}
.slider {
@ -256,8 +201,7 @@ onMounted(() => eventBus.on('INIT_EQUALIZER', () => init()))
justify-content: space-between;
align-items: center;
margin-left: -16px;
opacity: 0;
transition: .4s;
opacity: .5;
span:first-child {
line-height: 8px;
@ -267,10 +211,6 @@ onMounted(() => eventBus.on('INIT_EQUALIZER', () => init()))
line-height: 8px;
}
}
&:hover .indicators {
opacity: 1;
}
}
.noUi {
@ -325,25 +265,12 @@ onMounted(() => eventBus.on('INIT_EQUALIZER', () => init()))
&-vertical {
.noUi-handle {
width: 16px;
height: 2px;
height: 6px;
left: -16px;
border-radius: 9999px;
top: 0;
}
}
}
@media only screen and (max-width: 768px) {
position: fixed;
max-width: 414px;
left: auto;
right: 0;
bottom: var(--footer-height);
display: block;
height: auto;
label {
line-height: 20px;
}
}
}
</style>

View file

@ -8,7 +8,7 @@ import Volume from './Volume.vue'
new class extends UnitTestCase {
protected beforeEach (cb?: Closure) {
super.beforeEach(() => {
preferenceStore.state.volume = 5
preferenceStore.volume = 5
volumeManager.init(document.createElement('input'))
})
}

View file

@ -0,0 +1,88 @@
export const equalizerPresets: EqualizerPreset[] = [
{
id: 0,
name: 'Default',
preamp: 0,
gains: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
},
{
id: 1,
name: 'Classical',
preamp: -1,
gains: [-1, -1, -1, -1, -1, -1, -7, -7, -7, -9]
},
{
id: 2,
name: 'Club',
preamp: -6.7,
gains: [-1, -1, 8, 5, 5, 5, 3, -1, -1, -1]
},
{
id: 3,
name: 'Dance',
preamp: -4.3,
gains: [9, 7, 2, -1, -1, -5, -7, -7, -1, -1]
},
{
id: 4,
name: 'Full Bass',
preamp: -7.2,
gains: [-8, 9, 9, 5, 1, -4, -8, -10, -11, -11]
},
{
id: 5,
name: 'Full Treble',
preamp: -12,
gains: [-9, -9, -9, -4, 2, 11, 16, 16, 16, 16]
},
{
id: 6,
name: 'Headphone',
preamp: -8,
gains: [4, 11, 5, -3, -2, 1, 4, 9, 12, 14]
},
{
id: 7,
name: 'Large Hall',
preamp: -7.2,
gains: [10, 10, 5, 5, -1, -4, -4, -4, -1, -1]
},
{
id: 8,
name: 'Live',
preamp: -5.3,
gains: [-4, -1, 4, 5, 5, 5, 4, 2, 2, 2]
},
{
id: 9,
name: 'Pop',
preamp: -6.2,
gains: [-1, 4, 7, 8, 5, -1, -2, -2, -1, -1]
},
{
id: 10,
name: 'Reggae',
preamp: -8.2,
gains: [-1, -1, -1, -5, -1, 6, 6, -1, -1, -1]
},
{
id: 11,
name: 'Rock',
preamp: -10,
gains: [8, 4, -5, -8, -3, 4, 8, 11, 11, 11]
},
{
id: 12,
name: 'Soft Rock',
preamp: -5.3,
gains: [4, 4, 2, -1, -4, -5, -3, -1, 2, 8]
},
{
id: 13,
name: 'Techno',
preamp: -7.7,
gains: [8, 5, -1, -5, -4, -1, 8, 9, 9, 8]
}
]
export const frequencies = [60, 170, 310, 600, 1_000, 3_000, 6_000, 12_000, 14_000, 16_000]

View file

@ -26,6 +26,7 @@ export type EventName =
| 'MODAL_SHOW_CREATE_PLAYLIST_FOLDER_FORM'
| 'MODAL_SHOW_EDIT_PLAYLIST_FOLDER_FORM'
| 'MODAL_SHOW_ABOUT_KOEL'
| 'MODAL_SHOW_EQUALIZER'
| 'PLAYLIST_DELETE'
| 'PLAYLIST_FOLDER_DELETE'

View file

@ -2,3 +2,4 @@ export * from './events'
export * from './acceptedMediaTypes'
export * from './genres'
export * from './routes'
export * from './audio'

View file

@ -85,7 +85,7 @@ const DEFAULT_VOLUME = 7
const AlbumArtOverlay = defineAsyncComponent(() => import('@/components/ui/AlbumArtOverlay.vue'))
const LoginForm = defineAsyncComponent(() => import('@/components/auth/LoginForm.vue'))
const volumeSlider = ref<SliderElement>()
const volumeSlider = ref<EqualizerBandElement>()
const authenticated = ref(false)
const song = ref<Song>()
const connected = ref(false)

View file

@ -1,27 +1,75 @@
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,
bands: [] as Band[],
init (mediaElement: HTMLMediaElement) {
this.element = mediaElement
init (element: HTMLMediaElement) {
this.context = new AudioContext()
this.source = this.context.createMediaElementSource(element)
this.element = element
this.preampGainNode = this.context.createGain()
this.source = this.context.createMediaElementSource(this.element)
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.context.destination)
this.unlockAudioContext()
},
getContext () {
return this.context
changePreampGain (db: number) {
this.preampGainNode.gain.value = dbToGain(db)
},
getSource () {
return this.source
},
getElement () {
return this.element
changeFilterGain (node: BiquadFilterNode, db: number) {
this.bands.find(band => band.filter === node)!.db = db
node.gain.value = dbToGain(db)
},
/**

View file

@ -88,7 +88,7 @@ class PlaybackService {
// We'll just "restart" playing the song, which will handle notification, scrobbling etc.
// Fixes #898
if (isAudioContextSupported) {
await audioService.getContext().resume()
await audioService.context.resume()
}
await this.restart()

View file

@ -1,121 +1,34 @@
import { preferenceStore } from '@/stores'
import { preferenceStore as preferences } from '@/stores'
import { equalizerPresets as presets } from '@/config'
export const equalizerStore = {
presets: [
{
id: 0,
name: 'Default',
preamp: 0,
gains: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
},
{
id: 1,
name: 'Classical',
preamp: -1,
gains: [-1, -1, -1, -1, -1, -1, -7, -7, -7, -9]
},
{
id: 2,
name: 'Club',
preamp: -6.7,
gains: [-1, -1, 8, 5, 5, 5, 3, -1, -1, -1]
},
{
id: 3,
name: 'Dance',
preamp: -4.3,
gains: [9, 7, 2, -1, -1, -5, -7, -7, -1, -1]
},
{
id: 4,
name: 'Full Bass',
preamp: -7.2,
gains: [-8, 9, 9, 5, 1, -4, -8, -10, -11, -11]
},
{
id: 5,
name: 'Full Treble',
preamp: -12,
gains: [-9, -9, -9, -4, 2, 11, 16, 16, 16, 16]
},
{
id: 6,
name: 'Headphone',
preamp: -8,
gains: [4, 11, 5, -3, -2, 1, 4, 9, 12, 14]
},
{
id: 7,
name: 'Large Hall',
preamp: -7.2,
gains: [10, 10, 5, 5, -1, -4, -4, -4, -1, -1]
},
{
id: 8,
name: 'Live',
preamp: -5.3,
gains: [-4, -1, 4, 5, 5, 5, 4, 2, 2, 2]
},
{
id: 9,
name: 'Pop',
preamp: -6.2,
gains: [-1, 4, 7, 8, 5, -1, -2, -2, -1, -1]
},
{
id: 10,
name: 'Reggae',
preamp: -8.2,
gains: [-1, -1, -1, -5, -1, 6, 6, -1, -1, -1]
},
{
id: 11,
name: 'Rock',
preamp: -10,
gains: [8, 4, -5, -8, -3, 4, 8, 11, 11, 11]
},
{
id: 12,
name: 'Soft Rock',
preamp: -5.3,
gains: [4, 4, 2, -1, -4, -5, -3, -1, 2, 8]
},
{
id: 13,
name: 'Techno',
preamp: -7.7,
gains: [8, 5, -1, -5, -4, -1, 8, 9, 9, 8]
}
] as EqualizerPreset[],
getPresetById (id: number) {
return this.presets.find(preset => preset.id === id)
return presets.find(preset => preset.id === id)
},
/**
* Get the current equalizer config.
*/
get () {
if (!this.presets[preferenceStore.selectedPreset]) {
return preferenceStore.equalizer
getConfig () {
if (preferences.equalizer.id === -1) {
return preferences.equalizer
}
// If the user chose a preset (instead of customizing one), just return it.
return this.getPresetById(preferenceStore.selectedPreset)
return this.getPresetById(preferences.equalizer.id) || presets[0]
},
/**
* Save the current equalizer config.
*
* @param {number} preamp The preamp value (dB)
* @param {number[]} gains The band's gain value (dB)
*/
set: (preamp: number, gains: number[]) => {
preferenceStore.equalizer = {
id: -1,
name: 'Custom',
saveConfig (id: number, preamp: number, gains: number[]) {
const preset = this.getPresetById(id)
preferences.equalizer = preset || {
preamp,
gains
gains,
id: -1,
name: 'Custom'
}
}
}

View file

@ -1,5 +1,4 @@
import { reactive, ref } from 'vue'
import { userStore } from '@/stores'
import { localStorageService } from '@/services'
interface Preferences extends Record<string, any> {
@ -10,7 +9,6 @@ interface Preferences extends Record<string, any> {
equalizer: EqualizerPreset,
artistsViewMode: ArtistAlbumViewMode | null,
albumsViewMode: ArtistAlbumViewMode | null,
selectedPreset: number
transcodeOnMobile: boolean
supportBarNoBugging: boolean
showAlbumArtOverlay: boolean
@ -28,12 +26,13 @@ const preferenceStore = {
repeatMode: 'NO_REPEAT',
confirmClosing: false,
equalizer: {
id: 0,
name: 'Default',
preamp: 0,
gains: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
},
artistsViewMode: null,
albumsViewMode: null,
selectedPreset: -1,
transcodeOnMobile: false,
supportBarNoBugging: false,
showAlbumArtOverlay: true,
@ -41,9 +40,8 @@ const preferenceStore = {
theme: null
}),
init (user?: User): void {
const initUser = user || userStore.current
this.storeKey = `preferences_${initUser.id}`
init (user: User): void {
this.storeKey = `preferences_${user.id}`
Object.assign(this.state, localStorageService.get(this.storeKey, this.state))
this.setupProxy()

View file

@ -47,6 +47,7 @@ declare module 'nouislider' {
}
orientation: 'horizontal' | 'vertical'
direction: 'ltr' | 'rtl'
step?: number
}): void
}
@ -255,12 +256,14 @@ interface Interaction {
play_count: number
}
interface SliderElement extends HTMLElement {
noUiSlider?: {
interface EqualizerBandElement extends HTMLElement {
noUiSlider: {
destroy (): void
on (eventName: 'change' | 'slide', handler: (value: number[], handle: number) => unknown): void
on (eventName: 'change' | 'slide', handler: (value: string[], handle: number) => unknown): void
set (options: number | any[]): void
}
isPreamp: boolean
}
type OverlayState = {
@ -276,8 +279,8 @@ interface SongRow {
}
interface EqualizerPreset {
id?: number
name?: string
id: number
name: string
preamp: number
gains: number[]
}

View file

@ -39,3 +39,5 @@ export const requireInjection = <T> (key: InjectionKey<T>, defaultValue?: T) =>
return value
}
export const dbToGain = (db: number) => Math.pow(10, db / 20) || 0

View file

@ -45,10 +45,10 @@ class AudioAnalyser {
this.smoothing = smoothing
this.onUpdate = onUpdate
this.audio = audioService.getElement()
this.source = audioService.getSource()
this.audio = audioService.element
this.source = audioService.source
this.analyser = audioService.getContext().createAnalyser()
this.analyser = audioService.context.createAnalyser()
this.analyser.smoothingTimeConstant = this.smoothing
this.analyser.fftSize = this.bandCount * 2