/** * Relabi event generator */ import * as Tone from "tone"; import RelabiCanvas from "./canvas"; const TWO_PI = 2 * Math.PI; /** * Wave functions */ const WAVE_SHAPES = { 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 || [ { 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.bounds = bounds; this.previousValue = null; 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); const notes = this.play(values); this.canvas.append(time, values, notes); }, 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 = []; // Overshoot the line slightly each time let stepCount = this.steps + 20; // Generate several events per second for (step = 0; step < stepCount; 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_SHAPES[wave.shape](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; const notes = []; 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 && previousValue !== null ) { // Going down this.trigger(time, bound.sounds[0]); notes.push([time, bound.level, true]); noteCount += 1; } else if ( value > bound.level && bound.level > previousValue && previousValue !== null ) { // Going up this.trigger(time, bound.sounds[1]); notes.push([time, bound.level, false]); noteCount += 1; } } // Update the previous value previousValue = value; } // Store the latest value this.previousValue = previousValue; // Return the notes return notes; } /** * Trigger an event */ trigger(time, sound) { // console.log("trigger index", index, time); sound.instrument.play(time, sound); } }