Files
coolsoup/src/generators/bands.ts
Raphaël Forment 623082ce3b 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.
2025-09-29 14:44:48 +02:00

116 lines
3.0 KiB
TypeScript

import type { GeneratedImage } from '../stores'
interface Band {
x: number
width: number
y: number
}
interface BandLayer {
divisions: number
bands: Band[]
opacity: number
}
function generateBandLayer(canvasWidth: number, canvasHeight: number): BandLayer {
const possibleDivisions = [2, 4, 8, 16, 32, 64, 128]
const divisions = possibleDivisions[Math.floor(Math.random() * possibleDivisions.length)]
const sectionWidth = canvasWidth / divisions
// Density varies from very sparse (20%) to very dense (95%)
const densityOptions = [0.2, 0.4, 0.6, 0.8, 0.95]
const density = densityOptions[Math.floor(Math.random() * densityOptions.length)]
const bands: Band[] = []
for (let i = 0; i < divisions; i++) {
if (Math.random() < density) {
const x = i * sectionWidth
const yValue = Math.random() // 0.0 to 1.0
// Convert y value where 0.0 is bottom and 1.0 is top
const y = canvasHeight - (yValue * canvasHeight)
bands.push({
x,
width: sectionWidth,
y
})
}
}
return {
divisions,
bands,
opacity: 0.8 // Slight transparency for superimposition
}
}
function drawBandLayer(ctx: CanvasRenderingContext2D, layer: BandLayer, strokeHeight: number) {
ctx.globalAlpha = layer.opacity
layer.bands.forEach(band => {
ctx.fillRect(band.x, band.y - strokeHeight / 2, band.width, strokeHeight)
})
ctx.globalAlpha = 1.0 // Reset alpha
}
export function generateBandsImages(count: number, size: number): GeneratedImage[] {
const images: GeneratedImage[] = []
for (let i = 0; i < count; i++) {
try {
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')!
canvas.width = size
canvas.height = size
// Fill background (always black)
ctx.fillStyle = '#000000'
ctx.fillRect(0, 0, size, size)
// Generate three superimposed band layers
const layers: BandLayer[] = []
for (let j = 0; j < 3; j++) {
layers.push(generateBandLayer(size, size))
}
// Draw the three layers
ctx.fillStyle = '#ffffff'
const strokeHeight = Math.max(1, size * 0.002) // Stroke height is 0.2% of canvas size, minimum 1px - much thinner bands
layers.forEach(layer => {
drawBandLayer(ctx, layer, strokeHeight)
})
const imageData = ctx.getImageData(0, 0, size, size)
const image: GeneratedImage = {
id: `bands-${Date.now()}-${i}`,
canvas,
imageData,
generator: 'bands',
params: {
layers: layers.map(layer => ({
divisions: layer.divisions,
bands: layer.bands.map(band => ({
x: band.x,
width: band.width,
y: band.y
})),
opacity: layer.opacity
})),
strokeHeight,
size
}
}
images.push(image)
} catch (error) {
console.error(`Failed to generate bands image ${i + 1}:`, error)
}
}
return images
}