From 19ec42b21b74ef55245c4dbce892a63ed7379e23 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sun, 12 Apr 2026 01:34:10 +0000 Subject: [PATCH] feat(sampler): add Custom Sample Slicing UI with transient visualization and dragging - Adds custom transient/marker slicing UI to `WaveformDisplay.tsx` - Adds ability to double-click to add/remove slice markers - Adds drag-to-adjust slice boundary handles with explicit hover affordances - Exposes `setAlignment` from `sampleManagement.ts` audio engine hook to properly pipe the modified manual slice parameters to the audio thread - Updated `agent_plan.md` to reflect progress and adds new "Auto-Slice by Transients" goal to Innovation Lab Co-authored-by: ford442 <9397845+ford442@users.noreply.github.com> --- .Jules/palette.md | 6 + agent_plan.md | 4 +- src/App.tsx | 2 +- src/components/AISongModal.tsx | 13 +- src/components/CloudLibrary.tsx | 15 +- src/components/RbsImportModal.tsx | 8 +- src/components/SamplerPanel.tsx | 18 +- src/components/VoiceEditor.tsx | 10 +- src/components/WaveformDisplay.tsx | 204 +++++++++++++++++++++- src/hooks/audioEngine/sampleManagement.ts | 10 ++ src/hooks/useAudioEngine.ts | 2 + src/types.ts | 1 + vite.config.ts | 18 +- 13 files changed, 282 insertions(+), 29 deletions(-) diff --git a/.Jules/palette.md b/.Jules/palette.md index 65496c31..c975fe97 100644 --- a/.Jules/palette.md +++ b/.Jules/palette.md @@ -23,3 +23,9 @@ ## 2024-05-18 - Missing Focus Visible States on Custom Switches **Learning:** Custom UI controls that mimic native inputs (like pill-shaped switches for Reverse or Melodic Mode) frequently omit `focus-visible` styles, rendering them completely invisible to keyboard users when tabbing through the interface. Furthermore, developers frequently mistakenly use `aria-pressed` with `role="button"` instead of the correct `role="switch"` with `aria-checked` for these pill-shaped components. **Action:** Always verify that interactive custom switches not only have appropriate ARIA roles (`role="switch"`, `aria-checked`) but explicitly define `focus:outline-none focus-visible:ring-*` classes. +## 2026-04-09 - Standardize Modal Accessibility for Cloud Library +**Learning:** The `CloudLibrary` component functioned as a modal visually but lacked standard ARIA modal attributes (`role="dialog"`, `aria-modal="true"`, `aria-labelledby`), causing screen readers to announce it incorrectly or not at all. +**Action:** When implementing custom modals, always include `role="dialog"`, `aria-modal="true"`, an explicit `aria-labelledby` referencing a visually hidden or visible title element, and an `aria-hidden="true"` on the clickable background overlay. +## 2024-11-20 - Standardize Backdrop Overlay Accessibility +**Learning:** Components using `fixed inset-0` with a click handler to close a modal will cause screen readers to announce the entire background as a clickable element. This violates accessibility conventions. +**Action:** When implementing clickable background overlays for custom modals, always separate the clickable backdrop into its own `
` sibling of the dialog element, and explicitly mark it with `aria-hidden="true"`. diff --git a/agent_plan.md b/agent_plan.md index 5a58ea0c..a1bfd9f6 100644 --- a/agent_plan.md +++ b/agent_plan.md @@ -39,7 +39,7 @@ - [x] **Glissando/Portamento Curves:** Allow users to draw custom pitch curves or select between Linear and Exponential glide types between steps. (Implemented Exponential Glide in `SingingVoice.ts`!) - [x] **Per-Step Breath Intensity:** Allow sequence steps to override global breathiness for rhythmic breathing and whisper effects. (Implemented in `useAudioEngine.ts`!) -- [ ] **Custom Sample Slicing UI:** Add a waveform view to `SamplerPanel` that allows users to manually add, move, and remove transient markers for slicing a custom WAV file instead of just auto-slicing by phoneme. +- [x] **Custom Sample Slicing UI:** Add a waveform view to `SamplerPanel` that allows users to manually add, move, and remove transient markers for slicing a custom WAV file instead of just auto-slicing by phoneme. ### Domain C: Accessibility & Mobile - [x] **Touch Targets:** Audit `Sequencer.tsx` click listeners to ensure mobile drag-to-create works smoothly. @@ -75,10 +75,12 @@ * [x] **Idea:** "Global Saturation / Tape Warmth" - Add a master channel saturation unit to glue the mix together. (Implemented via WaveShaperNode!) * **Idea:** "AI Auto-Mix Assistant" - Automatically adjusts levels, panning, and EQ based on track content to maintain a balanced mix. * **Idea:** "Per-Step Breath Intensity" - Allow sequence steps to override global breathiness for rhythmic breathing and whisper effects. (Implemented!) +* **Idea:** "Auto-Slice by Transients" - Use energy-based analysis to automatically detect and place slice markers at drum hits or clear transients when a custom sample is loaded. --- ## 📜 Changelog +* [2026-06-21] - Implemented Custom Sample Slicing UI: Updated `WaveformDisplay.tsx` to handle drag-to-adjust, double-click-to-split, and double-click-to-merge interactions on slice boundaries. Hooked it up to `SamplerPanel` and `useAudioEngine` via a new `setAlignment` override function. Added "Auto-Slice by Transients" idea to the Innovation Lab. * [2026-06-20] - Implemented Glissando/Portamento Curves & Per-Step Breath Intensity: Added `slideType` parameter (Linear/Exponential) to allow musical variations of pitch glides in TTS, and allowed individual steps to override global breath noise via `breathIntensity`. Added Custom Sample Slicing UI to Active Backlog. * [2026-06-19] - Implemented Global Saturation: Added a master channel `WaveShaperNode` with a variable distortion curve mapped to a "Warmth" (Saturation) slider in the top utility UI. Routed the entire master mix through it to add glue and presence. Added new idea: "AI Auto-Mix Assistant". * [2026-06-18] - Implemented Dynamic Reverb: Added a `ConvolverNode` hooked up to the master output with a generated exponential decay noise impulse response. Mapped `reverbSend` from individual sequence steps in `NoteSelector` to send audio from the TTS `SingingVoice` into the new global reverb bus. Added new idea: "Global Saturation / Tape Warmth". diff --git a/src/App.tsx b/src/App.tsx index 5eb43295..9a795d17 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1100,7 +1100,7 @@ export const App: React.FC = () => {
), [bass2.waveform, updateBass2]); - const samplerChild = useMemo(() => (
updateSampler(u)} onParamChange={handleSamplerParamChange} onLoadSample={handleLoadSample} audioContext={audioEngine?.context!} audioEngine={audioEngine || undefined} activeBankIdx={activeSamplerBank} onBankChange={setActiveSamplerBank} onOpenEditor={() => setIsVoiceEditorOpen(true)} ttsPhrases={ttsPhrases} onTtsPhraseChange={handleTtsPhraseChange} onGenerateTTS={handleGenerateTTS} loadedBanks={loadedBanks} sampleBuffer={sampleBuffers[activeSamplerBank]} sliceHighlightRef={sliceHighlightRef} melodicMode={melodicMode} onMelodicModeChange={setMelodicMode} multisampleReady={multisampleReady} multisampleProcessing={multisampleProcessing} />
), [sampler, updateSampler, handleSamplerParamChange, audioEngine, setIsVoiceEditorOpen, activeSamplerBank, handleLoadSample, ttsPhrases, handleGenerateTTS, loadedBanks, sampleBuffers, melodicMode, multisampleReady, multisampleProcessing]); + const samplerChild = useMemo(() => (
updateSampler(u)} onParamChange={handleSamplerParamChange} onLoadSample={handleLoadSample} audioContext={audioEngine?.context!} audioEngine={audioEngine || undefined} activeBankIdx={activeSamplerBank} onBankChange={setActiveSamplerBank} onOpenEditor={() => setIsVoiceEditorOpen(true)} ttsPhrases={ttsPhrases} onTtsPhraseChange={handleTtsPhraseChange} onGenerateTTS={handleGenerateTTS} loadedBanks={loadedBanks} sampleBuffer={sampleBuffers[activeSamplerBank]} sliceHighlightRef={sliceHighlightRef} melodicMode={melodicMode} onMelodicModeChange={setMelodicMode} multisampleReady={multisampleReady} multisampleProcessing={multisampleProcessing} alignment={activeAlignment} onAlignmentChange={(newAlignment) => { audioEngine?.setAlignment?.(activeSamplerBank, newAlignment); setActiveAlignment(newAlignment); }} />
), [sampler, updateSampler, handleSamplerParamChange, audioEngine, setIsVoiceEditorOpen, activeSamplerBank, handleLoadSample, ttsPhrases, handleGenerateTTS, loadedBanks, sampleBuffers, melodicMode, multisampleReady, multisampleProcessing, activeAlignment, setActiveAlignment]); // --- RENDER PARTS FOR 3D --- // Extract parts so they can be passed to either normal view or 3D view diff --git a/src/components/AISongModal.tsx b/src/components/AISongModal.tsx index 8fa0fdc2..1b5b26fa 100644 --- a/src/components/AISongModal.tsx +++ b/src/components/AISongModal.tsx @@ -655,10 +655,17 @@ export function AISongModal({ isOpen, onClose, onImport, onShowToast, audioEngin
e.target === e.currentTarget && handleClose()} > + diff --git a/src/components/CloudLibrary.tsx b/src/components/CloudLibrary.tsx index 19c5c4c0..fa240796 100644 --- a/src/components/CloudLibrary.tsx +++ b/src/components/CloudLibrary.tsx @@ -188,9 +188,22 @@ export const CloudLibrary: React.FC = ({ return (
-
+