diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json
index 2a484a46c..cf5a4b9f1 100644
--- a/src/i18n/locales/en.json
+++ b/src/i18n/locales/en.json
@@ -1184,7 +1184,9 @@
"queryBuilderCustomFields_inputLabel": "Label",
"queryBuilderCustomFields_inputTag": "Tag",
"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": {
"column": {
diff --git a/src/renderer/features/player/components/player-config.tsx b/src/renderer/features/player/components/player-config.tsx
index af1cfc9b1..f78bc180a 100644
--- a/src/renderer/features/player/components/player-config.tsx
+++ b/src/renderer/features/player/components/player-config.tsx
@@ -14,6 +14,7 @@ import {
} from '/@/renderer/store';
import {
useCombinedLyricsAndVisualizer,
+ useMicrotonalPitchControls,
usePlaybackSettings,
useSettingsStore,
useSettingsStoreActions,
@@ -21,11 +22,14 @@ import {
useShowVisualizerInSidebar,
} from '/@/renderer/store/settings.store';
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 { SegmentedControl } from '/@/shared/components/segmented-control/segmented-control';
import { Select } from '/@/shared/components/select/select';
import { Slider } from '/@/shared/components/slider/slider';
import { Switch } from '/@/shared/components/switch/switch';
+import { Text } from '/@/shared/components/text/text';
import { CrossfadeStyle, PlayerStatus, PlayerStyle, PlayerType } from '/@/shared/types/types';
const ipc = isElectron() ? window.api.ipc : null;
@@ -93,6 +97,11 @@ export const PlayerConfig = () => {
id: 'playbackSpeed',
label: t('player.playbackSpeed'),
},
+ {
+ component: !preservePitch ? : <>>,
+ id: 'pitchControls',
+ label: '',
+ },
{
component: (
{
() => (value: number) => {
const bpmValue = Number(bpm);
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],
);
return (
{
]}
max={2}
min={0.5}
- onChangeEnd={setSpeed}
+ onChange={setSpeed}
onDoubleClick={() => setSpeed(1)}
step={0.01}
styles={{
markLabel: {},
root: {},
}}
+ value={speed}
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 (
+
+
+ {microtonal && (
+
+ )}
+
+ {speed.toFixed(2)}% {speedToPitch(speed) > 0 && '+'}
+ {speedToPitch(speed) == 0 && '±'}
+ {speedToPitch(speed).toFixed(2)}st
+
+ {microtonal && (
+
+ )}
+
+
+ );
+};
diff --git a/src/renderer/features/settings/components/general/control-settings.tsx b/src/renderer/features/settings/components/general/control-settings.tsx
index 66c2c2628..2989394ae 100644
--- a/src/renderer/features/settings/components/general/control-settings.tsx
+++ b/src/renderer/features/settings/components/general/control-settings.tsx
@@ -501,6 +501,26 @@ export const ControlSettings = memo(() => {
},
]
: []),
+ {
+ control: (
+
+ setSettings({
+ general: {
+ ...settings,
+ microtonalPitchControls: e.currentTarget.checked,
+ },
+ })
+ }
+ />
+ ),
+ description: t('setting.microtonalPitchControls', {
+ context: 'description',
+ }),
+ isHidden: false,
+ title: t('setting.microtonalPitchControls'),
+ },
];
return ;
diff --git a/src/renderer/store/settings.store.ts b/src/renderer/store/settings.store.ts
index 37b204956..78825e032 100644
--- a/src/renderer/store/settings.store.ts
+++ b/src/renderer/store/settings.store.ts
@@ -505,6 +505,7 @@ export const GeneralSettingsSchema = z.object({
lastFM: z.boolean(),
lastfmApiKey: z.string(),
listenBrainz: z.boolean(),
+ microtonalPitchControls: z.boolean(),
musicBrainz: z.boolean(),
nativeAspectRatio: z.boolean(),
nativeSpotify: z.boolean(),
@@ -1201,6 +1202,7 @@ const initialState: SettingsState = {
lastFM: true,
lastfmApiKey: '',
listenBrainz: true,
+ microtonalPitchControls: false,
musicBrainz: true,
nativeAspectRatio: false,
nativeSpotify: false,
@@ -2830,3 +2832,6 @@ export const useButterchurnSettings = () => {
};
}, shallow);
};
+
+export const useMicrotonalPitchControls = () =>
+ useSettingsStore((state) => state.general.microtonalPitchControls, shallow);