a little progress
This commit is contained in:
parent
be327165b8
commit
f776fe377a
|
|
@ -8,7 +8,7 @@
|
||||||
"bdrumnew-hit-v7-rr1-sum-(1).mp3"
|
"bdrumnew-hit-v7-rr1-sum-(1).mp3"
|
||||||
],
|
],
|
||||||
"midi-channel-name": "conductorOrchestralBass",
|
"midi-channel-name": "conductorOrchestralBass",
|
||||||
"image": { "filename": "bass.svg", "yPos": "13%" }
|
"image": { "filename": "bass.svg", "yPos": "13%", "width": "15vw", "height": "15vw" }
|
||||||
},
|
},
|
||||||
"Snare": {
|
"Snare": {
|
||||||
"directory": "/assets/instruments/snare",
|
"directory": "/assets/instruments/snare",
|
||||||
|
|
@ -17,20 +17,20 @@
|
||||||
"ropesnare-low-tsn-main-vl4-rr1.mp3"
|
"ropesnare-low-tsn-main-vl4-rr1.mp3"
|
||||||
],
|
],
|
||||||
"midi-channel-name": "conductorSnare",
|
"midi-channel-name": "conductorSnare",
|
||||||
"image": { "filename": "snare.svg", "yPos": "26%" }
|
"image": { "filename": "snare.svg", "yPos": "26%", "width": "15vw", "height": "15vw" }
|
||||||
},
|
},
|
||||||
"Surdo": {
|
"Surdo": {
|
||||||
"directory": "/assets/instruments/surdo",
|
"directory": "/assets/instruments/surdo",
|
||||||
"filenames": ["surdo-5.mp3", "surdo-6.mp3", "surdo-7.mp3"],
|
"filenames": ["surdo-5.mp3", "surdo-6.mp3", "surdo-7.mp3"],
|
||||||
"midi-channel-name":"conductorSnare",
|
"midi-channel-name":"conductorSnare",
|
||||||
"image": { "filename": "surdo.svg", "yPos": "39%" }
|
"image": { "filename": "surdo.svg", "yPos": "39%" , "width": "15vw", "height": "15vw"}
|
||||||
},
|
},
|
||||||
"Surdo Napa": {
|
"Surdo Napa": {
|
||||||
"directory": "/assets/instruments/surdo-napa",
|
"directory": "/assets/instruments/surdo-napa",
|
||||||
"filenames": ["surdo-1.mp3", "surdo-3.mp3", "surdo-4.mp3"],
|
"filenames": ["surdo-1.mp3", "surdo-3.mp3", "surdo-4.mp3"],
|
||||||
"midi-channel-name": "conductorSurdoNapa",
|
"midi-channel-name": "conductorSurdoNapa",
|
||||||
"image": { "filename": "surdo-napa.svg", "yPos": "42%" }
|
"image": { "filename": "surdo-napa.svg", "yPos": "42%" , "width": "15vw", "height": "15vw"
|
||||||
},
|
} },
|
||||||
"Timpani Large": {
|
"Timpani Large": {
|
||||||
"directory": "/assets/instruments/timpani-large",
|
"directory": "/assets/instruments/timpani-large",
|
||||||
"filenames": [
|
"filenames": [
|
||||||
|
|
@ -40,8 +40,8 @@
|
||||||
"timpani7d-hit-v2-rr1-main.mp3"
|
"timpani7d-hit-v2-rr1-main.mp3"
|
||||||
],
|
],
|
||||||
"midi-channel-name":"conductorTimpaniLarge",
|
"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",
|
"Timpani Small": { "directory": "/assets/instruments/timpani-small",
|
||||||
"filenames": [
|
"filenames": [
|
||||||
"timpani1a-hit-v5-rr1-main.mp3",
|
"timpani1a-hit-v5-rr1-main.mp3",
|
||||||
|
|
@ -49,7 +49,7 @@
|
||||||
"timpani4-hit-v2-rr1-sum.mp3"
|
"timpani4-hit-v2-rr1-sum.mp3"
|
||||||
],
|
],
|
||||||
"midi-channel-name": "conductorTimpaniSmall",
|
"midi-channel-name": "conductorTimpaniSmall",
|
||||||
"image": { "filename": "timpani-small.svg", "yPos": "68%" }
|
"image": { "filename": "timpani-small.svg", "yPos": "68%" , "width": "15vw", "height": "15vw"}
|
||||||
},
|
},
|
||||||
"Toms": {
|
"Toms": {
|
||||||
"directory": "/assets/instruments/toms",
|
"directory": "/assets/instruments/toms",
|
||||||
|
|
@ -59,6 +59,6 @@
|
||||||
"toml-rollm-v2-rr1-mid.mp3"
|
"toml-rollm-v2-rr1-mid.mp3"
|
||||||
],
|
],
|
||||||
"midi-channel-name": "conductorToms",
|
"midi-channel-name": "conductorToms",
|
||||||
"image": { "filename": "toms.svg", "yPos": "81%" }
|
"image": { "filename": "toms.svg", "yPos": "81%" , "width": "15vw", "height": "15vw"}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@
|
||||||
|
|
||||||
|
|
||||||
<div class="sheet-music-window" id="music-window">
|
<div class="sheet-music-window" id="music-window">
|
||||||
|
<canvas id="event-canvas"></canvas>
|
||||||
<object type="image/svg+xml" data="/assets/svg/time-pointer.svg" class="time-indicator"
|
<object type="image/svg+xml" data="/assets/svg/time-pointer.svg" class="time-indicator"
|
||||||
id="time-indicator"></object>
|
id="time-indicator"></object>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,8 @@ export async function loadInstruments() {
|
||||||
image: imageData,
|
image: imageData,
|
||||||
yPos: config.image.yPos, // Store base64-encoded image data
|
yPos: config.image.yPos, // Store base64-encoded image data
|
||||||
midiChannelName: config["midi-channel-name"],
|
midiChannelName: config["midi-channel-name"],
|
||||||
|
width: config.image.width,
|
||||||
|
height: config.image.width,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
108
src/render.js
108
src/render.js
|
|
@ -5,11 +5,29 @@ export class Renderer {
|
||||||
currStaveNumber;
|
currStaveNumber;
|
||||||
numStaves;
|
numStaves;
|
||||||
sheetWindow;
|
sheetWindow;
|
||||||
|
eventCanvas;
|
||||||
|
ctx;
|
||||||
|
canvasWidth;
|
||||||
|
canvasHeight;
|
||||||
|
iconCache = []; // stores drawn icons
|
||||||
|
imageCache = new Map(); // for memoizing loaded images
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.numStaves = 6;
|
this.numStaves = 6;
|
||||||
this.currStaveNumber = 1;
|
this.currStaveNumber = 1;
|
||||||
|
|
||||||
this.timeIndicator = document.querySelector("#time-indicator");
|
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.timeIndicator.addEventListener("animationiteration", () => {
|
||||||
this.currStaveNumber++;
|
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++) {
|
for (let i = 1; i <= this.numStaves; i++) {
|
||||||
const staveWrapper = document.createElement("div");
|
const staveWrapper = document.createElement("div");
|
||||||
staveWrapper.classList.add("stave-wrapper");
|
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;
|
if (this.currStaveNumber > this.numStaves) this.currStaveNumber = 1;
|
||||||
|
|
||||||
const targetWrapper = document.getElementById(
|
const targetWrapper = document.getElementById(
|
||||||
`stave-wrapper-${this.currStaveNumber}`
|
`stave-wrapper-${this.currStaveNumber}`
|
||||||
);
|
);
|
||||||
|
|
||||||
const rect = this.timeIndicator.getBoundingClientRect();
|
const rect = this.timeIndicator.getBoundingClientRect();
|
||||||
const sheetLeft = this.sheetWindow.getBoundingClientRect().left;
|
const sheetLeft = this.sheetWindow.getBoundingClientRect().left;
|
||||||
const xPosition = rect.left + window.scrollX - sheetLeft;
|
const xPosition = rect.left + window.scrollX - sheetLeft;
|
||||||
|
|
@ -50,34 +73,71 @@ export class Renderer {
|
||||||
const newObject = document.createElement("div");
|
const newObject = document.createElement("div");
|
||||||
newObject.classList.add("event-icon");
|
newObject.classList.add("event-icon");
|
||||||
newObject.style.position = "absolute";
|
newObject.style.position = "absolute";
|
||||||
newObject.style.left = `${
|
newObject.style.left = `${xPosition}px`;
|
||||||
xPosition - document.documentElement.clientWidth / 200
|
newObject.style.top = instrument.yPos;
|
||||||
}px`;
|
newObject.style.width = instrument.width;
|
||||||
newObject.style.top = `${instrument.yPos}`;
|
newObject.style.height = instrument.height;
|
||||||
|
|
||||||
const eventIcon = document.createElement("object");
|
// ✅ Detect and decode base64 if needed
|
||||||
eventIcon.type = "image/svg+xml";
|
let svgString = instrument.image;
|
||||||
eventIcon.data = 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);
|
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) {
|
cleanUpAndRestart(reload = false) {
|
||||||
const icons = document.querySelectorAll(".event-icon");
|
this.iconCache = [];
|
||||||
this.timeIndicator = document.getElementById("time-indicator");
|
this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
|
||||||
|
|
||||||
icons.forEach((icon) => {
|
|
||||||
icon.classList.add("fade-out");
|
|
||||||
icon.addEventListener("transitionend", () => {
|
|
||||||
icon.remove();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
this.currStaveNumber = 1;
|
this.currStaveNumber = 1;
|
||||||
|
|
||||||
Conductor.setTitle();
|
Conductor.setTitle();
|
||||||
|
|
||||||
if (reload) location.reload();
|
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.
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { sendMidiMessage } from "./midi.js";
|
||||||
import { Conductor } from "./conductor.js";
|
import { Conductor } from "./conductor.js";
|
||||||
import { DataStream } from "./socket.js";
|
import { DataStream } from "./socket.js";
|
||||||
import { playSound } from "./audio";
|
import { playSound } from "./audio";
|
||||||
import { Renderer, placeIcon } from "./render.js";
|
import { Renderer } from "./render.js";
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", async () => {
|
document.addEventListener("DOMContentLoaded", async () => {
|
||||||
sendMidiMessage();
|
sendMidiMessage();
|
||||||
|
|
|
||||||
26
styles.css
26
styles.css
|
|
@ -74,7 +74,10 @@ html {
|
||||||
transition: opacity 1s ease;
|
transition: opacity 1s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#event-canvas {
|
||||||
|
background-color: rgba(255, 255, 255, 0.02); /* lightly visible canvas for debugging */
|
||||||
|
}
|
||||||
|
|
||||||
.logo {
|
.logo {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
width: 20%;
|
width: 20%;
|
||||||
|
|
@ -195,10 +198,27 @@ html {
|
||||||
width: 2px;
|
width: 2px;
|
||||||
/* height: 100%; */
|
/* height: 100%; */
|
||||||
animation: moveRight 10s linear infinite;
|
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 {
|
.fade-in {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue