diff --git a/src/features/waveform/index.ts b/src/features/waveform/index.ts index 11d5032d..b2feb679 100644 --- a/src/features/waveform/index.ts +++ b/src/features/waveform/index.ts @@ -1,11 +1,15 @@ /** - * Waveform seekbar feature — the canvas seekbar, its waveform-bin loading, and - * the per-track refresh generation guard. Co-located mirror of `cover/` / - * `music-network/`. `waveformSilence` stays in `utils/waveform/` (crossfade / - * auto-dj concern, not seekbar rendering). + * Waveform seekbar feature — the canvas seekbar (rendering only). Co-located + * mirror of `cover/` / `music-network/`. + * + * The waveform DATA pipeline is audio-core, NOT this feature: the audio engine + * calls it on track change to write `waveformBins` into the player store. It + * therefore lives in core, consumed directly (never via this barrel): + * - `@/store/waveformRefresh` — `refreshWaveformForTrack` / `fetchWaveformBins` + * - `@/store/waveformRefreshGen` — `bumpWaveformRefreshGen` / `getWaveformRefreshGen` + * - `@/utils/waveform/waveformParse` — bin coercion (alongside `waveformSilence`, + * the crossfade / auto-dj silence helper) + * Keeping these in core preserves the iron rule (no core→feature inversion). */ export { default as WaveformSeek } from './components/WaveformSeek'; export { SeekbarPreview } from './components/WaveformSeekPreview'; -export { fetchWaveformBins, refreshWaveformForTrack } from './store/waveformRefresh'; -export type { WaveformCachePayload } from './store/waveformRefresh'; -export { bumpWaveformRefreshGen, getWaveformRefreshGen } from './store/waveformRefreshGen'; diff --git a/src/store/applyQueueHistorySnapshot.ts b/src/store/applyQueueHistorySnapshot.ts index 33e69c0b..01967e98 100644 --- a/src/store/applyQueueHistorySnapshot.ts +++ b/src/store/applyQueueHistorySnapshot.ts @@ -20,7 +20,7 @@ import { type QueueUndoSnapshot, } from './queueUndo'; import { refreshLoudnessForTrack } from './loudnessRefresh'; -import { refreshWaveformForTrack } from '@/features/waveform'; +import { refreshWaveformForTrack } from '@/store/waveformRefresh'; import { stopRadio } from './radioPlayer'; import { clearAllPlaybackScheduleTimers } from './scheduleTimers'; import { syncUserQueueMutationToServer } from './queueSync'; diff --git a/src/store/applyServerPlayQueue.idle.test.ts b/src/store/applyServerPlayQueue.idle.test.ts index 0e947ec8..e162f8a6 100644 --- a/src/store/applyServerPlayQueue.idle.test.ts +++ b/src/store/applyServerPlayQueue.idle.test.ts @@ -27,7 +27,7 @@ vi.mock('./pausedRestorePrepare', () => ({ preparePausedRestoreOnStartup: vi.fn(), })); -vi.mock('@/features/waveform', () => ({ +vi.mock('@/store/waveformRefresh', () => ({ refreshWaveformForTrack: vi.fn(), })); diff --git a/src/store/applyServerPlayQueue.ts b/src/store/applyServerPlayQueue.ts index c57da047..efcebaea 100644 --- a/src/store/applyServerPlayQueue.ts +++ b/src/store/applyServerPlayQueue.ts @@ -9,7 +9,7 @@ import { useAuthStore } from './authStore'; import { usePlayerStore } from './playerStore'; import { preparePausedRestoreOnStartup } from './pausedRestorePrepare'; import { pushQueueUndoFromGetter } from './queueUndo'; -import { refreshWaveformForTrack } from '@/features/waveform'; +import { refreshWaveformForTrack } from '@/store/waveformRefresh'; import { getIdlePullGeneration, isIdleQueuePullSuspended, diff --git a/src/store/audioEventHandlers.ts b/src/store/audioEventHandlers.ts index 104e2964..e4913d70 100644 --- a/src/store/audioEventHandlers.ts +++ b/src/store/audioEventHandlers.ts @@ -87,7 +87,7 @@ import { getSeekTarget, getSeekTargetSetAt, } from './seekTargetState'; -import { refreshWaveformForTrack } from '@/features/waveform'; +import { refreshWaveformForTrack } from '@/store/waveformRefresh'; import { analyzeBoundary, computeWaveformSilence } from '../utils/waveform/waveformSilence'; import { autodjMaxOverlapCapSec } from '../utils/playback/autodjOverlapCap'; import { diff --git a/src/store/audioListenerSetup/audioEngineListeners.ts b/src/store/audioListenerSetup/audioEngineListeners.ts index ca14ebc5..3e306f66 100644 --- a/src/store/audioListenerSetup/audioEngineListeners.ts +++ b/src/store/audioListenerSetup/audioEngineListeners.ts @@ -24,8 +24,8 @@ import { markNormalizationUiUpdate, } from '../playbackThrottles'; import { usePlayerStore } from '../playerStore'; -import { refreshWaveformForTrack } from '@/features/waveform'; -import { bumpWaveformRefreshGen } from '@/features/waveform'; +import { refreshWaveformForTrack } from '@/store/waveformRefresh'; +import { bumpWaveformRefreshGen } from '@/store/waveformRefreshGen'; import { setBytePreloadingId } from '../gaplessPreloadState'; type PreloadEventPayload = { diff --git a/src/store/audioListenerSetup/authSyncListener.ts b/src/store/audioListenerSetup/authSyncListener.ts index e5145df7..2b247cc9 100644 --- a/src/store/audioListenerSetup/authSyncListener.ts +++ b/src/store/audioListenerSetup/authSyncListener.ts @@ -7,8 +7,8 @@ import { invokeAudioSetNormalizationDeduped } from '../normalizationIpcDedupe'; import { usePlayerStore } from '../playerStore'; import { clearLoudnessCacheStateForTrackId } from '../loudnessGainCache'; import { refreshLoudnessForTrack } from '../loudnessRefresh'; -import { refreshWaveformForTrack } from '@/features/waveform'; -import { bumpWaveformRefreshGen } from '@/features/waveform'; +import { refreshWaveformForTrack } from '@/store/waveformRefresh'; +import { bumpWaveformRefreshGen } from '@/store/waveformRefreshGen'; /** * Keeps the Rust audio engine in sync whenever the auth store changes diff --git a/src/store/audioListenerSetup/initialAudioSync.ts b/src/store/audioListenerSetup/initialAudioSync.ts index 55758d43..a8fc253d 100644 --- a/src/store/audioListenerSetup/initialAudioSync.ts +++ b/src/store/audioListenerSetup/initialAudioSync.ts @@ -5,7 +5,7 @@ import { emitNormalizationDebug } from '../normalizationDebug'; import { invokeAudioSetNormalizationDeduped } from '../normalizationIpcDedupe'; import { usePlayerStore } from '../playerStore'; import { refreshLoudnessForTrack } from '../loudnessRefresh'; -import { refreshWaveformForTrack } from '@/features/waveform'; +import { refreshWaveformForTrack } from '@/store/waveformRefresh'; /** * One-shot startup sync: pushes the persisted audio settings to the Rust engine diff --git a/src/store/crossfadePreload.ts b/src/store/crossfadePreload.ts index 045b42ad..b9f0397b 100644 --- a/src/store/crossfadePreload.ts +++ b/src/store/crossfadePreload.ts @@ -15,7 +15,7 @@ import { import { getBytePreloadingId, setBytePreloadingId } from './gaplessPreloadState'; import { refreshLoudnessForTrack } from './loudnessRefresh'; import { usePlayerStore } from './playerStore'; -import { fetchWaveformBins } from '@/features/waveform'; +import { fetchWaveformBins } from '@/store/waveformRefresh'; // Crossfade pre-buffer budget: begin downloading the next track this many // seconds before it needs to play (the crossfade start), so a large lossless diff --git a/src/store/loudnessReseed.test.ts b/src/store/loudnessReseed.test.ts index a93d5692..48da88c9 100644 --- a/src/store/loudnessReseed.test.ts +++ b/src/store/loudnessReseed.test.ts @@ -39,7 +39,7 @@ vi.mock('./playerStore', () => ({ setState: hoisted.playerSetStateMock, }, })); -vi.mock('@/features/waveform', () => ({ +vi.mock('@/store/waveformRefreshGen', () => ({ bumpWaveformRefreshGen: hoisted.bumpWaveformRefreshGenMock, })); vi.mock('./loudnessGainCache', () => ({ diff --git a/src/store/loudnessReseed.ts b/src/store/loudnessReseed.ts index 9444bf9a..2121e0af 100644 --- a/src/store/loudnessReseed.ts +++ b/src/store/loudnessReseed.ts @@ -3,7 +3,7 @@ import { invoke } from '@tauri-apps/api/core'; import { getPlaybackIndexKey } from '../utils/playback/playbackServer'; import { useAuthStore } from './authStore'; import { usePlayerStore } from './playerStore'; -import { bumpWaveformRefreshGen } from '@/features/waveform'; +import { bumpWaveformRefreshGen } from '@/store/waveformRefreshGen'; import { clearLoudnessCacheStateForTrackId } from './loudnessGainCache'; import { resetLoudnessBackfillStateForTrackId } from './loudnessBackfillState'; diff --git a/src/store/playTrackAction.ts b/src/store/playTrackAction.ts index 3a0adc95..8b7e53bf 100644 --- a/src/store/playTrackAction.ts +++ b/src/store/playTrackAction.ts @@ -51,7 +51,7 @@ import { loudnessGainDbForEngineBind, } from './loudnessGainCache'; import { refreshLoudnessForTrack } from './loudnessRefresh'; -import { fetchWaveformBins, refreshWaveformForTrack } from '@/features/waveform'; +import { fetchWaveformBins, refreshWaveformForTrack } from '@/store/waveformRefresh'; import { deriveNormalizationSnapshot } from './normalizationSnapshot'; import { useOrbitStore } from './orbitStore'; import { diff --git a/src/store/transportLightActions.ts b/src/store/transportLightActions.ts index 16c02cea..5dfab022 100644 --- a/src/store/transportLightActions.ts +++ b/src/store/transportLightActions.ts @@ -11,7 +11,7 @@ import { clearSeekDebounce } from './seekDebounce'; import { clearSeekFallbackRetry } from './seekFallbackState'; import { clearSeekTarget } from './seekTargetState'; import { tryAcquireTogglePlayLock } from './togglePlayLock'; -import { refreshWaveformForTrack } from '@/features/waveform'; +import { refreshWaveformForTrack } from '@/store/waveformRefresh'; import { clearAutodjTransitionUi } from './autodjTransitionUi'; type SetState = ( diff --git a/src/features/waveform/store/waveformRefresh.test.ts b/src/store/waveformRefresh.test.ts similarity index 93% rename from src/features/waveform/store/waveformRefresh.test.ts rename to src/store/waveformRefresh.test.ts index 53761c3b..0fb24f32 100644 --- a/src/features/waveform/store/waveformRefresh.test.ts +++ b/src/store/waveformRefresh.test.ts @@ -22,18 +22,18 @@ const hoisted = vi.hoisted(() => ({ })); vi.mock('@tauri-apps/api/core', () => ({ invoke: hoisted.invokeMock })); -vi.mock('@/features/waveform/utils/waveformParse', () => ({ coerceWaveformBins: hoisted.coerceWaveformBinsMock })); +vi.mock('@/utils/waveform/waveformParse', () => ({ coerceWaveformBins: hoisted.coerceWaveformBinsMock })); vi.mock('@/store/playerStore', () => ({ usePlayerStore: { getState: () => hoisted.playerSnapshot, setState: hoisted.playerSetStateMock, }, })); -vi.mock('@/features/waveform/store/waveformRefreshGen', () => ({ +vi.mock('@/store/waveformRefreshGen', () => ({ getWaveformRefreshGen: hoisted.getGenMock, })); -import { refreshWaveformForTrack } from '@/features/waveform/store/waveformRefresh'; +import { refreshWaveformForTrack } from '@/store/waveformRefresh'; beforeEach(() => { hoisted.invokeMock.mockReset(); diff --git a/src/features/waveform/store/waveformRefresh.ts b/src/store/waveformRefresh.ts similarity index 94% rename from src/features/waveform/store/waveformRefresh.ts rename to src/store/waveformRefresh.ts index 0830ee2e..6ebebaa6 100644 --- a/src/features/waveform/store/waveformRefresh.ts +++ b/src/store/waveformRefresh.ts @@ -1,8 +1,8 @@ import { invoke } from '@tauri-apps/api/core'; -import { coerceWaveformBins } from '@/features/waveform/utils/waveformParse'; +import { coerceWaveformBins } from '@/utils/waveform/waveformParse'; import { getPlaybackIndexKey } from '@/utils/playback/playbackServer'; import { usePlayerStore } from '@/store/playerStore'; -import { getWaveformRefreshGen } from '@/features/waveform/store/waveformRefreshGen'; +import { getWaveformRefreshGen } from '@/store/waveformRefreshGen'; /** Subsonic-server waveform-cache row as Rust hands it back. */ export type WaveformCachePayload = { diff --git a/src/features/waveform/store/waveformRefreshGen.test.ts b/src/store/waveformRefreshGen.test.ts similarity index 95% rename from src/features/waveform/store/waveformRefreshGen.test.ts rename to src/store/waveformRefreshGen.test.ts index c246c3d5..d0fed0a7 100644 --- a/src/features/waveform/store/waveformRefreshGen.test.ts +++ b/src/store/waveformRefreshGen.test.ts @@ -3,7 +3,7 @@ import { _resetWaveformRefreshGenForTest, bumpWaveformRefreshGen, getWaveformRefreshGen, -} from '@/features/waveform/store/waveformRefreshGen'; +} from '@/store/waveformRefreshGen'; afterEach(() => { _resetWaveformRefreshGenForTest(); diff --git a/src/features/waveform/store/waveformRefreshGen.ts b/src/store/waveformRefreshGen.ts similarity index 100% rename from src/features/waveform/store/waveformRefreshGen.ts rename to src/store/waveformRefreshGen.ts diff --git a/src/features/waveform/utils/waveformParse.test.ts b/src/utils/waveform/waveformParse.test.ts similarity index 96% rename from src/features/waveform/utils/waveformParse.test.ts rename to src/utils/waveform/waveformParse.test.ts index ce78be39..8bb38167 100644 --- a/src/features/waveform/utils/waveformParse.test.ts +++ b/src/utils/waveform/waveformParse.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest'; -import { coerceWaveformBins, waveformBlobLenOk } from '@/features/waveform/utils/waveformParse'; +import { coerceWaveformBins, waveformBlobLenOk } from '@/utils/waveform/waveformParse'; describe('waveformBlobLenOk', () => { it('accepts the legacy single-curve length (500)', () => { diff --git a/src/features/waveform/utils/waveformParse.ts b/src/utils/waveform/waveformParse.ts similarity index 100% rename from src/features/waveform/utils/waveformParse.ts rename to src/utils/waveform/waveformParse.ts