From 8f5330972ac7bb825e99717b38bc35d534b0aea3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Forment?= Date: Tue, 30 Sep 2025 14:20:50 +0200 Subject: [PATCH] modularity --- index.html | 5 +- src/App.tsx | 83 +++++------ src/components/BytebeatTile.tsx | 34 +++-- src/components/EffectsBar.tsx | 10 ++ src/components/EngineControls.tsx | 7 +- src/components/Slider.tsx | 8 +- src/config/effects.ts | 49 ++++-- src/domain/audio/AudioPlayer.ts | 129 ++++++++++++++++ src/domain/audio/BytebeatCompiler.ts | 36 +++++ src/domain/audio/SampleGenerator.ts | 52 +++++++ src/domain/audio/WavExporter.ts | 25 ++++ src/domain/audio/effects/DelayEffect.ts | 63 ++++++++ src/domain/audio/effects/Effect.interface.ts | 11 ++ src/domain/audio/effects/EffectsChain.ts | 65 ++++++++ src/domain/audio/effects/PassThroughEffect.ts | 27 ++++ src/domain/audio/effects/ReverbEffect.ts | 70 +++++++++ src/domain/audio/index.ts | 6 + src/lib/bytebeat/BytebeatGenerator.ts | 139 ++++-------------- src/lib/bytebeat/EffectsChain.ts | 1 - src/services/DownloadService.ts | 86 +++++++++++ src/services/PlaybackManager.ts | 81 ++++++++++ src/services/index.ts | 2 + src/stores/settings.ts | 10 +- src/utils/bytebeatFormulas.ts | 13 -- src/utils/formatters.ts | 20 +++ src/utils/waveformGenerator.ts | 58 ++++++++ 26 files changed, 892 insertions(+), 198 deletions(-) create mode 100644 src/domain/audio/AudioPlayer.ts create mode 100644 src/domain/audio/BytebeatCompiler.ts create mode 100644 src/domain/audio/SampleGenerator.ts create mode 100644 src/domain/audio/WavExporter.ts create mode 100644 src/domain/audio/effects/DelayEffect.ts create mode 100644 src/domain/audio/effects/Effect.interface.ts create mode 100644 src/domain/audio/effects/EffectsChain.ts create mode 100644 src/domain/audio/effects/PassThroughEffect.ts create mode 100644 src/domain/audio/effects/ReverbEffect.ts create mode 100644 src/domain/audio/index.ts create mode 100644 src/services/DownloadService.ts create mode 100644 src/services/PlaybackManager.ts create mode 100644 src/services/index.ts create mode 100644 src/utils/formatters.ts create mode 100644 src/utils/waveformGenerator.ts diff --git a/index.html b/index.html index c742303a..a72add2c 100644 --- a/index.html +++ b/index.html @@ -3,7 +3,10 @@ - bytesample + + + + Bruitiste
diff --git a/src/App.tsx b/src/App.tsx index 3364dfe9..bc2fc0da 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,14 +1,13 @@ import { useState, useRef, useEffect } from 'react' import { useStore } from '@nanostores/react' -import JSZip from 'jszip' -import { BytebeatGenerator } from './lib/bytebeat' +import { PlaybackManager } from './services/PlaybackManager' +import { DownloadService } from './services/DownloadService' import { generateFormulaGrid } from './utils/bytebeatFormulas' import { BytebeatTile } from './components/BytebeatTile' import { EffectsBar } from './components/EffectsBar' import { EngineControls } from './components/EngineControls' import { getSampleRateFromIndex } from './config/effects' import { engineSettings, effectSettings } from './stores/settings' -import type { EffectValues } from './types/effects' function App() { const engineValues = useStore(engineSettings) @@ -21,7 +20,8 @@ function App() { const [queued, setQueued] = useState(null) const [playbackPosition, setPlaybackPosition] = useState(0) const [downloading, setDownloading] = useState(false) - const generatorRef = useRef(null) + const playbackManagerRef = useRef(null) + const downloadServiceRef = useRef(new DownloadService()) const animationFrameRef = useRef(null) useEffect(() => { @@ -37,22 +37,23 @@ function App() { const sampleRate = getSampleRateFromIndex(engineValues.sampleRate) const duration = engineValues.loopDuration - if (!generatorRef.current) { - generatorRef.current = new BytebeatGenerator({ sampleRate, duration }) + if (!playbackManagerRef.current) { + playbackManagerRef.current = new PlaybackManager({ sampleRate, duration }) } else { - generatorRef.current.updateOptions({ sampleRate, duration }) + playbackManagerRef.current.updateOptions({ sampleRate, duration }) } - try { - generatorRef.current.stop() - generatorRef.current.setFormula(formula) - generatorRef.current.setEffects(effectValues) - generatorRef.current.play() + playbackManagerRef.current.stop() + playbackManagerRef.current.setEffects(effectValues) + + const success = playbackManagerRef.current.play(formula, sampleRate, duration) + + if (success) { setPlaying(id) setQueued(null) startPlaybackTracking() - } catch (error) { - console.error('Failed to play formula:', error) + } else { + console.error('Failed to play formula') } } @@ -62,8 +63,8 @@ function App() { } const updatePosition = () => { - if (generatorRef.current) { - const position = generatorRef.current.getPlaybackPosition() + if (playbackManagerRef.current) { + const position = playbackManagerRef.current.getPlaybackPosition() setPlaybackPosition(position) animationFrameRef.current = requestAnimationFrame(updatePosition) } @@ -75,7 +76,7 @@ function App() { const id = `${row}-${col}` if (playing === id) { - generatorRef.current?.stop() + playbackManagerRef.current?.stop() setPlaying(null) setQueued(null) if (animationFrameRef.current) { @@ -89,8 +90,8 @@ function App() { playFormula(formula, id) } else { setQueued(id) - if (generatorRef.current) { - generatorRef.current.scheduleNextTrack(() => { + if (playbackManagerRef.current) { + playbackManagerRef.current.scheduleNextTrack(() => { const queuedFormula = formulas.flat()[parseInt(id.split('-')[0]) * 2 + parseInt(id.split('-')[1])] if (queuedFormula) { playFormula(queuedFormula, id) @@ -106,54 +107,35 @@ function App() { const handleEngineChange = (parameterId: string, value: number) => { - engineSettings.setKey(parameterId, value) + engineSettings.setKey(parameterId as keyof typeof engineValues, value) - if (parameterId === 'masterVolume' && generatorRef.current) { - generatorRef.current.setEffects(effectValues) + if (parameterId === 'masterVolume' && playbackManagerRef.current) { + playbackManagerRef.current.setEffects(effectValues) } } const handleEffectChange = (parameterId: string, value: number) => { - effectSettings.setKey(parameterId, value) - if (generatorRef.current) { - generatorRef.current.setEffects(effectValues) + effectSettings.setKey(parameterId as keyof typeof effectValues, value) + if (playbackManagerRef.current) { + playbackManagerRef.current.setEffects(effectValues) } } const handleDownloadAll = async () => { setDownloading(true) - const zip = new JSZip() - const gen = new BytebeatGenerator({ duration: 10 }) - - formulas.forEach((row, i) => { - row.forEach((formula, j) => { - try { - gen.setFormula(formula) - const blob = gen.exportWAV(8) - zip.file(`bytebeat_${i}_${j}.wav`, blob) - } catch (error) { - console.error(`Failed to generate ${i}_${j}:`, error) - } - }) - }) - - const content = await zip.generateAsync({ type: 'blob' }) - const url = URL.createObjectURL(content) - const a = document.createElement('a') - a.href = url - a.download = 'bytebeats.zip' - a.click() - URL.revokeObjectURL(url) - - gen.dispose() + await downloadServiceRef.current.downloadAll(formulas, { duration: 10, bitDepth: 8 }) setDownloading(false) } + const handleDownloadFormula = (formula: string, filename: string) => { + downloadServiceRef.current.downloadFormula(formula, filename, { duration: 10, bitDepth: 8 }) + } + return (
-

BYTEBEAT

+

BRUITISTE