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.
116 lines
3.0 KiB
TypeScript
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
|
|
} |