Initial CoolSoup implementation

CoolSoup is a React + TypeScript + Vite application that generates visual patterns and converts them to audio through spectral synthesis. Features multiple image generators (Tixy expressions, geometric tiles, external APIs) and an advanced audio synthesis engine that treats images as spectrograms.
This commit is contained in:
2025-09-29 14:44:48 +02:00
parent b564e41820
commit 623082ce3b
79 changed files with 6247 additions and 951 deletions

View File

@ -1,5 +1,7 @@
import { useStore } from '@nanostores/react'
import { appSettings, type GeneratorType } from '../stores'
import { appSettings, helpPopupOpen, generatedImages, type GeneratorType } from '../stores'
import { useEffect } from 'react'
import Tooltip from './Tooltip'
interface GeneratorSelectorProps {
onGenerate: () => void
@ -15,6 +17,11 @@ export default function GeneratorSelector({ onGenerate, isGenerating }: Generato
const handleGeneratorChange = (generator: GeneratorType) => {
console.log('Changing generator to:', generator)
appSettings.set({ ...settings, selectedGenerator: generator })
// Clear the grid when switching to photo or webcam modes to show drag/drop zones
if (generator === 'from-photo' || generator === 'webcam') {
generatedImages.set([])
}
}
const handleGenerateClick = () => {
@ -24,38 +31,102 @@ export default function GeneratorSelector({ onGenerate, isGenerating }: Generato
}
const generators = [
{ id: 'tixy' as const, name: 'Tixy', description: 'Mathematical expressions' },
{ id: 'picsum' as const, name: 'Picsum', description: 'Random photos' },
{ id: 'art-institute' as const, name: 'Art Institute', description: 'Famous artworks' },
{ id: 'geometric-tiles' as const, name: 'Geo Tiles', description: 'Geometric patterns' }
{ id: 'tixy' as const, name: 'Tixy', description: 'Math expressions' },
{ id: 'waveform' as const, name: 'Waveform', description: 'Random waveforms' },
{ id: 'partials' as const, name: 'Partials', description: 'Horizontal lines' },
{ id: 'slides' as const, name: 'Slides', description: 'Vertical strokes' },
{ id: 'shapes' as const, name: 'Shapes', description: 'Geometric shapes' },
{ id: 'bands' as const, name: 'Bands', description: 'Horizontal band layers' },
{ id: 'dust' as const, name: 'Dust', description: 'Point clouds' },
{ id: 'harmonics' as const, name: 'Harmonics', description: 'Musical harmonic series' },
{ id: 'geopattern' as const, name: 'Geopattern', description: 'Geometric patterns' },
{ id: 'from-photo' as const, name: 'Photo', description: 'Upload your image' },
{ id: 'webcam' as const, name: 'Webcam', description: 'Capture from camera' },
{ id: 'art-institute' as const, name: 'Artworks', description: 'Famous artworks' },
{ id: 'picsum' as const, name: 'Picsum', description: 'Random photos' }
]
// Automatically split generators into two rows
const generatorsPerRow = Math.ceil(generators.length / 2)
const firstRowGenerators = generators.slice(0, generatorsPerRow)
const secondRowGenerators = generators.slice(generatorsPerRow)
useEffect(() => {
const handleKeyPress = (event: KeyboardEvent) => {
if (event.key.toLowerCase() === 'g' && !isGenerating) {
// Don't trigger if user is typing in an input field
if (event.target instanceof HTMLInputElement || event.target instanceof HTMLTextAreaElement) {
return
}
event.preventDefault()
handleGenerateClick()
}
}
window.addEventListener('keydown', handleKeyPress)
return () => window.removeEventListener('keydown', handleKeyPress)
}, [isGenerating, onGenerate])
return (
<div className="border-b border-gray-800 p-6">
<div className="flex items-center space-x-6">
<button
onClick={handleGenerateClick}
disabled={isGenerating}
className="bg-white text-black px-6 py-2 font-medium hover:bg-gray-200 disabled:bg-gray-600 disabled:text-gray-400 disabled:cursor-not-allowed"
>
{isGenerating ? 'Generating...' : 'Generate'}
</button>
<div className="flex space-x-4">
{generators.map((generator) => (
<button
key={generator.id}
onClick={() => handleGeneratorChange(generator.id)}
className={`px-4 py-2 border ${
settings.selectedGenerator === generator.id
? 'border-white bg-white text-black'
: 'border-gray-600 text-gray-300 hover:border-gray-400'
}`}
<div className="flex space-x-6">
{/* First container: 15% width - Title and Generate button */}
<div className="w-[15%] flex flex-col space-y-2">
<div className="flex items-center justify-center h-10">
<h1
className="text-2xl font-bold text-white cursor-pointer hover:text-gray-300"
onClick={() => helpPopupOpen.set(true)}
>
<div className="font-medium">{generator.name}</div>
<div className="text-xs opacity-75">{generator.description}</div>
</button>
))}
CoolSoup
</h1>
</div>
<div className="flex items-center justify-center h-10">
{settings.selectedGenerator !== 'from-photo' && settings.selectedGenerator !== 'webcam' && (
<button
onClick={handleGenerateClick}
disabled={isGenerating}
className="bg-white text-black px-4 py-2 font-medium hover:bg-gray-200 disabled:bg-gray-600 disabled:text-gray-400 disabled:cursor-not-allowed"
>
<div>{isGenerating ? 'Generating...' : 'Generate (G)'}</div>
</button>
)}
</div>
</div>
{/* Second container: 85% width - Generator modes in two lines */}
<div className="w-[85%] flex flex-col space-y-2">
<div className="flex items-center space-x-4 h-10">
{firstRowGenerators.map((generator) => (
<Tooltip key={generator.id} content={generator.description}>
<button
onClick={() => handleGeneratorChange(generator.id)}
className={`px-4 py-2 border ${
settings.selectedGenerator === generator.id
? 'border-white bg-white text-black'
: 'border-gray-600 text-gray-300 hover:border-gray-400'
}`}
>
<div className="font-medium">{generator.name}</div>
</button>
</Tooltip>
))}
</div>
<div className="flex items-center space-x-4 h-10">
{secondRowGenerators.map((generator) => (
<Tooltip key={generator.id} content={generator.description}>
<button
onClick={() => handleGeneratorChange(generator.id)}
className={`px-4 py-2 border ${
settings.selectedGenerator === generator.id
? 'border-white bg-white text-black'
: 'border-gray-600 text-gray-300 hover:border-gray-400'
}`}
>
<div className="font-medium">{generator.name}</div>
</button>
</Tooltip>
))}
</div>
</div>
</div>
</div>