mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-22 15:25:46 +00:00
Merge pull request #316 from Psychotoxical/fix/seekbar-truewave-pseudowave-split
fix(seekbar): split waveform style into truewave + pseudowave
This commit is contained in:
@@ -750,7 +750,8 @@ export function drawSeekbar(
|
|||||||
) {
|
) {
|
||||||
const anim = animState ?? makeAnimState();
|
const anim = animState ?? makeAnimState();
|
||||||
switch (style) {
|
switch (style) {
|
||||||
case 'waveform': drawWaveform(canvas, heights, progress, buffered); break;
|
case 'truewave': drawWaveform(canvas, heights, progress, buffered); break;
|
||||||
|
case 'pseudowave': drawWaveform(canvas, heights, progress, buffered); break;
|
||||||
case 'linedot': drawLineDot(canvas, progress, buffered); break;
|
case 'linedot': drawLineDot(canvas, progress, buffered); break;
|
||||||
case 'bar': drawBar(canvas, progress, buffered); break;
|
case 'bar': drawBar(canvas, progress, buffered); break;
|
||||||
case 'thick': drawThick(canvas, progress, buffered); break;
|
case 'thick': drawThick(canvas, progress, buffered); break;
|
||||||
@@ -782,7 +783,7 @@ export function SeekbarPreview({
|
|||||||
const canvas = canvasRef.current;
|
const canvas = canvasRef.current;
|
||||||
if (!canvas) return;
|
if (!canvas) return;
|
||||||
let heights: Float32Array | null = null;
|
let heights: Float32Array | null = null;
|
||||||
if (style === 'waveform') {
|
if (style === 'truewave' || style === 'pseudowave') {
|
||||||
heights = makeHeights('seekbar-preview-demo');
|
heights = makeHeights('seekbar-preview-demo');
|
||||||
}
|
}
|
||||||
const animState = makeAnimState();
|
const animState = makeAnimState();
|
||||||
@@ -896,6 +897,17 @@ export default function WaveformSeek({ trackId }: Props) {
|
|||||||
heightsRef.current = null;
|
heightsRef.current = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// Pseudowave is the deterministic per-track-ID variant — no analysis needed,
|
||||||
|
// no morph animation, no flat-fallback. It just sits there looking like a
|
||||||
|
// waveform.
|
||||||
|
if (seekbarStyle === 'pseudowave') {
|
||||||
|
heightsRef.current = makeHeights(trackId);
|
||||||
|
const canvas = canvasRef.current;
|
||||||
|
if (canvas && !ANIMATED_STYLES.has(seekbarStyle)) {
|
||||||
|
drawSeekbar(canvas, seekbarStyle, heightsRef.current, progressRef.current, bufferedRef.current, animStateRef.current);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (waveformBins && waveformBins.length > 0) {
|
if (waveformBins && waveformBins.length > 0) {
|
||||||
const h = binsToHeights(waveformBins);
|
const h = binsToHeights(waveformBins);
|
||||||
const prev = heightsRef.current;
|
const prev = heightsRef.current;
|
||||||
@@ -959,7 +971,7 @@ export default function WaveformSeek({ trackId }: Props) {
|
|||||||
}
|
}
|
||||||
// No analysis bins yet: render 500 flat bars immediately.
|
// No analysis bins yet: render 500 flat bars immediately.
|
||||||
heightsRef.current = makeFlatWaveHeights();
|
heightsRef.current = makeFlatWaveHeights();
|
||||||
}, [trackId, waveformBins]);
|
}, [trackId, waveformBins, seekbarStyle]);
|
||||||
|
|
||||||
// Imperative subscription — no React re-renders from progress changes.
|
// Imperative subscription — no React re-renders from progress changes.
|
||||||
// Static styles draw here; animated styles only update refs.
|
// Static styles draw here; animated styles only update refs.
|
||||||
|
|||||||
+2
-1
@@ -888,7 +888,8 @@ export const deTranslation = {
|
|||||||
sidebarLyricsStyleAppleDesc: 'Aktive Zeile oben mit sanfter Animation.',
|
sidebarLyricsStyleAppleDesc: 'Aktive Zeile oben mit sanfter Animation.',
|
||||||
seekbarStyle: 'Seekbar-Stil',
|
seekbarStyle: 'Seekbar-Stil',
|
||||||
seekbarStyleDesc: 'Aussehen der Wiedergabe-Seekbar auswählen',
|
seekbarStyleDesc: 'Aussehen der Wiedergabe-Seekbar auswählen',
|
||||||
seekbarWaveform: 'Wellenform',
|
seekbarTruewave: 'Echte Wellenform',
|
||||||
|
seekbarPseudowave: 'Pseudo-Wellenform',
|
||||||
seekbarLinedot: 'Linie & Punkt',
|
seekbarLinedot: 'Linie & Punkt',
|
||||||
seekbarBar: 'Balken',
|
seekbarBar: 'Balken',
|
||||||
seekbarThick: 'Dicker Balken',
|
seekbarThick: 'Dicker Balken',
|
||||||
|
|||||||
+2
-1
@@ -894,7 +894,8 @@ export const enTranslation = {
|
|||||||
fsPortraitDim: 'Photo dimming',
|
fsPortraitDim: 'Photo dimming',
|
||||||
seekbarStyle: 'Seekbar Style',
|
seekbarStyle: 'Seekbar Style',
|
||||||
seekbarStyleDesc: 'Choose the look of the player seek bar',
|
seekbarStyleDesc: 'Choose the look of the player seek bar',
|
||||||
seekbarWaveform: 'Waveform',
|
seekbarTruewave: 'Truewave',
|
||||||
|
seekbarPseudowave: 'Pseudowave',
|
||||||
seekbarLinedot: 'Line & Dot',
|
seekbarLinedot: 'Line & Dot',
|
||||||
seekbarBar: 'Bar',
|
seekbarBar: 'Bar',
|
||||||
seekbarThick: 'Thick Bar',
|
seekbarThick: 'Thick Bar',
|
||||||
|
|||||||
+2
-1
@@ -881,7 +881,8 @@ export const esTranslation = {
|
|||||||
sidebarLyricsStyleAppleDesc: 'La línea activa se ancla arriba con animación suave.',
|
sidebarLyricsStyleAppleDesc: 'La línea activa se ancla arriba con animación suave.',
|
||||||
seekbarStyle: 'Estilo de Barra de Progreso',
|
seekbarStyle: 'Estilo de Barra de Progreso',
|
||||||
seekbarStyleDesc: 'Elige la apariencia de la barra de progreso del reproductor',
|
seekbarStyleDesc: 'Elige la apariencia de la barra de progreso del reproductor',
|
||||||
seekbarWaveform: 'Forma de Onda',
|
seekbarTruewave: 'Forma de Onda Real',
|
||||||
|
seekbarPseudowave: 'Forma de Onda Pseudo',
|
||||||
seekbarLinedot: 'Línea y Punto',
|
seekbarLinedot: 'Línea y Punto',
|
||||||
seekbarBar: 'Barra',
|
seekbarBar: 'Barra',
|
||||||
seekbarThick: 'Barra Gruesa',
|
seekbarThick: 'Barra Gruesa',
|
||||||
|
|||||||
+2
-1
@@ -876,7 +876,8 @@ export const frTranslation = {
|
|||||||
sidebarLyricsStyleAppleDesc: "La ligne active reste en haut avec une animation fluide.",
|
sidebarLyricsStyleAppleDesc: "La ligne active reste en haut avec une animation fluide.",
|
||||||
seekbarStyle: 'Style de la barre de lecture',
|
seekbarStyle: 'Style de la barre de lecture',
|
||||||
seekbarStyleDesc: 'Choisir l\'apparence de la barre de progression',
|
seekbarStyleDesc: 'Choisir l\'apparence de la barre de progression',
|
||||||
seekbarWaveform: 'Forme d\'onde',
|
seekbarTruewave: 'Forme d\'onde réelle',
|
||||||
|
seekbarPseudowave: 'Forme d\'onde pseudo',
|
||||||
seekbarLinedot: 'Ligne & point',
|
seekbarLinedot: 'Ligne & point',
|
||||||
seekbarBar: 'Barre',
|
seekbarBar: 'Barre',
|
||||||
seekbarThick: 'Barre épaisse',
|
seekbarThick: 'Barre épaisse',
|
||||||
|
|||||||
+2
-1
@@ -875,7 +875,8 @@ export const nbTranslation = {
|
|||||||
sidebarLyricsStyleAppleDesc: 'Aktiv linje forankres øverst med jevn animasjon.',
|
sidebarLyricsStyleAppleDesc: 'Aktiv linje forankres øverst med jevn animasjon.',
|
||||||
seekbarStyle: 'Søkefelt-stil',
|
seekbarStyle: 'Søkefelt-stil',
|
||||||
seekbarStyleDesc: 'Velg utseendet på avspillingssøkefeltet',
|
seekbarStyleDesc: 'Velg utseendet på avspillingssøkefeltet',
|
||||||
seekbarWaveform: 'Bølgeform',
|
seekbarTruewave: 'Ekte bølgeform',
|
||||||
|
seekbarPseudowave: 'Pseudo-bølgeform',
|
||||||
seekbarLinedot: 'Linje & punkt',
|
seekbarLinedot: 'Linje & punkt',
|
||||||
seekbarBar: 'Linje',
|
seekbarBar: 'Linje',
|
||||||
seekbarThick: 'Tykk linje',
|
seekbarThick: 'Tykk linje',
|
||||||
|
|||||||
+2
-1
@@ -875,7 +875,8 @@ export const nlTranslation = {
|
|||||||
sidebarLyricsStyleAppleDesc: 'Actieve regel verankerd bovenaan met vloeiende animatie.',
|
sidebarLyricsStyleAppleDesc: 'Actieve regel verankerd bovenaan met vloeiende animatie.',
|
||||||
seekbarStyle: 'Zoekbalkstijl',
|
seekbarStyle: 'Zoekbalkstijl',
|
||||||
seekbarStyleDesc: 'Kies het uiterlijk van de afspeelbalk',
|
seekbarStyleDesc: 'Kies het uiterlijk van de afspeelbalk',
|
||||||
seekbarWaveform: 'Golfvorm',
|
seekbarTruewave: 'Echte golfvorm',
|
||||||
|
seekbarPseudowave: 'Pseudo-golfvorm',
|
||||||
seekbarLinedot: 'Lijn & punt',
|
seekbarLinedot: 'Lijn & punt',
|
||||||
seekbarBar: 'Balk',
|
seekbarBar: 'Balk',
|
||||||
seekbarThick: 'Dikke balk',
|
seekbarThick: 'Dikke balk',
|
||||||
|
|||||||
+2
-1
@@ -927,7 +927,8 @@ export const ruTranslation = {
|
|||||||
sidebarLyricsStyleAppleDesc: 'Активная строка фиксируется сверху с плавной анимацией.',
|
sidebarLyricsStyleAppleDesc: 'Активная строка фиксируется сверху с плавной анимацией.',
|
||||||
seekbarStyle: 'Стиль прогресс-бара',
|
seekbarStyle: 'Стиль прогресс-бара',
|
||||||
seekbarStyleDesc: 'Выбор внешнего вида полосы воспроизведения',
|
seekbarStyleDesc: 'Выбор внешнего вида полосы воспроизведения',
|
||||||
seekbarWaveform: 'Форма волны',
|
seekbarTruewave: 'Реальная форма волны',
|
||||||
|
seekbarPseudowave: 'Псевдо форма волны',
|
||||||
seekbarLinedot: 'Линия и точка',
|
seekbarLinedot: 'Линия и точка',
|
||||||
seekbarBar: 'Полоса',
|
seekbarBar: 'Полоса',
|
||||||
seekbarThick: 'Толстая полоса',
|
seekbarThick: 'Толстая полоса',
|
||||||
|
|||||||
+2
-1
@@ -870,7 +870,8 @@ export const zhTranslation = {
|
|||||||
sidebarLyricsStyleAppleDesc: '活动行固定在顶部,带有平滑弹簧动画。',
|
sidebarLyricsStyleAppleDesc: '活动行固定在顶部,带有平滑弹簧动画。',
|
||||||
seekbarStyle: '进度条样式',
|
seekbarStyle: '进度条样式',
|
||||||
seekbarStyleDesc: '选择播放进度条的外观',
|
seekbarStyleDesc: '选择播放进度条的外观',
|
||||||
seekbarWaveform: '波形',
|
seekbarTruewave: '真实波形',
|
||||||
|
seekbarPseudowave: '伪波形',
|
||||||
seekbarLinedot: '线条与点',
|
seekbarLinedot: '线条与点',
|
||||||
seekbarBar: '条形',
|
seekbarBar: '条形',
|
||||||
seekbarThick: '粗条形',
|
seekbarThick: '粗条形',
|
||||||
|
|||||||
@@ -3676,7 +3676,7 @@ export default function Settings() {
|
|||||||
{t('settings.seekbarStyleDesc')}
|
{t('settings.seekbarStyleDesc')}
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 10 }}>
|
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 10 }}>
|
||||||
{(['waveform', 'linedot', 'bar', 'thick', 'segmented', 'neon', 'pulsewave', 'particletrail', 'liquidfill', 'retrotape'] as SeekbarStyle[]).map(style => (
|
{(['truewave', 'pseudowave', 'linedot', 'bar', 'thick', 'segmented', 'neon', 'pulsewave', 'particletrail', 'liquidfill', 'retrotape'] as SeekbarStyle[]).map(style => (
|
||||||
<SeekbarPreview
|
<SeekbarPreview
|
||||||
key={style}
|
key={style}
|
||||||
style={style}
|
style={style}
|
||||||
|
|||||||
+10
-2
@@ -18,7 +18,7 @@ export interface ServerProfile {
|
|||||||
password: string;
|
password: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SeekbarStyle = 'waveform' | 'linedot' | 'bar' | 'thick' | 'segmented' | 'neon' | 'pulsewave' | 'particletrail' | 'liquidfill' | 'retrotape';
|
export type SeekbarStyle = 'truewave' | 'pseudowave' | 'linedot' | 'bar' | 'thick' | 'segmented' | 'neon' | 'pulsewave' | 'particletrail' | 'liquidfill' | 'retrotape';
|
||||||
export type LoggingMode = 'off' | 'normal' | 'debug';
|
export type LoggingMode = 'off' | 'normal' | 'debug';
|
||||||
export type NormalizationEngine = 'off' | 'replaygain' | 'loudness';
|
export type NormalizationEngine = 'off' | 'replaygain' | 'loudness';
|
||||||
|
|
||||||
@@ -388,7 +388,7 @@ export const useAuthStore = create<AuthState>()(
|
|||||||
fsPortraitDim: 28,
|
fsPortraitDim: 28,
|
||||||
showChangelogOnUpdate: true,
|
showChangelogOnUpdate: true,
|
||||||
lastSeenChangelogVersion: '',
|
lastSeenChangelogVersion: '',
|
||||||
seekbarStyle: 'waveform',
|
seekbarStyle: 'truewave',
|
||||||
enableHiRes: false,
|
enableHiRes: false,
|
||||||
audioOutputDevice: null,
|
audioOutputDevice: null,
|
||||||
hotCacheEnabled: false,
|
hotCacheEnabled: false,
|
||||||
@@ -729,6 +729,13 @@ export const useAuthStore = create<AuthState>()(
|
|||||||
} catch { /* ignore */ }
|
} catch { /* ignore */ }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// One-time: 'waveform' style was renamed to 'truewave' (with 'pseudowave'
|
||||||
|
// added as the deterministic legacy variant). Existing persisted value
|
||||||
|
// 'waveform' should land on the new bins-based default.
|
||||||
|
const seekbarStyleMigrated = (state.seekbarStyle as string) === 'waveform'
|
||||||
|
? { seekbarStyle: 'truewave' as SeekbarStyle }
|
||||||
|
: {};
|
||||||
|
|
||||||
const st = state as { loudnessTargetLufs?: unknown; loudnessPreAnalysisAttenuationDb?: unknown };
|
const st = state as { loudnessTargetLufs?: unknown; loudnessPreAnalysisAttenuationDb?: unknown };
|
||||||
const targetSan = sanitizeLoudnessLufsPreset(st.loudnessTargetLufs, -12);
|
const targetSan = sanitizeLoudnessLufsPreset(st.loudnessTargetLufs, -12);
|
||||||
const preSan = clampLoudnessPreAnalysisAttenuationDb(st.loudnessPreAnalysisAttenuationDb);
|
const preSan = clampLoudnessPreAnalysisAttenuationDb(st.loudnessPreAnalysisAttenuationDb);
|
||||||
@@ -745,6 +752,7 @@ export const useAuthStore = create<AuthState>()(
|
|||||||
...conflictingLegacyState,
|
...conflictingLegacyState,
|
||||||
...lyricsSourcesMigrated,
|
...lyricsSourcesMigrated,
|
||||||
...wheelSmoothOneTime,
|
...wheelSmoothOneTime,
|
||||||
|
...seekbarStyleMigrated,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user