diff options
Diffstat (limited to 'src/relabi')
| -rw-r--r-- | src/relabi/canvas.js | 90 | ||||
| -rw-r--r-- | src/relabi/index.js | 37 |
2 files changed, 106 insertions, 21 deletions
diff --git a/src/relabi/canvas.js b/src/relabi/canvas.js index dfbffa0..d2c55a2 100644 --- a/src/relabi/canvas.js +++ b/src/relabi/canvas.js @@ -17,14 +17,14 @@ export default class RelabiCanvas { // Speed of the wave in pixels per second this.speed = 1 / 5; - // Attach to the DOM - this.parent = parent = document.body; - this.canvas = document.createElement("canvas"); - this.ctx = this.canvas.getContext("2d"); - this.parent.appendChild(this.canvas); + // Position of current time on the screen, from the left + this.tZeroOffset = 20; + // Attach to the DOM + this.appendCanvas(parent); // Initialize array this.values = new Array(window.innerWidth).fill(0); + this.notes = []; this.append([]); // Clear the canvas @@ -35,6 +35,16 @@ export default class RelabiCanvas { } /** + * Append the canvas + */ + appendCanvas(parent) { + this.parent = parent || document.body; + this.canvas = this.canvas || document.createElement("canvas"); + this.ctx = this.ctx || this.canvas.getContext("2d"); + this.parent.appendChild(this.canvas); + } + + /** * Draw the next frame */ requestAnimationFrame(frame) { @@ -63,10 +73,20 @@ export default class RelabiCanvas { /** * Append values */ - append(time, values) { + append(time, values, notes) { this.lastAppendTime = time; this.lastAppendFrame = this.lastFrame; - this.values = this.values.concat(values).slice(-this.canvas.width); + this.values = this.values + .concat(values) + .slice(-this.canvas.width) + .sort((a, b) => a[0] - b[0]); + + if (notes?.length) { + this.notes = this.notes + .concat(notes) + .slice(-50) + .sort((a, b) => a[0] - b[0]); + } } /** @@ -76,6 +96,7 @@ export default class RelabiCanvas { this.clear(); this.drawRelabiWave(frame); this.drawBounds(); + this.drawNotes(frame); } /** @@ -90,12 +111,22 @@ export default class RelabiCanvas { const y = getWaveHeight(bound.level, height); ctx.beginPath(); - ctx.setLineDash([10, 5]); + ctx.setLineDash([4, 4]); + ctx.lineWidth = 1; ctx.strokeStyle = bound.color || "#888"; ctx.moveTo(0, y); ctx.lineTo(width, y); ctx.stroke(); } + + // Draw the zero position + ctx.beginPath(); + ctx.setLineDash([1, 1]); + ctx.lineWidth = 2; + ctx.strokeStyle = "#666"; + ctx.moveTo(width - this.tZeroOffset, 0); + ctx.lineTo(width - this.tZeroOffset, height); + ctx.stroke(); } /** @@ -110,7 +141,8 @@ export default class RelabiCanvas { // Start the path ctx.beginPath(); - ctx.strokeStyle = "white"; + ctx.strokeStyle = "#dff"; + ctx.lineWidth = 0.75; ctx.setLineDash([]); // This is the offset in seconds from the last frame we computed @@ -130,13 +162,13 @@ export default class RelabiCanvas { // Subtracting the frame offset pulls it negative. const timeOffset = this.lastAppendTime + frameOffset - time; - const x = width - timeOffset * this.speed * width - 10; + const x = width - timeOffset * this.speed * width - this.tZeroOffset; + const y = getWaveHeight(value, height); if (x < 0) { continue; } - const y = getWaveHeight(value, height); if (index === 0) { ctx.moveTo(x, y); } @@ -147,6 +179,42 @@ export default class RelabiCanvas { // Paint the line ctx.stroke(); } + + /** + * Draw the notes + */ + drawNotes(frame) { + const { canvas, ctx } = this; + const { width, height } = canvas; + + // This is the offset in seconds from the last frame we computed + const frameOffset = (frame - this.lastAppendFrame) / 1000; + + // Make a path connecting all values + for (const note of this.notes) { + const [time, value, direction] = note; + const timeOffset = this.lastAppendTime + frameOffset - time; + const x = width - timeOffset * this.speed * width - this.tZeroOffset; + const y = getWaveHeight(value, height); + + // Don't draw notes that haven't played yet + if (x > width - this.tZeroOffset) { + continue; + } + + // Draw notes as a dot that fades out + const opacity = 1 - (timeOffset * this.speed * width) / 1000; + if (opacity < 0.001) { + continue; + } + ctx.beginPath(); + ctx.arc(x - 2.5, y, 5, 0, 2 * Math.PI); + ctx.fillStyle = direction + ? `rgba(255,128,192,${opacity})` + : `rgba(192,255,128,${opacity})`; + ctx.fill(); + } + } } /** diff --git a/src/relabi/index.js b/src/relabi/index.js index e392a27..89e4ce9 100644 --- a/src/relabi/index.js +++ b/src/relabi/index.js @@ -33,13 +33,13 @@ export default class Relabi { 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) }, + { type: "triangle", frequency: randrange(0.5, 1.5) }, + { type: "triangle", frequency: randrange(0.75, 2.25) }, + { type: "triangle", frequency: randrange(1, 3) }, + { type: "triangle", frequency: randrange(2, 4) }, ]; this.bounds = bounds; - this.previousValue = 0; + this.previousValue = null; this.canvas = new RelabiCanvas({ relabi: this, parent }); } @@ -51,8 +51,8 @@ export default class Relabi { this.stop(); this.clock = new Tone.Clock((time) => { const values = this.generate(time); - this.canvas.append(time, values); - this.play(values); + const notes = this.play(values); + this.canvas.append(time, values, notes); }, this.updateTime); this.clock.start(); } @@ -77,8 +77,11 @@ export default class Relabi { let value; let values = []; + // Overshoot the line slightly each time + let stepCount = this.steps + 20; + // Generate several events per second - for (step = 0; step < this.steps; step += 1) { + for (step = 0; step < stepCount; step += 1) { // Time offset for this event const timeOffset = time + (step * this.updateTime) / this.steps; @@ -109,6 +112,7 @@ export default class Relabi { let index; let step; let noteCount = 0; + const notes = []; for (step = 0; step < this.steps; step += 1) { // Get the next value @@ -117,13 +121,23 @@ export default class Relabi { // 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) { + 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) { + } 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; } } @@ -134,6 +148,9 @@ export default class Relabi { // Store the latest value this.previousValue = previousValue; + + // Return the notes + return notes; } /** |
