From 7af6dfc46f9a94e3966cdfa4e0d353e989eb9070 Mon Sep 17 00:00:00 2001 From: julian laplace Date: Wed, 10 May 2023 15:13:47 +0200 Subject: add stars and velocity --- src/index.jsx | 4 +++- src/lib/sampler.js | 7 ++++++- src/lib/stars.js | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/lib/util.js | 1 + src/relabi/canvas.js | 7 ++++++- src/relabi/index.js | 28 +++++++++++++++++++++----- src/ui/Controls.jsx | 35 ++++++++++++++++++++++++++++++-- 7 files changed, 128 insertions(+), 10 deletions(-) create mode 100644 src/lib/stars.js diff --git a/src/index.jsx b/src/index.jsx index 6c58df3..0428bcf 100644 --- a/src/index.jsx +++ b/src/index.jsx @@ -3,8 +3,8 @@ import { createRoot } from "react-dom/client"; import { requestAudioContext, randrange } from "./lib/util"; import App from "./ui/App.jsx"; +import Stars from "./lib/stars"; -document.body.style.backgroundColor = "#111"; document.body.style.color = "#fff"; document.body.style.margin = 0; document.body.style.padding = 0; @@ -15,6 +15,7 @@ document.body.parentNode.style.margin = 0; document.body.parentNode.style.padding = 0; document.body.parentNode.style.height = "100%"; document.body.parentNode.style.width = "100%"; +document.body.parentNode.style.backgroundColor = "#111"; requestAudioContext(() => { const container = document.createElement("div"); @@ -24,4 +25,5 @@ requestAudioContext(() => { document.body.appendChild(container); const root = createRoot(container); root.render(); + Stars(); }); diff --git a/src/lib/sampler.js b/src/lib/sampler.js index 0db53e1..0a00792 100644 --- a/src/lib/sampler.js +++ b/src/lib/sampler.js @@ -27,7 +27,7 @@ export default class Sampler { }); } - play(time, options) { + play(time, options, velocity) { const sound = options.index ? this.samples[options.index] : choice(this.samples); @@ -41,6 +41,11 @@ export default class Sampler { (options.frequency * choice([0.5, 1]) + randrange(0, 10)) / sound.root; } + if (velocity) { + player.volume.value = (1 - velocity) * -18; + console.log(player.volume.value); + } + player.start(time || 0); } } diff --git a/src/lib/stars.js b/src/lib/stars.js new file mode 100644 index 0000000..c4a0ea7 --- /dev/null +++ b/src/lib/stars.js @@ -0,0 +1,56 @@ +/** + * Stars + * @module src/lib/stars.js + */ + +export default function Stars() { + var canvas = document.createElement("canvas"), + ctx = canvas.getContext("2d"); + document.body.appendChild(canvas); + canvas.style.width = "100%"; + canvas.style.height = "100%"; + canvas.style.position = "absolute"; + canvas.style.top = "0px"; + canvas.style.left = "0px"; + canvas.style.zIndex = -1; + document.body.addEventListener("resize", go); + document.body.parentNode.style.backgroundColor = "black"; + ctx.strokeStyle = "white"; + var s = Math.sin, + c = Math.cos; + go(); + function ri(n) { + return Math.random() * n; + } + function rr(a, b) { + return (b - a) * Math.random() + a; + } + function go() { + var w = (canvas.width = window.innerWidth); + var h = (canvas.height = window.innerHeight); + ctx.clearRect(0, 0, w, h); + var n = Math.sqrt(w * h) | 0; + while (n--) { + var x = ri(w); + var y = ri(h); + var r0 = rr(0, 1); + var r1 = rr(0, 1); + var r2 = rr(0, 1); + var t0 = ri(2 * Math.PI); + var t1 = ri(2 * Math.PI); + var t2 = ri(2 * Math.PI); + var x0 = x + c(t0) * r0; + var y0 = y + s(t0) * r0; + var x1 = x + c(t1) * r1; + var y1 = y + s(t1) * r1; + var x2 = x + c(t2) * r2; + var y2 = y + s(t2) * r2; + ctx.beginPath(); + ctx.moveTo(x, y); + ctx.bezierCurveTo(x0, y0, x1, y1, x2, y2); + var color = rr(0, 255) | 0; + ctx.strokeStyle = "rgb(" + color + "," + color + "," + color + ")"; + ctx.stroke(); + } + } +} diff --git a/src/lib/util.js b/src/lib/util.js index 7980220..ff6dcf5 100644 --- a/src/lib/util.js +++ b/src/lib/util.js @@ -11,6 +11,7 @@ const isDesktop = !isMobile; document.body.classList.add(isMobile ? "mobile" : "desktop"); export const browser = { isIphone, isIpad, isMobile, isDesktop }; +export const clamp = (n, a, b) => (n < a ? a : n < b ? n : b); export const choice = (a) => a[Math.floor(Math.random() * a.length)]; export const mod = (n, m) => n - m * Math.floor(n / m); export const random = () => Math.random(); diff --git a/src/relabi/canvas.js b/src/relabi/canvas.js index 62e5442..68f18c5 100644 --- a/src/relabi/canvas.js +++ b/src/relabi/canvas.js @@ -34,6 +34,8 @@ export default class RelabiCanvas { // Start drawing this.requestAnimationFrame(0); + + window.addEventListener("resize", () => this.resize()); } /** @@ -53,6 +55,9 @@ export default class RelabiCanvas { requestAnimationFrame((frame) => { this.requestAnimationFrame(frame); }); + if (this.relabi.paused) { + return; + } this.paint(frame); this.lastFrame = frame; } @@ -211,7 +216,7 @@ export default class RelabiCanvas { continue; } ctx.beginPath(); - ctx.arc(x, y, NOTE_RADIUS, 0, 2 * Math.PI); + ctx.arc(x - NOTE_RADIUS / 2, y, NOTE_RADIUS, 0, 2 * Math.PI); ctx.fillStyle = direction ? `rgba(255,96,128,${opacity})` : `rgba(128,255,96,${opacity})`; diff --git a/src/relabi/index.js b/src/relabi/index.js index b77a83f..2dd6222 100644 --- a/src/relabi/index.js +++ b/src/relabi/index.js @@ -5,6 +5,7 @@ import * as Tone from "tone"; import RelabiCanvas from "./canvas"; import * as Instruments from "../lib/instruments"; +import { clamp, randrange } from "../lib/util"; const TWO_PI = 2 * Math.PI; @@ -39,6 +40,7 @@ export default class Relabi { this.bounds = bounds; this.settings = settings; this.previousValue = null; + this.paused = false; this.canvas = new RelabiCanvas({ relabi: this, parent }); } @@ -49,6 +51,9 @@ export default class Relabi { console.log("Start Relabi"); this.stop(); this.clock = new Tone.Clock((time) => { + if (this.paused) { + return; + } const values = this.generate(time); const notes = this.play(values); this.canvas.append(time, values, notes); @@ -123,6 +128,7 @@ export default class Relabi { let previousValue = this.previousValue; let index; let step; + let lastTrigger = 0; let noteCount = 0; const notes = []; @@ -133,13 +139,18 @@ export default class Relabi { // Compute whether we crossed a boundary, and which direction for (index = 0; index < boundsCount; index += 1) { const bound = this.bounds[index]; + const slope = value - previousValue; if ( value < bound.level && bound.level < previousValue && previousValue !== null ) { // Going down - this.trigger(time, bound.sounds[0]); + if (step - lastTrigger < randrange(3, 6)) { + continue; + } + lastTrigger = step; + this.trigger(time, bound.sounds[0], slope); notes.push([time, bound.level, true]); noteCount += 1; } else if ( @@ -148,7 +159,11 @@ export default class Relabi { previousValue !== null ) { // Going up - this.trigger(time, bound.sounds[1]); + if (step - lastTrigger < randrange(3, 6)) { + continue; + } + lastTrigger = step; + this.trigger(time, bound.sounds[1], slope); notes.push([time, bound.level, false]); noteCount += 1; } @@ -168,12 +183,15 @@ export default class Relabi { /** * Trigger an event */ - trigger(time, sound) { + trigger(time, sound, slope) { // console.log("trigger index", index, time); + const velocity = clamp(Math.abs(slope * 10), 0, 1); + + console.log(velocity.toFixed(3)); if (sound.instrument in Instruments) { - Instruments[sound.instrument].play(time, sound); + Instruments[sound.instrument].play(time, sound, velocity); } else { - sound.instrument.play(time, sound); + sound.instrument.play(time, sound, velocity); } } } diff --git a/src/ui/Controls.jsx b/src/ui/Controls.jsx index e8dca45..8666378 100644 --- a/src/ui/Controls.jsx +++ b/src/ui/Controls.jsx @@ -4,7 +4,7 @@ */ import * as React from "react"; -import { useCallback, useRef } from "react"; +import { useState, useCallback, useRef } from "react"; import Slider from "rc-slider"; import TooltipSlider, { handleRender } from "./TooltipSlider.tsx"; @@ -20,6 +20,8 @@ const WAVE_SHAPE_NAMES = [ ]; export default function Controls({ relabi }) { + const [paused, setPaused] = useState(false); + /** * Handle updating a slider */ @@ -32,7 +34,7 @@ export default function Controls({ relabi }) { relabi.waves[index].shape = WAVE_SHAPE_NAMES[value]; } if (type === "frequency") { - relabi.waves[index].frequency = Math.exp(value); + relabi.waves[index].frequency = Math.round(Math.exp(value) * 10) / 10; } if (type === "weight") { relabi.waves[index].weight = value; @@ -44,6 +46,14 @@ export default function Controls({ relabi }) { [relabi] ); + /** + * Handle pause/unpause + */ + const onPause = useCallback(() => { + relabi.paused = !paused; + setPaused(!paused); + }, [relabi, paused]); + if (!relabi) { return null; } @@ -162,6 +172,9 @@ export default function Controls({ relabi }) { /> + + + ); } @@ -208,6 +221,24 @@ const Label = ({ children }) => (
{children}
); +const Button = ({ color, onClick, children }) => ( + +); + const ControlSlider = ({ type, index, -- cgit v1.2.3-70-g09d2