mirror of
https://github.com/jeffvli/feishin.git
synced 2026-07-11 13:10:02 +02:00
@@ -1184,7 +1184,9 @@
|
|||||||
"queryBuilderCustomFields_inputLabel": "Label",
|
"queryBuilderCustomFields_inputLabel": "Label",
|
||||||
"queryBuilderCustomFields_inputTag": "Tag",
|
"queryBuilderCustomFields_inputTag": "Tag",
|
||||||
"queryBuilderCustomFields": "Custom fields",
|
"queryBuilderCustomFields": "Custom fields",
|
||||||
"queryBuilderCustomFields_description": "Add custom fields to use in query builders"
|
"queryBuilderCustomFields_description": "Add custom fields to use in query builders",
|
||||||
|
"microtonalPitchControls": "Microtonal pitch controls",
|
||||||
|
"microtonalPitchControls_description": "Add more pitch controls to go between semi-tones"
|
||||||
},
|
},
|
||||||
"table": {
|
"table": {
|
||||||
"column": {
|
"column": {
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
} from '/@/renderer/store';
|
} from '/@/renderer/store';
|
||||||
import {
|
import {
|
||||||
useCombinedLyricsAndVisualizer,
|
useCombinedLyricsAndVisualizer,
|
||||||
|
useMicrotonalPitchControls,
|
||||||
usePlaybackSettings,
|
usePlaybackSettings,
|
||||||
useSettingsStore,
|
useSettingsStore,
|
||||||
useSettingsStoreActions,
|
useSettingsStoreActions,
|
||||||
@@ -21,11 +22,14 @@ import {
|
|||||||
useShowVisualizerInSidebar,
|
useShowVisualizerInSidebar,
|
||||||
} from '/@/renderer/store/settings.store';
|
} from '/@/renderer/store/settings.store';
|
||||||
import { ActionIcon } from '/@/shared/components/action-icon/action-icon';
|
import { ActionIcon } from '/@/shared/components/action-icon/action-icon';
|
||||||
|
import { Button } from '/@/shared/components/button/button';
|
||||||
|
import { Group } from '/@/shared/components/group/group';
|
||||||
import { Popover } from '/@/shared/components/popover/popover';
|
import { Popover } from '/@/shared/components/popover/popover';
|
||||||
import { SegmentedControl } from '/@/shared/components/segmented-control/segmented-control';
|
import { SegmentedControl } from '/@/shared/components/segmented-control/segmented-control';
|
||||||
import { Select } from '/@/shared/components/select/select';
|
import { Select } from '/@/shared/components/select/select';
|
||||||
import { Slider } from '/@/shared/components/slider/slider';
|
import { Slider } from '/@/shared/components/slider/slider';
|
||||||
import { Switch } from '/@/shared/components/switch/switch';
|
import { Switch } from '/@/shared/components/switch/switch';
|
||||||
|
import { Text } from '/@/shared/components/text/text';
|
||||||
import { CrossfadeStyle, PlayerStatus, PlayerStyle, PlayerType } from '/@/shared/types/types';
|
import { CrossfadeStyle, PlayerStatus, PlayerStyle, PlayerType } from '/@/shared/types/types';
|
||||||
|
|
||||||
const ipc = isElectron() ? window.api.ipc : null;
|
const ipc = isElectron() ? window.api.ipc : null;
|
||||||
@@ -93,6 +97,11 @@ export const PlayerConfig = () => {
|
|||||||
id: 'playbackSpeed',
|
id: 'playbackSpeed',
|
||||||
label: t('player.playbackSpeed'),
|
label: t('player.playbackSpeed'),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
component: !preservePitch ? <PitchControls /> : <></>,
|
||||||
|
id: 'pitchControls',
|
||||||
|
label: '',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
component: (
|
component: (
|
||||||
<Switch
|
<Switch
|
||||||
@@ -364,16 +373,15 @@ export const PlaybackSpeedSlider = () => {
|
|||||||
() => (value: number) => {
|
() => (value: number) => {
|
||||||
const bpmValue = Number(bpm);
|
const bpmValue = Number(bpm);
|
||||||
if (bpmValue > 0) {
|
if (bpmValue > 0) {
|
||||||
return `${value} x / ${(bpmValue * value).toFixed(1)} BPM`;
|
return `${value.toFixed(2)} x / ${(bpmValue * value).toFixed(1)} BPM`;
|
||||||
}
|
}
|
||||||
return `${value} x`;
|
return `${value.toFixed(2)} x`;
|
||||||
},
|
},
|
||||||
[bpm],
|
[bpm],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Slider
|
<Slider
|
||||||
defaultValue={speed}
|
|
||||||
label={formatPlaybackSpeedSliderLabel}
|
label={formatPlaybackSpeedSliderLabel}
|
||||||
marks={[
|
marks={[
|
||||||
{ label: '0.5', value: 0.5 },
|
{ label: '0.5', value: 0.5 },
|
||||||
@@ -386,14 +394,81 @@ export const PlaybackSpeedSlider = () => {
|
|||||||
]}
|
]}
|
||||||
max={2}
|
max={2}
|
||||||
min={0.5}
|
min={0.5}
|
||||||
onChangeEnd={setSpeed}
|
onChange={setSpeed}
|
||||||
onDoubleClick={() => setSpeed(1)}
|
onDoubleClick={() => setSpeed(1)}
|
||||||
step={0.01}
|
step={0.01}
|
||||||
styles={{
|
styles={{
|
||||||
markLabel: {},
|
markLabel: {},
|
||||||
root: {},
|
root: {},
|
||||||
}}
|
}}
|
||||||
|
value={speed}
|
||||||
w="100%"
|
w="100%"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const PitchControls = () => {
|
||||||
|
const microtonal = useMicrotonalPitchControls();
|
||||||
|
const speed = usePlayerSpeed();
|
||||||
|
const { setSpeed } = usePlayerActions();
|
||||||
|
|
||||||
|
const speedToPitch = (speed: number) => {
|
||||||
|
return 12 * Math.log2(speed);
|
||||||
|
};
|
||||||
|
|
||||||
|
const pitchToSpeed = (pitch: number) => {
|
||||||
|
return 2 ** (pitch / 12);
|
||||||
|
};
|
||||||
|
|
||||||
|
const adjustMusicalSpeed = (adjustment: number) => {
|
||||||
|
const curPitch = speedToPitch(speed);
|
||||||
|
const newSpeed = pitchToSpeed(curPitch + adjustment);
|
||||||
|
setSpeed(newSpeed);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Group gap={microtonal ? 'xs' : 'md'} my="sm" w="100%" wrap="nowrap">
|
||||||
|
<Button
|
||||||
|
aria-label="-1 semitone"
|
||||||
|
fullWidth
|
||||||
|
onClick={() => adjustMusicalSpeed(-1)}
|
||||||
|
size="compact-xs"
|
||||||
|
>
|
||||||
|
-1st
|
||||||
|
</Button>
|
||||||
|
{microtonal && (
|
||||||
|
<Button
|
||||||
|
aria-label="-10 cents"
|
||||||
|
fullWidth
|
||||||
|
onClick={() => adjustMusicalSpeed(-0.1)}
|
||||||
|
size="compact-xs"
|
||||||
|
>
|
||||||
|
-10ct
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<Text size="xs" style={{ fontFamily: 'monospace' }} ta="center" w="60px">
|
||||||
|
{speed.toFixed(2)}% {speedToPitch(speed) > 0 && '+'}
|
||||||
|
{speedToPitch(speed) == 0 && '±'}
|
||||||
|
{speedToPitch(speed).toFixed(2)}st
|
||||||
|
</Text>
|
||||||
|
{microtonal && (
|
||||||
|
<Button
|
||||||
|
aria-label="+10 cents"
|
||||||
|
fullWidth
|
||||||
|
onClick={() => adjustMusicalSpeed(0.1)}
|
||||||
|
size="compact-xs"
|
||||||
|
>
|
||||||
|
+10ct
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<Button
|
||||||
|
aria-label="+1 semitone"
|
||||||
|
fullWidth
|
||||||
|
onClick={() => adjustMusicalSpeed(1)}
|
||||||
|
size="compact-xs"
|
||||||
|
>
|
||||||
|
+1st
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
@@ -501,6 +501,26 @@ export const ControlSettings = memo(() => {
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
: []),
|
: []),
|
||||||
|
{
|
||||||
|
control: (
|
||||||
|
<Switch
|
||||||
|
defaultChecked={settings.microtonalPitchControls}
|
||||||
|
onChange={(e) =>
|
||||||
|
setSettings({
|
||||||
|
general: {
|
||||||
|
...settings,
|
||||||
|
microtonalPitchControls: e.currentTarget.checked,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
description: t('setting.microtonalPitchControls', {
|
||||||
|
context: 'description',
|
||||||
|
}),
|
||||||
|
isHidden: false,
|
||||||
|
title: t('setting.microtonalPitchControls'),
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
return <SettingsSection options={controlOptions} title={t('page.setting.controls')} />;
|
return <SettingsSection options={controlOptions} title={t('page.setting.controls')} />;
|
||||||
|
|||||||
@@ -505,6 +505,7 @@ export const GeneralSettingsSchema = z.object({
|
|||||||
lastFM: z.boolean(),
|
lastFM: z.boolean(),
|
||||||
lastfmApiKey: z.string(),
|
lastfmApiKey: z.string(),
|
||||||
listenBrainz: z.boolean(),
|
listenBrainz: z.boolean(),
|
||||||
|
microtonalPitchControls: z.boolean(),
|
||||||
musicBrainz: z.boolean(),
|
musicBrainz: z.boolean(),
|
||||||
nativeAspectRatio: z.boolean(),
|
nativeAspectRatio: z.boolean(),
|
||||||
nativeSpotify: z.boolean(),
|
nativeSpotify: z.boolean(),
|
||||||
@@ -1201,6 +1202,7 @@ const initialState: SettingsState = {
|
|||||||
lastFM: true,
|
lastFM: true,
|
||||||
lastfmApiKey: '',
|
lastfmApiKey: '',
|
||||||
listenBrainz: true,
|
listenBrainz: true,
|
||||||
|
microtonalPitchControls: false,
|
||||||
musicBrainz: true,
|
musicBrainz: true,
|
||||||
nativeAspectRatio: false,
|
nativeAspectRatio: false,
|
||||||
nativeSpotify: false,
|
nativeSpotify: false,
|
||||||
@@ -2830,3 +2832,6 @@ export const useButterchurnSettings = () => {
|
|||||||
};
|
};
|
||||||
}, shallow);
|
}, shallow);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useMicrotonalPitchControls = () =>
|
||||||
|
useSettingsStore((state) => state.general.microtonalPitchControls, shallow);
|
||||||
|
|||||||
Reference in New Issue
Block a user