diff --git a/src/App.svelte b/src/App.svelte index 704c3ee..535a40a 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -31,6 +31,8 @@ glowIntensity: 0.6, logoSpin: false, logoSpinSpeed: 5, + particleImage: "", + particleSize: 1.0, titlePosition: "top", }; @@ -71,6 +73,8 @@ let glowIntensity = saved.glowIntensity; let logoSpin = saved.logoSpin; let logoSpinSpeed = saved.logoSpinSpeed; + let particleImage = saved.particleImage; + let particleSize = saved.particleSize; let titlePosition = saved.titlePosition; let visualizerComponent; let toggleHidden = false; @@ -126,6 +130,8 @@ glowIntensity, logoSpin, logoSpinSpeed, + particleImage, + particleSize, titlePosition, }; try { @@ -183,6 +189,8 @@ glowIntensity = defaults.glowIntensity; logoSpin = defaults.logoSpin; logoSpinSpeed = defaults.logoSpinSpeed; + particleImage = defaults.particleImage; + particleSize = defaults.particleSize; titlePosition = defaults.titlePosition; } @@ -311,6 +319,8 @@ {glowIntensity} {logoSpin} {logoSpinSpeed} + {particleImage} + {particleSize} bind:isListening bind:fileCurrentTime bind:fileDuration @@ -381,6 +391,8 @@ bind:glowIntensity bind:logoSpin bind:logoSpinSpeed + bind:particleImage + bind:particleSize bind:titlePosition {isListening} {colorPresets} diff --git a/src/lib/AudioVisualizer.svelte b/src/lib/AudioVisualizer.svelte index f43b0e9..f34d168 100644 --- a/src/lib/AudioVisualizer.svelte +++ b/src/lib/AudioVisualizer.svelte @@ -19,6 +19,8 @@ export let glowIntensity = 0.6; export let logoSpin = false; export let logoSpinSpeed = 5; + export let particleImage = ""; + export let particleSize = 1.0; const DEFAULT_SIZE = 250; const LINE_LIFT = 8; @@ -55,6 +57,16 @@ let visualizerWrapper; let blobPhase = 0; + // Particle image cache + let pImg = null; + $: if (particleImage) { + const img = new Image(); + img.src = particleImage; + pImg = img; + } else { + pImg = null; + } + // Particle system let particles = []; @@ -100,7 +112,7 @@ vy: -(1.5 + Math.random() * 4 * loudness), life: 1.0, decay: 0.008 + Math.random() * 0.02, - radius: 2 + Math.random() * 5, + radius: (2 + Math.random() * 5) * particleSize, }; break; } @@ -124,7 +136,7 @@ vy: -(0.8 + Math.random() * 3 * loudness), life: 1.0, decay: 0.006 + Math.random() * 0.012, - radius: 2 + Math.random() * 4, + radius: (2 + Math.random() * 4) * particleSize, }; break; } @@ -147,7 +159,7 @@ vy: Math.sin(tangent) * speed + Math.sin(angle) * speed * 0.3, life: 1.0, decay: 0.006 + Math.random() * 0.015, - radius: 2 + Math.random() * 6, + radius: (2 + Math.random() * 6) * particleSize, }; break; } @@ -164,7 +176,7 @@ vy: Math.sin(angle) * speed, life: 1.0, decay: 0.005 + Math.random() * 0.015, - radius: 3 + Math.random() * 7, + radius: (3 + Math.random() * 7) * particleSize, }; break; } @@ -193,15 +205,24 @@ particles.pop(); continue; } - const alpha = Math.floor(p.life * 200) - .toString(16) - .padStart(2, "0"); - const color = - useSecondary && Math.random() > 0.5 ? colors.secondary : colors.primary; - ctx.beginPath(); - ctx.arc(p.x, p.y, p.radius * p.life, 0, Math.PI * 2); - ctx.fillStyle = color + alpha; - ctx.fill(); + if (pImg && pImg.complete) { + const sz = p.radius * p.life * 3; + ctx.globalAlpha = p.life; + ctx.drawImage(pImg, p.x - sz / 2, p.y - sz / 2, sz, sz); + ctx.globalAlpha = 1; + } else { + const alpha = Math.floor(p.life * 200) + .toString(16) + .padStart(2, "0"); + const color = + useSecondary && Math.random() > 0.5 + ? colors.secondary + : colors.primary; + ctx.beginPath(); + ctx.arc(p.x, p.y, p.radius * p.life, 0, Math.PI * 2); + ctx.fillStyle = color + alpha; + ctx.fill(); + } } } diff --git a/src/lib/Controls.svelte b/src/lib/Controls.svelte index 47751f7..0c21da5 100644 --- a/src/lib/Controls.svelte +++ b/src/lib/Controls.svelte @@ -56,6 +56,8 @@ export let glowIntensity = 0.6; export let logoSpin = false; export let logoSpinSpeed = 5; + export let particleImage = ""; + export let particleSize = 1.0; export let titlePosition = "top"; const dispatch = createEventDispatcher(); @@ -89,6 +91,13 @@ if (file) readFileAsDataUrl(file, (url) => (logoUrl = url)); } + let particleImgInput; + + function handleParticleImgUpload(e) { + const file = e.target.files[0]; + if (file) readFileAsDataUrl(file, (url) => (particleImage = url)); + } + function handleBgUpload(e) { const file = e.target.files[0]; if (file) readFileAsDataUrl(file, (url) => (bgImage = url)); @@ -553,6 +562,42 @@ bind:value={particleDensity} /> +
+