diff --git a/src/core/PianoRenderer.ts b/src/core/PianoRenderer.ts index c52ed43..1c41e18 100644 --- a/src/core/PianoRenderer.ts +++ b/src/core/PianoRenderer.ts @@ -8,6 +8,9 @@ let overlayCtx: CanvasRenderingContext2D; const notePositions: Map = new Map(); const noteSize = 0.7; +let drawOutOfRange: boolean = true; +const outOfRangeNoteSize = 10; + let whiteKeyWidth: number; let blackKeyWidth: number; @@ -80,9 +83,51 @@ export function drawKeys(from: Audio.Note, to: Audio.Note) { } } -export function drawNote(note: Audio.Note, volume: number, color: string) { +export function drawNote( + note: Audio.Note, + range: [Audio.Note, Audio.Note], + volume: number, + color: string +) { const middlePos = notePositions.get(note); if (!middlePos) { + if (!drawOutOfRange) return; + overlayCtx.fillStyle = color; + if (note < range[0]) { + //Path for a Triangle ◀ + overlayCtx.beginPath(); + overlayCtx.moveTo(outOfRangeNoteSize, overlayCanvas.height - 2); + overlayCtx.lineTo( + outOfRangeNoteSize, + overlayCanvas.height - 2 - outOfRangeNoteSize + ); + overlayCtx.lineTo( + 0, + overlayCanvas.height - 2 - outOfRangeNoteSize / 2 + ); + overlayCtx.lineTo(outOfRangeNoteSize, overlayCanvas.height - 2); + } else if (note > range[1]) { + //Path for a Triangle ▶ + overlayCtx.beginPath(); + overlayCtx.moveTo( + overlayCanvas.width - outOfRangeNoteSize, + overlayCanvas.height - 2 + ); + overlayCtx.lineTo( + overlayCanvas.width - outOfRangeNoteSize, + overlayCanvas.height - 2 - outOfRangeNoteSize + ); + overlayCtx.lineTo( + overlayCanvas.width, + overlayCanvas.height - 2 - outOfRangeNoteSize / 2 + ); + overlayCtx.lineTo( + overlayCanvas.width - outOfRangeNoteSize, + overlayCanvas.height - 2 + ); + } + overlayCtx.fill(); + overlayCtx.closePath(); return; } @@ -142,3 +187,7 @@ export function resize(yPos: number, width: number, height: number) { overlayCanvas.height = height; overlayCanvas.style.top = `${yPos}px`; } + +export function setDrawOutOfRange(value: boolean) { + drawOutOfRange = value; +} diff --git a/src/core/Renderer.ts b/src/core/Renderer.ts index 5ea8642..2caa81a 100644 --- a/src/core/Renderer.ts +++ b/src/core/Renderer.ts @@ -86,6 +86,7 @@ function render() { PianoRenderer.drawNote( Math.round(note.note), + noteRange, note.volume, colors[i] ); @@ -110,6 +111,28 @@ export function alignNotesToPiano(value: boolean) { TimelineRenderer.alignNotesToPiano(value); } +export function setOutOfRangeBehaviour(value: string) { + switch (value) { + default: + case "On Keys": + PianoRenderer.setDrawOutOfRange(true); + TimelineRenderer.setDrawOutOfRange(false); + break; + case "On Timeline": + PianoRenderer.setDrawOutOfRange(false); + TimelineRenderer.setDrawOutOfRange(true); + break; + case "On Both": + PianoRenderer.setDrawOutOfRange(true); + TimelineRenderer.setDrawOutOfRange(true); + break; + case "Off": + PianoRenderer.setDrawOutOfRange(false); + TimelineRenderer.setDrawOutOfRange(false); + break; + } +} + export function setPianoRange(value: [number, number]) { noteRange = value; PianoRenderer.drawKeys(noteRange[0], noteRange[1]); diff --git a/src/core/TimelineRenderer.ts b/src/core/TimelineRenderer.ts index 40bdde3..b22aa82 100644 --- a/src/core/TimelineRenderer.ts +++ b/src/core/TimelineRenderer.ts @@ -4,6 +4,7 @@ import { SynthState } from "./SynthState"; import { Audio } from "nitro-fs"; let alignNotes = true; +let drawOutOfRange = false; export class TimelineRenderer { constructor(canvas: HTMLCanvasElement) { @@ -58,6 +59,78 @@ export class TimelineRenderer { continue; } + if ( + note.note < noteRange[0] || + noteRange[1] < note.note + ) { + if ( + !drawOutOfRange || + note.state !== Audio.EnvelopeState.Attack + ) + continue; + + const outOfRangeNoteSize = 10; + const noteY = + invLerp( + absoluteTimeRange[0], + absoluteTimeRange[1], + s.time + ) * this.canvas.height; + + if (note.note < noteRange[0]) { + //Path for a Triangle ◀ + this.ctx.beginPath(); + this.ctx.moveTo( + outOfRangeNoteSize, + this.canvas.height - noteY + ); + this.ctx.lineTo( + outOfRangeNoteSize, + this.canvas.height - + noteY - + outOfRangeNoteSize + ); + this.ctx.lineTo( + 0, + this.canvas.height - + noteY - + outOfRangeNoteSize / 2 + ); + this.ctx.lineTo( + outOfRangeNoteSize, + this.canvas.height - noteY + ); + this.ctx.fill(); + this.ctx.closePath(); + } else if (note.note > noteRange[0]) { + //Path for a Triangle ▶ + this.ctx.beginPath(); + this.ctx.moveTo( + this.canvas.width - outOfRangeNoteSize, + this.canvas.height - noteY + ); + this.ctx.lineTo( + this.canvas.width - outOfRangeNoteSize, + this.canvas.height - + noteY - + outOfRangeNoteSize + ); + this.ctx.lineTo( + this.canvas.width, + this.canvas.height - + noteY - + outOfRangeNoteSize / 2 + ); + this.ctx.lineTo( + this.canvas.width - outOfRangeNoteSize, + this.canvas.height - noteY + ); + this.ctx.fill(); + this.ctx.closePath(); + } + continue; + } + const noteWidth = (this.canvas.width / noteRangeCount) * note.volume; @@ -114,6 +187,10 @@ export class TimelineRenderer { static alignNotesToPiano(value: boolean) { alignNotes = value; } + + static setDrawOutOfRange(value: boolean) { + drawOutOfRange = value; + } } function invLerp(a: number, b: number, v: number) { diff --git a/src/ui/ConfigSection.tsx b/src/ui/ConfigSection.tsx index 0737691..f6c53a9 100644 --- a/src/ui/ConfigSection.tsx +++ b/src/ui/ConfigSection.tsx @@ -2,7 +2,8 @@ import { ConfigGrid, ConfigGridCheckbox, ConfigGridMinMax, - ConfigGridNumber + ConfigGridNumber, + ConfigGridSelect } from "./ConfigGrid"; import * as Renderer from "../core/Renderer"; import { Collapsible } from "./Collapsible"; @@ -55,6 +56,20 @@ export function ConfigSection() { Renderer.setPianoRange(value) } /> + { + Renderer.setOutOfRangeBehaviour(value); + }} + />