From 61167457953459eed7e9ce1bea07b4a53b3339ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Forment?= Date: Mon, 13 Oct 2025 18:09:47 +0200 Subject: [PATCH] Working on processors a tiny bit --- src/App.svelte | 352 ++++++++++++------ src/lib/audio/processors/AudioProcessor.ts | 3 + src/lib/audio/processors/BitCrusher.ts | 6 +- src/lib/audio/processors/Chorus.ts | 6 +- src/lib/audio/processors/Compressor.ts | 6 +- src/lib/audio/processors/ConvolutionReverb.ts | 6 +- src/lib/audio/processors/DCOffsetRemover.ts | 6 +- src/lib/audio/processors/ExpFadeIn.ts | 6 +- src/lib/audio/processors/ExpFadeOut.ts | 6 +- src/lib/audio/processors/HaasEffect.ts | 6 +- src/lib/audio/processors/HighPassSweepDown.ts | 65 ++++ src/lib/audio/processors/HighPassSweepUp.ts | 65 ++++ src/lib/audio/processors/LinearFadeIn.ts | 6 +- src/lib/audio/processors/LinearFadeOut.ts | 6 +- src/lib/audio/processors/LowPassSweepDown.ts | 65 ++++ src/lib/audio/processors/LowPassSweepUp.ts | 65 ++++ src/lib/audio/processors/MicroPitch.ts | 6 +- src/lib/audio/processors/Normalize.ts | 6 +- src/lib/audio/processors/OctaveDown.ts | 6 +- src/lib/audio/processors/OctaveUp.ts | 6 +- src/lib/audio/processors/PanLeftToRight.ts | 34 ++ src/lib/audio/processors/PanRightToLeft.ts | 34 ++ src/lib/audio/processors/PanShuffler.ts | 6 +- src/lib/audio/processors/PhaseInverter.ts | 6 +- src/lib/audio/processors/Phaser.ts | 6 +- src/lib/audio/processors/PitchShifter.ts | 6 +- src/lib/audio/processors/PitchWobble.ts | 6 +- src/lib/audio/processors/Resonator.ts | 6 +- src/lib/audio/processors/Reverser.ts | 6 +- src/lib/audio/processors/RingModulator.ts | 6 +- src/lib/audio/processors/SegmentShuffler.ts | 6 +- src/lib/audio/processors/SlowTapeStop.ts | 50 +++ src/lib/audio/processors/SpectralBlur.ts | 6 +- src/lib/audio/processors/SpectralShift.ts | 6 +- src/lib/audio/processors/StereoSwap.ts | 6 +- src/lib/audio/processors/StereoWidener.ts | 6 +- src/lib/audio/processors/Stutter.ts | 6 +- src/lib/audio/processors/TapeSpeedUp.ts | 49 +++ src/lib/audio/processors/TapeStop.ts | 50 +++ src/lib/audio/processors/TapeWobble.ts | 48 +++ src/lib/audio/processors/Tremolo.ts | 6 +- src/lib/audio/processors/TrimSilence.ts | 6 +- src/lib/audio/processors/VinylStop.ts | 50 +++ src/lib/audio/processors/Waveshaper.ts | 6 +- src/lib/audio/processors/registry.ts | 22 ++ src/lib/components/ProcessorPopup.svelte | 154 ++++++-- src/lib/utils/keyboard.ts | 2 +- src/lib/utils/settings.ts | 18 + 48 files changed, 1138 insertions(+), 174 deletions(-) create mode 100644 src/lib/audio/processors/HighPassSweepDown.ts create mode 100644 src/lib/audio/processors/HighPassSweepUp.ts create mode 100644 src/lib/audio/processors/LowPassSweepDown.ts create mode 100644 src/lib/audio/processors/LowPassSweepUp.ts create mode 100644 src/lib/audio/processors/PanLeftToRight.ts create mode 100644 src/lib/audio/processors/PanRightToLeft.ts create mode 100644 src/lib/audio/processors/SlowTapeStop.ts create mode 100644 src/lib/audio/processors/TapeSpeedUp.ts create mode 100644 src/lib/audio/processors/TapeStop.ts create mode 100644 src/lib/audio/processors/TapeWobble.ts create mode 100644 src/lib/audio/processors/VinylStop.ts diff --git a/src/App.svelte b/src/App.svelte index 14cde93..b44cc68 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -5,15 +5,35 @@ import WelcomeModal from "./lib/components/WelcomeModal.svelte"; import ProcessorPopup from "./lib/components/ProcessorPopup.svelte"; import { engines } from "./lib/audio/engines/registry"; - import type { SynthEngine, PitchLock } from "./lib/audio/engines/base/SynthEngine"; + import type { + SynthEngine, + PitchLock, + } from "./lib/audio/engines/base/SynthEngine"; import type { EngineType } from "./lib/audio/engines/base/SynthEngine"; import { AudioService } from "./lib/audio/services/AudioService"; import { downloadWAV } from "./lib/audio/utils/WAVEncoder"; - import { loadVolume, saveVolume, loadDuration, saveDuration, loadPitchLockEnabled, savePitchLockEnabled, loadPitchLockFrequency, savePitchLockFrequency, loadExpandedCategories, saveExpandedCategories } from "./lib/utils/settings"; - import { cropAudio, cutAudio, processSelection } from "./lib/audio/utils/AudioEdit"; + import { + loadVolume, + saveVolume, + loadDuration, + saveDuration, + loadPitchLockEnabled, + savePitchLockEnabled, + loadPitchLockFrequency, + savePitchLockFrequency, + loadExpandedCategories, + saveExpandedCategories, + loadEnabledProcessorCategories, + saveEnabledProcessorCategories, + } 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"; + import type { AudioProcessor, ProcessorCategory } from "./lib/audio/processors/AudioProcessor"; import { Sample } from "./lib/audio/engines/Sample"; import { Input } from "./lib/audio/engines/Input"; import { createKeyboardHandler } from "./lib/utils/keyboard"; @@ -48,15 +68,25 @@ let canUndo = $state(false); let sidebarOpen = $state(false); let expandedCategories = $state>(loadExpandedCategories()); + let enabledProcessorCategories = $state>( + loadEnabledProcessorCategories() as Set + ); - const showDuration = $derived(engineType !== 'sample'); - const showRandomButton = $derived(engineType === 'generative'); - const showRecordButton = $derived(engineType === 'input'); - const showFileDropZone = $derived(engineType === 'sample' && !currentBuffer); - const showMutateButton = $derived(engineType === 'generative' && !isProcessed && currentBuffer); - const showPitchLock = $derived(engineType === 'generative'); - const pitchLock = $derived({ enabled: pitchLockEnabled, frequency: pitchLockFrequency }); - const hasSelection = $derived(selectionStart !== null && selectionEnd !== null && currentBuffer !== null); + const showDuration = $derived(engineType !== "sample"); + const showRandomButton = $derived(engineType === "generative"); + const showRecordButton = $derived(engineType === "input"); + const showFileDropZone = $derived(engineType === "sample" && !currentBuffer); + const showMutateButton = $derived( + engineType === "generative" && !isProcessed && currentBuffer, + ); + const showPitchLock = $derived(engineType === "generative"); + const pitchLock = $derived({ + enabled: pitchLockEnabled, + frequency: pitchLockFrequency, + }); + const hasSelection = $derived( + selectionStart !== null && selectionEnd !== null && currentBuffer !== null, + ); const showEditButtons = $derived(hasSelection); $effect(() => { @@ -80,6 +110,10 @@ saveExpandedCategories(expandedCategories); }); + $effect(() => { + saveEnabledProcessorCategories(enabledProcessorCategories as Set); + }); + // Group engines by category const enginesByCategory = $derived.by(() => { const grouped = new Map(); @@ -103,6 +137,16 @@ expandedCategories = newSet; } + function toggleProcessorCategory(category: ProcessorCategory) { + const newSet = new Set(enabledProcessorCategories); + if (newSet.has(category)) { + newSet.delete(category); + } else { + newSet.add(category); + } + enabledProcessorCategories = newSet; + } + onMount(() => { audioService.setPlaybackUpdateCallback((position) => { playbackPosition = position; @@ -116,7 +160,7 @@ currentParams, isProcessed, waveformColor, - currentEngineIndex + currentEngineIndex, ); } @@ -129,7 +173,10 @@ } function restoreState(state: AudioState): void { - currentBuffer = audioService.createAudioBuffer([state.leftChannel, state.rightChannel]); + currentBuffer = audioService.createAudioBuffer([ + state.leftChannel, + state.rightChannel, + ]); currentParams = state.params; isProcessed = state.isProcessed; waveformColor = state.waveformColor; @@ -219,7 +266,12 @@ if (!currentParams) return; const sampleRate = audioService.getSampleRate(); - const data = await engine.generate(currentParams, sampleRate, duration, pitchLock); + const data = await engine.generate( + currentParams, + sampleRate, + duration, + pitchLock, + ); currentBuffer = audioService.createAudioBuffer(data); audioService.play(currentBuffer); } @@ -264,15 +316,21 @@ start, end, processor, - sampleRate + sampleRate, ); } else { const leftChannel = currentBuffer.getChannelData(0); const rightChannel = currentBuffer.getChannelData(1); - [processedLeft, processedRight] = await processor.process(leftChannel, rightChannel); + [processedLeft, processedRight] = await processor.process( + leftChannel, + rightChannel, + ); } - currentBuffer = audioService.createAudioBuffer([processedLeft, processedRight]); + currentBuffer = audioService.createAudioBuffer([ + processedLeft, + processedRight, + ]); isProcessed = true; audioService.play(currentBuffer); hideProcessorPopup(); @@ -288,7 +346,7 @@ clearSelection(); sidebarOpen = false; - if (engineType === 'generative') { + if (engineType === "generative") { generateRandom(); } } @@ -311,7 +369,7 @@ isProcessed = false; regenerateBuffer(); } catch (error) { - console.error('Failed to load audio file:', error); + console.error("Failed to load audio file:", error); alert(`Failed to load audio file: ${error}`); } } @@ -329,7 +387,7 @@ isProcessed = false; regenerateBuffer(); } catch (error) { - console.error('Failed to record audio:', error); + console.error("Failed to record audio:", error); alert(`Failed to record audio: ${error}`); } finally { isRecording = false; @@ -344,8 +402,8 @@ if (!files?.length) return; const file = files[0]; - if (!file.type.startsWith('audio/')) { - alert('Please drop an audio file'); + if (!file.type.startsWith("audio/")) { + alert("Please drop an audio file"); return; } @@ -365,12 +423,17 @@ function showPopup() { if (popupTimeout) clearTimeout(popupTimeout); showProcessorPopup = true; - popupTimeout = setTimeout(() => showProcessorPopup = false, 2000); + popupTimeout = setTimeout(() => (showProcessorPopup = false), 2000); + } + + function keepPopupOpen() { + if (popupTimeout) clearTimeout(popupTimeout); + showProcessorPopup = true; } function scheduleHidePopup() { if (popupTimeout) clearTimeout(popupTimeout); - popupTimeout = setTimeout(() => showProcessorPopup = false, 200); + popupTimeout = setTimeout(() => (showProcessorPopup = false), 200); } function hideProcessorPopup() { @@ -389,7 +452,8 @@ } function cropSelection() { - if (!currentBuffer || selectionStart === null || selectionEnd === null) return; + if (!currentBuffer || selectionStart === null || selectionEnd === null) + return; pushState(); @@ -397,7 +461,12 @@ const end = Math.max(selectionStart, selectionEnd); const sampleRate = audioService.getSampleRate(); - const [newLeft, newRight] = cropAudio(currentBuffer, start, end, sampleRate); + const [newLeft, newRight] = cropAudio( + currentBuffer, + start, + end, + sampleRate, + ); currentBuffer = audioService.createAudioBuffer([newLeft, newRight]); clearSelection(); @@ -405,7 +474,8 @@ } function cutSelection() { - if (!currentBuffer || selectionStart === null || selectionEnd === null) return; + if (!currentBuffer || selectionStart === null || selectionEnd === null) + return; pushState(); @@ -434,31 +504,54 @@
{#if sidebarOpen} - + {/if}
- -

Poof: a sample generator

-
+ +

Poof: a sample generator and processor

+
{#if showPitchLock}
-
-
- {#if showFileDropZone} -
-
-

Drop an audio file here

- -
-
- {:else} - - {/if} - -
- {#if showRandomButton} - - {/if} - {#if showRecordButton} - - {/if} - {#if showMutateButton} - - {/if} - {#if showEditButtons} - - - {/if} - {#if currentBuffer} +
+ {#if showFileDropZone}
- - {#if showProcessorPopup} - - {/if} +
+

Drop an audio file here

+ +
- - + {:else} + {/if} + +
+ {#if showRandomButton} + + {/if} + {#if showRecordButton} + + {/if} + {#if showMutateButton} + + {/if} + {#if showEditButtons} + + + {/if} + {#if currentBuffer} +
+ + {#if showProcessorPopup} +
+ +
+ {/if} +
+ + + {/if} +
+
+
+
-
- -
-
{#if showModal} @@ -692,7 +802,9 @@ border: 1px solid #3a3a3a; color: #fff; cursor: pointer; - transition: border-color 0.2s, background-color 0.2s; + transition: + border-color 0.2s, + background-color 0.2s; } .hamburger svg { @@ -759,7 +871,9 @@ color: #999; text-align: left; cursor: pointer; - transition: color 0.2s, background-color 0.2s; + transition: + color 0.2s, + background-color 0.2s; flex-shrink: 0; } @@ -950,7 +1064,10 @@ color: #fff; font-size: 0.7rem; font-weight: 600; - transition: border-color 0.2s, background-color 0.2s, box-shadow 0.2s; + transition: + border-color 0.2s, + background-color 0.2s, + box-shadow 0.2s; font-variant-numeric: tabular-nums; box-sizing: border-box; } @@ -981,7 +1098,6 @@ font-weight: 400; } - @media (min-width: 768px) { .sidebar { position: static; diff --git a/src/lib/audio/processors/AudioProcessor.ts b/src/lib/audio/processors/AudioProcessor.ts index 5cc0bc1..bedff8d 100644 --- a/src/lib/audio/processors/AudioProcessor.ts +++ b/src/lib/audio/processors/AudioProcessor.ts @@ -1,6 +1,9 @@ +export type ProcessorCategory = 'Amplitude' | 'Filter' | 'Time' | 'Space' | 'Pitch' | 'Modulation' | 'Distortion' | 'Spectral' | 'Utility'; + export interface AudioProcessor { getName(): string; getDescription(): string; + getCategory(): ProcessorCategory; process( leftChannel: Float32Array, rightChannel: Float32Array diff --git a/src/lib/audio/processors/BitCrusher.ts b/src/lib/audio/processors/BitCrusher.ts index 5dd0187..6ee21c7 100644 --- a/src/lib/audio/processors/BitCrusher.ts +++ b/src/lib/audio/processors/BitCrusher.ts @@ -1,4 +1,4 @@ -import type { AudioProcessor } from "./AudioProcessor"; +import type { AudioProcessor, ProcessorCategory } from "./AudioProcessor"; export class BitCrusher implements AudioProcessor { getName(): string { @@ -9,6 +9,10 @@ export class BitCrusher implements AudioProcessor { return "Reduces bit depth for lo-fi digital distortion"; } + getCategory(): ProcessorCategory { + return 'Distortion'; + } + async process( leftChannel: Float32Array, rightChannel: Float32Array diff --git a/src/lib/audio/processors/Chorus.ts b/src/lib/audio/processors/Chorus.ts index 0dacfec..3350e4b 100644 --- a/src/lib/audio/processors/Chorus.ts +++ b/src/lib/audio/processors/Chorus.ts @@ -1,4 +1,4 @@ -import type { AudioProcessor } from './AudioProcessor'; +import type { AudioProcessor, ProcessorCategory } from './AudioProcessor'; export class Chorus implements AudioProcessor { private readonly sampleRate = 44100; @@ -11,6 +11,10 @@ export class Chorus implements AudioProcessor { return 'Multiple delayed copies with pitch modulation for thick, ensemble sounds'; } + getCategory(): ProcessorCategory { + return 'Time'; + } + process( leftChannel: Float32Array, rightChannel: Float32Array diff --git a/src/lib/audio/processors/Compressor.ts b/src/lib/audio/processors/Compressor.ts index 7992dc8..32701c0 100644 --- a/src/lib/audio/processors/Compressor.ts +++ b/src/lib/audio/processors/Compressor.ts @@ -1,4 +1,4 @@ -import type { AudioProcessor } from "./AudioProcessor"; +import type { AudioProcessor, ProcessorCategory } from "./AudioProcessor"; export class Compressor implements AudioProcessor { getName(): string { @@ -9,6 +9,10 @@ export class Compressor implements AudioProcessor { return "Reduces dynamic range by taming peaks with makeup gain"; } + getCategory(): ProcessorCategory { + return 'Amplitude'; + } + async process( leftChannel: Float32Array, rightChannel: Float32Array diff --git a/src/lib/audio/processors/ConvolutionReverb.ts b/src/lib/audio/processors/ConvolutionReverb.ts index b95814c..e29fa59 100644 --- a/src/lib/audio/processors/ConvolutionReverb.ts +++ b/src/lib/audio/processors/ConvolutionReverb.ts @@ -1,4 +1,4 @@ -import type { AudioProcessor } from './AudioProcessor'; +import type { AudioProcessor, ProcessorCategory } from './AudioProcessor'; type RoomType = 'small' | 'medium' | 'large' | 'hall' | 'plate' | 'chamber'; @@ -22,6 +22,10 @@ export class ConvolutionReverb implements AudioProcessor { return 'Realistic room ambience using Web Audio ConvolverNode with synthetic impulse responses'; } + getCategory(): ProcessorCategory { + return 'Space'; + } + async process( leftChannel: Float32Array, rightChannel: Float32Array diff --git a/src/lib/audio/processors/DCOffsetRemover.ts b/src/lib/audio/processors/DCOffsetRemover.ts index c105818..f29cf59 100644 --- a/src/lib/audio/processors/DCOffsetRemover.ts +++ b/src/lib/audio/processors/DCOffsetRemover.ts @@ -1,4 +1,4 @@ -import type { AudioProcessor } from "./AudioProcessor"; +import type { AudioProcessor, ProcessorCategory } from "./AudioProcessor"; export class DCOffsetRemover implements AudioProcessor { getName(): string { @@ -9,6 +9,10 @@ export class DCOffsetRemover implements AudioProcessor { return "Removes DC offset bias from the audio signal"; } + getCategory(): ProcessorCategory { + return 'Utility'; + } + async process( leftChannel: Float32Array, rightChannel: Float32Array diff --git a/src/lib/audio/processors/ExpFadeIn.ts b/src/lib/audio/processors/ExpFadeIn.ts index a7221d3..6908404 100644 --- a/src/lib/audio/processors/ExpFadeIn.ts +++ b/src/lib/audio/processors/ExpFadeIn.ts @@ -1,4 +1,4 @@ -import type { AudioProcessor } from './AudioProcessor'; +import type { AudioProcessor, ProcessorCategory } from './AudioProcessor'; export class ExpFadeIn implements AudioProcessor { getName(): string { @@ -9,6 +9,10 @@ export class ExpFadeIn implements AudioProcessor { return 'Applies an exponential fade from silence to current level'; } + getCategory(): ProcessorCategory { + return 'Amplitude'; + } + process(leftIn: Float32Array, rightIn: Float32Array): [Float32Array, Float32Array] { const length = leftIn.length; diff --git a/src/lib/audio/processors/ExpFadeOut.ts b/src/lib/audio/processors/ExpFadeOut.ts index e9fb0d4..b9f3756 100644 --- a/src/lib/audio/processors/ExpFadeOut.ts +++ b/src/lib/audio/processors/ExpFadeOut.ts @@ -1,4 +1,4 @@ -import type { AudioProcessor } from './AudioProcessor'; +import type { AudioProcessor, ProcessorCategory } from './AudioProcessor'; export class ExpFadeOut implements AudioProcessor { getName(): string { @@ -9,6 +9,10 @@ export class ExpFadeOut implements AudioProcessor { return 'Applies an exponential fade from current level to silence'; } + getCategory(): ProcessorCategory { + return 'Amplitude'; + } + process(leftIn: Float32Array, rightIn: Float32Array): [Float32Array, Float32Array] { const length = leftIn.length; diff --git a/src/lib/audio/processors/HaasEffect.ts b/src/lib/audio/processors/HaasEffect.ts index 91d8c57..75ff9ee 100644 --- a/src/lib/audio/processors/HaasEffect.ts +++ b/src/lib/audio/processors/HaasEffect.ts @@ -1,4 +1,4 @@ -import type { AudioProcessor } from "./AudioProcessor"; +import type { AudioProcessor, ProcessorCategory } from "./AudioProcessor"; export class HaasEffect implements AudioProcessor { getName(): string { @@ -9,6 +9,10 @@ export class HaasEffect implements AudioProcessor { return "Creates stereo width with micro-delay (precedence effect)"; } + getCategory(): ProcessorCategory { + return 'Space'; + } + async process( leftChannel: Float32Array, rightChannel: Float32Array diff --git a/src/lib/audio/processors/HighPassSweepDown.ts b/src/lib/audio/processors/HighPassSweepDown.ts new file mode 100644 index 0000000..87126da --- /dev/null +++ b/src/lib/audio/processors/HighPassSweepDown.ts @@ -0,0 +1,65 @@ +import type { AudioProcessor, ProcessorCategory } from './AudioProcessor'; + +export class HighPassSweepDown implements AudioProcessor { + getName(): string { + return 'HP Sweep Down'; + } + + getDescription(): string { + return 'Sweeps a high-pass filter from thin to full'; + } + + getCategory(): ProcessorCategory { + return 'Filter'; + } + + process(leftIn: Float32Array, rightIn: Float32Array): [Float32Array, Float32Array] { + const length = leftIn.length; + const leftOut = new Float32Array(length); + const rightOut = new Float32Array(length); + + const sampleRate = 44100; + const startFreq = 8000; + const endFreq = 20; + const Q = 1.5; + + let lx1 = 0, lx2 = 0, ly1 = 0, ly2 = 0; + let rx1 = 0, rx2 = 0, ry1 = 0, ry2 = 0; + + let b0 = 0, b1 = 0, b2 = 0, a1 = 0, a2 = 0; + + for (let i = 0; i < length; i++) { + if (i % 64 === 0) { + const t = i / length; + const freq = startFreq * Math.pow(endFreq / startFreq, t); + const omega = 2.0 * Math.PI * freq / sampleRate; + const alpha = Math.sin(omega) / (2.0 * Q); + + const a0 = 1.0 + alpha; + b0 = ((1.0 + Math.cos(omega)) / 2.0) / a0; + b1 = (-(1.0 + Math.cos(omega))) / a0; + b2 = b0; + a1 = (-2.0 * Math.cos(omega)) / a0; + a2 = (1.0 - alpha) / a0; + } + + const lx0 = leftIn[i]; + const rx0 = rightIn[i]; + + leftOut[i] = b0 * lx0 + b1 * lx1 + b2 * lx2 - a1 * ly1 - a2 * ly2; + rightOut[i] = b0 * rx0 + b1 * rx1 + b2 * rx2 - a1 * ry1 - a2 * ry2; + + lx2 = lx1; + lx1 = lx0; + ly2 = ly1; + ly1 = leftOut[i]; + + rx2 = rx1; + rx1 = rx0; + ry2 = ry1; + ry1 = rightOut[i]; + } + + return [leftOut, rightOut]; + } +} diff --git a/src/lib/audio/processors/HighPassSweepUp.ts b/src/lib/audio/processors/HighPassSweepUp.ts new file mode 100644 index 0000000..76680ae --- /dev/null +++ b/src/lib/audio/processors/HighPassSweepUp.ts @@ -0,0 +1,65 @@ +import type { AudioProcessor, ProcessorCategory } from './AudioProcessor'; + +export class HighPassSweepUp implements AudioProcessor { + getName(): string { + return 'HP Sweep Up'; + } + + getDescription(): string { + return 'Sweeps a high-pass filter from full to thin'; + } + + getCategory(): ProcessorCategory { + return 'Filter'; + } + + process(leftIn: Float32Array, rightIn: Float32Array): [Float32Array, Float32Array] { + const length = leftIn.length; + const leftOut = new Float32Array(length); + const rightOut = new Float32Array(length); + + const sampleRate = 44100; + const startFreq = 20; + const endFreq = 8000; + const Q = 1.5; + + let lx1 = 0, lx2 = 0, ly1 = 0, ly2 = 0; + let rx1 = 0, rx2 = 0, ry1 = 0, ry2 = 0; + + let b0 = 0, b1 = 0, b2 = 0, a1 = 0, a2 = 0; + + for (let i = 0; i < length; i++) { + if (i % 64 === 0) { + const t = i / length; + const freq = startFreq * Math.pow(endFreq / startFreq, t); + const omega = 2.0 * Math.PI * freq / sampleRate; + const alpha = Math.sin(omega) / (2.0 * Q); + + const a0 = 1.0 + alpha; + b0 = ((1.0 + Math.cos(omega)) / 2.0) / a0; + b1 = (-(1.0 + Math.cos(omega))) / a0; + b2 = b0; + a1 = (-2.0 * Math.cos(omega)) / a0; + a2 = (1.0 - alpha) / a0; + } + + const lx0 = leftIn[i]; + const rx0 = rightIn[i]; + + leftOut[i] = b0 * lx0 + b1 * lx1 + b2 * lx2 - a1 * ly1 - a2 * ly2; + rightOut[i] = b0 * rx0 + b1 * rx1 + b2 * rx2 - a1 * ry1 - a2 * ry2; + + lx2 = lx1; + lx1 = lx0; + ly2 = ly1; + ly1 = leftOut[i]; + + rx2 = rx1; + rx1 = rx0; + ry2 = ry1; + ry1 = rightOut[i]; + } + + return [leftOut, rightOut]; + } +} diff --git a/src/lib/audio/processors/LinearFadeIn.ts b/src/lib/audio/processors/LinearFadeIn.ts index c7c1c91..32113de 100644 --- a/src/lib/audio/processors/LinearFadeIn.ts +++ b/src/lib/audio/processors/LinearFadeIn.ts @@ -1,4 +1,4 @@ -import type { AudioProcessor } from './AudioProcessor'; +import type { AudioProcessor, ProcessorCategory } from './AudioProcessor'; export class LinearFadeIn implements AudioProcessor { getName(): string { @@ -9,6 +9,10 @@ export class LinearFadeIn implements AudioProcessor { return 'Applies a linear fade from silence to current level'; } + getCategory(): ProcessorCategory { + return 'Amplitude'; + } + process(leftIn: Float32Array, rightIn: Float32Array): [Float32Array, Float32Array] { const length = leftIn.length; diff --git a/src/lib/audio/processors/LinearFadeOut.ts b/src/lib/audio/processors/LinearFadeOut.ts index aa62262..ab20c6a 100644 --- a/src/lib/audio/processors/LinearFadeOut.ts +++ b/src/lib/audio/processors/LinearFadeOut.ts @@ -1,4 +1,4 @@ -import type { AudioProcessor } from './AudioProcessor'; +import type { AudioProcessor, ProcessorCategory } from './AudioProcessor'; export class LinearFadeOut implements AudioProcessor { getName(): string { @@ -9,6 +9,10 @@ export class LinearFadeOut implements AudioProcessor { return 'Applies a linear fade from current level to silence'; } + getCategory(): ProcessorCategory { + return 'Amplitude'; + } + process(leftIn: Float32Array, rightIn: Float32Array): [Float32Array, Float32Array] { const length = leftIn.length; diff --git a/src/lib/audio/processors/LowPassSweepDown.ts b/src/lib/audio/processors/LowPassSweepDown.ts new file mode 100644 index 0000000..9d51149 --- /dev/null +++ b/src/lib/audio/processors/LowPassSweepDown.ts @@ -0,0 +1,65 @@ +import type { AudioProcessor, ProcessorCategory } from './AudioProcessor'; + +export class LowPassSweepDown implements AudioProcessor { + getName(): string { + return 'LP Sweep Down'; + } + + getDescription(): string { + return 'Sweeps a low-pass filter from bright to dark'; + } + + getCategory(): ProcessorCategory { + return 'Filter'; + } + + process(leftIn: Float32Array, rightIn: Float32Array): [Float32Array, Float32Array] { + const length = leftIn.length; + const leftOut = new Float32Array(length); + const rightOut = new Float32Array(length); + + const sampleRate = 44100; + const startFreq = 18000; + const endFreq = 100; + const Q = 1.5; + + let lx1 = 0, lx2 = 0, ly1 = 0, ly2 = 0; + let rx1 = 0, rx2 = 0, ry1 = 0, ry2 = 0; + + let b0 = 0, b1 = 0, b2 = 0, a1 = 0, a2 = 0; + + for (let i = 0; i < length; i++) { + if (i % 64 === 0) { + const t = i / length; + const freq = startFreq * Math.pow(endFreq / startFreq, t); + const omega = 2.0 * Math.PI * freq / sampleRate; + const alpha = Math.sin(omega) / (2.0 * Q); + + const a0 = 1.0 + alpha; + b0 = ((1.0 - Math.cos(omega)) / 2.0) / a0; + b1 = (1.0 - Math.cos(omega)) / a0; + b2 = b0; + a1 = (-2.0 * Math.cos(omega)) / a0; + a2 = (1.0 - alpha) / a0; + } + + const lx0 = leftIn[i]; + const rx0 = rightIn[i]; + + leftOut[i] = b0 * lx0 + b1 * lx1 + b2 * lx2 - a1 * ly1 - a2 * ly2; + rightOut[i] = b0 * rx0 + b1 * rx1 + b2 * rx2 - a1 * ry1 - a2 * ry2; + + lx2 = lx1; + lx1 = lx0; + ly2 = ly1; + ly1 = leftOut[i]; + + rx2 = rx1; + rx1 = rx0; + ry2 = ry1; + ry1 = rightOut[i]; + } + + return [leftOut, rightOut]; + } +} diff --git a/src/lib/audio/processors/LowPassSweepUp.ts b/src/lib/audio/processors/LowPassSweepUp.ts new file mode 100644 index 0000000..7215c79 --- /dev/null +++ b/src/lib/audio/processors/LowPassSweepUp.ts @@ -0,0 +1,65 @@ +import type { AudioProcessor, ProcessorCategory } from './AudioProcessor'; + +export class LowPassSweepUp implements AudioProcessor { + getName(): string { + return 'LP Sweep Up'; + } + + getDescription(): string { + return 'Sweeps a low-pass filter from dark to bright'; + } + + getCategory(): ProcessorCategory { + return 'Filter'; + } + + process(leftIn: Float32Array, rightIn: Float32Array): [Float32Array, Float32Array] { + const length = leftIn.length; + const leftOut = new Float32Array(length); + const rightOut = new Float32Array(length); + + const sampleRate = 44100; + const startFreq = 100; + const endFreq = 18000; + const Q = 1.5; + + let lx1 = 0, lx2 = 0, ly1 = 0, ly2 = 0; + let rx1 = 0, rx2 = 0, ry1 = 0, ry2 = 0; + + let b0 = 0, b1 = 0, b2 = 0, a1 = 0, a2 = 0; + + for (let i = 0; i < length; i++) { + if (i % 64 === 0) { + const t = i / length; + const freq = startFreq * Math.pow(endFreq / startFreq, t); + const omega = 2.0 * Math.PI * freq / sampleRate; + const alpha = Math.sin(omega) / (2.0 * Q); + + const a0 = 1.0 + alpha; + b0 = ((1.0 - Math.cos(omega)) / 2.0) / a0; + b1 = (1.0 - Math.cos(omega)) / a0; + b2 = b0; + a1 = (-2.0 * Math.cos(omega)) / a0; + a2 = (1.0 - alpha) / a0; + } + + const lx0 = leftIn[i]; + const rx0 = rightIn[i]; + + leftOut[i] = b0 * lx0 + b1 * lx1 + b2 * lx2 - a1 * ly1 - a2 * ly2; + rightOut[i] = b0 * rx0 + b1 * rx1 + b2 * rx2 - a1 * ry1 - a2 * ry2; + + lx2 = lx1; + lx1 = lx0; + ly2 = ly1; + ly1 = leftOut[i]; + + rx2 = rx1; + rx1 = rx0; + ry2 = ry1; + ry1 = rightOut[i]; + } + + return [leftOut, rightOut]; + } +} diff --git a/src/lib/audio/processors/MicroPitch.ts b/src/lib/audio/processors/MicroPitch.ts index cb2ec8b..f67b583 100644 --- a/src/lib/audio/processors/MicroPitch.ts +++ b/src/lib/audio/processors/MicroPitch.ts @@ -1,4 +1,4 @@ -import type { AudioProcessor } from './AudioProcessor'; +import type { AudioProcessor, ProcessorCategory } from './AudioProcessor'; export class MicroPitch implements AudioProcessor { getName(): string { @@ -9,6 +9,10 @@ export class MicroPitch implements AudioProcessor { return 'Applies subtle random pitch variations for analog warmth and character'; } + getCategory(): ProcessorCategory { + return 'Pitch'; + } + process( leftChannel: Float32Array, rightChannel: Float32Array diff --git a/src/lib/audio/processors/Normalize.ts b/src/lib/audio/processors/Normalize.ts index ac8441c..c665251 100644 --- a/src/lib/audio/processors/Normalize.ts +++ b/src/lib/audio/processors/Normalize.ts @@ -1,4 +1,4 @@ -import type { AudioProcessor } from "./AudioProcessor"; +import type { AudioProcessor, ProcessorCategory } from "./AudioProcessor"; export class Normalize implements AudioProcessor { getName(): string { @@ -9,6 +9,10 @@ export class Normalize implements AudioProcessor { return "Normalizes audio to maximum amplitude without clipping"; } + getCategory(): ProcessorCategory { + return 'Amplitude'; + } + async process( leftChannel: Float32Array, rightChannel: Float32Array diff --git a/src/lib/audio/processors/OctaveDown.ts b/src/lib/audio/processors/OctaveDown.ts index eaebae7..ee404ec 100644 --- a/src/lib/audio/processors/OctaveDown.ts +++ b/src/lib/audio/processors/OctaveDown.ts @@ -1,4 +1,4 @@ -import type { AudioProcessor } from "./AudioProcessor"; +import type { AudioProcessor, ProcessorCategory } from "./AudioProcessor"; export class OctaveDown implements AudioProcessor { getName(): string { @@ -9,6 +9,10 @@ export class OctaveDown implements AudioProcessor { return "Shifts pitch down one octave by halving playback rate"; } + getCategory(): ProcessorCategory { + return 'Pitch'; + } + async process( leftChannel: Float32Array, rightChannel: Float32Array diff --git a/src/lib/audio/processors/OctaveUp.ts b/src/lib/audio/processors/OctaveUp.ts index 65c3d0e..76c9a2d 100644 --- a/src/lib/audio/processors/OctaveUp.ts +++ b/src/lib/audio/processors/OctaveUp.ts @@ -1,4 +1,4 @@ -import type { AudioProcessor } from "./AudioProcessor"; +import type { AudioProcessor, ProcessorCategory } from "./AudioProcessor"; export class OctaveUp implements AudioProcessor { getName(): string { @@ -9,6 +9,10 @@ export class OctaveUp implements AudioProcessor { return "Shifts pitch up one octave by doubling playback rate"; } + getCategory(): ProcessorCategory { + return 'Pitch'; + } + async process( leftChannel: Float32Array, rightChannel: Float32Array diff --git a/src/lib/audio/processors/PanLeftToRight.ts b/src/lib/audio/processors/PanLeftToRight.ts new file mode 100644 index 0000000..d1f3bf8 --- /dev/null +++ b/src/lib/audio/processors/PanLeftToRight.ts @@ -0,0 +1,34 @@ +import type { AudioProcessor, ProcessorCategory } from './AudioProcessor'; + +export class PanLeftToRight implements AudioProcessor { + getName(): string { + return 'Pan L→R'; + } + + getDescription(): string { + return 'Gradually pans the sound from left to right'; + } + + getCategory(): ProcessorCategory { + return 'Space'; + } + + process(leftIn: Float32Array, rightIn: Float32Array): [Float32Array, Float32Array] { + const length = leftIn.length; + const leftOut = new Float32Array(length); + const rightOut = new Float32Array(length); + + for (let i = 0; i < length; i++) { + const t = i / length; + const panAngle = t * Math.PI * 0.5; + const leftGain = Math.cos(panAngle); + const rightGain = Math.sin(panAngle); + + const mono = (leftIn[i] + rightIn[i]) * 0.5; + leftOut[i] = mono * leftGain; + rightOut[i] = mono * rightGain; + } + + return [leftOut, rightOut]; + } +} diff --git a/src/lib/audio/processors/PanRightToLeft.ts b/src/lib/audio/processors/PanRightToLeft.ts new file mode 100644 index 0000000..c1d62e5 --- /dev/null +++ b/src/lib/audio/processors/PanRightToLeft.ts @@ -0,0 +1,34 @@ +import type { AudioProcessor, ProcessorCategory } from './AudioProcessor'; + +export class PanRightToLeft implements AudioProcessor { + getName(): string { + return 'Pan R→L'; + } + + getDescription(): string { + return 'Gradually pans the sound from right to left'; + } + + getCategory(): ProcessorCategory { + return 'Space'; + } + + process(leftIn: Float32Array, rightIn: Float32Array): [Float32Array, Float32Array] { + const length = leftIn.length; + const leftOut = new Float32Array(length); + const rightOut = new Float32Array(length); + + for (let i = 0; i < length; i++) { + const t = i / length; + const panAngle = (1.0 - t) * Math.PI * 0.5; + const leftGain = Math.cos(panAngle); + const rightGain = Math.sin(panAngle); + + const mono = (leftIn[i] + rightIn[i]) * 0.5; + leftOut[i] = mono * leftGain; + rightOut[i] = mono * rightGain; + } + + return [leftOut, rightOut]; + } +} diff --git a/src/lib/audio/processors/PanShuffler.ts b/src/lib/audio/processors/PanShuffler.ts index 5f631d9..76b96c4 100644 --- a/src/lib/audio/processors/PanShuffler.ts +++ b/src/lib/audio/processors/PanShuffler.ts @@ -1,4 +1,4 @@ -import type { AudioProcessor } from './AudioProcessor'; +import type { AudioProcessor, ProcessorCategory } from './AudioProcessor'; export class PanShuffler implements AudioProcessor { getName(): string { @@ -9,6 +9,10 @@ export class PanShuffler implements AudioProcessor { return 'Smoothly pans segments across the stereo field'; } + getCategory(): ProcessorCategory { + return 'Space'; + } + process( leftChannel: Float32Array, rightChannel: Float32Array diff --git a/src/lib/audio/processors/PhaseInverter.ts b/src/lib/audio/processors/PhaseInverter.ts index dead268..b9a5b05 100644 --- a/src/lib/audio/processors/PhaseInverter.ts +++ b/src/lib/audio/processors/PhaseInverter.ts @@ -1,4 +1,4 @@ -import type { AudioProcessor } from "./AudioProcessor"; +import type { AudioProcessor, ProcessorCategory } from "./AudioProcessor"; export class PhaseInverter implements AudioProcessor { getName(): string { @@ -9,6 +9,10 @@ export class PhaseInverter implements AudioProcessor { return "Inverts polarity of one or both channels"; } + getCategory(): ProcessorCategory { + return 'Utility'; + } + async process( leftChannel: Float32Array, rightChannel: Float32Array diff --git a/src/lib/audio/processors/Phaser.ts b/src/lib/audio/processors/Phaser.ts index ce9f9e9..06e4ffd 100644 --- a/src/lib/audio/processors/Phaser.ts +++ b/src/lib/audio/processors/Phaser.ts @@ -1,4 +1,4 @@ -import type { AudioProcessor } from './AudioProcessor'; +import type { AudioProcessor, ProcessorCategory } from './AudioProcessor'; export class Phaser implements AudioProcessor { private readonly sampleRate = 44100; @@ -11,6 +11,10 @@ export class Phaser implements AudioProcessor { return 'Classic phaser effect with sweeping all-pass filters for swirling, spacey sounds'; } + getCategory(): ProcessorCategory { + return 'Filter'; + } + process( leftChannel: Float32Array, rightChannel: Float32Array diff --git a/src/lib/audio/processors/PitchShifter.ts b/src/lib/audio/processors/PitchShifter.ts index e1c6738..1ec01d6 100644 --- a/src/lib/audio/processors/PitchShifter.ts +++ b/src/lib/audio/processors/PitchShifter.ts @@ -1,4 +1,4 @@ -import type { AudioProcessor } from './AudioProcessor'; +import type { AudioProcessor, ProcessorCategory } from './AudioProcessor'; export class PitchShifter implements AudioProcessor { getName(): string { @@ -9,6 +9,10 @@ export class PitchShifter implements AudioProcessor { return 'Transposes audio up or down in semitones without changing duration'; } + getCategory(): ProcessorCategory { + return 'Pitch'; + } + process( leftChannel: Float32Array, rightChannel: Float32Array diff --git a/src/lib/audio/processors/PitchWobble.ts b/src/lib/audio/processors/PitchWobble.ts index a2ddda6..095f184 100644 --- a/src/lib/audio/processors/PitchWobble.ts +++ b/src/lib/audio/processors/PitchWobble.ts @@ -1,4 +1,4 @@ -import type { AudioProcessor } from './AudioProcessor'; +import type { AudioProcessor, ProcessorCategory } from './AudioProcessor'; export class PitchWobble implements AudioProcessor { getName(): string { @@ -9,6 +9,10 @@ export class PitchWobble implements AudioProcessor { return 'Variable-rate playback with LFO modulation for tape wow/vibrato effects'; } + getCategory(): ProcessorCategory { + return 'Pitch'; + } + process( leftChannel: Float32Array, rightChannel: Float32Array diff --git a/src/lib/audio/processors/Resonator.ts b/src/lib/audio/processors/Resonator.ts index 5cc3e13..3e6e1b6 100644 --- a/src/lib/audio/processors/Resonator.ts +++ b/src/lib/audio/processors/Resonator.ts @@ -1,4 +1,4 @@ -import type { AudioProcessor } from './AudioProcessor'; +import type { AudioProcessor, ProcessorCategory } from './AudioProcessor'; export class Resonator implements AudioProcessor { private readonly sampleRate = 44100; @@ -11,6 +11,10 @@ export class Resonator implements AudioProcessor { return 'Multi-band resonant filter bank that adds tonal character through resonance'; } + getCategory(): ProcessorCategory { + return 'Filter'; + } + process( leftChannel: Float32Array, rightChannel: Float32Array diff --git a/src/lib/audio/processors/Reverser.ts b/src/lib/audio/processors/Reverser.ts index ad1bd63..749c727 100644 --- a/src/lib/audio/processors/Reverser.ts +++ b/src/lib/audio/processors/Reverser.ts @@ -1,4 +1,4 @@ -import type { AudioProcessor } from './AudioProcessor'; +import type { AudioProcessor, ProcessorCategory } from './AudioProcessor'; export class Reverser implements AudioProcessor { getName(): string { @@ -9,6 +9,10 @@ export class Reverser implements AudioProcessor { return 'Plays the sound backwards'; } + getCategory(): ProcessorCategory { + return 'Time'; + } + process( leftChannel: Float32Array, rightChannel: Float32Array diff --git a/src/lib/audio/processors/RingModulator.ts b/src/lib/audio/processors/RingModulator.ts index ebb5584..d449a2e 100644 --- a/src/lib/audio/processors/RingModulator.ts +++ b/src/lib/audio/processors/RingModulator.ts @@ -1,4 +1,4 @@ -import type { AudioProcessor } from "./AudioProcessor"; +import type { AudioProcessor, ProcessorCategory } from "./AudioProcessor"; export class RingModulator implements AudioProcessor { getName(): string { @@ -9,6 +9,10 @@ export class RingModulator implements AudioProcessor { return "Frequency modulation for metallic, bell-like tones"; } + getCategory(): ProcessorCategory { + return 'Modulation'; + } + async process( leftChannel: Float32Array, rightChannel: Float32Array diff --git a/src/lib/audio/processors/SegmentShuffler.ts b/src/lib/audio/processors/SegmentShuffler.ts index 52b9eee..26a4df4 100644 --- a/src/lib/audio/processors/SegmentShuffler.ts +++ b/src/lib/audio/processors/SegmentShuffler.ts @@ -1,4 +1,4 @@ -import type { AudioProcessor } from './AudioProcessor'; +import type { AudioProcessor, ProcessorCategory } from './AudioProcessor'; export class SegmentShuffler implements AudioProcessor { getName(): string { @@ -9,6 +9,10 @@ export class SegmentShuffler implements AudioProcessor { return 'Randomly reorganizes and swaps parts of the sound'; } + getCategory(): ProcessorCategory { + return 'Time'; + } + process( leftChannel: Float32Array, rightChannel: Float32Array diff --git a/src/lib/audio/processors/SlowTapeStop.ts b/src/lib/audio/processors/SlowTapeStop.ts new file mode 100644 index 0000000..b729e75 --- /dev/null +++ b/src/lib/audio/processors/SlowTapeStop.ts @@ -0,0 +1,50 @@ +import type { AudioProcessor, ProcessorCategory } from './AudioProcessor'; + +export class SlowTapeStop implements AudioProcessor { + getName(): string { + return 'Slow Tape Stop'; + } + + getDescription(): string { + return 'Simulates a tape machine gradually slowing to a stop'; + } + + getCategory(): ProcessorCategory { + return 'Time'; + } + + process(leftIn: Float32Array, rightIn: Float32Array): [Float32Array, Float32Array] { + const length = leftIn.length; + const leftOut = new Float32Array(length); + const rightOut = new Float32Array(length); + + const stopPoint = length * 0.4; + const stopDuration = length * 0.6; + + let readPos = 0; + + for (let i = 0; i < length; i++) { + if (i < stopPoint) { + readPos = i; + } else { + const t = (i - stopPoint) / stopDuration; + const curve = 1.0 - (t * t * t); + const speed = Math.max(0, curve); + readPos += speed; + } + + const idx = Math.floor(readPos); + const frac = readPos - idx; + + if (idx < length - 1) { + leftOut[i] = leftIn[idx] * (1.0 - frac) + leftIn[idx + 1] * frac; + rightOut[i] = rightIn[idx] * (1.0 - frac) + rightIn[idx + 1] * frac; + } else if (idx < length) { + leftOut[i] = leftIn[idx]; + rightOut[i] = rightIn[idx]; + } + } + + return [leftOut, rightOut]; + } +} diff --git a/src/lib/audio/processors/SpectralBlur.ts b/src/lib/audio/processors/SpectralBlur.ts index a8763c8..7d8d1c6 100644 --- a/src/lib/audio/processors/SpectralBlur.ts +++ b/src/lib/audio/processors/SpectralBlur.ts @@ -1,4 +1,4 @@ -import type { AudioProcessor } from './AudioProcessor'; +import type { AudioProcessor, ProcessorCategory } from './AudioProcessor'; export class SpectralBlur implements AudioProcessor { getName(): string { @@ -9,6 +9,10 @@ export class SpectralBlur implements AudioProcessor { return 'Smears frequency content across neighboring bins for dreamy, diffused textures'; } + getCategory(): ProcessorCategory { + return 'Spectral'; + } + process( leftChannel: Float32Array, rightChannel: Float32Array diff --git a/src/lib/audio/processors/SpectralShift.ts b/src/lib/audio/processors/SpectralShift.ts index b03b56c..91c4e4c 100644 --- a/src/lib/audio/processors/SpectralShift.ts +++ b/src/lib/audio/processors/SpectralShift.ts @@ -1,4 +1,4 @@ -import type { AudioProcessor } from './AudioProcessor'; +import type { AudioProcessor, ProcessorCategory } from './AudioProcessor'; export class SpectralShift implements AudioProcessor { getName(): string { @@ -9,6 +9,10 @@ export class SpectralShift implements AudioProcessor { return 'Shifts all frequencies by a fixed Hz amount creating inharmonic, metallic timbres'; } + getCategory(): ProcessorCategory { + return 'Spectral'; + } + process( leftChannel: Float32Array, rightChannel: Float32Array diff --git a/src/lib/audio/processors/StereoSwap.ts b/src/lib/audio/processors/StereoSwap.ts index 60d60fb..a102239 100644 --- a/src/lib/audio/processors/StereoSwap.ts +++ b/src/lib/audio/processors/StereoSwap.ts @@ -1,4 +1,4 @@ -import type { AudioProcessor } from './AudioProcessor'; +import type { AudioProcessor, ProcessorCategory } from './AudioProcessor'; export class StereoSwap implements AudioProcessor { getName(): string { @@ -9,6 +9,10 @@ export class StereoSwap implements AudioProcessor { return 'Swaps left and right channels'; } + getCategory(): ProcessorCategory { + return 'Space'; + } + process( leftChannel: Float32Array, rightChannel: Float32Array diff --git a/src/lib/audio/processors/StereoWidener.ts b/src/lib/audio/processors/StereoWidener.ts index 1e2d3e8..b3ec347 100644 --- a/src/lib/audio/processors/StereoWidener.ts +++ b/src/lib/audio/processors/StereoWidener.ts @@ -1,4 +1,4 @@ -import type { AudioProcessor } from "./AudioProcessor"; +import type { AudioProcessor, ProcessorCategory } from "./AudioProcessor"; export class StereoWidener implements AudioProcessor { getName(): string { @@ -9,6 +9,10 @@ export class StereoWidener implements AudioProcessor { return "Expands stereo field using mid-side processing"; } + getCategory(): ProcessorCategory { + return 'Space'; + } + async process( leftChannel: Float32Array, rightChannel: Float32Array diff --git a/src/lib/audio/processors/Stutter.ts b/src/lib/audio/processors/Stutter.ts index 13c84ca..dcb40a4 100644 --- a/src/lib/audio/processors/Stutter.ts +++ b/src/lib/audio/processors/Stutter.ts @@ -1,4 +1,4 @@ -import type { AudioProcessor } from './AudioProcessor'; +import type { AudioProcessor, ProcessorCategory } from './AudioProcessor'; export class Stutter implements AudioProcessor { getName(): string { @@ -9,6 +9,10 @@ export class Stutter implements AudioProcessor { return 'Rapidly repeats small fragments with smooth crossfades'; } + getCategory(): ProcessorCategory { + return 'Time'; + } + process( leftChannel: Float32Array, rightChannel: Float32Array diff --git a/src/lib/audio/processors/TapeSpeedUp.ts b/src/lib/audio/processors/TapeSpeedUp.ts new file mode 100644 index 0000000..b57b9cd --- /dev/null +++ b/src/lib/audio/processors/TapeSpeedUp.ts @@ -0,0 +1,49 @@ +import type { AudioProcessor, ProcessorCategory } from './AudioProcessor'; + +export class TapeSpeedUp implements AudioProcessor { + getName(): string { + return 'Tape Speed Up'; + } + + getDescription(): string { + return 'Simulates a tape machine accelerating from slow to normal speed'; + } + + getCategory(): ProcessorCategory { + return 'Time'; + } + + process(leftIn: Float32Array, rightIn: Float32Array): [Float32Array, Float32Array] { + const length = leftIn.length; + const leftOut = new Float32Array(length); + const rightOut = new Float32Array(length); + + const accelDuration = length * 0.3; + + let readPos = 0; + + for (let i = 0; i < length; i++) { + if (i < accelDuration) { + const t = i / accelDuration; + const curve = t * t * t; + const speed = curve; + readPos += speed; + } else { + readPos += 1.0; + } + + const idx = Math.floor(readPos); + const frac = readPos - idx; + + if (idx < length - 1) { + leftOut[i] = leftIn[idx] * (1.0 - frac) + leftIn[idx + 1] * frac; + rightOut[i] = rightIn[idx] * (1.0 - frac) + rightIn[idx + 1] * frac; + } else if (idx < length) { + leftOut[i] = leftIn[idx]; + rightOut[i] = rightIn[idx]; + } + } + + return [leftOut, rightOut]; + } +} diff --git a/src/lib/audio/processors/TapeStop.ts b/src/lib/audio/processors/TapeStop.ts new file mode 100644 index 0000000..9ded179 --- /dev/null +++ b/src/lib/audio/processors/TapeStop.ts @@ -0,0 +1,50 @@ +import type { AudioProcessor, ProcessorCategory } from './AudioProcessor'; + +export class TapeStop implements AudioProcessor { + getName(): string { + return 'Tape Stop'; + } + + getDescription(): string { + return 'Simulates a tape machine slowing to a stop'; + } + + getCategory(): ProcessorCategory { + return 'Time'; + } + + process(leftIn: Float32Array, rightIn: Float32Array): [Float32Array, Float32Array] { + const length = leftIn.length; + const leftOut = new Float32Array(length); + const rightOut = new Float32Array(length); + + const stopPoint = length * 0.7; + const stopDuration = length * 0.3; + + let readPos = 0; + + for (let i = 0; i < length; i++) { + if (i < stopPoint) { + readPos = i; + } else { + const t = (i - stopPoint) / stopDuration; + const curve = 1.0 - (t * t * t); + const speed = Math.max(0, curve); + readPos += speed; + } + + const idx = Math.floor(readPos); + const frac = readPos - idx; + + if (idx < length - 1) { + leftOut[i] = leftIn[idx] * (1.0 - frac) + leftIn[idx + 1] * frac; + rightOut[i] = rightIn[idx] * (1.0 - frac) + rightIn[idx + 1] * frac; + } else if (idx < length) { + leftOut[i] = leftIn[idx]; + rightOut[i] = rightIn[idx]; + } + } + + return [leftOut, rightOut]; + } +} diff --git a/src/lib/audio/processors/TapeWobble.ts b/src/lib/audio/processors/TapeWobble.ts new file mode 100644 index 0000000..24b007c --- /dev/null +++ b/src/lib/audio/processors/TapeWobble.ts @@ -0,0 +1,48 @@ +import type { AudioProcessor, ProcessorCategory } from './AudioProcessor'; + +export class TapeWobble implements AudioProcessor { + getName(): string { + return 'Tape Wobble'; + } + + getDescription(): string { + return 'Simulates tape machine speed instability with pitch variations'; + } + + getCategory(): ProcessorCategory { + return 'Time'; + } + + process(leftIn: Float32Array, rightIn: Float32Array): [Float32Array, Float32Array] { + const length = leftIn.length; + const leftOut = new Float32Array(length); + const rightOut = new Float32Array(length); + + const sampleRate = 44100; + const wobbleFreq = 2.0 + Math.random() * 2.0; + const wobbleDepth = 0.015; + + let readPos = 0; + + for (let i = 0; i < length; i++) { + const t = i / sampleRate; + const wobble = Math.sin(2.0 * Math.PI * wobbleFreq * t) * wobbleDepth; + const speed = 1.0 + wobble; + + readPos += speed; + + const idx = Math.floor(readPos); + const frac = readPos - idx; + + if (idx < length - 1) { + leftOut[i] = leftIn[idx] * (1.0 - frac) + leftIn[idx + 1] * frac; + rightOut[i] = rightIn[idx] * (1.0 - frac) + rightIn[idx + 1] * frac; + } else if (idx < length) { + leftOut[i] = leftIn[idx]; + rightOut[i] = rightIn[idx]; + } + } + + return [leftOut, rightOut]; + } +} diff --git a/src/lib/audio/processors/Tremolo.ts b/src/lib/audio/processors/Tremolo.ts index a03660a..880439c 100644 --- a/src/lib/audio/processors/Tremolo.ts +++ b/src/lib/audio/processors/Tremolo.ts @@ -1,4 +1,4 @@ -import type { AudioProcessor } from './AudioProcessor'; +import type { AudioProcessor, ProcessorCategory } from './AudioProcessor'; export class Tremolo implements AudioProcessor { getName(): string { @@ -9,6 +9,10 @@ export class Tremolo implements AudioProcessor { return 'Applies rhythmic volume modulation'; } + getCategory(): ProcessorCategory { + return 'Amplitude'; + } + process( leftChannel: Float32Array, rightChannel: Float32Array diff --git a/src/lib/audio/processors/TrimSilence.ts b/src/lib/audio/processors/TrimSilence.ts index 28eee67..39af509 100644 --- a/src/lib/audio/processors/TrimSilence.ts +++ b/src/lib/audio/processors/TrimSilence.ts @@ -1,4 +1,4 @@ -import type { AudioProcessor } from "./AudioProcessor"; +import type { AudioProcessor, ProcessorCategory } from "./AudioProcessor"; export class TrimSilence implements AudioProcessor { getName(): string { @@ -9,6 +9,10 @@ export class TrimSilence implements AudioProcessor { return "Removes leading and trailing silence from audio"; } + getCategory(): ProcessorCategory { + return 'Utility'; + } + async process( leftChannel: Float32Array, rightChannel: Float32Array diff --git a/src/lib/audio/processors/VinylStop.ts b/src/lib/audio/processors/VinylStop.ts new file mode 100644 index 0000000..95897cd --- /dev/null +++ b/src/lib/audio/processors/VinylStop.ts @@ -0,0 +1,50 @@ +import type { AudioProcessor, ProcessorCategory } from './AudioProcessor'; + +export class VinylStop implements AudioProcessor { + getName(): string { + return 'Vinyl Stop'; + } + + getDescription(): string { + return 'Simulates a turntable slowing to a stop with realistic physics'; + } + + getCategory(): ProcessorCategory { + return 'Time'; + } + + process(leftIn: Float32Array, rightIn: Float32Array): [Float32Array, Float32Array] { + const length = leftIn.length; + const leftOut = new Float32Array(length); + const rightOut = new Float32Array(length); + + const stopPoint = length * 0.7; + const stopDuration = length * 0.3; + + let readPos = 0; + + for (let i = 0; i < length; i++) { + if (i < stopPoint) { + readPos = i; + } else { + const t = (i - stopPoint) / stopDuration; + const curve = Math.exp(-5.0 * t); + const speed = Math.max(0, curve); + readPos += speed; + } + + const idx = Math.floor(readPos); + const frac = readPos - idx; + + if (idx < length - 1) { + leftOut[i] = leftIn[idx] * (1.0 - frac) + leftIn[idx + 1] * frac; + rightOut[i] = rightIn[idx] * (1.0 - frac) + rightIn[idx + 1] * frac; + } else if (idx < length) { + leftOut[i] = leftIn[idx]; + rightOut[i] = rightIn[idx]; + } + } + + return [leftOut, rightOut]; + } +} diff --git a/src/lib/audio/processors/Waveshaper.ts b/src/lib/audio/processors/Waveshaper.ts index 1cd7898..071cf63 100644 --- a/src/lib/audio/processors/Waveshaper.ts +++ b/src/lib/audio/processors/Waveshaper.ts @@ -1,4 +1,4 @@ -import type { AudioProcessor } from "./AudioProcessor"; +import type { AudioProcessor, ProcessorCategory } from "./AudioProcessor"; export class Waveshaper implements AudioProcessor { getName(): string { @@ -9,6 +9,10 @@ export class Waveshaper implements AudioProcessor { return "Transfer function distortion with various curve shapes"; } + getCategory(): ProcessorCategory { + return 'Distortion'; + } + async process( leftChannel: Float32Array, rightChannel: Float32Array diff --git a/src/lib/audio/processors/registry.ts b/src/lib/audio/processors/registry.ts index 1d118b1..77b01ac 100644 --- a/src/lib/audio/processors/registry.ts +++ b/src/lib/audio/processors/registry.ts @@ -29,6 +29,17 @@ import { LinearFadeOut } from './LinearFadeOut'; import { ExpFadeOut } from './ExpFadeOut'; import { LinearFadeIn } from './LinearFadeIn'; import { ExpFadeIn } from './ExpFadeIn'; +import { PanLeftToRight } from './PanLeftToRight'; +import { PanRightToLeft } from './PanRightToLeft'; +import { LowPassSweepDown } from './LowPassSweepDown'; +import { LowPassSweepUp } from './LowPassSweepUp'; +import { HighPassSweepDown } from './HighPassSweepDown'; +import { HighPassSweepUp } from './HighPassSweepUp'; +import { TapeStop } from './TapeStop'; +import { SlowTapeStop } from './SlowTapeStop'; +import { TapeSpeedUp } from './TapeSpeedUp'; +import { VinylStop } from './VinylStop'; +import { TapeWobble } from './TapeWobble'; const processors: AudioProcessor[] = [ new SegmentShuffler(), @@ -61,6 +72,17 @@ const processors: AudioProcessor[] = [ new ExpFadeOut(), new LinearFadeIn(), new ExpFadeIn(), + new PanLeftToRight(), + new PanRightToLeft(), + new LowPassSweepDown(), + new LowPassSweepUp(), + new HighPassSweepDown(), + new HighPassSweepUp(), + new TapeStop(), + new SlowTapeStop(), + new TapeSpeedUp(), + new VinylStop(), + new TapeWobble(), ]; export function getRandomProcessor(): AudioProcessor { diff --git a/src/lib/components/ProcessorPopup.svelte b/src/lib/components/ProcessorPopup.svelte index 88dd43a..6997822 100644 --- a/src/lib/components/ProcessorPopup.svelte +++ b/src/lib/components/ProcessorPopup.svelte @@ -1,28 +1,59 @@
- {#each allProcessors as processor} - - {/each} + +
+ {#each filteredProcessors as processor} + + {/each} +