support sliders with profiles

This commit is contained in:
SrGooglo 2023-10-13 17:59:30 +00:00
parent 510ad757bb
commit fec281dece
7 changed files with 483 additions and 128 deletions

View File

@ -0,0 +1,127 @@
import React from "react"
import { Select, Input, Button, Modal } from "antd"
import { Icons } from "components/Icons"
import Sliders from "../sliderValues"
export default (props) => {
const [selectedPreset, setSelectedPreset] = React.useState(props.controller.presets.currentPresetKey)
const [presets, setPresets] = React.useState(props.controller.presets.presets ?? {})
const createPreset = (key) => {
setPresets(props.controller.createPreset(key))
setSelectedPreset(key)
}
const handleCreateNewPreset = () => {
app.layout.modal.open("create_preset", (props) => {
const [presetKey, setPresetKey] = React.useState("")
return <div
style={{
display: "flex",
flexDirection: "column",
gap: "10px",
width: "100%",
}}
>
<h3>New preset</h3>
<Input
placeholder="New preset name"
value={presetKey}
onChange={(e) => {
setPresetKey(e.target.value.trim())
}}
/>
<Button
type="primary"
disabled={!presetKey || presetKey.length === 0}
onClick={() => {
createPreset(presetKey)
props.close()
}}
>
Create
</Button>
</div>
})
}
const handleDeletePreset = () => {
Modal.confirm({
title: "Delete preset",
content: "Are you sure you want to delete this preset?",
onOk: () => {
props.controller.deletePreset(selectedPreset)
setPresets(props.controller.presets.presets ?? {})
setSelectedPreset(props.controller.presets.currentPresetKey)
}
})
}
const options = [
{
value: "new",
label: <span><Icons.MdAdd /> Create new</span>,
},
...Object.keys(presets).map((key) => {
return {
value: key,
label: key,
}
})
]
React.useEffect(() => {
const presets = props.controller.presets.presets ?? {}
const preset = presets[selectedPreset]
if (props.controller.presets.currentPresetKey !== selectedPreset) {
props.controller.changePreset(selectedPreset)
}
props.ctx.updateCurrentValue(preset)
}, [selectedPreset])
return <>
<div
style={{
display: "inline-flex",
alignItems: "center",
gap: "5px",
}}
>
<Icons.MdList
style={{
margin: "0"
}}
/>
<Select
style={{
width: "50%"
}}
value={selectedPreset}
options={options}
onChange={(key) => {
if (key === "new") {
handleCreateNewPreset()
} else {
setSelectedPreset(key)
}
}}
/>
<Button
onClick={handleDeletePreset}
icon={<Icons.MdDelete />}
disabled={selectedPreset === "default"}
/>
</div>
<Sliders
{...props}
/>
</>
}

View File

@ -1,5 +1,7 @@
import loadable from "@loadable/component" import loadable from "@loadable/component"
export default { export default {
id: "player", id: "player",
icon: "PlayCircleOutlined", icon: "PlayCircleOutlined",
@ -104,27 +106,10 @@ export default {
group: "general", group: "general",
description: "Adjust compression values (Warning: may cause distortion when changing values)", description: "Adjust compression values (Warning: may cause distortion when changing values)",
experimental: true, experimental: true,
extraActions: [ dependsOn: {
{ "player.compressor": true
id: "reset",
title: "Reset",
icon: "MdRefresh",
onClick: (ctx) => {
const values = app.cores.player.compressor.resetDefaultValues()
ctx.updateCurrentValue(values)
}
}
],
defaultValue: () => {
return app.cores.player.compressor.values
}, },
onUpdate: (value) => { component: loadable(() => import("./items/player.compressor")),
app.cores.player.compressor.modifyValues(value)
return value
},
component: loadable(() => import("../components/sliderValues")),
props: { props: {
valueFormat: (value) => `${value}dB`, valueFormat: (value) => `${value}dB`,
sliders: [ sliders: [
@ -163,8 +148,22 @@ export default {
}, },
], ],
}, },
dependsOn: { extraActions: [
"player.compressor": true {
id: "reset",
title: "Reset",
icon: "MdRefresh",
onClick: async (ctx) => {
const values = await app.cores.player.compressor.resetDefaultValues()
ctx.updateCurrentValue(values)
}
}
],
onUpdate: (value) => {
app.cores.player.compressor.modifyValues(value)
return value
}, },
storaged: false, storaged: false,
}, },
@ -201,7 +200,7 @@ export default {
group: "general", group: "general",
icon: "MdGraphicEq", icon: "MdGraphicEq",
description: "Enable equalizer for audio output", description: "Enable equalizer for audio output",
component: loadable(() => import("../components/sliderValues")), component: loadable(() => import("./items/player.eq")),
extraActions: [ extraActions: [
{ {
id: "reset", id: "reset",
@ -212,12 +211,12 @@ export default {
ctx.updateCurrentValue(values) ctx.updateCurrentValue(values)
} }
} },
], ],
usePadding: false, usePadding: false,
props: { props: {
valueFormat: (value) => `${value}dB`, valueFormat: (value) => `${value}dB`,
marks:[ marks: [
{ {
value: 0, value: 0,
} }
@ -286,20 +285,9 @@ export default {
} }
] ]
}, },
defaultValue: () => {
const values = app.cores.player.eq.values().eqValues
return Object.keys(values).reduce((acc, key) => {
acc[key] = values[key].gain
return acc
}, {})
},
onUpdate: (value) => { onUpdate: (value) => {
const values = Object.keys(value).reduce((acc, key) => { const values = Object.keys(value).reduce((acc, key) => {
acc[key] = { acc[key] = value[key]
gain: value[key]
}
return acc return acc
}, {}) }, {})

View File

@ -0,0 +1,8 @@
import SlidersWithPresets from "../../../components/slidersWithPresets"
export default (props) => {
return <SlidersWithPresets
{...props}
controller={app.cores.player.compressor}
/>
}

View File

@ -0,0 +1,8 @@
import SlidersWithPresets from "../../../components/slidersWithPresets"
export default (props) => {
return <SlidersWithPresets
{...props}
controller={app.cores.player.eq}
/>
}

View File

@ -0,0 +1,117 @@
import AudioPlayerStorage from "./player.storage"
export default class Presets {
constructor({
storage_key,
defaultPresetValue,
}) {
if (!storage_key) {
throw new Error("storage_key is required")
}
this.storage_key = storage_key
this.defaultPresetValue = defaultPresetValue
return this
}
get presets() {
return AudioPlayerStorage.get(`${this.storage_key}_presets`) ?? {
default: this.defaultPresetValue
}
}
set presets(presets) {
AudioPlayerStorage.set(`${this.storage_key}_presets`, presets)
return presets
}
set currentPresetKey(key) {
AudioPlayerStorage.set(`${this.storage_key}_current-key`, key)
return key
}
get currentPresetKey() {
return AudioPlayerStorage.get(`${this.storage_key}_current-key`) ?? "default"
}
get currentPresetValues() {
const presets = this.presets
const key = this.currentPresetKey
if (!presets || !presets[key]) {
return this.defaultPresetValue
}
return presets[key]
}
deletePreset(key) {
if (key === "default") {
app.message.error("Cannot delete default preset")
return false
}
if (this.currentPresetKey === key) {
this.changePreset("default")
}
let presets = this.presets
delete presets[key]
this.presets = presets
return presets
}
createPreset(key, values) {
let presets = this.presets
if (presets[key]) {
app.message.error("Preset already exists")
return false
}
presets[key] = values ?? this.defaultPresetValue
this.presets = presets
return presets[key]
}
changePreset(key) {
let presets = this.presets
// create new one
if (!presets[key]) {
presets[key] = this.defaultPresetValue
this.presets = presets
}
this.currentPresetKey = key
return presets[key]
}
setToCurrent(values) {
let preset = this.currentPresetValues
preset = {
...preset,
...values,
}
// update presets
let presets = this.presets
presets[this.currentPresetKey] = preset
this.presets = presets
return preset
}
}

View File

@ -1,40 +1,110 @@
import AudioPlayerStorage from "../../player.storage" import { Modal } from "antd"
import ProcessorNode from "../node" import ProcessorNode from "../node"
import Presets from "../../presets"
export default class CompressorProcessorNode extends ProcessorNode { export default class CompressorProcessorNode extends ProcessorNode {
constructor(props) {
super(props)
this.presets_controller = new Presets({
storage_key: "compressor",
defaultPresetValue: {
threshold: -50,
knee: 40,
ratio: 12,
attack: 0.003,
release: 0.25,
},
})
this.state = {
compressorValues: this.presets_controller.currentPresetValues,
}
this.exposeToPublic = {
presets: new Proxy(this.presets_controller, {
get: function (target, key) {
if (!key) {
return target
}
return target[key]
}
}),
deletePreset: this.deletePreset.bind(this),
createPreset: this.createPreset.bind(this),
changePreset: this.changePreset.bind(this),
resetDefaultValues: this.resetDefaultValues.bind(this),
modifyValues: this.modifyValues.bind(this),
detach: this._detach.bind(this),
attach: this._attach.bind(this),
values: this.state.compressorValues,
}
}
static refName = "compressor" static refName = "compressor"
static dependsOnSettings = ["player.compressor"] static dependsOnSettings = ["player.compressor"]
static defaultCompressorValues = {
threshold: -50, deletePreset(key) {
knee: 40, this.changePreset("default")
ratio: 12,
attack: 0.003, this.presets_controller.deletePreset(key)
release: 0.25,
return this.presets_controller.presets
} }
state = { createPreset(key, values) {
compressorValues: AudioPlayerStorage.get("compressor") ?? CompressorProcessorNode.defaultCompressorValues, this.state = {
...this.state,
compressorValues: this.presets_controller.createPreset(key, values),
}
this.presets_controller.changePreset(key)
return this.presets_controller.presets
} }
exposeToPublic = { changePreset(key) {
modifyValues: function (values) { const values = this.presets_controller.changePreset(key)
this.state.compressorValues = {
...this.state.compressorValues,
...values,
}
AudioPlayerStorage.set("compressor", this.state.compressorValues) this.state = {
...this.state,
compressorValues: values,
}
this.applyValues() this.applyValues()
}.bind(this),
resetDefaultValues: function () {
this.exposeToPublic.modifyValues(CompressorProcessorNode.defaultCompressorValues)
return this.state.compressorValues return values
}.bind(this), }
detach: this._detach.bind(this),
attach: this._attach.bind(this), modifyValues(values) {
values: this.state.compressorValues, values = this.presets_controller.setToCurrent(values)
this.state.compressorValues = {
...this.state.compressorValues,
...values,
}
this.applyValues()
return this.state.compressorValues
}
async resetDefaultValues() {
return await new Promise((resolve) => {
Modal.confirm({
title: "Reset to default values?",
content: "Are you sure you want to reset to default values?",
onOk: () => {
this.modifyValues(this.presets_controller.defaultPresetValue)
resolve(this.state.compressorValues)
},
onCancel: () => {
resolve(this.state.compressorValues)
}
})
})
} }
async init(AudioContext) { async init(AudioContext) {

View File

@ -1,70 +1,119 @@
import { Modal } from "antd"
import ProcessorNode from "../node" import ProcessorNode from "../node"
import AudioPlayerStorage from "../../player.storage" import Presets from "../../presets"
export default class EqProcessorNode extends ProcessorNode { export default class EqProcessorNode extends ProcessorNode {
static refName = "eq" constructor(props) {
static lock = true super(props)
static defaultEqValue = { this.presets_controller = new Presets({
32: { storage_key: "eq",
gain: 0, defaultPresetValue: {
}, 32: 0,
64: { 64: 0,
gain: 0, 125: 0,
}, 250: 0,
125: { 500: 0,
gain: 0, 1000: 0,
}, 2000: 0,
250: { 4000: 0,
gain: 0, 8000: 0,
}, 16000: 0,
500: { },
gain: 0, })
},
1000: { this.state = {
gain: 0, eqValues: this.presets_controller.currentPresetValues,
}, }
2000: {
gain: 0, this.exposeToPublic = {
}, presets: new Proxy(this.presets_controller, {
4000: { get: function (target, key) {
gain: 0, if (!key) {
}, return target
8000: { }
gain: 0,
}, return target[key]
16000: { },
gain: 0, }),
deletePreset: this.deletePreset.bind(this),
createPreset: this.createPreset.bind(this),
changePreset: this.changePreset.bind(this),
modifyValues: this.modifyValues.bind(this),
resetDefaultValues: this.resetDefaultValues.bind(this),
} }
} }
state = { static refName = "eq"
eqValues: AudioPlayerStorage.get("eq_values") ?? EqProcessorNode.defaultEqValue, static lock = true
deletePreset(key) {
this.changePreset("default")
this.presets_controller.deletePreset(key)
return this.presets_controller.presets
} }
exposeToPublic = { createPreset(key, values) {
modifyValues: function (values) { this.state = {
Object.keys(values).forEach((key) => { ...this.state,
if (isNaN(key)) { eqValues: this.presets_controller.createPreset(key, values),
delete values[key] }
}
})
this.state.eqValues = { this.presets_controller.changePreset(key)
...this.state.eqValues,
...values, return this.presets_controller.presets
}
changePreset(key) {
const values = this.presets_controller.changePreset(key)
this.state = {
...this.state,
eqValues: values,
}
this.applyValues()
return values
}
modifyValues(values) {
values = this.presets_controller.setToCurrent(values)
this.state = {
...this.state,
eqValues: values,
}
this.applyValues()
return values
}
resetDefaultValues() {
Modal.confirm({
title: "Reset to default values?",
content: "Are you sure you want to reset to default values?",
onOk: () => {
this.modifyValues(this.presets_controller.defaultPresetValue)
} }
})
AudioPlayerStorage.set("eq_values", this.state.eqValues) return this.state.eqValues
}
this.applyValues() applyValues() {
}.bind(this), // apply to current instance
resetDefaultValues: function () { this.processor.eqNodes.forEach((processor) => {
this.exposeToPublic.modifyValues(EqProcessorNode.defaultEqValue) const gainValue = this.state.eqValues[processor.frequency.value]
return this.state if (processor.gain.value !== gainValue) {
}.bind(this), console.debug(`[EQ] Applying values to ${processor.frequency.value} Hz frequency with gain ${gainValue}`)
values: () => this.state, processor.gain.value = gainValue
}
})
} }
async init() { async init() {
@ -81,7 +130,7 @@ export default class EqProcessorNode extends ProcessorNode {
const values = Object.entries(this.state.eqValues).map((entry) => { const values = Object.entries(this.state.eqValues).map((entry) => {
return { return {
freq: parseFloat(entry[0]), freq: parseFloat(entry[0]),
gain: parseFloat(entry[1].gain), gain: parseFloat(entry[1]),
} }
}) })
@ -116,16 +165,4 @@ export default class EqProcessorNode extends ProcessorNode {
// set last processor for processor node can properly connect to the next node // set last processor for processor node can properly connect to the next node
this.processor._last = this.processor.eqNodes.at(-1) this.processor._last = this.processor.eqNodes.at(-1)
} }
applyValues() {
// apply to current instance
this.processor.eqNodes.forEach((processor) => {
const gainValue = this.state.eqValues[processor.frequency.value].gain
if (processor.gain.value !== gainValue) {
console.debug(`[EQ] Applying values to ${processor.frequency.value} Hz frequency with gain ${gainValue}`)
processor.gain.value = this.state.eqValues[processor.frequency.value].gain
}
})
}
} }