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:
@ -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>
|
||||
|
||||
Reference in New Issue
Block a user