diff --git a/assets/instruments/instruments.json b/assets/instruments/instruments.json
index 1137269..3d3512c 100644
--- a/assets/instruments/instruments.json
+++ b/assets/instruments/instruments.json
@@ -8,7 +8,7 @@
"bdrumnew-hit-v7-rr1-sum-(1).mp3"
],
"midi-channel-name": "conductorOrchestralBass",
- "image": { "filename": "bass.svg", "yPos": "13%" }
+ "image": { "filename": "bass.svg", "yPos": "13%", "width": "15vw", "height": "15vw" }
},
"Snare": {
"directory": "/assets/instruments/snare",
@@ -17,20 +17,20 @@
"ropesnare-low-tsn-main-vl4-rr1.mp3"
],
"midi-channel-name": "conductorSnare",
- "image": { "filename": "snare.svg", "yPos": "26%" }
+ "image": { "filename": "snare.svg", "yPos": "26%", "width": "15vw", "height": "15vw" }
},
"Surdo": {
"directory": "/assets/instruments/surdo",
"filenames": ["surdo-5.mp3", "surdo-6.mp3", "surdo-7.mp3"],
"midi-channel-name":"conductorSnare",
- "image": { "filename": "surdo.svg", "yPos": "39%" }
+ "image": { "filename": "surdo.svg", "yPos": "39%" , "width": "15vw", "height": "15vw"}
},
"Surdo Napa": {
"directory": "/assets/instruments/surdo-napa",
"filenames": ["surdo-1.mp3", "surdo-3.mp3", "surdo-4.mp3"],
"midi-channel-name": "conductorSurdoNapa",
- "image": { "filename": "surdo-napa.svg", "yPos": "42%" }
- },
+ "image": { "filename": "surdo-napa.svg", "yPos": "42%" , "width": "15vw", "height": "15vw"
+} },
"Timpani Large": {
"directory": "/assets/instruments/timpani-large",
"filenames": [
@@ -40,8 +40,8 @@
"timpani7d-hit-v2-rr1-main.mp3"
],
"midi-channel-name":"conductorTimpaniLarge",
- "image": { "filename": "timpani-large.svg", "yPos": "55%" }
- },
+ "image": { "filename": "timpani-large.svg", "yPos": "55%" , "width": "15vw", "height": "15vw"
+} },
"Timpani Small": { "directory": "/assets/instruments/timpani-small",
"filenames": [
"timpani1a-hit-v5-rr1-main.mp3",
@@ -49,7 +49,7 @@
"timpani4-hit-v2-rr1-sum.mp3"
],
"midi-channel-name": "conductorTimpaniSmall",
- "image": { "filename": "timpani-small.svg", "yPos": "68%" }
+ "image": { "filename": "timpani-small.svg", "yPos": "68%" , "width": "15vw", "height": "15vw"}
},
"Toms": {
"directory": "/assets/instruments/toms",
@@ -59,6 +59,6 @@
"toml-rollm-v2-rr1-mid.mp3"
],
"midi-channel-name": "conductorToms",
- "image": { "filename": "toms.svg", "yPos": "81%" }
+ "image": { "filename": "toms.svg", "yPos": "81%" , "width": "15vw", "height": "15vw"}
}
}
diff --git a/index.html b/index.html
index c3b93c1..5229146 100644
--- a/index.html
+++ b/index.html
@@ -34,6 +34,7 @@
+
diff --git a/src/instruments.js b/src/instruments.js
index 36bbaf2..9afed55 100644
--- a/src/instruments.js
+++ b/src/instruments.js
@@ -59,6 +59,8 @@ export async function loadInstruments() {
image: imageData,
yPos: config.image.yPos, // Store base64-encoded image data
midiChannelName: config["midi-channel-name"],
+ width: config.image.width,
+ height: config.image.width,
};
}
diff --git a/src/render.js b/src/render.js
index 9b6244c..4c4be3d 100644
--- a/src/render.js
+++ b/src/render.js
@@ -5,11 +5,29 @@ export class Renderer {
currStaveNumber;
numStaves;
sheetWindow;
+ eventCanvas;
+ ctx;
+ canvasWidth;
+ canvasHeight;
+ iconCache = []; // stores drawn icons
+ imageCache = new Map(); // for memoizing loaded images
constructor() {
this.numStaves = 6;
this.currStaveNumber = 1;
+
this.timeIndicator = document.querySelector("#time-indicator");
+ this.sheetWindow = document.getElementById("music-window");
+
+ // Setup canvas
+ this.eventCanvas = document.getElementById("event-canvas");
+ this.ctx = this.eventCanvas.getContext("2d");
+
+ this.resizeCanvas();
+ window.addEventListener("resize", () => {
+ this.resizeCanvas();
+ this.redrawIcons();
+ });
this.timeIndicator.addEventListener("animationiteration", () => {
this.currStaveNumber++;
@@ -18,8 +36,7 @@ export class Renderer {
}
});
- this.sheetWindow = document.getElementById("music-window");
-
+ // Render stave SVGs
for (let i = 1; i <= this.numStaves; i++) {
const staveWrapper = document.createElement("div");
staveWrapper.classList.add("stave-wrapper");
@@ -36,13 +53,19 @@ export class Renderer {
}
}
- placeIcon = (instrument) => {
+ resizeCanvas() {
+ this.canvasWidth = this.sheetWindow.offsetWidth;
+ this.canvasHeight = this.sheetWindow.offsetHeight;
+ this.eventCanvas.width = this.canvasWidth;
+ this.eventCanvas.height = this.canvasHeight;
+ }
+
+ async placeIcon(instrument) {
if (this.currStaveNumber > this.numStaves) this.currStaveNumber = 1;
const targetWrapper = document.getElementById(
`stave-wrapper-${this.currStaveNumber}`
);
-
const rect = this.timeIndicator.getBoundingClientRect();
const sheetLeft = this.sheetWindow.getBoundingClientRect().left;
const xPosition = rect.left + window.scrollX - sheetLeft;
@@ -50,34 +73,71 @@ export class Renderer {
const newObject = document.createElement("div");
newObject.classList.add("event-icon");
newObject.style.position = "absolute";
- newObject.style.left = `${
- xPosition - document.documentElement.clientWidth / 200
- }px`;
- newObject.style.top = `${instrument.yPos}`;
+ newObject.style.left = `${xPosition}px`;
+ newObject.style.top = instrument.yPos;
+ newObject.style.width = instrument.width;
+ newObject.style.height = instrument.height;
- const eventIcon = document.createElement("object");
- eventIcon.type = "image/svg+xml";
- eventIcon.data = instrument.image;
+ // ✅ Detect and decode base64 if needed
+ let svgString = instrument.image;
+ if (svgString.startsWith("data:image/svg+xml;base64,")) {
+ const base64 = svgString.split(",")[1];
+ svgString = atob(base64);
+ }
- newObject.appendChild(eventIcon);
+ const parser = new DOMParser();
+ const svgDoc = parser.parseFromString(svgString, "image/svg+xml");
+ const svgElement = svgDoc.documentElement;
+
+ // ✅ Handle parsing error
+ if (svgElement.nodeName === "parsererror") {
+ console.error("Failed to parse SVG:", svgString);
+ return;
+ }
+
+ svgElement.style.width = "100%";
+ svgElement.style.height = "100%";
+
+ newObject.appendChild(svgElement);
targetWrapper.appendChild(newObject);
- };
+ }
+
+ async loadImageFromSVG(svgString) {
+ if (this.imageCache.has(svgString)) {
+ return this.imageCache.get(svgString);
+ }
+
+ return new Promise((resolve) => {
+ const blob = new Blob([svgString], { type: "image/svg+xml" });
+ const url = URL.createObjectURL(blob);
+ const img = new Image();
+
+ img.onload = () => {
+ URL.revokeObjectURL(url); // clean up after load
+ this.imageCache.set(svgString, img);
+ resolve(img);
+ };
+
+ img.src = url;
+ });
+ }
+
+ redrawIcons() {
+ this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
+ this.iconCache.forEach(({ image, x, y }) => {
+ this.ctx.drawImage(image, x, y, 24, 24); // Adjust size as needed
+ });
+ }
cleanUpAndRestart(reload = false) {
- const icons = document.querySelectorAll(".event-icon");
- this.timeIndicator = document.getElementById("time-indicator");
-
- icons.forEach((icon) => {
- icon.classList.add("fade-out");
- icon.addEventListener("transitionend", () => {
- icon.remove();
- });
- });
-
+ this.iconCache = [];
+ this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
this.currStaveNumber = 1;
Conductor.setTitle();
-
if (reload) location.reload();
}
}
+
+// This will need to be worked on. There is no need for the loadImageFromSVG function here and the placeIcon function will need to be changed to reflect it.
+// image size and z-index isn't correct and the canvas will need to be enlarged to allow icons to overhang the staves.
diff --git a/src/script.js b/src/script.js
index 5264e24..311b54f 100644
--- a/src/script.js
+++ b/src/script.js
@@ -2,7 +2,7 @@ import { sendMidiMessage } from "./midi.js";
import { Conductor } from "./conductor.js";
import { DataStream } from "./socket.js";
import { playSound } from "./audio";
-import { Renderer, placeIcon } from "./render.js";
+import { Renderer } from "./render.js";
document.addEventListener("DOMContentLoaded", async () => {
sendMidiMessage();
diff --git a/styles.css b/styles.css
index f9feceb..61e7175 100644
--- a/styles.css
+++ b/styles.css
@@ -74,7 +74,10 @@ html {
transition: opacity 1s ease;
}
-
+#event-canvas {
+ background-color: rgba(255, 255, 255, 0.02); /* lightly visible canvas for debugging */
+ }
+
.logo {
color: #fff;
width: 20%;
@@ -195,10 +198,27 @@ html {
width: 2px;
/* height: 100%; */
animation: moveRight 10s linear infinite;
- z-index: 2;
+ z-index: 3;
}
-
+#event-canvas {
+ position: absolute;
+ top: 0;
+ left: 0;
+ pointer-events: none;
+ z-index: 2;
+ }
+
+ #time-indicator {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ width: 2px;
+ background-color: red;
+ z-index: 3;
+ will-change: transform;
+ }
+
.fade-in {
opacity: 1;
}