summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorjulian laplace <julescarbon@gmail.com>2023-05-09 22:56:44 +0200
committerjulian laplace <julescarbon@gmail.com>2023-05-09 22:56:44 +0200
commit8c19ceb702ebf1fdd66d1c71e491a21974e9c638 (patch)
tree879b84f5c00f3e29bd342bd61cf844fffb1c3e3a /src
parent886f106be26f9d1dc2dc867c89f2964f46a3a065 (diff)
chaos knob
Diffstat (limited to 'src')
-rw-r--r--src/lib/instruments.js33
-rw-r--r--src/relabi/canvas.js8
-rw-r--r--src/relabi/index.js45
-rw-r--r--src/ui/App.jsx59
-rw-r--r--src/ui/Controls.jsx76
5 files changed, 163 insertions, 58 deletions
diff --git a/src/lib/instruments.js b/src/lib/instruments.js
index 7bd0230..081aff0 100644
--- a/src/lib/instruments.js
+++ b/src/lib/instruments.js
@@ -11,12 +11,39 @@ export const Kalimba = new Sampler({
});
export const Drums = new Sampler({
- tonal: false,
samples: [
{ fn: "samples/707_bd.mp3" },
{ fn: "samples/707_clap.mp3" },
- // { fn: "samples/707_cow.mp3" },
- { fn: "samples/707_hat.mp3" },
+ { fn: "samples/707_ch.mp3" },
{ fn: "samples/707_rim.mp3" },
],
});
+
+export const Kick = new Sampler({
+ samples: [{ fn: "samples/707_bd.mp3" }, { fn: "samples/707_bd2.mp3" }],
+});
+
+export const Snare = new Sampler({
+ samples: [{ fn: "samples/707_snare1.mp3" }, { fn: "samples/707_snare2.mp3" }],
+});
+
+export const Hat = new Sampler({
+ samples: [
+ { fn: "samples/707_ch.mp3" },
+ { fn: "samples/707_ch.mp3" },
+ { fn: "samples/707_ch.mp3" },
+ { fn: "samples/707_oh.mp3" },
+ ],
+});
+
+export const Perc = new Sampler({
+ samples: [{ fn: "samples/707_rim.mp3" }, { fn: "samples/707_tamb.mp3" }],
+});
+
+export const Cymbal = new Sampler({
+ samples: [{ fn: "samples/707_crash.mp3" }, { fn: "samples/707_ride.mp3" }],
+});
+
+export const Tom = new Sampler({
+ samples: [{ fn: "samples/707_tom1.mp3" }, { fn: "samples/707_tom2.mp3" }],
+});
diff --git a/src/relabi/canvas.js b/src/relabi/canvas.js
index 2003a5a..fed4f6d 100644
--- a/src/relabi/canvas.js
+++ b/src/relabi/canvas.js
@@ -3,6 +3,8 @@
* @module src/relabi/canvas.js;
*/
+const NOTE_RADIUS = 7;
+
export default class RelabiCanvas {
/**
* Initialize relabi wave renderer
@@ -209,10 +211,10 @@ export default class RelabiCanvas {
continue;
}
ctx.beginPath();
- ctx.arc(x - 2.5, y, 5, 0, 2 * Math.PI);
+ ctx.arc(x - NOTE_RADIUS / 4, y, NOTE_RADIUS, 0, 2 * Math.PI);
ctx.fillStyle = direction
- ? `rgba(255,128,192,${opacity})`
- : `rgba(192,255,128,${opacity})`;
+ ? `rgba(255,96,128,${opacity})`
+ : `rgba(128,255,96,${opacity})`;
ctx.fill();
}
}
diff --git a/src/relabi/index.js b/src/relabi/index.js
index 8e23d2a..9e649f2 100644
--- a/src/relabi/index.js
+++ b/src/relabi/index.js
@@ -4,6 +4,7 @@
import * as Tone from "tone";
import RelabiCanvas from "./canvas";
+import * as Instruments from "../lib/instruments";
const TWO_PI = 2 * Math.PI;
@@ -19,7 +20,8 @@ const WAVE_SHAPES = {
) -
1,
square: (time) => (time % TWO_PI < Math.PI ? 1 : -1),
- saw: (time) => ((time % TWO_PI) - Math.PI) / Math.PI,
+ saw: (time) => (time % TWO_PI) / Math.PI - 1,
+ reverse_saw: (time) => 1 - (time % TWO_PI) / Math.PI,
};
/**
@@ -29,16 +31,12 @@ export default class Relabi {
/**
* Initialize generator
*/
- constructor({ waves, bounds, parent }) {
+ constructor({ waves, bounds, settings, parent }) {
this.updateTime = 1;
this.steps = 50;
- this.waves = waves || [
- { shape: "triangle", frequency: randrange(0.5, 1.5) },
- { shape: "triangle", frequency: randrange(0.75, 2.25) },
- { shape: "triangle", frequency: randrange(1, 3) },
- { shape: "triangle", frequency: randrange(2, 4) },
- ];
+ this.waves = waves;
this.bounds = bounds;
+ this.settings = settings;
this.previousValue = null;
this.canvas = new RelabiCanvas({ relabi: this, parent });
}
@@ -71,14 +69,17 @@ export default class Relabi {
* Generate relabi events
*/
generate(time) {
- const waveCount = this.waves.length;
let index;
let step;
let value;
let values = [];
+ let previousWaveValue = this.previousWaveValue || 0;
+
+ // Weight individual waves rather than simply averaging them
+ let totalWeight = this.waves.reduce((sum, wave) => sum + wave.weight, 0.0);
// Overshoot the line slightly each time
- let stepCount = this.steps + 20;
+ let stepCount = this.steps;
// Generate several events per second
for (step = 0; step < stepCount; step += 1) {
@@ -89,17 +90,27 @@ export default class Relabi {
value = 0;
// Compute the wave functions for this event
- for (index = 0; index < waveCount; index += 1) {
- const wave = this.waves[index];
- value += WAVE_SHAPES[wave.shape](timeOffset * wave.frequency);
+ for (const wave of this.waves) {
+ const waveOffset =
+ (wave.offset || 0) +
+ (wave.frequency * this.settings.speed) / this.steps +
+ previousWaveValue * this.settings.feedback;
+
+ const waveValue = WAVE_SHAPES[wave.shape](waveOffset);
+ value += waveValue * wave.weight;
+ previousWaveValue = waveValue;
+ wave.offset = waveOffset;
}
// Scale to [-1, 1]
- value /= waveCount;
+ value /= totalWeight;
+ previousWaveValue = value;
values.push([timeOffset, value]);
}
+ this.previousWaveValue = previousWaveValue;
+
return values;
}
@@ -158,6 +169,10 @@ export default class Relabi {
*/
trigger(time, sound) {
// console.log("trigger index", index, time);
- sound.instrument.play(time, sound);
+ if (sound.instrument in Instruments) {
+ Instruments[sound.instrument].play(time, sound);
+ } else {
+ sound.instrument.play(time, sound);
+ }
}
}
diff --git a/src/ui/App.jsx b/src/ui/App.jsx
index fbbbd35..db3d762 100644
--- a/src/ui/App.jsx
+++ b/src/ui/App.jsx
@@ -6,7 +6,6 @@
import * as React from "react";
import { useState, useEffect, useRef } from "react";
import Relabi from "../relabi";
-import { Kalimba, Drums } from "../lib/instruments";
import Controls from "./Controls.jsx";
export default function App() {
@@ -19,45 +18,43 @@ export default function App() {
useEffect(() => {
if (!relabi) {
const relabiGenerator = new Relabi({
+ settings: {
+ speed: 1.0,
+ feedback: 0.2,
+ },
waves: [
- { shape: "sine", frequency: 0.75 },
- { shape: "sine", frequency: 1.0 },
- { shape: "sine", frequency: 1.617 },
- { shape: "sine", frequency: 3.141 },
+ { shape: "sine", frequency: 0.75, weight: 1 },
+ { shape: "sine", frequency: 1.0, weight: 1 },
+ { shape: "sine", frequency: 1.617, weight: 1 },
+ { shape: "sine", frequency: 3.141, weight: 1 },
],
bounds: [
{
- level: -0.75,
- color: "#f33",
- sounds: [
- { instrument: Drums, index: 0 },
- { instrument: Drums, index: 1 },
- ],
- },
- {
- level: 0.75,
- color: "#f83",
- sounds: [
- { instrument: Drums, index: 2 },
- { instrument: Drums, index: 3 },
- ],
- },
- {
level: -0.25,
- color: "#3b8",
- sounds: [
- { instrument: Kalimba, frequency: 440 },
- { instrument: Kalimba, frequency: (440 * 3) / 2 },
- ],
+ color: "#f33",
+ sounds: [{ instrument: "Kick" }, { instrument: "Snare" }],
},
{
level: 0.25,
- color: "#36f",
- sounds: [
- { instrument: Kalimba, frequency: (440 * 6) / 5 },
- { instrument: Kalimba, frequency: (440 * 6) / 7 },
- ],
+ color: "#f83",
+ sounds: [{ instrument: "Hat" }, { instrument: "Perc" }],
},
+ // {
+ // level: -0.25,
+ // color: "#3b8",
+ // sounds: [
+ // { instrument: Kalimba, frequency: 440 },
+ // { instrument: Kalimba, frequency: (440 * 3) / 2 },
+ // ],
+ // },
+ // {
+ // level: 0.25,
+ // color: "#36f",
+ // sounds: [
+ // { instrument: Kalimba, frequency: (440 * 6) / 5 },
+ // { instrument: Kalimba, frequency: (440 * 6) / 7 },
+ // ],
+ // },
],
});
relabiGenerator.start();
diff --git a/src/ui/Controls.jsx b/src/ui/Controls.jsx
index f999e4f..3bc1c2e 100644
--- a/src/ui/Controls.jsx
+++ b/src/ui/Controls.jsx
@@ -10,7 +10,7 @@ import Slider from "rc-slider";
import TooltipSlider, { handleRender } from "./TooltipSlider.tsx";
import "rc-slider/assets/index.css";
-const WAVE_SHAPE_NAMES = ["sine", "triangle", "saw", "square"];
+const WAVE_SHAPE_NAMES = ["sine", "triangle", "saw", "reverse_saw", "square"];
export default function Controls({ relabi }) {
/**
@@ -21,16 +21,26 @@ export default function Controls({ relabi }) {
if (type === "bound") {
relabi.bounds[index].level = value;
}
+ if (type === "shape") {
+ relabi.waves[index].shape = WAVE_SHAPE_NAMES[value];
+ }
if (type === "frequency") {
relabi.waves[index].frequency = Math.exp(value);
}
- if (type === "shape") {
- relabi.waves[index].shape = WAVE_SHAPE_NAMES[value];
+ if (type === "weight") {
+ relabi.waves[index].weight = value;
+ }
+ if (type === "settings") {
+ relabi.settings[index] = index === "speed" ? Math.exp(value) : value;
}
},
[relabi]
);
+ if (!relabi) {
+ return null;
+ }
+
return (
<ControlRow>
<ControlBox>
@@ -45,6 +55,7 @@ export default function Controls({ relabi }) {
min={-1}
max={1}
step={0.01}
+ tipFormatter={(value) => -value}
defaultValue={bound.level}
color={bound.color}
reverse
@@ -54,7 +65,7 @@ export default function Controls({ relabi }) {
</ControlBox>
<ControlBox>
- <Label>Wave speeds</Label>
+ <Label>Wave frequencies</Label>
<SliderRow>
{relabi?.waves.map((wave, index) => (
<ControlSlider
@@ -66,6 +77,7 @@ export default function Controls({ relabi }) {
max={3}
step={0.001}
defaultValue={Math.log(wave.frequency)}
+ tipFormatter={(value) => Math.exp(value).toFixed(1)}
color={"#8a8"}
/>
))}
@@ -82,15 +94,67 @@ export default function Controls({ relabi }) {
index={index}
onChange={onChange}
min={0}
- max={3}
+ max={WAVE_SHAPE_NAMES.length - 1}
step={1}
defaultValue={WAVE_SHAPE_NAMES.indexOf(wave.shape)}
- color={"#998"}
+ color={"#aa8"}
tipFormatter={(value) => WAVE_SHAPE_NAMES[value]}
/>
))}
</SliderRow>
</ControlBox>
+
+ <ControlBox>
+ <Label>Wave weights</Label>
+ <SliderRow>
+ {relabi?.waves.map((wave, index) => (
+ <ControlSlider
+ rel={`weight_${index}`}
+ type="weight"
+ index={index}
+ onChange={onChange}
+ min={0}
+ max={2}
+ step={0.01}
+ defaultValue={wave.weight}
+ color={"#99b"}
+ />
+ ))}
+ </SliderRow>
+ </ControlBox>
+
+ <ControlBox>
+ <Label>Speed</Label>
+ <SliderRow>
+ <ControlSlider
+ type="settings"
+ index="speed"
+ onChange={onChange}
+ min={-2}
+ max={3}
+ step={0.01}
+ defaultValue={Math.log(relabi.settings.speed)}
+ tipFormatter={(value) => Math.exp(value).toFixed(1)}
+ color={"#a88"}
+ />
+ </SliderRow>
+ </ControlBox>
+
+ <ControlBox>
+ <Label>Chaos</Label>
+ <SliderRow>
+ <ControlSlider
+ type="settings"
+ index="feedback"
+ onChange={onChange}
+ min={0}
+ max={0.5}
+ step={0.01}
+ defaultValue={relabi.settings.feedback}
+ color={"#a88"}
+ />
+ </SliderRow>
+ </ControlBox>
</ControlRow>
);
}