temporary

This commit is contained in:
2025-09-30 10:56:38 +02:00
commit 95845e8af8
23 changed files with 3414 additions and 0 deletions

173
src/App.tsx Normal file
View File

@ -0,0 +1,173 @@
import { useState, useRef } from 'react'
import JSZip from 'jszip'
import { BytebeatGenerator } from './lib/bytebeat'
import { generateFormulaGrid } from './utils/bytebeatFormulas'
import { BytebeatTile } from './components/BytebeatTile'
import { EffectsBar } from './components/EffectsBar'
import { getDefaultEffectValues } from './config/effects'
import type { EffectValues } from './types/effects'
function App() {
const [formulas, setFormulas] = useState<string[][]>(() => generateFormulaGrid(100, 2))
const [playing, setPlaying] = useState<string | null>(null)
const [queued, setQueued] = useState<string | null>(null)
const [playbackPosition, setPlaybackPosition] = useState<number>(0)
const [downloading, setDownloading] = useState(false)
const [effectValues, setEffectValues] = useState<EffectValues>(getDefaultEffectValues())
const generatorRef = useRef<BytebeatGenerator | null>(null)
const animationFrameRef = useRef<number | null>(null)
const handleRandom = () => {
if (generatorRef.current) {
generatorRef.current.stop()
setPlaying(null)
}
setFormulas(generateFormulaGrid(100, 2))
}
const playFormula = (formula: string, id: string) => {
if (!generatorRef.current) {
generatorRef.current = new BytebeatGenerator({ duration: 30 })
}
try {
generatorRef.current.stop()
generatorRef.current.setFormula(formula)
generatorRef.current.setEffects(effectValues)
generatorRef.current.play()
setPlaying(id)
setQueued(null)
startPlaybackTracking()
} catch (error) {
console.error('Failed to play formula:', error)
}
}
const startPlaybackTracking = () => {
if (animationFrameRef.current) {
cancelAnimationFrame(animationFrameRef.current)
}
const updatePosition = () => {
if (generatorRef.current) {
const position = generatorRef.current.getPlaybackPosition()
setPlaybackPosition(position)
animationFrameRef.current = requestAnimationFrame(updatePosition)
}
}
updatePosition()
}
const handleTileClick = (formula: string, row: number, col: number, isDoubleClick: boolean = false) => {
const id = `${row}-${col}`
if (playing === id) {
generatorRef.current?.stop()
setPlaying(null)
setQueued(null)
if (animationFrameRef.current) {
cancelAnimationFrame(animationFrameRef.current)
animationFrameRef.current = null
}
return
}
if (isDoubleClick || playing === null) {
playFormula(formula, id)
} else {
setQueued(id)
}
}
const handleTileDoubleClick = (formula: string, row: number, col: number) => {
handleTileClick(formula, row, col, true)
}
const handleEffectChange = (parameterId: string, value: number) => {
setEffectValues(prev => {
const newValues = { ...prev, [parameterId]: value }
if (generatorRef.current) {
generatorRef.current.setEffects(newValues)
}
return newValues
})
}
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()
setDownloading(false)
}
return (
<div className="w-screen h-screen flex flex-col bg-black overflow-hidden">
<header className="bg-black border-b-2 border-white flex items-center justify-between px-6 py-3">
<h1 className="font-mono text-sm tracking-[0.3em] text-white">BYTEBEAT</h1>
<div className="flex gap-4">
<button
onClick={handleRandom}
className="px-6 py-2 bg-white text-black font-mono text-[11px] tracking-[0.2em] hover:bg-black hover:text-white border-2 border-white transition-all"
>
RANDOM
</button>
<button
onClick={handleDownloadAll}
disabled={downloading}
className="px-6 py-2 bg-black text-white border-2 border-white font-mono text-[11px] tracking-[0.2em] hover:bg-white hover:text-black transition-all disabled:opacity-30 disabled:cursor-not-allowed"
>
{downloading ? 'DOWNLOADING...' : 'DOWNLOAD ALL'}
</button>
</div>
</header>
<div className="flex-1 grid grid-cols-2 auto-rows-min gap-[1px] bg-white p-[1px] overflow-auto">
{formulas.map((row, i) =>
row.map((formula, j) => {
const id = `${i}-${j}`
return (
<BytebeatTile
key={id}
formula={formula}
row={i}
col={j}
isPlaying={playing === id}
isQueued={queued === id}
playbackPosition={playing === id ? playbackPosition : 0}
onPlay={handleTileClick}
onDoubleClick={handleTileDoubleClick}
/>
)
})
)}
</div>
<EffectsBar values={effectValues} onChange={handleEffectChange} />
</div>
)
}
export default App