add Dial to replace sliders
This commit is contained in:
@@ -13,7 +13,7 @@ import { selectById } from './timbreSlice.js';
|
|||||||
export default function Timbre(props) {
|
export default function Timbre(props) {
|
||||||
const ids = useSelector((state) => selectById(state, props.id));
|
const ids = useSelector((state) => selectById(state, props.id));
|
||||||
return (
|
return (
|
||||||
<div className='h-screen grid grid-cols-8 grid-rows-3 gap-4 m-6'>
|
<div className='h-screen grid grid-cols-8 grid-rows-3 gap-4 p-6'>
|
||||||
<VCO
|
<VCO
|
||||||
className='col-span-2'
|
className='col-span-2'
|
||||||
name='VCO 1'
|
name='VCO 1'
|
||||||
@@ -119,7 +119,7 @@ export default function Timbre(props) {
|
|||||||
<Amp className='col-span-2' name='Amp' id={ids.amp} />
|
<Amp className='col-span-2' name='Amp' id={ids.amp} />
|
||||||
<EG className='col-span-2' name='EG 1' id={ids.eg1} />
|
<EG className='col-span-2' name='EG 1' id={ids.eg1} />
|
||||||
<EG className='col-span-2' name='EG 2' id={ids.eg2} />
|
<EG className='col-span-2' name='EG 2' id={ids.eg2} />
|
||||||
<LFO
|
{/*<LFO
|
||||||
className='col-span-2'
|
className='col-span-2'
|
||||||
name='LFO 1'
|
name='LFO 1'
|
||||||
id={ids.lfo1}
|
id={ids.lfo1}
|
||||||
@@ -141,7 +141,7 @@ export default function Timbre(props) {
|
|||||||
{ value: 'Sample & Hold' },
|
{ value: 'Sample & Hold' },
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
<Patches className='col-span-2' name='Patches' id={ids.patches} />
|
<Patches className='col-span-2' name='Patches' id={ids.patches} />*/}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ export default function Card(props = { className: 'col-span-2' }) {
|
|||||||
props.className +
|
props.className +
|
||||||
' flex flex-col justify-start border border-gray-200 rounded-md shadow-md bg-white'
|
' flex flex-col justify-start border border-gray-200 rounded-md shadow-md bg-white'
|
||||||
}>
|
}>
|
||||||
<header className='flex items-start justify-between py-3 px-4 border-b border-solid border-gray-200'>
|
<header className='flex items-start justify-between py-2 px-4 border-b border-solid border-gray-200'>
|
||||||
<h3 className='text-xl font-semibold text-gray-900'>{props.header}</h3>
|
<h3 className='text-xl font-semibold text-gray-900'>{props.header}</h3>
|
||||||
</header>
|
</header>
|
||||||
<section className='flex-grow flex flex-col justify-evenly px-6'>
|
<section className='flex-grow flex flex-col justify-evenly px-6'>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { useSelector } from 'react-redux';
|
|||||||
export default function Editor() {
|
export default function Editor() {
|
||||||
const activeTab = useSelector((state) => state.parameters.activeTab);
|
const activeTab = useSelector((state) => state.parameters.activeTab);
|
||||||
return (
|
return (
|
||||||
<div className='col-span-5'>
|
<div className='col-span-5 overflow-y-scroll overscroll-contain'>
|
||||||
{activeTab === 'Timbre1' ? <Timbre id='timbre1' /> : null}
|
{activeTab === 'Timbre1' ? <Timbre id='timbre1' /> : null}
|
||||||
{activeTab === 'Timbre2' ? <Timbre id='timbre2' /> : null}
|
{activeTab === 'Timbre2' ? <Timbre id='timbre2' /> : null}
|
||||||
{activeTab === 'Vocoder' ? <Vocoder id='vocoder' /> : null}
|
{activeTab === 'Vocoder' ? <Vocoder id='vocoder' /> : null}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import Card from '../card';
|
||||||
|
import { Select, Knob } from '../../utils/components';
|
||||||
import { useSelector, useDispatch } from 'react-redux';
|
import { useSelector, useDispatch } from 'react-redux';
|
||||||
import { selectById, parameterUpdated } from './parameterSlice.js';
|
import { selectById, parameterUpdated } from './parameterSlice.js';
|
||||||
import { Slider, SelectList } from '../../helpers/Helpers';
|
import { Slider, SelectList } from '../../helpers/Helpers';
|
||||||
@@ -8,83 +10,102 @@ export default function Filter(props) {
|
|||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={props.className}>
|
<Card className={props.className} header={props.name}>
|
||||||
<span>{`${props.name}`}</span>
|
<div className='-mt-4 flex items-center justify-between'>
|
||||||
Filter Type
|
<h3 className='text-lg font-semibold text-gray-900'>Filter Type</h3>
|
||||||
<SelectList
|
<Select
|
||||||
value={parameters.filterType}
|
className='select w-4/6'
|
||||||
list={[
|
value={parameters.filterType}
|
||||||
{ value: '-24db Low Pass' },
|
list={[
|
||||||
{ value: '-12db Low Pass' },
|
{ value: '-24db Low Pass' },
|
||||||
{ value: 'Band Pass' },
|
{ value: '-12db Low Pass' },
|
||||||
{ value: 'High Pass' },
|
{ value: 'Band Pass' },
|
||||||
]}
|
{ value: 'High Pass' },
|
||||||
onChange={(value) =>
|
]}
|
||||||
dispatch(
|
onChange={(value) =>
|
||||||
parameterUpdated({
|
dispatch(
|
||||||
id: props.id,
|
parameterUpdated({
|
||||||
changes: { filterType: value },
|
id: props.id,
|
||||||
})
|
changes: { filterType: value },
|
||||||
)
|
})
|
||||||
}
|
)
|
||||||
/>
|
}
|
||||||
Cutoff
|
/>
|
||||||
<Slider
|
</div>
|
||||||
parameter={parameters.cutoff}
|
<div className='flex flex-row justify-evenly'>
|
||||||
min={0}
|
<div className='flex flex-col w-1/2'>
|
||||||
max={127}
|
<div>
|
||||||
onChange={(value) =>
|
<h3 className='text-lg font-semibold text-gray-900'>Cutoff</h3>
|
||||||
dispatch(
|
<Knob
|
||||||
parameterUpdated({
|
value={parameters.cutoff}
|
||||||
id: props.id,
|
maxValue={127}
|
||||||
changes: { cutoff: value },
|
onChange={(value) =>
|
||||||
})
|
dispatch(
|
||||||
)
|
parameterUpdated({
|
||||||
}
|
id: props.id,
|
||||||
/>
|
changes: { cutoff: value },
|
||||||
Resonance
|
})
|
||||||
<Slider
|
)
|
||||||
parameter={parameters.resonance}
|
}
|
||||||
min={0}
|
/>
|
||||||
max={127}
|
</div>
|
||||||
onChange={(value) =>
|
<div className='pt-3'>
|
||||||
dispatch(
|
<h3 className='text-lg font-semibold text-gray-900'>Resonance</h3>
|
||||||
parameterUpdated({
|
<Knob
|
||||||
id: props.id,
|
value={parameters.resonance}
|
||||||
changes: { resonance: value },
|
max={127}
|
||||||
})
|
onChange={(value) =>
|
||||||
)
|
dispatch(
|
||||||
}
|
parameterUpdated({
|
||||||
/>
|
id: props.id,
|
||||||
EG Intensity
|
changes: { resonance: value },
|
||||||
<Slider
|
})
|
||||||
parameter={parameters.egIntensity}
|
)
|
||||||
min={-63}
|
}
|
||||||
max={63}
|
/>
|
||||||
onChange={(value) =>
|
</div>
|
||||||
dispatch(
|
</div>
|
||||||
parameterUpdated({
|
<div className='px-4'></div>
|
||||||
id: props.id,
|
<div className='flex flex-col w-1/2'>
|
||||||
changes: { egIntensity: value },
|
<div>
|
||||||
})
|
<h3 className='text-lg font-semibold text-gray-900'>
|
||||||
)
|
EG Intensity
|
||||||
}
|
</h3>
|
||||||
/>
|
<Knob
|
||||||
Keyboard Tracking
|
value={parameters.egIntensity}
|
||||||
<Slider
|
dual={true}
|
||||||
parameter={parameters.keyboardTrack}
|
maxValue={63}
|
||||||
min={-63}
|
onChange={(value) =>
|
||||||
max={63}
|
dispatch(
|
||||||
onChange={(value) =>
|
parameterUpdated({
|
||||||
dispatch(
|
id: props.id,
|
||||||
parameterUpdated({
|
changes: { egIntensity: value },
|
||||||
id: props.id,
|
})
|
||||||
changes: { keyboardTrack: value },
|
)
|
||||||
})
|
}
|
||||||
)
|
/>
|
||||||
}
|
</div>
|
||||||
/>
|
<div className='pt-3'>
|
||||||
</div>
|
<h3 className='text-lg font-semibold text-gray-900'>
|
||||||
|
Keyboard Tracking
|
||||||
|
</h3>
|
||||||
|
<Knob
|
||||||
|
value={parameters.keyboardTrack}
|
||||||
|
dual={true}
|
||||||
|
max={63}
|
||||||
|
onChange={(value) =>
|
||||||
|
dispatch(
|
||||||
|
parameterUpdated({
|
||||||
|
id: props.id,
|
||||||
|
changes: { keyboardTrack: value },
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,53 +10,55 @@ export default function Mixer(props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Card header={props.name}>
|
<Card header={props.name}>
|
||||||
<div className='-mt-2'>
|
<div className='-mt-3'>
|
||||||
<h3 className='text-lg font-semibold'>Volume 1</h3>
|
<div>
|
||||||
<Slider
|
<h3 className='text-lg font-semibold'>Volume 1</h3>
|
||||||
min={0}
|
<Slider
|
||||||
max={127}
|
min={0}
|
||||||
parameter={parameters.vol1}
|
max={127}
|
||||||
onChange={(value) =>
|
parameter={parameters.vol1}
|
||||||
dispatch(
|
onChange={(value) =>
|
||||||
parameterUpdated({
|
dispatch(
|
||||||
id: props.id,
|
parameterUpdated({
|
||||||
changes: { vol1: value },
|
id: props.id,
|
||||||
})
|
changes: { vol1: value },
|
||||||
)
|
})
|
||||||
}
|
)
|
||||||
/>
|
}
|
||||||
</div>
|
/>
|
||||||
<div className='-mt-2'>
|
</div>
|
||||||
<h3 className='text-lg font-semibold'>Volume 2</h3>
|
<div>
|
||||||
<Slider
|
<h3 className='text-lg font-semibold'>Volume 2</h3>
|
||||||
min={0}
|
<Slider
|
||||||
max={127}
|
min={0}
|
||||||
parameter={parameters.vol2}
|
max={127}
|
||||||
onChange={(value) =>
|
parameter={parameters.vol2}
|
||||||
dispatch(
|
onChange={(value) =>
|
||||||
parameterUpdated({
|
dispatch(
|
||||||
id: props.id,
|
parameterUpdated({
|
||||||
changes: { vol2: value },
|
id: props.id,
|
||||||
})
|
changes: { vol2: value },
|
||||||
)
|
})
|
||||||
}
|
)
|
||||||
/>
|
}
|
||||||
</div>
|
/>
|
||||||
<div>
|
</div>
|
||||||
<h3 className='text-lg font-semibold'>Volume 3</h3>
|
<div>
|
||||||
<Slider
|
<h3 className='text-lg font-semibold'>Volume 3</h3>
|
||||||
min={0}
|
<Slider
|
||||||
max={127}
|
min={0}
|
||||||
parameter={parameters.vol3}
|
max={127}
|
||||||
onChange={(value) =>
|
parameter={parameters.vol3}
|
||||||
dispatch(
|
onChange={(value) =>
|
||||||
parameterUpdated({
|
dispatch(
|
||||||
id: props.id,
|
parameterUpdated({
|
||||||
changes: { vol3: value },
|
id: props.id,
|
||||||
})
|
changes: { vol3: value },
|
||||||
)
|
})
|
||||||
}
|
)
|
||||||
/>
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -11,123 +11,147 @@ export default function Pitch(props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className={props.className} header={props.name}>
|
<Card className={props.className} header={props.name}>
|
||||||
<div className='flex flex-row justify-evenly'>
|
<div className='flex flex-row justify-evenly -mt-4'>
|
||||||
<div className='flex flex-col w-1/2'>
|
<div className='w-1/2'>
|
||||||
<h3 className='text-lg font-semibold text-gray-900'>Voice Assign</h3>
|
<div className='flex items-center justify-between'>
|
||||||
<Select
|
<h3 className='text-lg font-semibold text-gray-900'>
|
||||||
className='select w-2/4'
|
Voice Assign
|
||||||
value={parameters.voiceAssign}
|
</h3>
|
||||||
list={[{ value: 'Mono' }, { value: 'Poly' }, { value: 'Unison' }]}
|
<Select
|
||||||
onChange={(value) =>
|
className='select w-2/4'
|
||||||
dispatch(
|
value={parameters.voiceAssign}
|
||||||
parameterUpdated({
|
list={[{ value: 'Mono' }, { value: 'Poly' }, { value: 'Unison' }]}
|
||||||
id: props.id,
|
onChange={(value) =>
|
||||||
changes: { voiceAssign: value },
|
dispatch(
|
||||||
})
|
parameterUpdated({
|
||||||
)
|
id: props.id,
|
||||||
}
|
changes: { voiceAssign: value },
|
||||||
/>
|
})
|
||||||
<h3 className='text-lg font-semibold text-gray-900'>Trigger Mode</h3>
|
)
|
||||||
<Select
|
}
|
||||||
className='select w-2/4'
|
/>
|
||||||
value={parameters.triggerMode}
|
</div>
|
||||||
list={[{ value: 'Single' }, { value: 'Multiple' }]}
|
<div className='mt-2'>
|
||||||
onChange={(value) =>
|
<h3 className='text-lg font-semibold text-gray-900'>Transpose</h3>
|
||||||
dispatch(
|
<Slider
|
||||||
parameterUpdated({
|
parameter={parameters.transpose}
|
||||||
id: props.id,
|
min={-24}
|
||||||
changes: { triggerMode: value },
|
max={24}
|
||||||
})
|
onChange={(value) =>
|
||||||
)
|
dispatch(
|
||||||
}
|
parameterUpdated({
|
||||||
/>
|
id: props.id,
|
||||||
<h3 className='font-normal mt-1'>Transpose</h3>
|
changes: { transpose: value },
|
||||||
<Slider
|
})
|
||||||
parameter={parameters.transpose}
|
)
|
||||||
min={-24}
|
}
|
||||||
max={24}
|
/>
|
||||||
onChange={(value) =>
|
</div>
|
||||||
dispatch(
|
<div>
|
||||||
parameterUpdated({
|
<h3 className='text-lg font-semibold text-gray-900'>Tune</h3>
|
||||||
id: props.id,
|
<Slider
|
||||||
changes: { transpose: value },
|
parameter={parameters.tune}
|
||||||
})
|
min={-50}
|
||||||
)
|
max={50}
|
||||||
}
|
onChange={(value) =>
|
||||||
/>
|
dispatch(
|
||||||
<h3 className='font-normal'>Tune</h3>
|
parameterUpdated({
|
||||||
<Slider
|
id: props.id,
|
||||||
parameter={parameters.tune}
|
changes: { tune: value },
|
||||||
min={-50}
|
})
|
||||||
max={50}
|
)
|
||||||
onChange={(value) =>
|
}
|
||||||
dispatch(
|
/>
|
||||||
parameterUpdated({
|
</div>
|
||||||
id: props.id,
|
<div>
|
||||||
changes: { tune: value },
|
<h3 className='text-lg font-semibold text-gray-900'>
|
||||||
})
|
Unison Detune
|
||||||
)
|
</h3>
|
||||||
}
|
<Slider
|
||||||
/>
|
parameter={parameters.unisonDetune}
|
||||||
|
min={0}
|
||||||
|
max={99}
|
||||||
|
onChange={(value) =>
|
||||||
|
dispatch(
|
||||||
|
parameterUpdated({
|
||||||
|
id: props.id,
|
||||||
|
changes: { unisonDetune: value },
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='w-10'></div>
|
<div className='px-4'></div>
|
||||||
<div className='flex flex-col justify-evenly w-1/2'>
|
<div className='w-1/2'>
|
||||||
<h3 className='font-normal mt-1'>Unison Detune</h3>
|
<div className='flex items-center justify-between'>
|
||||||
<Slider
|
<h3 className='text-lg font-semibold text-gray-900'>
|
||||||
parameter={parameters.unisonDetune}
|
Trigger Mode
|
||||||
min={0}
|
</h3>
|
||||||
max={99}
|
<Select
|
||||||
onChange={(value) =>
|
className='select w-2/4'
|
||||||
dispatch(
|
value={parameters.triggerMode}
|
||||||
parameterUpdated({
|
list={[{ value: 'Single' }, { value: 'Multiple' }]}
|
||||||
id: props.id,
|
onChange={(value) =>
|
||||||
changes: { unisonDetune: value },
|
dispatch(
|
||||||
})
|
parameterUpdated({
|
||||||
)
|
id: props.id,
|
||||||
}
|
changes: { triggerMode: value },
|
||||||
/>
|
})
|
||||||
<h3 className='font-normal mt-1'>Portamento</h3>
|
)
|
||||||
<Slider
|
}
|
||||||
parameter={parameters.portamento}
|
/>
|
||||||
min={0}
|
</div>
|
||||||
max={127}
|
<div className='mt-2'>
|
||||||
onChange={(value) =>
|
<h3 className='text-lg font-semibold text-gray-900'>Portamento</h3>
|
||||||
dispatch(
|
<Slider
|
||||||
parameterUpdated({
|
parameter={parameters.portamento}
|
||||||
id: props.id,
|
min={0}
|
||||||
changes: { portamento: value },
|
max={127}
|
||||||
})
|
onChange={(value) =>
|
||||||
)
|
dispatch(
|
||||||
}
|
parameterUpdated({
|
||||||
/>
|
id: props.id,
|
||||||
<h3 className='font-normal mt-1'>Bend Range</h3>
|
changes: { portamento: value },
|
||||||
<Slider
|
})
|
||||||
parameter={parameters.bendRange}
|
)
|
||||||
min={-12}
|
}
|
||||||
max={12}
|
/>
|
||||||
onChange={(value) =>
|
</div>
|
||||||
dispatch(
|
<div>
|
||||||
parameterUpdated({
|
<h3 className='text-lg font-semibold text-gray-900'>Bend Range</h3>
|
||||||
id: props.id,
|
<Slider
|
||||||
changes: { bendRange: value },
|
parameter={parameters.bendRange}
|
||||||
})
|
min={-12}
|
||||||
)
|
max={12}
|
||||||
}
|
onChange={(value) =>
|
||||||
/>
|
dispatch(
|
||||||
<h3 className='font-normal mt-1'>Vibrato Intensity</h3>
|
parameterUpdated({
|
||||||
<Slider
|
id: props.id,
|
||||||
parameter={parameters.vibratoIntensity}
|
changes: { bendRange: value },
|
||||||
min={-63}
|
})
|
||||||
max={63}
|
)
|
||||||
onChange={(value) =>
|
}
|
||||||
dispatch(
|
/>
|
||||||
parameterUpdated({
|
</div>
|
||||||
id: props.id,
|
<div>
|
||||||
changes: { vibratoIntensity: value },
|
<h3 className='text-lg font-semibold text-gray-900'>
|
||||||
})
|
Vibrato Intensity
|
||||||
)
|
</h3>
|
||||||
}
|
<Slider
|
||||||
/>
|
parameter={parameters.vibratoIntensity}
|
||||||
|
min={-63}
|
||||||
|
max={63}
|
||||||
|
onChange={(value) =>
|
||||||
|
dispatch(
|
||||||
|
parameterUpdated({
|
||||||
|
id: props.id,
|
||||||
|
changes: { vibratoIntensity: value },
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import React from 'react';
|
|||||||
import Card from '../card';
|
import Card from '../card';
|
||||||
import { useSelector, useDispatch } from 'react-redux';
|
import { useSelector, useDispatch } from 'react-redux';
|
||||||
import { selectById, parameterUpdated } from './parameterSlice.js';
|
import { selectById, parameterUpdated } from './parameterSlice.js';
|
||||||
import { Select } from '../../utils/components';
|
import { Select, Knob } from '../../utils/components';
|
||||||
import { Slider, Checkbox } from '../../helpers/Helpers';
|
import { Slider, Checkbox } from '../../helpers/Helpers';
|
||||||
|
|
||||||
export default function VCO(props) {
|
export default function VCO(props) {
|
||||||
@@ -11,10 +11,10 @@ export default function VCO(props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className='col-span-2' header={props.name}>
|
<Card className='col-span-2' header={props.name}>
|
||||||
<div className='flex items-center justify-between'>
|
<div className='-mt-2 flex items-center justify-between'>
|
||||||
<h3 className='text-lg font-semibold text-gray-900'>Waveform</h3>
|
<h3 className='text-lg font-semibold text-gray-900'>Waveform</h3>
|
||||||
<Select
|
<Select
|
||||||
className='select w-3/5'
|
className='select w-4/6'
|
||||||
value={parameters.waveform}
|
value={parameters.waveform}
|
||||||
list={props.waveforms}
|
list={props.waveforms}
|
||||||
onChange={(value) =>
|
onChange={(value) =>
|
||||||
@@ -27,10 +27,10 @@ export default function VCO(props) {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex items-center justify-between'>
|
<div className='-mt-4 flex items-center justify-between'>
|
||||||
<h3 className='text-lg font-semibold text-gray-900'>{`${props.dwgsOrModTypeName}`}</h3>
|
<h3 className='text-lg font-semibold text-gray-900'>{`${props.dwgsOrModTypeName}`}</h3>
|
||||||
<Select
|
<Select
|
||||||
className='select w-3/5'
|
className='select w-4/6'
|
||||||
value={parameters.dwgsOrModType}
|
value={parameters.dwgsOrModType}
|
||||||
list={props.dwgsOrModType}
|
list={props.dwgsOrModType}
|
||||||
onChange={(value) =>
|
onChange={(value) =>
|
||||||
@@ -43,37 +43,37 @@ export default function VCO(props) {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex items-center justify-center'>
|
<div className='mb-2 flex flex-row'>
|
||||||
<h3 className='text-lg font-semibold text-gray-900'>Waveform Mod</h3>
|
<div className='w-1/2'>
|
||||||
<Slider
|
<h3 className='text-lg font-semibold text-gray-900'>Waveform Mod</h3>
|
||||||
min={0}
|
<Knob
|
||||||
max={127}
|
maxValue={127}
|
||||||
parameter={parameters.waveMod}
|
value={parameters.waveMod}
|
||||||
onChange={(value) =>
|
onChange={(value) =>
|
||||||
dispatch(
|
dispatch(
|
||||||
parameterUpdated({
|
parameterUpdated({
|
||||||
id: props.id,
|
id: props.id,
|
||||||
changes: { waveMod: value },
|
changes: { waveMod: value },
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex items-center justify-center'>
|
<div className='w-1/2'>
|
||||||
<h3 className='text-lg font-semibold text-gray-900'>LFO Mod</h3>
|
<h3 className='text-lg font-semibold text-gray-900'>LFO Mod</h3>
|
||||||
<Slider
|
<Knob
|
||||||
min={0}
|
maxValue={127}
|
||||||
max={127}
|
value={parameters.lfoMod}
|
||||||
parameter={parameters.lfoMod}
|
onChange={(value) =>
|
||||||
onChange={(value) =>
|
dispatch(
|
||||||
dispatch(
|
parameterUpdated({
|
||||||
parameterUpdated({
|
id: props.id,
|
||||||
id: props.id,
|
changes: { lfoMod: value },
|
||||||
changes: { lfoMod: value },
|
})
|
||||||
})
|
)
|
||||||
)
|
}
|
||||||
}
|
/>
|
||||||
/>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
// TODO -- CUSTOM DROPDOWN STYLING
|
// TODO -- CUSTOM DROPDOWN STYLING
|
||||||
|
|
||||||
|
import { useMemo, useCallback, useEffect, useState } from 'react';
|
||||||
|
|
||||||
const Select = (props) => {
|
const Select = (props) => {
|
||||||
return (
|
return (
|
||||||
<select
|
<select
|
||||||
@@ -7,10 +9,222 @@ const Select = (props) => {
|
|||||||
onChange={(e) => props.onChange(e.target.value)}
|
onChange={(e) => props.onChange(e.target.value)}
|
||||||
value={props.value}>
|
value={props.value}>
|
||||||
{props.list.map((element) => (
|
{props.list.map((element) => (
|
||||||
<option value={element.value}>{element.value}</option>
|
<option key={element.value} value={element.value}>
|
||||||
|
{element.value}
|
||||||
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export { Select };
|
const RAD = Math.PI / 180;
|
||||||
|
const START_ANGLE = Math.PI * 1.5 - (RAD * 45) / 2;
|
||||||
|
const END_ANGLE = Math.PI * -0.5 + (RAD * 45) / 2;
|
||||||
|
|
||||||
|
function getPointPair(r, angle, distance) {
|
||||||
|
return [r + distance * Math.cos(angle), r - distance * Math.sin(angle)];
|
||||||
|
}
|
||||||
|
|
||||||
|
function DialMask({ id, size, startAngle, endAngle }) {
|
||||||
|
const r = size / 2;
|
||||||
|
|
||||||
|
const intermediatePoints = [0, 0.25, 0.5, 0.75, 1]
|
||||||
|
.map((fraction) =>
|
||||||
|
getPointPair(
|
||||||
|
r,
|
||||||
|
startAngle - (startAngle - endAngle) * fraction,
|
||||||
|
r * 4
|
||||||
|
).join(',')
|
||||||
|
)
|
||||||
|
.join(' ');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<mask id={id}>
|
||||||
|
<polygon
|
||||||
|
style={{ fill: '#fff' }}
|
||||||
|
points={`${r},${r} ${intermediatePoints} ${r},${r}`}
|
||||||
|
/>
|
||||||
|
</mask>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getKnobValue(dragStart, currentY, bipolar) {
|
||||||
|
const { y, value } = dragStart;
|
||||||
|
const deltaY = y - currentY;
|
||||||
|
const slope = 0.01;
|
||||||
|
let newValue = value + deltaY * slope;
|
||||||
|
if (bipolar) {
|
||||||
|
const zeroCrossingResistance = 0.3;
|
||||||
|
if (value >= 0 && newValue < 0) {
|
||||||
|
newValue += Math.min(zeroCrossingResistance, -newValue);
|
||||||
|
} else if (value < 0 && newValue > 0) {
|
||||||
|
newValue -= Math.min(zeroCrossingResistance, newValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Clamp
|
||||||
|
return Math.max(bipolar ? -1 : 0, Math.min(1, newValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
let knob_id = 0;
|
||||||
|
|
||||||
|
function Knob({
|
||||||
|
maxValue = 127,
|
||||||
|
dual = false,
|
||||||
|
size = 42,
|
||||||
|
value = 0,
|
||||||
|
fillWidth = 4,
|
||||||
|
onChange,
|
||||||
|
}) {
|
||||||
|
value = value / maxValue;
|
||||||
|
const uid = useMemo(() => knob_id++, []);
|
||||||
|
const radius = size / 2;
|
||||||
|
const dial_zero = dual ? Math.PI / 2 : START_ANGLE;
|
||||||
|
const dial_range = dual
|
||||||
|
? (START_ANGLE - END_ANGLE) / 2
|
||||||
|
: START_ANGLE - END_ANGLE;
|
||||||
|
const valueAngle = dual
|
||||||
|
? Math.PI / 2 - value * dial_range
|
||||||
|
: START_ANGLE - value * dial_range;
|
||||||
|
|
||||||
|
const [lineX1, lineY1] = getPointPair(
|
||||||
|
size / 2,
|
||||||
|
valueAngle,
|
||||||
|
radius - fillWidth + 2
|
||||||
|
);
|
||||||
|
|
||||||
|
const [dragStart, setDragStart] = useState(null);
|
||||||
|
const isDragging = Boolean(dragStart);
|
||||||
|
|
||||||
|
const onMouseMove = useCallback(
|
||||||
|
(e) => {
|
||||||
|
if (!dragStart) return;
|
||||||
|
|
||||||
|
const newValue = getKnobValue(dragStart, e.clientY, dual);
|
||||||
|
onChange(Math.round(newValue * maxValue));
|
||||||
|
},
|
||||||
|
[dragStart, onChange, dual, maxValue]
|
||||||
|
);
|
||||||
|
const onMouseDown = useCallback(
|
||||||
|
(e) => {
|
||||||
|
if (e.nativeEvent.button !== 0) return;
|
||||||
|
e.stopPropagation();
|
||||||
|
e.nativeEvent.stopImmediatePropagation();
|
||||||
|
setDragStart({ value, y: e.nativeEvent.clientY });
|
||||||
|
window.addEventListener('mousemove', onMouseMove);
|
||||||
|
},
|
||||||
|
[setDragStart, onMouseMove, value]
|
||||||
|
);
|
||||||
|
const onMouseUp = useCallback(() => {
|
||||||
|
setDragStart(null);
|
||||||
|
}, [setDragStart]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isDragging) {
|
||||||
|
window.removeEventListener('mousemove', onMouseMove);
|
||||||
|
}
|
||||||
|
window.addEventListener('mousemove', onMouseMove);
|
||||||
|
return () => window.removeEventListener('mousemove', onMouseMove);
|
||||||
|
}, [isDragging, onMouseMove]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
window.addEventListener('mouseup', onMouseUp);
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('mouseup', onMouseUp);
|
||||||
|
};
|
||||||
|
}, [onMouseUp]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!onMouseMove) return;
|
||||||
|
return () => window.removeEventListener('mousemove', onMouseMove);
|
||||||
|
}, [onMouseMove]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: 'relative',
|
||||||
|
cursor: 'pointer',
|
||||||
|
height: size,
|
||||||
|
width: size,
|
||||||
|
overflow: 'hidden',
|
||||||
|
flexShrink: 0,
|
||||||
|
}}
|
||||||
|
onMouseDown={onMouseDown}>
|
||||||
|
<svg className='absolute top-0 left-0' width={size} height={size}>
|
||||||
|
<defs>
|
||||||
|
<DialMask
|
||||||
|
size={size}
|
||||||
|
startAngle={START_ANGLE}
|
||||||
|
endAngle={END_ANGLE}
|
||||||
|
id={`value-track-mask-${uid}`}
|
||||||
|
/>
|
||||||
|
<DialMask
|
||||||
|
size={size}
|
||||||
|
startAngle={dial_zero}
|
||||||
|
endAngle={valueAngle}
|
||||||
|
id={`value-mask-${uid}`}
|
||||||
|
/>
|
||||||
|
</defs>
|
||||||
|
{/* Track outer */}
|
||||||
|
<circle
|
||||||
|
cx={size / 2}
|
||||||
|
cy={size / 2}
|
||||||
|
r={size / 2}
|
||||||
|
className='opacity-50'
|
||||||
|
style={{ mask: `url(#value-track-mask-${uid})`, fill: 'black' }}
|
||||||
|
/>
|
||||||
|
{/* Value fill */}
|
||||||
|
<circle
|
||||||
|
cx={size / 2}
|
||||||
|
cy={size / 2}
|
||||||
|
r={size / 2}
|
||||||
|
className='fill-current text-indigo-500'
|
||||||
|
style={{ mask: `url(#value-mask-${uid})` }}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<svg
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
}}>
|
||||||
|
{/* Knob */}
|
||||||
|
<circle
|
||||||
|
cx={radius}
|
||||||
|
cy={radius}
|
||||||
|
r={radius - fillWidth}
|
||||||
|
style={{
|
||||||
|
fill: 'white',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{/* Highlight */}
|
||||||
|
<circle
|
||||||
|
cx={radius}
|
||||||
|
cy={radius}
|
||||||
|
r={radius - fillWidth}
|
||||||
|
style={{
|
||||||
|
fill: 'transparent',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<svg
|
||||||
|
width={size}
|
||||||
|
height={size}
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
}}>
|
||||||
|
{
|
||||||
|
<line
|
||||||
|
x1={lineX1}
|
||||||
|
y1={lineY1}
|
||||||
|
x2={size / 2}
|
||||||
|
y2={size / 2}
|
||||||
|
strokeLinecap='round'
|
||||||
|
className='stroke-current text-indigo-900 stroke-2'></line>
|
||||||
|
}
|
||||||
|
</svg>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export { Select, Knob };
|
||||||
|
|||||||
+7
-1
@@ -18,6 +18,12 @@ code {
|
|||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
|
@layer base {
|
||||||
|
h3 {
|
||||||
|
@apply pointer-events-none select-none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@layer components {
|
@layer components {
|
||||||
.btn {
|
.btn {
|
||||||
@apply mx-4 my-1 bg-gray-100 rounded-md w-32 h-10 border-transparent hover:bg-gray-200;
|
@apply mx-4 my-1 bg-gray-100 rounded-md w-32 h-10 border-transparent hover:bg-gray-200;
|
||||||
@@ -26,6 +32,6 @@ code {
|
|||||||
@apply opacity-60 hover:bg-gray-100 cursor-default;
|
@apply opacity-60 hover:bg-gray-100 cursor-default;
|
||||||
}
|
}
|
||||||
.select {
|
.select {
|
||||||
@apply block rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50;
|
@apply select-none block rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user