添加音调升降调节
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
<template>
|
||||
<base-btn min :class="[$style.newPreset, {[$style.editing]: isEditing}]" :aria-label="$t('player__sound_effect_biquad_filter_save_btn')" @click="handleEditing($event)">
|
||||
<svg-icon name="plus" />
|
||||
<base-input ref="input" :class="$style.newPresetInput" :value="newPresetName" :placeholder="$t('player__sound_effect_biquad_filter_save_input')" @keyup.enter="handleSave($event)" @blur="handleSave($event)" />
|
||||
</base-btn>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, nextTick } from '@common/utils/vueTools'
|
||||
import { appSetting } from '@renderer/store/setting'
|
||||
import { saveUserPitchShifterPreset } from '@renderer/store/soundEffect'
|
||||
|
||||
const isEditing = ref(false)
|
||||
const input = ref(false)
|
||||
const newPresetName = ref('')
|
||||
|
||||
const handleEditing = () => {
|
||||
if (isEditing.value) return
|
||||
// if (!this.newPresetName) this.newPresetName = this.listName
|
||||
isEditing.value = true
|
||||
nextTick(() => {
|
||||
input.value.$el.focus()
|
||||
})
|
||||
}
|
||||
|
||||
const handleSave = (event) => {
|
||||
let name = event.target.value.trim()
|
||||
newPresetName.value = event.target.value = ''
|
||||
isEditing.value = false
|
||||
if (!name) return
|
||||
if (name.length > 20) name = name.substring(0, 20)
|
||||
saveUserPitchShifterPreset({
|
||||
id: Date.now().toString(),
|
||||
name,
|
||||
playbackRate: appSetting['player.soundEffect.pitchShifter.playbackRate'],
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" module>
|
||||
@import '@renderer/assets/styles/layout.less';
|
||||
|
||||
.newPreset {
|
||||
position: relative;
|
||||
border: 1px dashed var(--color-primary-font-hover);
|
||||
// background-color: var(--color-main-background);
|
||||
color: var(--color-primary-font-hover);
|
||||
opacity: .7;
|
||||
height: 22px;
|
||||
|
||||
&.editing {
|
||||
opacity: 1;
|
||||
width: 90px;
|
||||
|
||||
svg {
|
||||
display: none;
|
||||
}
|
||||
.newPresetInput {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
:global {
|
||||
.svg-icon {
|
||||
vertical-align: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
.newPresetInput {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
// line-height: 16px;
|
||||
background: none !important;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
font-family: inherit;
|
||||
box-sizing: border-box;
|
||||
padding: 0 3px;
|
||||
border-radius: 0;
|
||||
display: none;
|
||||
&::placeholder {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -27,7 +27,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div :class="['scroll', $style.saveList]">
|
||||
<div :class="$style.saveList">
|
||||
<base-btn v-for="item in userPresetList" :key="item.id" min @click="handleSetPreset(item)" @contextmenu="handleRemovePreset(item.id)">{{ item.name }}</base-btn>
|
||||
<AddConvolutionPresetBtn v-if="userPresetList.length < 31" />
|
||||
</div>
|
||||
@@ -88,6 +88,7 @@ onMounted(() => {
|
||||
flex-flow: column nowrap;
|
||||
gap: 3px;
|
||||
min-height: 0;
|
||||
flex: none;
|
||||
}
|
||||
.convolution {
|
||||
display: flex;
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<span :class="$style.value">{{ appSetting[`player.soundEffect.biquadFilter.hz${v}`] }}db</span>
|
||||
</div>
|
||||
</div>
|
||||
<div :class="['scroll', $style.saveList]">
|
||||
<div :class="$style.saveList">
|
||||
<!-- <base-btn min @click="handleSetPreset(item)">{{ $t(`player__sound_effect_biquad_filter_preset_slow`) }}</base-btn> -->
|
||||
<base-btn v-for="item in freqsPreset" :key="item.name" min @click="handleSetPreset(item)">{{ $t(`player__sound_effect_biquad_filter_preset_${item.name}`) }}</base-btn>
|
||||
<base-btn v-for="item in userPresetList" :key="item.id" min @click="handleSetPreset(item)" @contextmenu="handleRemovePreset(item.id)">{{ item.name }}</base-btn>
|
||||
@@ -83,6 +83,7 @@ onMounted(() => {
|
||||
flex-flow: column nowrap;
|
||||
gap: 8px;
|
||||
min-height: 0;
|
||||
flex: none;
|
||||
}
|
||||
.header {
|
||||
display: flex;
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
<template>
|
||||
<div :class="$style.contnet">
|
||||
<div class="player__sound_effect_title" :class="$style.header">
|
||||
<h3>{{ $t('player__sound_effect_pitch_shifter') }}</h3>
|
||||
<base-btn min @click="handleSetPreset(1)">{{ $t('player__sound_effect_pitch_shifter_reset_btn') }}</base-btn>
|
||||
</div>
|
||||
<div :class="$style.eqList">
|
||||
<div :class="$style.eqItem">
|
||||
<span :class="$style.label">{{ playbackRate.toFixed(2) }}x</span>
|
||||
<base-slider-bar :class="$style.slider" :value="playbackRate * 100" :min="50" :max="150" @change="handleUpdatePlaybackRate" />
|
||||
</div>
|
||||
</div>
|
||||
<div :class="$style.saveList">
|
||||
<base-btn v-for="num in semitones" :key="num" min @click="handleSetSemitones(num)">{{ $t(`player__sound_effect_pitch_shifter_preset_semitones`, { num: num > 0 ? `+${num}` : num }) }}</base-btn>
|
||||
<base-btn v-for="item in userPresetList" :key="item.id" min @click="handleSetPreset(item.playbackRate)" @contextmenu="handleRemovePreset(item.id)">{{ item.name }}</base-btn>
|
||||
<AddPitchShifterPresetBtn v-if="userPresetList.length < 31" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from '@common/utils/vueTools'
|
||||
import { appSetting, updateSetting } from '@renderer/store/setting'
|
||||
import AddPitchShifterPresetBtn from './AddPitchShifterPresetBtn.vue'
|
||||
import { getUserPitchShifterPresetList, removeUserPitchShifterPreset } from '@renderer/store/soundEffect'
|
||||
import { semitones } from '@renderer/plugins/player'
|
||||
|
||||
// const setting = reactive({
|
||||
// enabled: false,
|
||||
// soundR: 5,
|
||||
// speed: 25,
|
||||
// })
|
||||
|
||||
|
||||
const playbackRate = computed(() => appSetting['player.soundEffect.pitchShifter.playbackRate'])
|
||||
|
||||
const handleSetPreset = (value) => {
|
||||
updateSetting({ 'player.soundEffect.pitchShifter.playbackRate': value })
|
||||
}
|
||||
|
||||
const handleSetSemitones = (value) => {
|
||||
// https://zpl.fi/pitch-shifting-in-web-audio-api/
|
||||
handleSetPreset(2 ** (value / 12))
|
||||
}
|
||||
|
||||
const handleUpdatePlaybackRate = (value) => {
|
||||
value = parseFloat((Math.round(value) / 100).toFixed(2))
|
||||
handleSetPreset(value)
|
||||
}
|
||||
|
||||
|
||||
const userPresetList = ref([])
|
||||
|
||||
const handleRemovePreset = id => {
|
||||
removeUserPitchShifterPreset(id)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getUserPitchShifterPresetList().then(list => {
|
||||
userPresetList.value = list
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" module>
|
||||
@import '@renderer/assets/styles/layout.less';
|
||||
.contnet {
|
||||
padding-top: 15px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
gap: 8px;
|
||||
min-height: 0;
|
||||
flex: none;
|
||||
&:before {
|
||||
.mixin-after;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
height: 1px;
|
||||
width: 100%;
|
||||
border-top: 1px dashed var(--color-primary-light-100-alpha-700);
|
||||
}
|
||||
}
|
||||
.header {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding-bottom: 5px;
|
||||
// padding-top: 5px;
|
||||
}
|
||||
.eqList {
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
gap: 15px;
|
||||
width: 100%;
|
||||
}
|
||||
.eqItem {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
gap: 8px;
|
||||
}
|
||||
.label {
|
||||
flex: none;
|
||||
// width: 50px;
|
||||
font-size: 12px;
|
||||
}
|
||||
.value {
|
||||
flex: none;
|
||||
width: 40px;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
|
||||
&.active {
|
||||
color: var(--color-primary-font);
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
// justify-content: space-between;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
// font-size: 13px;
|
||||
span {
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.slider {
|
||||
flex: auto;
|
||||
}
|
||||
|
||||
.checkbox {
|
||||
margin-right: 10px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.saveList {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
margin-top: 10px;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -8,12 +8,13 @@
|
||||
<!-- <main :class="$style.main"> -->
|
||||
<!-- <h2 :class="$style.title">{{ $t('theme_edit_modal__title') }}</h2> -->
|
||||
<div :class="$style.content">
|
||||
<div :class="$style.row">
|
||||
<div :class="['scroll', $style.row]">
|
||||
<AudioConvolution />
|
||||
<AudioPanner />
|
||||
<PitchShifter />
|
||||
</div>
|
||||
<div :class="$style.row">
|
||||
<div :class="['scroll', $style.row]">
|
||||
<BiquadFilter />
|
||||
<AudioPanner />
|
||||
</div>
|
||||
</div>
|
||||
<!-- </main> -->
|
||||
@@ -31,6 +32,7 @@ import { ref } from '@common/utils/vueTools'
|
||||
import BiquadFilter from './BiquadFilter'
|
||||
import AudioPanner from './AudioPanner'
|
||||
import AudioConvolution from './AudioConvolution'
|
||||
import PitchShifter from './PitchShifter.vue'
|
||||
|
||||
defineProps({
|
||||
teleport: {
|
||||
@@ -97,9 +99,9 @@ const visible = ref(false)
|
||||
.content {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
padding: 0 15px;
|
||||
padding: 0 5px;
|
||||
margin: 15px 0;
|
||||
gap: 30px;
|
||||
gap: 10px;
|
||||
position: relative;
|
||||
min-height: 0;
|
||||
|
||||
@@ -129,6 +131,7 @@ const visible = ref(false)
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
flex-flow: column nowrap;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user