/** * Relabi event generator */ import * as Tone from "tone"; import RelabiCanvas from "./canvas"; 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, }; /** * Relabi generator */ export default class Relabi { /** * Initialize generator */ constructor({ waves, bounds, parent }) { this.updateTime = 1; this.steps = 50; this.waves = waves || [ { type: "sine", frequency: randrange(0.5, 1.5) }, { type: "sine", frequency: randrange(0.75, 2.25) }, { type: "sine", frequency: randrange(1, 3) }, { type: "sine", frequency: randrange(2, 4) }, ]; this.bounds = bounds; this.previousValue = 0; this.canvas = new RelabiCanvas({ relabi: this, parent }); } /** * Start the generator */ start() { console.log("Start Relabi"); this.stop(); this.clock = new Tone.Clock((time) => { const values = this.generate(time); this.canvas.append(time, values); this.play(values); }, this.updateTime); this.clock.start(); } /** * Stop the generator and reset it */ stop() { if (this.clock) { this.clock.stop(); this.clock.dispose(); } } /** * Generate relabi events */ generate(time) { const waveCount = this.waves.length; let index; let step; let value; let values = []; // Generate several events per second for (step = 0; step < this.steps; step += 1) { // Time offset for this event const timeOffset = 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](timeOffset * wave.frequency); } // Scale to [-1, 1] value /= waveCount; values.push([timeOffset, value]); } return values; } /** * Schedule relabi events */ play(values) { const boundsCount = this.bounds.length; let previousValue = this.previousValue; let index; let step; let noteCount = 0; for (step = 0; step < this.steps; step += 1) { // Get the next value const [time, value] = values[step]; // Compute whether we crossed a boundary, and which direction for (index = 0; index < boundsCount; index += 1) { const bound = this.bounds[index]; if (value < bound.level && bound.level < previousValue) { // Going down this.trigger(time, bound.sounds[0]); noteCount += 1; } else if (value > bound.level && bound.level > previousValue) { // Going up this.trigger(time, bound.sounds[1]); noteCount += 1; } } // Update the previous value previousValue = value; } // Store the latest value this.previousValue = previousValue; } /** * Trigger an event */ trigger(time, sound) { // console.log("trigger index", index, time); sound.instrument.play(time, sound); } }