diff --git a/index.html b/index.html
index 2b115d8..11a7f62 100644
--- a/index.html
+++ b/index.html
@@ -22,6 +22,11 @@
padding: 0;
}
+ .fluid-bg-transition {
+ transition: background-color 0.05s ease-in-out;
+ }
+
+
#hydra-bg {
position: fixed; /* ignore margins */
top: 0px;
diff --git a/src/API.ts b/src/API.ts
index 1c604b6..9367af5 100644
--- a/src/API.ts
+++ b/src/API.ts
@@ -26,6 +26,7 @@ import {
} from "superdough";
import { Speaker } from "./StringExtensions";
import { getScaleNotes } from "zifferjs";
+import { blinkScript } from "./AudioVisualisation";
interface ControlChange {
channel: number;
@@ -269,10 +270,13 @@ export class UserAPI {
* @returns The result of the evaluation
*/
args.forEach((arg) => {
- tryEvaluate(
- this.app,
- this.app.universes[this.app.selected_universe].locals[arg]
- );
+ if (arg >= 1 && arg <= 9) {
+ blinkScript(this.app, "local", arg);
+ tryEvaluate(
+ this.app,
+ this.app.universes[this.app.selected_universe].locals[arg]
+ );
+ }
});
};
s = this.script;
diff --git a/src/AudioVisualisation.ts b/src/AudioVisualisation.ts
index 423fb51..307d760 100644
--- a/src/AudioVisualisation.ts
+++ b/src/AudioVisualisation.ts
@@ -1,2 +1,115 @@
// @ts-ignore
import { analyser, getAnalyzerData } from "superdough";
+import { type Editor } from "./main";
+
+/**
+ * Draw a circle at a specific position on the canvas.
+ * @param {number} x - The x-coordinate of the circle's center.
+ * @param {number} y - The y-coordinate of the circle's center.
+ * @param {number} radius - The radius of the circle.
+ * @param {string} color - The fill color of the circle.
+ */
+export const drawCircle = (
+ app: Editor,
+ x: number,
+ y: number,
+ radius: number,
+ color: string
+): void => {
+ // @ts-ignore
+ const canvas: HTMLCanvasElement = app.interface.feedback;
+ const ctx = canvas.getContext("2d");
+ if (!ctx) return;
+
+ ctx.beginPath();
+ ctx.arc(x, y, radius, 0, Math.PI * 2);
+ ctx.fillStyle = color;
+ ctx.fill();
+ ctx.closePath();
+};
+
+/**
+ * Blinks a script indicator circle.
+ * @param script - The type of script.
+ * @param no - The shift amount multiplier.
+ */
+export const blinkScript = (
+ app: Editor,
+ script: "local" | "global" | "init",
+ no?: number
+) => {
+ if (no !== undefined && no < 1 && no > 9) return;
+ const blinkDuration =
+ (app.clock.bpm / 60 / app.clock.time_signature[1]) * 200;
+ // @ts-ignore
+ const ctx = app.interface.feedback.getContext("2d"); // Assuming a canvas context
+
+ /**
+ * Draws a circle at a given shift.
+ * @param shift - The pixel distance from the origin.
+ */
+ const _drawBlinker = (shift: number) => {
+ const horizontalOffset = 50;
+ drawCircle(
+ app,
+ horizontalOffset + shift,
+ app.interface.feedback.clientHeight - 15,
+ 8,
+ "#fdba74"
+ );
+ };
+
+ /**
+ * Clears the circle at a given shift.
+ * @param shift - The pixel distance from the origin.
+ */
+ const _clearBlinker = (shift: number) => {
+ const x = 50 + shift;
+ const y = app.interface.feedback.clientHeight - 15;
+ const radius = 8;
+ ctx.clearRect(x - radius, y - radius, radius * 2, radius * 2);
+ };
+
+ if (script === "local" && no !== undefined) {
+ const shiftAmount = no * 25;
+
+ // Clear existing timeout if any
+ if (app.blinkTimeouts[shiftAmount]) {
+ clearTimeout(app.blinkTimeouts[shiftAmount]);
+ }
+
+ _drawBlinker(shiftAmount);
+
+ // Save timeout ID for later clearing
+ app.blinkTimeouts[shiftAmount] = setTimeout(() => {
+ _clearBlinker(shiftAmount);
+ // Clear the canvas before drawing new blinkers
+ (app.interface.feedback as HTMLCanvasElement)
+ .getContext("2d")!
+ .clearRect(
+ 0,
+ 0,
+ (app.interface.feedback as HTMLCanvasElement).width,
+ (app.interface.feedback as HTMLCanvasElement).height
+ );
+ drawEmptyBlinkers(app);
+ }, blinkDuration);
+ }
+};
+
+/**
+ * Draws a series of 9 white circles.
+ * @param app - The Editor application context.
+ */
+export const drawEmptyBlinkers = (app: Editor) => {
+ for (let no = 1; no <= 9; no++) {
+ const shiftAmount = no * 25;
+ drawCircle(
+ app,
+ 50 + shiftAmount,
+ app.interface.feedback.clientHeight - 15,
+ 8,
+ "white"
+ );
+ }
+};
diff --git a/src/WindowBehavior.ts b/src/WindowBehavior.ts
index fe3a6bd..84a966f 100644
--- a/src/WindowBehavior.ts
+++ b/src/WindowBehavior.ts
@@ -1,10 +1,28 @@
import { type Editor } from "./main";
+const handleResize = (app: Editor) => {
+ const canvas = app.interface.feedback as HTMLCanvasElement | null; // add type guard
+ if (!canvas) return;
+ canvas.width = window.innerWidth;
+ canvas.height = window.innerHeight;
+ const ctx = canvas.getContext("2d");
+ const dpr = window.devicePixelRatio || 1;
+
+ // Assuming the canvas takes up the whole window
+ canvas.width = window.innerWidth * dpr;
+ canvas.height = window.innerHeight * dpr;
+
+ if (ctx) {
+ ctx.scale(dpr, dpr);
+ }
+};
+
export const installWindowBehaviors = (
app: Editor,
window: Window,
preventMultipleTabs: boolean = false
) => {
+ window.addEventListener("resize", () => handleResize(app));
window.addEventListener("beforeunload", () => {
// @ts-ignore
event.preventDefault();
diff --git a/src/main.ts b/src/main.ts
index a9064b1..52f612e 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -24,6 +24,7 @@ import showdown from "showdown";
import { makeStringExtensions } from "./StringExtensions";
import { installInterfaceLogic } from "./InterfaceLogic";
import { installWindowBehaviors } from "./WindowBehavior";
+import { drawEmptyBlinkers } from "./AudioVisualisation";
export class Editor {
// Universes and settings
@@ -56,6 +57,7 @@ export class Editor {
show_error: boolean = false;
buttonElements: Record = {};
interface: ElementMap = {};
+ blinkTimeouts: Record = {};
// UserAPI
api: UserAPI;
@@ -79,6 +81,7 @@ export class Editor {
this.initializeElements();
this.initializeButtonGroups();
this.initializeHydra();
+ this.setCanvas();
// ================================================================================
// Loading the universe from local storage
@@ -127,6 +130,7 @@ export class Editor {
registerFillKeys(this);
registerOnKeyDown(this);
installInterfaceLogic(this);
+ drawEmptyBlinkers(this);
// ================================================================================
// Building CodeMirror Editor
@@ -381,25 +385,36 @@ export class Editor {
}
/**
- * @param color the color to flash the background
- * @param duration the duration of the flash
+ * Flashes the background of the view and its gutters.
+ * @param {string} color - The color to set.
+ * @param {number} duration - Duration in milliseconds to maintain the color.
*/
flashBackground(color: string, duration: number): void {
- // Set the flashing color
- this.view.dom.style.backgroundColor = color;
- const gutters = this.view.dom.getElementsByClassName(
+ const domElement = this.view.dom;
+ const gutters = domElement.getElementsByClassName(
"cm-gutter"
) as HTMLCollectionOf;
+
+ domElement.classList.add("fluid-bg-transition");
+ Array.from(gutters).forEach((gutter) =>
+ gutter.classList.add("fluid-bg-transition")
+ );
+
+ domElement.style.backgroundColor = color;
Array.from(gutters).forEach(
(gutter) => (gutter.style.backgroundColor = color)
);
- // Reset to original color after duration
setTimeout(() => {
- this.view.dom.style.backgroundColor = "";
+ domElement.style.backgroundColor = "";
Array.from(gutters).forEach(
(gutter) => (gutter.style.backgroundColor = "")
);
+
+ domElement.classList.remove("fluid-bg-transition");
+ Array.from(gutters).forEach((gutter) =>
+ gutter.classList.remove("fluid-bg-transition")
+ );
}, duration);
}
@@ -428,6 +443,23 @@ export class Editor {
});
this.hydra = this.hydra_backend.synth;
}
+
+ private setCanvas(): void {
+ const canvas = this.interface.feedback as HTMLCanvasElement | null; // add type guard
+
+ if (!canvas) return;
+ const ctx = canvas.getContext("2d");
+
+ const dpr = window.devicePixelRatio || 1;
+
+ // Assuming the canvas takes up the whole window
+ canvas.width = window.innerWidth * dpr;
+ canvas.height = window.innerHeight * dpr;
+
+ if (ctx) {
+ ctx.scale(dpr, dpr);
+ }
+ }
}
let app = new Editor();