koel/resources/assets/js/components/site-footer/equalizer.vue

412 lines
9.1 KiB
Vue
Raw Normal View History

2016-01-11 15:25:58 +00:00
<template>
2016-06-25 16:05:24 +00:00
<div id="equalizer">
<div class="presets">
2016-06-28 06:39:42 +00:00
<label class="select-wrapper">
2016-06-25 16:05:24 +00:00
<select v-model="selectedPresetIndex">
2016-06-28 06:39:42 +00:00
<option v-for="p in presets" :value="p.id" v-once>{{ p.name }}</option>
2016-06-25 16:05:24 +00:00
</select>
</label>
2016-01-11 15:25:58 +00:00
</div>
2016-06-25 16:05:24 +00:00
<div class="bands">
<span class="band preamp">
<input
type="range"
min="-20"
max="20"
step="0.01"
data-orientation="vertical"
v-model="preampGainValue">
<label>Preamp</label>
</span>
<span class="indicators">
<span>+20</span>
<span>0</span>
<span>-20</span>
</span>
<span class="band amp" v-for="band in bands">
<input
type="range"
min="-20"
max="20"
step="0.01"
data-orientation="vertical"
:value="band.filter.gain.value">
2016-06-28 06:39:42 +00:00
<label>{{ band.label }}</label>
2016-06-25 16:05:24 +00:00
</span>
</div>
</div>
2016-01-11 15:25:58 +00:00
</template>
<script>
2016-11-26 03:25:35 +00:00
import { map, cloneDeep } from 'lodash'
import $ from 'jquery'
// eslint-disable-next-line no-unused-vars
import rangeslider from 'rangeslider.js'
2016-06-25 16:05:24 +00:00
2016-11-26 03:25:35 +00:00
import { isAudioContextSupported, event } from '../../utils'
import { equalizerStore, preferenceStore as preferences } from '../../stores'
2016-06-25 16:05:24 +00:00
export default {
2016-11-26 03:25:35 +00:00
data () {
2016-06-25 16:05:24 +00:00
return {
idx: 0,
bands: [],
preampGainValue: 0,
2016-11-26 03:25:35 +00:00
selectedPresetIndex: -1
}
2016-06-25 16:05:24 +00:00
},
2016-06-28 06:39:42 +00:00
computed: {
2016-11-26 03:25:35 +00:00
presets () {
const clonedPreset = cloneDeep(equalizerStore.presets)
2016-06-28 06:39:42 +00:00
// Prepend an empty option for instruction purpose.
clonedPreset.unshift({
id: -1,
2016-11-26 03:25:35 +00:00
name: 'Preset'
})
return clonedPreset
}
2016-06-28 06:39:42 +00:00
},
2016-06-25 16:05:24 +00:00
watch: {
/**
2016-06-28 06:39:42 +00:00
* Watch selectedPresetIndex and trigger our logic.
* @param {Number} val
2016-06-25 16:05:24 +00:00
*/
2016-11-26 03:25:35 +00:00
selectedPresetIndex (val) {
2016-06-28 06:39:42 +00:00
/**
* Save the selected preset (index) into local storage every time the value's changed.
*/
2016-11-26 03:25:35 +00:00
preferences.selectedPreset = val
2016-06-28 06:39:42 +00:00
2016-10-21 07:34:27 +00:00
if (~~val !== -1) {
2016-11-26 03:25:35 +00:00
this.loadPreset(equalizerStore.getPresetById(val))
2016-06-28 06:39:42 +00:00
}
2016-11-26 03:25:35 +00:00
}
2016-06-25 16:05:24 +00:00
},
methods: {
/**
* Init the equalizer.
*
* @param {Element} player The audio player's DOM.
*/
2016-11-26 03:25:35 +00:00
init (player) {
const settings = equalizerStore.get()
2016-06-25 16:05:24 +00:00
const AudioContext = window.AudioContext ||
window.webkitAudioContext ||
window.mozAudioContext ||
window.oAudioContext ||
2016-11-26 03:25:35 +00:00
window.msAudioContext
2016-06-25 16:05:24 +00:00
2016-11-26 03:25:35 +00:00
const context = new AudioContext()
2016-06-25 16:05:24 +00:00
2016-11-26 03:25:35 +00:00
this.preampGainNode = context.createGain()
this.changePreampGain(settings.preamp)
2016-06-25 16:05:24 +00:00
2016-11-26 03:25:35 +00:00
const source = context.createMediaElementSource(player)
source.connect(this.preampGainNode)
2016-06-25 16:05:24 +00:00
2016-11-26 03:25:35 +00:00
let prevFilter = null
2016-06-25 16:05:24 +00:00
// Create 10 bands with the frequencies similar to those of Winamp and connect them together.
2016-11-26 03:25:35 +00:00
const freqs = [60, 170, 310, 600, 1000, 3000, 6000, 12000, 14000, 16000]
freqs.forEach((f, i) => {
const filter = context.createBiquadFilter()
2016-06-25 16:05:24 +00:00
if (i === 0) {
2016-11-26 03:25:35 +00:00
filter.type = 'lowshelf'
2016-06-25 16:05:24 +00:00
} else if (i === 9) {
2016-11-26 03:25:35 +00:00
filter.type = 'highshelf'
2016-06-25 16:05:24 +00:00
} else {
2016-11-26 03:25:35 +00:00
filter.type = 'peaking'
2016-06-25 16:05:24 +00:00
}
2016-11-26 03:25:35 +00:00
filter.gain.value = settings.gains[i] ? settings.gains[i] : 0
filter.Q.value = 1
filter.frequency.value = f
2016-06-25 16:05:24 +00:00
2016-11-26 03:25:35 +00:00
prevFilter ? prevFilter.connect(filter) : this.preampGainNode.connect(filter)
prevFilter = filter
2016-06-25 16:05:24 +00:00
this.bands.push({
filter,
2016-11-26 03:25:35 +00:00
label: (f + '').replace('000', 'K')
})
})
2016-06-25 16:05:24 +00:00
2016-11-26 03:25:35 +00:00
prevFilter.connect(context.destination)
2016-06-25 16:05:24 +00:00
2016-11-26 03:25:35 +00:00
this.$nextTick(this.createRangeSliders)
2016-06-28 06:39:42 +00:00
// Now we set this value to trigger the audio processing.
2016-11-26 03:25:35 +00:00
this.selectedPresetIndex = preferences.selectedPreset
2016-06-25 16:05:24 +00:00
},
/**
* Create the UI slider for both the preamp and the normal bands using rangeslider.js.
*/
2016-11-26 03:25:35 +00:00
createRangeSliders () {
2016-06-25 16:05:24 +00:00
$('#equalizer input[type="range"]').each((i, el) => {
$(el).rangeslider({
/**
* Force the polyfill and its styles on all browsers.
*
* @type {Boolean}
*/
polyfill: false,
/**
* Change the gain/preamp value when the user drags the sliders.
*
* @param {Float} position
* @param {Float} value
*/
onSlide: (position, value) => {
if ($(el).parents('.band').is('.preamp')) {
2016-11-26 03:25:35 +00:00
this.changePreampGain(value)
2016-06-25 16:05:24 +00:00
} else {
2016-11-26 03:25:35 +00:00
this.changeFilterGain(this.bands[i - 1].filter, value)
2016-06-25 16:05:24 +00:00
}
},
/**
* Save the settings and set the preset index to -1 (which is None) on slideEnd.
*/
onSlideEnd: () => {
2016-11-26 03:25:35 +00:00
this.selectedPresetIndex = -1
this.save()
}
})
})
2016-06-25 16:05:24 +00:00
},
/**
* Change the gain value for the preamp.
*
* @param {Number} dbValue The value of the gain, in dB.
*/
2016-11-26 03:25:35 +00:00
changePreampGain (dbValue) {
this.preampGainValue = dbValue
this.preampGainNode.gain.value = Math.pow(10, dbValue / 20)
2016-06-25 16:05:24 +00:00
},
/**
* Change the gain value for a band/filter.
*
* @param {Object} filter The filter object
* @param {Object} value Value of the gain, in dB.
*/
2016-11-26 03:25:35 +00:00
changeFilterGain (filter, value) {
filter.gain.value = value
2016-06-25 16:05:24 +00:00
},
/**
* Load a preset when the user select it from the dropdown.
*/
2016-11-26 03:25:35 +00:00
loadPreset (preset) {
2016-06-25 16:05:24 +00:00
$('#equalizer input[type=range]').each((i, input) => {
// We treat our preamp slider differently.
if ($(input).parents('.band').is('.preamp')) {
2016-11-26 03:25:35 +00:00
this.changePreampGain(preset.preamp)
2016-06-25 16:05:24 +00:00
} else {
2016-11-26 03:25:35 +00:00
this.changeFilterGain(this.bands[i - 1].filter, preset.gains[i - 1])
input.value = preset.gains[i - 1]
2016-06-25 16:05:24 +00:00
}
2016-11-26 03:25:35 +00:00
})
2016-06-25 16:05:24 +00:00
this.$nextTick(() => {
// Update the slider values into GUI.
2016-11-26 03:25:35 +00:00
$('#equalizer input[type="range"]').rangeslider('update', true)
})
2016-06-25 16:05:24 +00:00
2016-11-26 03:25:35 +00:00
this.save()
2016-06-25 16:05:24 +00:00
},
/**
* Save the current user's equalizer preferences into local storage.
*/
2016-11-26 03:25:35 +00:00
save () {
equalizerStore.set(this.preampGainValue, map(this.bands, 'filter.gain.value'))
}
2016-06-25 16:05:24 +00:00
},
2016-11-26 03:25:35 +00:00
mounted () {
2016-06-25 16:05:24 +00:00
event.on('equalizer:init', player => {
if (isAudioContextSupported()) {
2016-11-26 03:25:35 +00:00
this.init(player)
2016-06-25 16:05:24 +00:00
}
2016-11-26 03:25:35 +00:00
})
}
}
2016-01-11 15:25:58 +00:00
</script>
2016-01-17 05:14:12 +00:00
<style lang="sass">
2016-06-25 16:05:24 +00:00
@import "../../../sass/partials/_vars.scss";
@import "../../../sass/partials/_mixins.scss";
#equalizer {
2016-07-07 15:08:06 +00:00
user-select: none;
2016-06-25 16:05:24 +00:00
position: absolute;
bottom: $footerHeight;
width: 100%;
background: rgba(0, 0, 0, 0.9);
display: flex;
flex-direction: column;
left: 0;
label {
margin-top: 8px;
margin-bottom: 0;
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);
.select-wrapper {
position: relative;
margin-bottom: 0;
&::after {
content: '\f107';
font-family: FontAwesome;
color: $colorHighlight;
display: inline-block;
2016-01-11 15:25:58 +00:00
position: absolute;
2016-06-25 16:05:24 +00:00
right: 8px;
top: 3px;
pointer-events: none;
}
}
2016-01-19 11:00:23 +00:00
2016-06-25 16:05:24 +00:00
select {
background: none;
color: $colorMainText;
padding-left: 0;
width: 100px;
text-transform: none;
2016-01-17 05:14:12 +00:00
2016-06-25 16:05:24 +00:00
option {
color: #333;
}
}
}
.bands {
padding: 16px;
z-index: 1;
left: 0;
display: flex;
justify-content: space-between;
align-items: flex-start;
label, .indicators {
font-size: .8rem;
}
2016-02-24 13:21:12 +00:00
2016-06-25 16:05:24 +00:00
.band {
display: flex;
flex-direction: column;
align-items: center;
}
2016-01-11 15:25:58 +00:00
2016-06-25 16:05:24 +00:00
.indicators {
height: 100px;
width: 20px;
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
margin-left: -16px;
opacity: 0;
transition: .4s;
span:first-child {
line-height: 8px;
}
span:last-child {
line-height: 8px;
}
}
2016-01-11 15:25:58 +00:00
2016-06-25 16:05:24 +00:00
&:hover .indicators {
opacity: 1;
}
}
2016-06-09 17:29:01 +00:00
2016-06-25 16:05:24 +00:00
/**
* The range slider styles
*/
.rangeslider {
background: transparent;
box-shadow: none;
2016-01-19 11:00:23 +00:00
2016-06-25 16:05:24 +00:00
&--vertical {
min-height: 100px;
width: 16px;
2016-01-19 11:00:23 +00:00
2016-06-25 16:05:24 +00:00
&::before {
content: " ";
position: absolute;
left: 7px;
width: 2px;
background: rgba(255, 255, 255, 0.2);
z-index: 1;
height: 100%;
pointer-events: none;
}
.rangeslider__fill {
width: 2px;
background: #fff;
position: absolute;
left: 7px;
box-shadow: none;
border-radius: 0;
}
2016-01-12 14:53:15 +00:00
2016-06-25 16:05:24 +00:00
.rangeslider__handle {
left: 0;
background: #fff;
border: 0;
height: 2px;
width: 100%;
border-radius: 0;
box-shadow: none;
2016-01-17 05:14:12 +00:00
2016-06-25 16:05:24 +00:00
&::after {
display: none;
2016-01-12 14:53:15 +00:00
}
2016-06-25 16:05:24 +00:00
}
}
}
@media only screen and (max-width : 768px) {
position: fixed;
max-width: 414px;
left: auto;
right: 0;
2016-07-08 01:03:18 +00:00
bottom: $footerHeightMobile + 14px;
2016-06-25 16:05:24 +00:00
display: block;
height: auto;
label {
line-height: 20px;
2016-01-11 15:25:58 +00:00
}
2016-06-25 16:05:24 +00:00
}
}
2016-01-11 15:25:58 +00:00
</style>