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:
116
src/generators/bands.ts
Normal file
116
src/generators/bands.ts
Normal file
@ -0,0 +1,116 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user