modularity
This commit is contained in:
83
src/App.tsx
83
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<string | null>(null)
|
||||
const [playbackPosition, setPlaybackPosition] = useState<number>(0)
|
||||
const [downloading, setDownloading] = useState(false)
|
||||
const generatorRef = useRef<BytebeatGenerator | null>(null)
|
||||
const playbackManagerRef = useRef<PlaybackManager | null>(null)
|
||||
const downloadServiceRef = useRef<DownloadService>(new DownloadService())
|
||||
const animationFrameRef = useRef<number | null>(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 (
|
||||
<div className="w-screen h-screen flex flex-col bg-black overflow-hidden">
|
||||
<header className="bg-black border-b-2 border-white px-6 py-3">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<h1 className="font-mono text-sm tracking-[0.3em] text-white">BYTEBEAT</h1>
|
||||
<h1 className="font-mono text-sm tracking-[0.3em] text-white">BRUITISTE</h1>
|
||||
<div className="flex gap-4">
|
||||
<button
|
||||
onClick={handleRandom}
|
||||
@ -188,6 +170,7 @@ function App() {
|
||||
playbackPosition={playing === id ? playbackPosition : 0}
|
||||
onPlay={handleTileClick}
|
||||
onDoubleClick={handleTileDoubleClick}
|
||||
onDownload={handleDownloadFormula}
|
||||
/>
|
||||
)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user