fully functional runtime stuff. Need settings then new features

This commit is contained in:
michalcourson
2026-02-21 20:42:11 -05:00
parent c1948182ec
commit a761b81dd1
8 changed files with 155 additions and 21 deletions

View File

@ -18,16 +18,16 @@
"filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\recordings\\audio_capture_20260220_193822.wav", "filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\recordings\\audio_capture_20260220_193822.wav",
"name": "Pee pee poo poo", "name": "Pee pee poo poo",
"playbackType": "playStop", "playbackType": "playStop",
"startTime": 27.756510985786615, "startTime": 27.587412587412587,
"volume": 1 "volume": 0.69
}, },
{ {
"endTime": 28.597210828548004, "endTime": 27.516843118383072,
"filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\recordings\\audio_capture_20260220_200442.wav", "filename": "C:\\Users\\mickl\\Desktop\\cliptrim-ui\\ClipTrimApp\\audio-service\\recordings\\audio_capture_20260220_200442.wav",
"name": "Clip 20260220_200442", "name": "Clip 20260220_200442",
"playbackType": "playStop", "playbackType": "playOverlap",
"startTime": 26.1853978671042, "startTime": 25.120307988450435,
"volume": 1 "volume": 0.41
} }
] ]
} }

View File

@ -28,9 +28,9 @@ class AudioRecorder:
self.channels = 2 self.channels = 2
self.buffer = np.zeros((int(self.duration * self.sample_rate), self.channels), dtype=np.float32) self.buffer = np.zeros((int(self.duration * self.sample_rate), self.channels), dtype=np.float32)
self.recordings_dir = "recordings" self.recordings_dir = "recordings"
self.stream = sd.InputStream( self.stream = sd.InputStream(
samplerate=self.sample_rate, samplerate=self.sample_rate,
channels=self.channels,
callback=self.record_callback callback=self.record_callback
) )
@ -43,10 +43,9 @@ class AudioRecorder:
self.stream.stop() self.stream.stop()
self.buffer = np.zeros((int(self.duration * self.sample_rate), self.channels), dtype=np.float32) self.buffer = np.zeros((int(self.duration * self.sample_rate), self.channels), dtype=np.float32)
print(f"AudioRecorder initialized with duration={self.duration}s, sample_rate={self.sample_rate}Hz, channels={self.channels}")
self.stream = sd.InputStream( self.stream = sd.InputStream(
samplerate=self.sample_rate, samplerate=self.sample_rate,
channels=self.channels,
callback=self.record_callback callback=self.record_callback
) )

View File

@ -2,6 +2,8 @@ import { MemoryRouter as Router, Routes, Route } from 'react-router-dom';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import Dialog from '@mui/material/Dialog'; import Dialog from '@mui/material/Dialog';
import { ThemeProvider, createTheme } from '@mui/material/styles';
import DialogTitle from '@mui/material/DialogTitle'; import DialogTitle from '@mui/material/DialogTitle';
import DialogContent from '@mui/material/DialogContent'; import DialogContent from '@mui/material/DialogContent';
import DialogActions from '@mui/material/DialogActions'; import DialogActions from '@mui/material/DialogActions';
@ -150,13 +152,25 @@ function MainPage() {
} }
export default function App() { export default function App() {
const theme = createTheme({
palette: {
primary: {
main: '#6e44ba', // plum
},
secondary: {
main: '#4f3186', // plumDark
},
},
});
return ( return (
<Provider store={store}> <Provider store={store}>
<ThemeProvider theme={theme}>
<Router> <Router>
<Routes> <Routes>
<Route path="/" element={<MainPage />} /> <Route path="/" element={<MainPage />} />
</Routes> </Routes>
</Router> </Router>
</ThemeProvider>
</Provider> </Provider>
); );
} }

View File

@ -9,6 +9,9 @@ import Dialog from '@mui/material/Dialog';
import DialogTitle from '@mui/material/DialogTitle'; import DialogTitle from '@mui/material/DialogTitle';
import DialogContent from '@mui/material/DialogContent'; import DialogContent from '@mui/material/DialogContent';
import DialogActions from '@mui/material/DialogActions'; import DialogActions from '@mui/material/DialogActions';
import Slider from '@mui/material/Slider';
import ToggleButton from '@mui/material/ToggleButton';
import ToggleButtonGroup from '@mui/material/ToggleButtonGroup';
import { useWavesurfer } from '@wavesurfer/react'; import { useWavesurfer } from '@wavesurfer/react';
import RegionsPlugin from 'wavesurfer.js/dist/plugins/regions.esm.js'; import RegionsPlugin from 'wavesurfer.js/dist/plugins/regions.esm.js';
import ZoomPlugin from 'wavesurfer.js/dist/plugins/zoom.esm.js'; import ZoomPlugin from 'wavesurfer.js/dist/plugins/zoom.esm.js';
@ -19,8 +22,10 @@ import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward';
import DeleteIcon from '@mui/icons-material/Delete'; import DeleteIcon from '@mui/icons-material/Delete';
import { useSortable } from '@dnd-kit/sortable'; import { useSortable } from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities'; import { CSS } from '@dnd-kit/utilities';
import { ClipMetadata } from '../../redux/types'; import { ClipMetadata, PlaybackType } from '../../redux/types';
import { useAppSelector } from '../hooks'; import { useAppSelector } from '../hooks';
import PlayStopIcon from './playStopIcon';
import PlayOverlapIcon from './playOverlapIcon';
export interface AudioTrimmerProps { export interface AudioTrimmerProps {
metadata: ClipMetadata; metadata: ClipMetadata;
@ -43,6 +48,7 @@ export default function AudioTrimmer({
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
const [dropdownOpen, setDropdownOpen] = useState(false); const [dropdownOpen, setDropdownOpen] = useState(false);
const [nameInput, setNameInput] = useState<string>(metadata.name); const [nameInput, setNameInput] = useState<string>(metadata.name);
const [volumeInput, setVolumeInput] = useState<number>(metadata.volume ?? 1);
const collectionNames = useAppSelector((state) => const collectionNames = useAppSelector((state) =>
state.collections.map((col) => col.name), state.collections.map((col) => col.name),
); );
@ -223,6 +229,7 @@ export default function AudioTrimmer({
} else { } else {
const allRegions = (plugins[0] as RegionsPlugin).getRegions(); const allRegions = (plugins[0] as RegionsPlugin).getRegions();
if (allRegions.length > 0) { if (allRegions.length > 0) {
wavesurfer.setVolume(metadata.volume ?? 1);
wavesurfer.play(allRegions[0].start, allRegions[0].end); wavesurfer.play(allRegions[0].start, allRegions[0].end);
} else { } else {
wavesurfer.play(); wavesurfer.play();
@ -404,12 +411,74 @@ export default function AudioTrimmer({
<div className="m-1 wavesurfer-scroll-container"> <div className="m-1 wavesurfer-scroll-container">
<div ref={containerRef} className="wavesurfer-inner" /> <div ref={containerRef} className="wavesurfer-inner" />
</div> </div>
<div className="grid justify-items-stretch grid-cols-2 text-neutral-500"> <div className="flex justify-between mt-2">
<div className="m-1 flex justify-start"> <span className="w-1/5 flex-none text-sm text-neutral-500 self-center">
<text className="text-sm ">
Clip: {formatTime(metadata.startTime ?? 0)} -{' '} Clip: {formatTime(metadata.startTime ?? 0)} -{' '}
{formatTime(metadata.endTime ?? 0)} {formatTime(metadata.endTime ?? 0)}
</text> </span>
<div className="w-3/5 flex-1 flex justify-center items-center">
<Slider
value={volumeInput}
min={0}
max={1}
step={0.01}
onChange={(e, newValue) => setVolumeInput(newValue as number)}
onChangeCommitted={(e, newValue) => {
const newVolume = newValue as number;
console.log('Volume change:', newVolume);
if (onSave) onSave({ ...metadata, volume: newVolume });
}}
color="secondary"
className="p-0 m-0"
/>
{/* <input
type="range"
min={0}
max={1}
step={0.01}
value={volumeInput}
onChange={(e) => {
const newVolume = parseFloat(e.target.value);
setVolumeInput(newVolume);
}}
onDragEnd={(e) => {
console.log('Volume change:');
// const newVolume = parseFloat(e.target.value);
// if (onSave) onSave({ ...metadata, volume: newVolume });
}}
className="mx-2 w-full accent-plum"
aria-label="Volume slider"
/> */}
</div>
<div className="w-1/5 flex justify-end text-sm text-neutral-500">
<ToggleButtonGroup value={metadata.playbackType}>
<ToggleButton
value="playStop"
color="primary"
onClick={() => {
if (onSave)
onSave({
...metadata,
playbackType: PlaybackType.PlayStop,
});
}}
>
<PlayStopIcon />
</ToggleButton>
<ToggleButton
value="playOverlap"
color="primary"
onClick={() => {
if (onSave)
onSave({
...metadata,
playbackType: PlaybackType.PlayOverlap,
});
}}
>
<PlayOverlapIcon />
</ToggleButton>
</ToggleButtonGroup>
</div> </div>
</div> </div>
</div> </div>

View File

@ -0,0 +1,29 @@
import React from 'react';
export default function PlayOverlapIcon({
size = 24,
color = 'currentColor',
}: {
size?: number;
color?: string;
}) {
return (
<svg
width={size}
height={size}
viewBox="0 0 32 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
{/* Filled play arrow */}
<polygon points="4,4 4,20 16,12" fill={color} />
{/* Outlined play arrow (underneath and to the right) */}
<polygon
points="12,4 12,20 24,12"
fill="none"
stroke={color}
strokeWidth={1}
/>
</svg>
);
}

View File

@ -0,0 +1,23 @@
export default function PlayStopIcon({
size = 24,
color = 'currentColor',
}: {
size?: number;
color?: string;
}) {
return (
<svg
width={size}
height={size}
viewBox="0 0 48 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
aria-label="Play/Stop Icon"
>
{/* Play Arrow */}
<polygon points="4,4 20,12 4,20" fill={color} />
{/* Stop Square */}
<rect x="28" y="4" width="16" height="16" rx="2" fill={color} />
</svg>
);
}