Region processing

This commit is contained in:
2025-10-12 11:47:31 +02:00
parent fcb784d403
commit a56b089bb2
3 changed files with 429 additions and 7 deletions

View File

@ -10,6 +10,7 @@
import { AudioService } from "./lib/audio/services/AudioService";
import { downloadWAV } from "./lib/audio/utils/WAVEncoder";
import { loadVolume, saveVolume, loadDuration, saveDuration, loadPitchLockEnabled, savePitchLockEnabled, loadPitchLockFrequency, savePitchLockFrequency } from "./lib/utils/settings";
import { cropAudio, cutAudio, processSelection } from "./lib/audio/utils/AudioEdit";
import { generateRandomColor } from "./lib/utils/colors";
import { getRandomProcessor } from "./lib/audio/processors/registry";
import type { AudioProcessor } from "./lib/audio/processors/AudioProcessor";
@ -39,6 +40,8 @@
let pitchLockFrequency = $state(loadPitchLockFrequency());
let pitchLockInput = $state(formatFrequency(loadPitchLockFrequency()));
let pitchLockInputValid = $state(true);
let selectionStart = $state<number | null>(null);
let selectionEnd = $state<number | null>(null);
const showDuration = $derived(engineType !== 'sample');
const showRandomButton = $derived(engineType === 'generative');
@ -47,6 +50,8 @@
const showMutateButton = $derived(engineType === 'generative' && !isProcessed && currentBuffer);
const showPitchLock = $derived(engineType === 'generative');
const pitchLock = $derived<PitchLock>({ enabled: pitchLockEnabled, frequency: pitchLockFrequency });
const hasSelection = $derived(selectionStart !== null && selectionEnd !== null && currentBuffer !== null);
const showEditButtons = $derived(hasSelection);
$effect(() => {
audioService.setVolume(volume);
@ -89,13 +94,20 @@
onVolumeIncrease: (large) => {
volume = Math.min(1, volume + (large ? 0.2 : 0.05));
},
onEscape: () => showModal && closeModal(),
onEscape: () => {
if (hasSelection) {
clearSelection();
} else if (showModal) {
closeModal();
}
},
});
function generateRandom() {
currentParams = engine.randomParams(pitchLock);
waveformColor = generateRandomColor();
isProcessed = false;
clearSelection();
regenerateBuffer();
}
@ -154,9 +166,26 @@
async function applyProcessor(processor: AudioProcessor) {
if (!currentBuffer) return;
const leftChannel = currentBuffer.getChannelData(0);
const rightChannel = currentBuffer.getChannelData(1);
const [processedLeft, processedRight] = await processor.process(leftChannel, rightChannel);
let processedLeft: Float32Array;
let processedRight: Float32Array;
if (hasSelection) {
const start = Math.min(selectionStart!, selectionEnd!);
const end = Math.max(selectionStart!, selectionEnd!);
const sampleRate = audioService.getSampleRate();
[processedLeft, processedRight] = await processSelection(
currentBuffer,
start,
end,
processor,
sampleRate
);
} else {
const leftChannel = currentBuffer.getChannelData(0);
const rightChannel = currentBuffer.getChannelData(1);
[processedLeft, processedRight] = await processor.process(leftChannel, rightChannel);
}
currentBuffer = audioService.createAudioBuffer([processedLeft, processedRight]);
isProcessed = true;
@ -169,6 +198,7 @@
currentBuffer = null;
currentParams = null;
isProcessed = false;
clearSelection();
if (engineType === 'generative') {
generateRandom();
@ -256,6 +286,44 @@
showProcessorPopup = false;
}
function handleSelectionChange(start: number | null, end: number | null) {
selectionStart = start;
selectionEnd = end;
}
function clearSelection() {
selectionStart = null;
selectionEnd = null;
}
function cropSelection() {
if (!currentBuffer || selectionStart === null || selectionEnd === null) return;
const start = Math.min(selectionStart, selectionEnd);
const end = Math.max(selectionStart, selectionEnd);
const sampleRate = audioService.getSampleRate();
const [newLeft, newRight] = cropAudio(currentBuffer, start, end, sampleRate);
currentBuffer = audioService.createAudioBuffer([newLeft, newRight]);
clearSelection();
audioService.play(currentBuffer);
}
function cutSelection() {
if (!currentBuffer || selectionStart === null || selectionEnd === null) return;
const start = Math.min(selectionStart, selectionEnd);
const end = Math.max(selectionStart, selectionEnd);
const sampleRate = audioService.getSampleRate();
const [newLeft, newRight] = cutAudio(currentBuffer, start, end, sampleRate);
currentBuffer = audioService.createAudioBuffer([newLeft, newRight]);
clearSelection();
audioService.play(currentBuffer);
}
async function closeModal() {
showModal = false;
await audioService.initialize();
@ -375,6 +443,9 @@
buffer={currentBuffer}
color={waveformColor}
{playbackPosition}
{selectionStart}
{selectionEnd}
onselectionchange={handleSelectionChange}
onclick={replaySound}
/>
{/if}
@ -391,6 +462,10 @@
{#if showMutateButton}
<button onclick={mutate}>Mutate (M)</button>
{/if}
{#if showEditButtons}
<button onclick={cropSelection}>Crop</button>
<button onclick={cutSelection}>Cut</button>
{/if}
{#if currentBuffer}
<div
class="process-button-container"