diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/lib/instruments.js | 33 | ||||
| -rw-r--r-- | src/relabi/canvas.js | 8 | ||||
| -rw-r--r-- | src/relabi/index.js | 45 | ||||
| -rw-r--r-- | src/ui/App.jsx | 59 | ||||
| -rw-r--r-- | src/ui/Controls.jsx | 76 |
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> ); } |
