summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/index.jsx4
-rw-r--r--src/lib/sampler.js7
-rw-r--r--src/lib/stars.js56
-rw-r--r--src/lib/util.js1
-rw-r--r--src/relabi/canvas.js7
-rw-r--r--src/relabi/index.js28
-rw-r--r--src/ui/Controls.jsx35
7 files changed, 128 insertions, 10 deletions
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(<App />);
+ 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 }) {
/>
</SliderRow>
</ControlBox>
+ <ControlBox>
+ <Button onClick={onPause}>{paused ? "Play" : "Pause"}</Button>
+ </ControlBox>
</ControlRow>
);
}
@@ -208,6 +221,24 @@ const Label = ({ children }) => (
<div style={{ fontSize: "0.75rem" }}>{children}</div>
);
+const Button = ({ color, onClick, children }) => (
+ <button
+ onClick={onClick}
+ style={{
+ fontSize: "0.75rem",
+ color: color || "#ddd",
+ backgroundColor: "black",
+ border: "1px solid",
+ borderRadius: "8px",
+ padding: "0.25rem 0.5rem",
+ margin: "0.25rem",
+ cursor: "pointer",
+ }}
+ >
+ {children}
+ </button>
+);
+
const ControlSlider = ({
type,
index,