From 467558efd2ece96e958f47788ce6cb8884a8cf64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Forment?= Date: Mon, 13 Oct 2025 12:41:39 +0200 Subject: [PATCH] Rework the interface a bit --- src/App.svelte | 398 +++++++++--------- src/lib/audio/engines/AdditiveEngine.ts | 2 +- src/lib/audio/engines/BassDrum.ts | 2 +- src/lib/audio/engines/Benjolin.ts | 2 +- src/lib/audio/engines/DubSiren.ts | 2 +- src/lib/audio/engines/DustNoise.ts | 2 +- src/lib/audio/engines/FourOpFM.ts | 2 +- src/lib/audio/engines/HiHat.ts | 2 +- src/lib/audio/engines/Input.ts | 2 +- src/lib/audio/engines/KarplusStrong.ts | 2 +- src/lib/audio/engines/NoiseDrum.ts | 2 +- src/lib/audio/engines/ParticleNoise.ts | 2 +- src/lib/audio/engines/PhaseDistortionFM.ts | 2 +- src/lib/audio/engines/Ring.ts | 2 +- src/lib/audio/engines/Sample.ts | 2 +- src/lib/audio/engines/Snare.ts | 2 +- src/lib/audio/engines/SubtractiveThreeOsc.ts | 6 +- src/lib/audio/engines/TwoOpFM.ts | 2 +- src/lib/audio/engines/WavetableEngine.ts | 2 +- src/lib/audio/engines/ZzfxEngine.ts | 2 +- .../audio/engines/{ => base}/CsoundEngine.ts | 0 .../audio/engines/{ => base}/SynthEngine.ts | 0 src/lib/audio/engines/registry.ts | 2 +- 23 files changed, 218 insertions(+), 224 deletions(-) rename src/lib/audio/engines/{ => base}/CsoundEngine.ts (100%) rename src/lib/audio/engines/{ => base}/SynthEngine.ts (100%) diff --git a/src/App.svelte b/src/App.svelte index 9e65e29..5d1668c 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -5,8 +5,8 @@ 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/SynthEngine"; - import type { EngineType } from "./lib/audio/engines/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 } from "./lib/utils/settings"; @@ -45,6 +45,7 @@ let selectionStart = $state(null); let selectionEnd = $state(null); let canUndo = $state(false); + let sidebarOpen = $state(false); const showDuration = $derived(engineType !== 'sample'); const showRandomButton = $derived(engineType === 'generative'); @@ -256,6 +257,7 @@ currentParams = null; isProcessed = false; clearSelection(); + sidebarOpen = false; if (engineType === 'generative') { generateRandom(); @@ -393,25 +395,28 @@ showModal = false; await audioService.initialize(); } + + function toggleSidebar() { + sidebarOpen = !sidebarOpen; + }
+ {#if sidebarOpen} + + {/if}
-
- {#each engines as currentEngine, index} - - {/each} -
-
+ +

Poof: a sample generator

+
{#if showPitchLock}
@@ -477,7 +482,23 @@
-
+
+ + +
{#if showFileDropZone}
+
{#if showModal} @@ -568,33 +590,105 @@ .top-bar { display: flex; - flex-direction: column; - gap: 0.75rem; - padding: 0.5rem; + flex-direction: row; + align-items: center; + gap: 0.5rem; + padding: 0.35rem 0.5rem; background-color: #1a1a1a; border-bottom: 1px solid #333; + width: 100%; + box-sizing: border-box; + overflow: hidden; } - .mode-buttons { + .content-wrapper { + flex: 1; display: flex; - gap: 0.5rem; - overflow-x: auto; - overflow-y: hidden; - -webkit-overflow-scrolling: touch; + flex-direction: row; + overflow: hidden; + } + + .sidebar { + display: flex; + flex-direction: column; + position: fixed; + left: -100%; + top: 0; + width: 70%; + max-width: 300px; + height: 100%; + background-color: #1a1a1a; + border-right: 1px solid #333; + z-index: 2000; + transition: left 0.3s ease; + } + + .sidebar.open { + left: 0; + } + + .sidebar-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.5); + z-index: 1999; + } + + .hamburger { + display: flex; + align-items: center; + justify-content: center; + padding: 0.25rem; + background-color: transparent; + border: 1px solid #3a3a3a; + color: #fff; + cursor: pointer; + transition: border-color 0.2s, background-color 0.2s; + } + + .hamburger svg { + width: 20px; + height: 20px; + } + + .hamburger:hover { + border-color: #646cff; + background-color: #0f0f0f; + } + + .app-title { + font-size: 0.8rem; + font-weight: 600; + color: #fff; + margin: 0; + padding: 0; + letter-spacing: 0.05em; + white-space: nowrap; + } + + .sidebar-content { + display: flex; + flex-direction: column; + gap: 0; + overflow-y: auto; + overflow-x: hidden; scrollbar-width: thin; scrollbar-color: #444 transparent; - padding-bottom: 0.25rem; + height: 100%; } - .mode-buttons::-webkit-scrollbar { - height: 4px; + .sidebar-content::-webkit-scrollbar { + width: 4px; } - .mode-buttons::-webkit-scrollbar-track { + .sidebar-content::-webkit-scrollbar-track { background: transparent; } - .mode-buttons::-webkit-scrollbar-thumb { + .sidebar-content::-webkit-scrollbar-thumb { background: #444; border-radius: 0; } @@ -603,10 +697,14 @@ opacity: 0.7; position: relative; flex-shrink: 0; - font-size: 0.85rem; - padding: 0.5rem 0.75rem; + font-size: 0.75rem; + padding: 0.5rem 0.5rem; white-space: nowrap; - min-width: calc(25vw - 1rem); + overflow: hidden; + text-overflow: ellipsis; + text-align: left; + border: none; + border-bottom: 1px solid #2a2a2a; } .engine-button:hover { @@ -621,15 +719,15 @@ .engine-button::after { content: attr(data-description); position: absolute; - top: 100%; - left: 0; + top: 0; + left: 100%; + margin-left: 0.5rem; padding: 0.5rem 0.75rem; background-color: #0a0a0a; border: 1px solid #444; color: #ccc; - font-size: 0.85rem; + font-size: 0.75rem; width: 200px; - max-width: 90vw; white-space: normal; word-wrap: break-word; pointer-events: none; @@ -641,19 +739,25 @@ .controls-group { display: flex; - flex-direction: column; - gap: 0.75rem; - width: 100%; + flex-direction: row; + gap: 0.25rem; + align-items: center; + margin-left: auto; + flex-shrink: 1; + min-width: 0; } .control-item { display: flex; - flex-direction: column; - gap: 0.35rem; + flex-direction: row; + gap: 0.5rem; + align-items: center; background-color: #0f0f0f; - padding: 0.5rem 0.65rem; + padding: 0.25rem 0.5rem; border: 1px solid #2a2a2a; transition: border-color 0.2s; + flex: 1 1 auto; + min-width: 0; } .control-item:hover { @@ -662,38 +766,39 @@ .control-header { display: flex; - justify-content: space-between; align-items: center; - gap: 0.5rem; + gap: 0.25rem; } .control-header label { - font-size: 0.75rem; + font-size: 0.65rem; text-transform: uppercase; - letter-spacing: 0.05em; + letter-spacing: 0.03em; color: #999; font-weight: 500; white-space: nowrap; } .control-value-display { - font-size: 0.8rem; + font-size: 0.65rem; color: #fff; font-weight: 600; font-variant-numeric: tabular-nums; - min-width: 3.5rem; + min-width: 2rem; text-align: right; } .control-item input[type="range"] { width: 100%; + min-width: 80px; + max-width: 120px; margin: 0; } .custom-checkbox { display: flex; align-items: center; - gap: 0.35rem; + gap: 0.25rem; cursor: pointer; user-select: none; } @@ -709,8 +814,6 @@ display: flex; align-items: center; justify-content: center; - width: 14px; - height: 14px; border: 1px solid #3a3a3a; background-color: #1a1a1a; transition: all 0.2s; @@ -733,25 +836,35 @@ } .checkbox-text { - font-size: 0.7rem; + font-size: 0.6rem; text-transform: uppercase; - letter-spacing: 0.05em; + letter-spacing: 0.03em; color: #999; font-weight: 500; } + .checkbox-box { + width: 11px; + height: 11px; + } + + .checkbox-box svg { + width: 7px; + height: 7px; + } + .custom-checkbox:hover .checkbox-text { color: #aaa; } .pitch-input { - width: 100%; + width: 50px; min-width: 0; - padding: 0.35rem 0.5rem; + padding: 0.2rem 0.3rem; background-color: #1a1a1a; border: 1px solid #3a3a3a; color: #fff; - font-size: 0.85rem; + font-size: 0.7rem; font-weight: 600; transition: border-color 0.2s, background-color 0.2s, box-shadow 0.2s; font-variant-numeric: tabular-nums; @@ -784,163 +897,44 @@ font-weight: 400; } - @media (min-width: 640px) { - .controls-group { - flex-direction: row; - flex-wrap: wrap; - } - - .control-item { - min-width: 140px; - flex: 1; - } - - .pitch-lock-control { - min-width: 160px; - } - - .control-item input[type="range"] { - min-width: 80px; - } - } @media (min-width: 768px) { - .top-bar { - flex-direction: row; - justify-content: space-between; - align-items: center; - gap: 1rem; + .sidebar { + position: static; + left: 0; + width: 12%; + min-width: 120px; + max-width: 200px; + transition: none; + height: auto; } - .mode-buttons { - flex: 1; - overflow-x: auto; - padding-bottom: 0; - max-width: 60%; + .sidebar-overlay { + display: none; } - .engine-button { - font-size: 0.9rem; - padding: 0.6rem 1rem; - min-width: auto; + .hamburger { + display: none; + } + + .app-title { + font-size: 1rem; } .engine-button::after { display: block; - width: 250px; } .engine-button:hover::after { opacity: 1; } - - .controls-group { - width: auto; - flex-shrink: 0; - gap: 0.5rem; - flex-wrap: nowrap; - } - - .control-item { - min-width: 120px; - padding: 0.45rem 0.6rem; - } - - .pitch-lock-control { - min-width: 140px; - } - - .control-header label { - font-size: 0.7rem; - } - - .control-value-display { - font-size: 0.75rem; - min-width: 3rem; - } - - .checkbox-text { - font-size: 0.65rem; - } - - .checkbox-box { - width: 13px; - height: 13px; - } - - .checkbox-box svg { - width: 9px; - height: 9px; - } - - .pitch-input { - font-size: 0.8rem; - padding: 0.3rem 0.45rem; - } - - .control-item input[type="range"] { - min-width: 70px; - } } @media (min-width: 1024px) { - .mode-buttons { - max-width: 65%; - } - - .control-item { + .sidebar { + width: 10%; min-width: 140px; - padding: 0.5rem 0.65rem; - } - - .pitch-lock-control { - min-width: 160px; - } - - .control-header label { - font-size: 0.75rem; - } - - .control-value-display { - font-size: 0.8rem; - min-width: 3.5rem; - } - - .checkbox-text { - font-size: 0.7rem; - } - - .checkbox-box { - width: 14px; - height: 14px; - } - - .checkbox-box svg { - width: 10px; - height: 10px; - } - - .pitch-input { - font-size: 0.85rem; - padding: 0.35rem 0.5rem; - } - - .control-item input[type="range"] { - min-width: 90px; - } - } - - @media (min-width: 1280px) { - .control-item { - min-width: 160px; - } - - .pitch-lock-control { - min-width: 180px; - } - - .control-item input[type="range"] { - min-width: 110px; + max-width: 180px; } } @@ -1037,13 +1031,13 @@ -webkit-appearance: none; appearance: none; background: transparent; - height: 20px; + height: 1.5rem; border-radius: 0; } input[type="range"]::-webkit-slider-track { background: #333; - height: 4px; + height: 0.375rem; border: 1px solid #444; border-radius: 0; } @@ -1051,13 +1045,13 @@ input[type="range"]::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; - width: 16px; - height: 16px; + width: 1rem; + height: 1rem; background: #fff; border: 1px solid #000; border-radius: 0; cursor: pointer; - margin-top: -6px; + margin-top: -0.375rem; } input[type="range"]::-webkit-slider-thumb:hover { @@ -1066,14 +1060,14 @@ input[type="range"]::-moz-range-track { background: #333; - height: 4px; + height: 0.375rem; border: 1px solid #444; border-radius: 0; } input[type="range"]::-moz-range-thumb { - width: 16px; - height: 16px; + width: 1rem; + height: 1rem; background: #fff; border: 1px solid #000; border-radius: 0; diff --git a/src/lib/audio/engines/AdditiveEngine.ts b/src/lib/audio/engines/AdditiveEngine.ts index 91a6d59..3f6b485 100644 --- a/src/lib/audio/engines/AdditiveEngine.ts +++ b/src/lib/audio/engines/AdditiveEngine.ts @@ -1,4 +1,4 @@ -import type { SynthEngine, PitchLock } from './SynthEngine'; +import type { SynthEngine, PitchLock } from './base/SynthEngine'; enum EnvCurve { Linear, diff --git a/src/lib/audio/engines/BassDrum.ts b/src/lib/audio/engines/BassDrum.ts index 5113f6c..fc15f53 100644 --- a/src/lib/audio/engines/BassDrum.ts +++ b/src/lib/audio/engines/BassDrum.ts @@ -1,4 +1,4 @@ -import type { PitchLock, SynthEngine } from './SynthEngine'; +import type { PitchLock, SynthEngine } from './base/SynthEngine'; interface BassDrumParams { // Core frequency (base pitch of the kick) diff --git a/src/lib/audio/engines/Benjolin.ts b/src/lib/audio/engines/Benjolin.ts index 928aab5..5115af3 100644 --- a/src/lib/audio/engines/Benjolin.ts +++ b/src/lib/audio/engines/Benjolin.ts @@ -1,4 +1,4 @@ -import type { SynthEngine, PitchLock } from './SynthEngine'; +import type { SynthEngine, PitchLock } from './base/SynthEngine'; interface BenjolinParams { // Core oscillators diff --git a/src/lib/audio/engines/DubSiren.ts b/src/lib/audio/engines/DubSiren.ts index e756158..9242702 100644 --- a/src/lib/audio/engines/DubSiren.ts +++ b/src/lib/audio/engines/DubSiren.ts @@ -1,4 +1,4 @@ -import type { SynthEngine, PitchLock } from './SynthEngine'; +import type { SynthEngine, PitchLock } from './base/SynthEngine'; enum OscillatorWaveform { Sine, diff --git a/src/lib/audio/engines/DustNoise.ts b/src/lib/audio/engines/DustNoise.ts index ed98766..4c24c42 100644 --- a/src/lib/audio/engines/DustNoise.ts +++ b/src/lib/audio/engines/DustNoise.ts @@ -1,4 +1,4 @@ -import type { SynthEngine, PitchLock } from './SynthEngine'; +import type { SynthEngine, PitchLock } from './base/SynthEngine'; interface DustNoiseParams { // Dust density and character diff --git a/src/lib/audio/engines/FourOpFM.ts b/src/lib/audio/engines/FourOpFM.ts index ba6f8d8..865099e 100644 --- a/src/lib/audio/engines/FourOpFM.ts +++ b/src/lib/audio/engines/FourOpFM.ts @@ -1,4 +1,4 @@ -import type { SynthEngine, PitchLock } from './SynthEngine'; +import type { SynthEngine, PitchLock } from './base/SynthEngine'; enum EnvCurve { Linear, diff --git a/src/lib/audio/engines/HiHat.ts b/src/lib/audio/engines/HiHat.ts index 8b6ea72..54808b4 100644 --- a/src/lib/audio/engines/HiHat.ts +++ b/src/lib/audio/engines/HiHat.ts @@ -1,4 +1,4 @@ -import type { PitchLock, SynthEngine } from './SynthEngine'; +import type { PitchLock, SynthEngine } from './base/SynthEngine'; interface HiHatParams { // Decay time (0 = closed/tight, 1 = open/long) diff --git a/src/lib/audio/engines/Input.ts b/src/lib/audio/engines/Input.ts index aee8c12..9f179d9 100644 --- a/src/lib/audio/engines/Input.ts +++ b/src/lib/audio/engines/Input.ts @@ -1,4 +1,4 @@ -import type { SynthEngine, PitchLock } from './SynthEngine'; +import type { SynthEngine, PitchLock } from './base/SynthEngine'; interface InputParams { recorded: boolean; diff --git a/src/lib/audio/engines/KarplusStrong.ts b/src/lib/audio/engines/KarplusStrong.ts index adf0369..d461213 100644 --- a/src/lib/audio/engines/KarplusStrong.ts +++ b/src/lib/audio/engines/KarplusStrong.ts @@ -1,4 +1,4 @@ -import type { SynthEngine, PitchLock } from './SynthEngine'; +import type { SynthEngine, PitchLock } from './base/SynthEngine'; type HarmonicMode = | 'single' // Just fundamental diff --git a/src/lib/audio/engines/NoiseDrum.ts b/src/lib/audio/engines/NoiseDrum.ts index 215d7bf..06c71b9 100644 --- a/src/lib/audio/engines/NoiseDrum.ts +++ b/src/lib/audio/engines/NoiseDrum.ts @@ -1,4 +1,4 @@ -import type { SynthEngine } from './SynthEngine'; +import type { SynthEngine } from './base/SynthEngine'; interface NoiseDrumParams { // Noise characteristics diff --git a/src/lib/audio/engines/ParticleNoise.ts b/src/lib/audio/engines/ParticleNoise.ts index c8ed482..1c2894c 100644 --- a/src/lib/audio/engines/ParticleNoise.ts +++ b/src/lib/audio/engines/ParticleNoise.ts @@ -1,4 +1,4 @@ -import type { SynthEngine, PitchLock } from './SynthEngine'; +import type { SynthEngine, PitchLock } from './base/SynthEngine'; interface ParticleNoiseParams { // Particle characteristics diff --git a/src/lib/audio/engines/PhaseDistortionFM.ts b/src/lib/audio/engines/PhaseDistortionFM.ts index 5614382..786c80e 100644 --- a/src/lib/audio/engines/PhaseDistortionFM.ts +++ b/src/lib/audio/engines/PhaseDistortionFM.ts @@ -1,4 +1,4 @@ -import type { SynthEngine, PitchLock } from './SynthEngine'; +import type { SynthEngine, PitchLock } from './base/SynthEngine'; enum PDWaveform { Sine, diff --git a/src/lib/audio/engines/Ring.ts b/src/lib/audio/engines/Ring.ts index 3dae20d..236b79b 100644 --- a/src/lib/audio/engines/Ring.ts +++ b/src/lib/audio/engines/Ring.ts @@ -1,4 +1,4 @@ -import type { SynthEngine, PitchLock } from './SynthEngine'; +import type { SynthEngine, PitchLock } from './base/SynthEngine'; enum LFOWaveform { Sine, diff --git a/src/lib/audio/engines/Sample.ts b/src/lib/audio/engines/Sample.ts index e2624c4..13160eb 100644 --- a/src/lib/audio/engines/Sample.ts +++ b/src/lib/audio/engines/Sample.ts @@ -1,4 +1,4 @@ -import type { SynthEngine, PitchLock } from './SynthEngine'; +import type { SynthEngine, PitchLock } from './base/SynthEngine'; interface SampleParams { loaded: boolean; diff --git a/src/lib/audio/engines/Snare.ts b/src/lib/audio/engines/Snare.ts index 937d6f7..6a98995 100644 --- a/src/lib/audio/engines/Snare.ts +++ b/src/lib/audio/engines/Snare.ts @@ -1,4 +1,4 @@ -import type { PitchLock, SynthEngine } from './SynthEngine'; +import type { PitchLock, SynthEngine } from './base/SynthEngine'; interface SnareParams { // Core frequency (base pitch of the snare) diff --git a/src/lib/audio/engines/SubtractiveThreeOsc.ts b/src/lib/audio/engines/SubtractiveThreeOsc.ts index 2ae4ced..6db291c 100644 --- a/src/lib/audio/engines/SubtractiveThreeOsc.ts +++ b/src/lib/audio/engines/SubtractiveThreeOsc.ts @@ -1,5 +1,5 @@ -import { CsoundEngine, type CsoundParameter } from './CsoundEngine'; -import type { PitchLock } from './SynthEngine'; +import { CsoundEngine, type CsoundParameter } from './base/CsoundEngine'; +import type { PitchLock } from './base/SynthEngine'; enum Waveform { Sine = 0, @@ -39,7 +39,7 @@ export interface SubtractiveThreeOscParams { export class SubtractiveThreeOsc extends CsoundEngine { getName(): string { - return 'Subtractive 3-OSC'; + return '3OSC'; } getDescription(): string { diff --git a/src/lib/audio/engines/TwoOpFM.ts b/src/lib/audio/engines/TwoOpFM.ts index 183a971..f0a1aa6 100644 --- a/src/lib/audio/engines/TwoOpFM.ts +++ b/src/lib/audio/engines/TwoOpFM.ts @@ -1,4 +1,4 @@ -import type { SynthEngine, PitchLock } from './SynthEngine'; +import type { SynthEngine, PitchLock } from './base/SynthEngine'; enum EnvCurve { Linear, diff --git a/src/lib/audio/engines/WavetableEngine.ts b/src/lib/audio/engines/WavetableEngine.ts index 6d8229b..274ec9a 100644 --- a/src/lib/audio/engines/WavetableEngine.ts +++ b/src/lib/audio/engines/WavetableEngine.ts @@ -1,4 +1,4 @@ -import type { SynthEngine, PitchLock } from './SynthEngine'; +import type { SynthEngine, PitchLock } from './base/SynthEngine'; interface WavetableParams { bankIndex: number; diff --git a/src/lib/audio/engines/ZzfxEngine.ts b/src/lib/audio/engines/ZzfxEngine.ts index f29feec..b1a3022 100644 --- a/src/lib/audio/engines/ZzfxEngine.ts +++ b/src/lib/audio/engines/ZzfxEngine.ts @@ -1,4 +1,4 @@ -import type { SynthEngine, PitchLock } from './SynthEngine'; +import type { SynthEngine, PitchLock } from './base/SynthEngine'; // @ts-ignore import { ZZFX } from 'zzfx'; diff --git a/src/lib/audio/engines/CsoundEngine.ts b/src/lib/audio/engines/base/CsoundEngine.ts similarity index 100% rename from src/lib/audio/engines/CsoundEngine.ts rename to src/lib/audio/engines/base/CsoundEngine.ts diff --git a/src/lib/audio/engines/SynthEngine.ts b/src/lib/audio/engines/base/SynthEngine.ts similarity index 100% rename from src/lib/audio/engines/SynthEngine.ts rename to src/lib/audio/engines/base/SynthEngine.ts diff --git a/src/lib/audio/engines/registry.ts b/src/lib/audio/engines/registry.ts index d59a5de..d59a3fb 100644 --- a/src/lib/audio/engines/registry.ts +++ b/src/lib/audio/engines/registry.ts @@ -1,4 +1,4 @@ -import type { SynthEngine } from './SynthEngine'; +import type { SynthEngine } from './base/SynthEngine'; import { FourOpFM } from './FourOpFM'; import { TwoOpFM } from './TwoOpFM'; import { PhaseDistortionFM } from './PhaseDistortionFM';