summaryrefslogtreecommitdiff
path: root/src/relabi
diff options
context:
space:
mode:
Diffstat (limited to 'src/relabi')
-rw-r--r--src/relabi/index.js121
1 files changed, 121 insertions, 0 deletions
diff --git a/src/relabi/index.js b/src/relabi/index.js
new file mode 100644
index 0000000..867bc0d
--- /dev/null
+++ b/src/relabi/index.js
@@ -0,0 +1,121 @@
+import * as Tone from "tone";
+import kalimba from "../lib/kalimba";
+import { randrange } from "../lib/util";
+
+const TWO_PI = 2 * Math.PI;
+
+/**
+ * Wave functions
+ */
+const WAVE_FUNCTIONS = {
+ sine: Math.cos,
+ triangle: (time) =>
+ (4 / TWO_PI) *
+ Math.abs(
+ ((((time - TWO_PI / 4) % TWO_PI) + TWO_PI) % TWO_PI) - TWO_PI / 2
+ ) -
+ 1,
+ square: (time) => (time % TWO_PI < Math.PI ? 1 : -1),
+ saw: (time) => ((time % TWO_PI) - Math.PI) / Math.PI,
+};
+
+class Relabi {
+ /**
+ * Initialize relabi generator
+ */
+ constructor() {
+ this.updateTime = 1.0;
+ this.steps = 100;
+ this.waves = [
+ { type: "sine", frequency: randrange(0.5, 2) },
+ { type: "sine", frequency: randrange(0.5, 2) },
+ { type: "sine", frequency: randrange(1, 10) },
+ { type: "sine", frequency: randrange(5, 10) },
+ ];
+ this.bounds = [-0.5, 0.5];
+ this.frequencies = [220, (220 * 3) / 2, 440, (440 * 3) / 2];
+ }
+
+ /**
+ * Start the generator
+ */
+ start() {
+ console.log("Start Relabi");
+ this.stop();
+ this.clock = new Tone.Clock((time) => this.step(time), this.updateTime);
+ this.clock.start();
+ }
+
+ /**
+ * Stop the generator and reset it
+ */
+ stop() {
+ if (this.clock) {
+ this.clock.stop();
+ this.clock.dispose();
+ }
+ }
+
+ /**
+ * Generate relabi events
+ */
+ step(time) {
+ const waveCount = this.waves.length;
+ const boundsCount = this.bounds.length;
+ let previousValue = this.previousValue;
+ let index;
+ let step;
+ let value;
+ let noteCount = 0;
+
+ // Generate several events per second
+ for (step = 0; step < this.steps; step += 1) {
+ // Time offset for this event
+ const offset = time + (step * this.updateTime) / this.steps;
+
+ // Initialize value
+ value = 0;
+
+ // Compute the wave functions for this event
+ for (index = 0; index < waveCount; index += 1) {
+ const wave = this.waves[index];
+ value += WAVE_FUNCTIONS[wave.type](offset * wave.frequency);
+ }
+
+ // Scale to [-1, 1]
+ value /= waveCount / Math.PI;
+
+ // Compute whether we crossed a boundary, and which direction
+ for (index = 0; index < boundsCount; index += 1) {
+ const bound = this.bounds[index];
+ if (value < bound && bound < previousValue) {
+ // Going down
+ this.trigger(offset, index * 2);
+ noteCount += 1;
+ } else if (value > bound && bound > previousValue) {
+ // Going up
+ this.trigger(offset, index * 2 + 1);
+ noteCount += 1;
+ }
+ }
+
+ // Update the previous value
+ previousValue = value;
+ }
+
+ // Store the latest value
+ this.previousValue = value;
+
+ console.log(`Tick ${Math.floor(time)}, played ${noteCount} notes`);
+ }
+
+ /**
+ * Trigger an event
+ */
+ trigger(time, index) {
+ // console.log("trigger index", index, time);
+ kalimba.play(this.frequencies[index], time);
+ }
+}
+
+export default Relabi;