nearly sorted with the rendering

This commit is contained in:
Joe 2025-04-23 12:32:44 +01:00
parent 75b66e245e
commit 8993615953
18 changed files with 204 additions and 322 deletions

View File

@ -8,7 +8,12 @@
"bdrumnew-hit-v7-rr1-sum-(1).mp3"
],
"midi-channel-name": "conductorOrchestralBass",
"image": { "filename": "bass.svg", "yPos": "13%", "width": "100", "height": "100" }
"image": {
"filename": "bass.svg",
"yPos": "13%",
"width": "100",
"height": "100"
}
},
"Snare": {
"directory": "/assets/instruments/snare",
@ -17,20 +22,35 @@
"ropesnare-low-tsn-main-vl4-rr1.mp3"
],
"midi-channel-name": "conductorSnare",
"image": { "filename": "snare.svg", "yPos": "26%", "width": "100", "height": "100" }
"image": {
"filename": "snare.svg",
"yPos": "26%",
"width": "100",
"height": "100"
}
},
"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%" , "width": "100", "height": "100"}
"midi-channel-name": "conductorSnare",
"image": {
"filename": "surdo.svg",
"yPos": "39%",
"width": "100",
"height": "100"
}
},
"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%" , "width": "100", "height": "100"
} },
"image": {
"filename": "surdo-napa.svg",
"yPos": "42%",
"width": "100",
"height": "100"
}
},
"Timpani Large": {
"directory": "/assets/instruments/timpani-large",
"filenames": [
@ -39,17 +59,28 @@
"timpani7b-hit-v5-rr2-main.mp3",
"timpani7d-hit-v2-rr1-main.mp3"
],
"midi-channel-name":"conductorTimpaniLarge",
"image": { "filename": "timpani-large.svg", "yPos": "55%" , "width": "100", "height": "100"
} },
"Timpani Small": { "directory": "/assets/instruments/timpani-small",
"midi-channel-name": "conductorTimpaniLarge",
"image": {
"filename": "timpani-large.svg",
"yPos": "55%",
"width": "100",
"height": "100"
}
},
"Timpani Small": {
"directory": "/assets/instruments/timpani-small",
"filenames": [
"timpani1a-hit-v5-rr1-main.mp3",
"timpani2-hit-v3-rr1-sum.mp3",
"timpani4-hit-v2-rr1-sum.mp3"
],
"midi-channel-name": "conductorTimpaniSmall",
"image": { "filename": "timpani-small.svg", "yPos": "68%" , "width": "100", "height": "100"}
"image": {
"filename": "timpani-small.svg",
"yPos": "68%",
"width": "100",
"height": "100"
}
},
"Toms": {
"directory": "/assets/instruments/toms",
@ -59,6 +90,11 @@
"toml-rollm-v2-rr1-mid.mp3"
],
"midi-channel-name": "conductorToms",
"image": { "filename": "toms.svg", "yPos": "81%" , "width": "100", "height": "100"}
"image": {
"filename": "toms.svg",
"yPos": "81%",
"width": "100",
"height": "100"
}
}
}

View File

@ -1 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="Layer_2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 23.33 18.79"><defs><style>.cls-1{fill:#fff;stroke-width:0px;}.cls-2{fill:none;stroke:#fff;stroke-miterlimit:10;}</style></defs><g id="Layer_1-2"><path class="cls-1" d="M19.09,7.44c0,4.11-3.32,7.44-7.42,7.44s-7.42-3.33-7.42-7.44S7.56,0,11.66,0s7.42,3.33,7.42,7.44"/><path class="cls-2" d="M22.83,9.31c0,4.96-5,8.98-11.16,8.98S.5,14.27.5,9.31"/></g></svg>
<?xml version="1.0" encoding="UTF-8"?><svg id="Layer_2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 23.33 18.79" width="100" height="100"><defs><style>.cls-1{fill:#fff;stroke-width:0px;}.cls-2{fill:none;stroke:#fff;stroke-miterlimit:10;}</style></defs><g id="Layer_1-2"><path class="cls-1" d="M19.09,7.44c0,4.11-3.32,7.44-7.42,7.44s-7.42-3.33-7.42-7.44S7.56,0,11.66,0s7.42,3.33,7.42,7.44"/><path class="cls-2" d="M22.83,9.31c0,4.96-5,8.98-11.16,8.98S.5,14.27.5,9.31"/></g></svg>

Before

Width:  |  Height:  |  Size: 457 B

After

Width:  |  Height:  |  Size: 482 B

View File

@ -1 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="Layer_2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15.27 23.95"><defs><style>.cls-1{fill:#fff;stroke-width:0px;}.cls-2{fill:none;stroke:#fff;stroke-miterlimit:10;}</style></defs><g id="Layer_1-2"><polyline class="cls-2" points="14.97 23.55 .78 12.61 10.65 3.86"/><path class="cls-1" d="M14.98,0c-1.35,2.55-2.72,6.05-3.05,8.78l-1.84-4.44-4.31-2.26c2.8-.05,6.49-1.02,9.2-2.08"/><line class="cls-2" x1=".76" y1="23.55" x2="7.02" y2="17.42"/></g></svg>
<?xml version="1.0" encoding="UTF-8"?><svg id="Layer_2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15.27 23.95" width="100" height="100"><defs><style>.cls-1{fill:#fff;stroke-width:0px;}.cls-2{fill:none;stroke:#fff;stroke-miterlimit:10;}</style></defs><g id="Layer_1-2"><polyline class="cls-2" points="14.97 23.55 .78 12.61 10.65 3.86"/><path class="cls-1" d="M14.98,0c-1.35,2.55-2.72,6.05-3.05,8.78l-1.84-4.44-4.31-2.26c2.8-.05,6.49-1.02,9.2-2.08"/><line class="cls-2" x1=".76" y1="23.55" x2="7.02" y2="17.42"/></g></svg>

Before

Width:  |  Height:  |  Size: 501 B

After

Width:  |  Height:  |  Size: 526 B

View File

@ -1 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="Layer_2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 13.43 23.52"><defs><style>.cls-1{fill:none;stroke:#fff;stroke-miterlimit:10;stroke-width:.87px;}</style></defs><g id="Layer_1-2"><polygon class="cls-1" points="6.71 22.55 .71 10.73 12.72 10.73 6.71 22.55"/><path class="cls-1" d="M1.03,4.12h11.36M2.7.32l8.03,7.6M10.73.32L2.7,7.92"/></g></svg>
<?xml version="1.0" encoding="UTF-8"?><svg id="Layer_2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 13.43 23.52" width="100" height="100"><defs><style>.cls-1{fill:none;stroke:#fff;stroke-miterlimit:10;stroke-width:.87px;}</style></defs><g id="Layer_1-2"><polygon class="cls-1" points="6.71 22.55 .71 10.73 12.72 10.73 6.71 22.55"/><path class="cls-1" d="M1.03,4.12h11.36M2.7.32l8.03,7.6M10.73.32L2.7,7.92"/></g></svg>

Before

Width:  |  Height:  |  Size: 396 B

After

Width:  |  Height:  |  Size: 422 B

View File

@ -1 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="Layer_2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 13.43 23.67"><defs><style>.cls-1{fill:#fff;stroke-width:0px;}.cls-2{fill:none;stroke:#fff;stroke-miterlimit:10;stroke-width:.87px;}</style></defs><g id="Layer_1-2"><polygon class="cls-2" points="6.71 22.71 .71 10.89 12.72 10.89 6.71 22.71"/><path class="cls-1" d="M11.33,4.54c0,2.51-2.07,4.54-4.62,4.54S2.09,7.05,2.09,4.54,4.16,0,6.71,0s4.62,2.03,4.62,4.54"/></g></svg>
<?xml version="1.0" encoding="UTF-8"?><svg id="Layer_2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 13.43 23.67" width="100" height="100"><defs><style>.cls-1{fill:#fff;stroke-width:0px;}.cls-2{fill:none;stroke:#fff;stroke-miterlimit:10;stroke-width:.87px;}</style></defs><g id="Layer_1-2"><polygon class="cls-2" points="6.71 22.71 .71 10.89 12.72 10.89 6.71 22.71"/><path class="cls-1" d="M11.33,4.54c0,2.51-2.07,4.54-4.62,4.54S2.09,7.05,2.09,4.54,4.16,0,6.71,0s4.62,2.03,4.62,4.54"/></g></svg>

Before

Width:  |  Height:  |  Size: 473 B

After

Width:  |  Height:  |  Size: 498 B

View File

@ -1 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="Layer_2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 21.29 23.81"><defs><style>.cls-1{fill:#fff;stroke-width:0px;}.cls-2{fill:none;stroke:#fff;stroke-miterlimit:10;}</style></defs><g id="Layer_1-2"><polyline class="cls-2" points="0 .5 19.76 .5 5.02 11.32 15.28 19.98"/><path class="cls-1" d="M19.84,23.81c-2.83-1.05-6.66-2.01-9.57-2.06l4.48-2.23,1.92-4.4c.35,2.7,1.76,6.17,3.18,8.69"/><line class="cls-2" x1="1.74" y1="23.81" x2="1.74" y2=".47"/></g></svg>
<?xml version="1.0" encoding="UTF-8"?><svg id="Layer_2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 21.29 23.81" width="100" height="100"><defs><style>.cls-1{fill:#fff;stroke-width:0px;}.cls-2{fill:none;stroke:#fff;stroke-miterlimit:10;}</style></defs><g id="Layer_1-2"><polyline class="cls-2" points="0 .5 19.76 .5 5.02 11.32 15.28 19.98"/><path class="cls-1" d="M19.84,23.81c-2.83-1.05-6.66-2.01-9.57-2.06l4.48-2.23,1.92-4.4c.35,2.7,1.76,6.17,3.18,8.69"/><line class="cls-2" x1="1.74" y1="23.81" x2="1.74" y2=".47"/></g></svg>

Before

Width:  |  Height:  |  Size: 507 B

After

Width:  |  Height:  |  Size: 533 B

View File

@ -1 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="Layer_2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15.32 23.67"><defs><style>.cls-1{fill:#fff;stroke-width:0px;}.cls-2{fill:none;stroke:#fff;stroke-miterlimit:10;}</style></defs><g id="Layer_1-2"><polyline class="cls-2" points="15.02 .4 .79 11.21 10.3 19.39"/><path class="cls-1" d="M15.22,23.67c-2.73-1.05-6.43-2.01-9.24-2.06l4.33-2.23,1.85-4.39c.34,2.7,1.7,6.16,3.07,8.68"/></g></svg>
<?xml version="1.0" encoding="UTF-8"?><svg id="Layer_2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15.32 23.67" width="100" height="100"><defs><style>.cls-1{fill:#fff;stroke-width:0px;}.cls-2{fill:none;stroke:#fff;stroke-miterlimit:10;}</style></defs><g id="Layer_1-2"><polyline class="cls-2" points="15.02 .4 .79 11.21 10.3 19.39"/><path class="cls-1" d="M15.22,23.67c-2.73-1.05-6.43-2.01-9.24-2.06l4.33-2.23,1.85-4.39c.34,2.7,1.7,6.16,3.07,8.68"/></g></svg>

Before

Width:  |  Height:  |  Size: 439 B

After

Width:  |  Height:  |  Size: 465 B

View File

@ -1 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="Layer_2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 13.43 23.96"><defs><style>.cls-1{fill:#fff;stroke-width:0px;}.cls-2{stroke-miterlimit:10;}.cls-2,.cls-3{fill:none;stroke:#fff;stroke-width:.87px;}</style></defs><g id="Layer_1-2"><polygon class="cls-2" points="6.71 22.99 .71 11.18 12.72 11.18 6.71 22.99"/><path class="cls-1" d="M9.11,7.86c0,1.3-1.07,2.36-2.4,2.36s-2.4-1.05-2.4-2.36,1.07-2.36,2.4-2.36,2.4,1.06,2.4,2.36"/><path class="cls-1" d="M9.11,2.36c0,1.3-1.07,2.36-2.4,2.36s-2.4-1.06-2.4-2.36S5.39,0,6.71,0s2.4,1.06,2.4,2.36"/><line class="cls-3" x1="3.46" y1="16.34" x2="9.97" y2="16.34"/></g></svg>
<?xml version="1.0" encoding="UTF-8"?><svg id="Layer_2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 13.43 23.96" width="100" height="100"><defs><style>.cls-1{fill:#fff;stroke-width:0px;}.cls-2{stroke-miterlimit:10;}.cls-2,.cls-3{fill:none;stroke:#fff;stroke-width:.87px;}</style></defs><g id="Layer_1-2"><polygon class="cls-2" points="6.71 22.99 .71 11.18 12.72 11.18 6.71 22.99"/><path class="cls-1" d="M9.11,7.86c0,1.3-1.07,2.36-2.4,2.36s-2.4-1.05-2.4-2.36,1.07-2.36,2.4-2.36,2.4,1.06,2.4,2.36"/><path class="cls-1" d="M9.11,2.36c0,1.3-1.07,2.36-2.4,2.36s-2.4-1.06-2.4-2.36S5.39,0,6.71,0s2.4,1.06,2.4,2.36"/><line class="cls-3" x1="3.46" y1="16.34" x2="9.97" y2="16.34"/></g></svg>

Before

Width:  |  Height:  |  Size: 662 B

After

Width:  |  Height:  |  Size: 688 B

View File

@ -1 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="Layer_2" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 330.51 33.72"><defs><style>.cls-1{stroke:#828383;stroke-width:.5px;}.cls-1,.cls-2{fill:none;}.cls-3{clip-path:url(#clippath-2);}.cls-2{stroke-width:0px;}.cls-4{clip-path:url(#clippath-1);}.cls-5{opacity:.75;}.cls-6{clip-path:url(#clippath);}</style><clipPath id="clippath"><rect class="cls-2" width="330.51" height="33.72"/></clipPath><clipPath id="clippath-1"><rect class="cls-2" y="0" width="330.51" height="33.72"/></clipPath><clipPath id="clippath-2"><rect class="cls-2" x="-25" y="-22" width="380.51" height="56.72"/></clipPath></defs><g id="Layer_1-2"><g class="cls-6"><g class="cls-4"><g class="cls-5"><g class="cls-3"><rect class="cls-1" x=".25" y=".25" width="33" height="33.22"/><rect class="cls-1" x="132.26" y=".25" width="33" height="33.22"/><rect class="cls-1" x="66.26" y=".25" width="33" height="33.22"/><rect class="cls-1" x="198.27" y=".25" width="33" height="33.22"/><rect class="cls-1" x="264.26" y=".25" width="33" height="33.22"/><rect class="cls-1" x="33.26" y=".25" width="33" height="33.22"/><rect class="cls-1" x="165.27" y=".25" width="33" height="33.22"/><rect class="cls-1" x="99.25" y=".25" width="33" height="33.22"/><rect class="cls-1" x="231.27" y=".25" width="33" height="33.22"/><rect class="cls-1" x="297.26" y=".25" width="33" height="33.22"/><rect class="cls-1" x=".25" y=".25" width="330.01" height="6.56"/><rect class="cls-1" x=".25" y="13.58" width="330.01" height="6.56"/><rect class="cls-1" x=".25" y="6.91" width="330.01" height="6.56"/><rect class="cls-1" x=".25" y="20.24" width="330.01" height="6.56"/><rect class="cls-1" x=".25" y="26.91" width="330.01" height="6.56"/></g></g></g></g></g></svg>
<?xml version="1.0" encoding="UTF-8"?><svg id="Layer_2" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 330.51 33.72" ><defs><style>.cls-1{stroke:#828383;stroke-width:.5px;}.cls-1,.cls-2{fill:none;}.cls-3{clip-path:url(#clippath-2);}.cls-2{stroke-width:0px;}.cls-4{clip-path:url(#clippath-1);}.cls-5{opacity:.75;}.cls-6{clip-path:url(#clippath);}</style><clipPath id="clippath"><rect class="cls-2" width="330.51" height="33.72"/></clipPath><clipPath id="clippath-1"><rect class="cls-2" y="0" width="330.51" height="33.72"/></clipPath><clipPath id="clippath-2"><rect class="cls-2" x="-25" y="-22" width="380.51" height="56.72"/></clipPath></defs><g id="Layer_1-2"><g class="cls-6"><g class="cls-4"><g class="cls-5"><g class="cls-3"><rect class="cls-1" x=".25" y=".25" width="33" height="33.22"/><rect class="cls-1" x="132.26" y=".25" width="33" height="33.22"/><rect class="cls-1" x="66.26" y=".25" width="33" height="33.22"/><rect class="cls-1" x="198.27" y=".25" width="33" height="33.22"/><rect class="cls-1" x="264.26" y=".25" width="33" height="33.22"/><rect class="cls-1" x="33.26" y=".25" width="33" height="33.22"/><rect class="cls-1" x="165.27" y=".25" width="33" height="33.22"/><rect class="cls-1" x="99.25" y=".25" width="33" height="33.22"/><rect class="cls-1" x="231.27" y=".25" width="33" height="33.22"/><rect class="cls-1" x="297.26" y=".25" width="33" height="33.22"/><rect class="cls-1" x=".25" y=".25" width="330.01" height="6.56"/><rect class="cls-1" x=".25" y="13.58" width="330.01" height="6.56"/><rect class="cls-1" x=".25" y="6.91" width="330.01" height="6.56"/><rect class="cls-1" x=".25" y="20.24" width="330.01" height="6.56"/><rect class="cls-1" x=".25" y="26.91" width="330.01" height="6.56"/></g></g></g></g></g></svg>

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -15,11 +15,6 @@
<div class="loading">
</div>
</div>
<div id="buttons">
<img src="/assets/svg/mute.svg" class="audio" id="mute" />
<img src="/assets/svg/menu.svg" class="menu" id="menu-button" />
</div>
<div id="main-content">
@ -34,59 +29,8 @@
<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"
id="time-indicator"></object>
</div>
</div>
<div id="about-content">
<div class="about-title">
<img src="/assets/svg/the-conductor-title.svg"></img>
</div>
<div class="about-content">
<div class="safari">
<div class="instruments-key" id="instrument-key-div"></div>
</div>
<p>
The Conductor translates live lightning strikes from around the world into a graphic score for seven percussion
instruments.
</p>
<p>
The project was conceived and developed by the artist Mishka Henner between 2023 and 2024 with a team of
musicians, sound artists and acoustics engineers during an artist's residency at Energy House 2.0 at the
University of Salford.
</p>
<p>
The Conductor was first performed at the University of Salfords Acoustic Laboratories during the Sounds From
the Other City Festival on 5 May 2024.
</p>
<p>
Click <a href="https://mishkahenner.com/The-Conductor" style="color: #fff; text-decoration:underline">here</a>
to read more about the project. Download a copy of the artist's graphic score <a
href="https://files.cargocollective.com/c20096/CONDUCTOR-SHEET-MUSIC-Black-BG.pdf"
style="color: #fff; text-decoration:underline">here</a>.
</p>
<br />
<div class="production-credits">
<br />
<div>
Project Conception and Design by Mishka Henner.
</div>
Data Capture, Web Design and Coding by Joe Gibson.
<div>
Lightning data from Blitzortung.org
</div>
<p>
With support from the University of Salford Art Collection in partnership with Open Eye Gallery, Liverpool as
part of the LOOK Photo Biennial, and Castlefield Gallery, Manchester. Generously supported by Friends of
Energy House Labs.
</p>
</div>
id="time-indicator"></object>
</div>
</div>
<script type="module" src="/src/script.js"></script>

View File

@ -16,3 +16,16 @@ sort out the blitzortung connection issue
make the text max size smaller
about page make title smaller
lOOK AT SPACING BETWEEN THE TITLE AND STAVES AND THEN MAYBE ALSO THE STAVE SPACING
TRY BOTH THE SVG RENDER AND THE RASTER RENDERING APPROACH...
SCROLL BAR MUST GO (VERTICAL)
Remove all passwords and google accounts from laptop.
Ensure it boots into landscape mode

11
planeNotes.txt Normal file
View File

@ -0,0 +1,11 @@
placeIcon method:
Now img is an <img> element - does it have a img.naturalHeight/Width property?
Check because we are using these values to draw the img.
What is the sheetWindow.offsetHeight? - This is probably okay but take a look.
Moving forward:
We need to measure the stavewrapper and place the icons using an offset factor based on the stave number.

View File

@ -35,37 +35,7 @@ export class Conductor {
showMainContent() {
Conductor.setTitle();
const mute = document.getElementById("mute");
mute.addEventListener("click", () => {
const mutedSrc = "/assets/svg/sound-on.svg";
const unMutedSrc = "/assets/svg/mute.svg";
interacted = !interacted;
interacted ? unMute() : muter();
mute.src = interacted ? mutedSrc : unMutedSrc;
startAudio();
fadeInVolume();
});
this.fadeAndReplaceADiv("main-content", "loading-screen");
this.fadeAndReplaceADiv("buttons", "buttons");
this.showingWhichContent = "main-content";
document.getElementById("menu-button").onclick = function () {
toggleInfo();
};
}
toggleInfo() {
if (this.showingWhichContent === "main-content") {
this.fadeAndReplaceADiv("about-content", "main-content");
cleanUpAndRestart();
this.showingWhichContent = "about-content";
} else {
this.fadeAndReplaceADiv("main-content", "about-content");
this.showingWhichContent = "main-content";
}
}
fadeAndReplaceADiv(innyDivId, outtyDivId) {
@ -87,20 +57,6 @@ export class Conductor {
}
async init() {
const instrumentsKey = document.getElementById("instrument-key-div");
this.instruments = await loadInstruments();
const instNames = Object.keys(this.instruments);
for (const instrument of instNames) {
const keyDiv = document.createElement("div");
const keySvg = document.createElement("object");
const keyName = document.createElement("div");
keyName.innerText = instrument.toUpperCase();
keySvg.type = "image/svg+xml";
keySvg.data = this.instruments[instrument].image;
keyDiv.appendChild(keySvg);
keyDiv.appendChild(keyName);
keyDiv.classList.add("instrument-key");
instrumentsKey.appendChild(keyDiv);
}
}
}

View File

@ -3,64 +3,64 @@ async function loadInstrumentsConfig() {
return await response.json();
}
async function fetchAudioData(url) {
async function fetchSvgText(url) {
const response = await fetch(url);
const arrayBuffer = await response.arrayBuffer();
return arrayBuffer;
return await response.text();
}
async function decodeAudioData(context, arrayBuffer) {
function svgTextToImage(svgText, defaultWidth = 100, defaultHeight = 100) {
return new Promise((resolve, reject) => {
context.decodeAudioData(arrayBuffer, resolve, reject);
});
}
// Create a blob from the SVG text
const blob = new Blob([svgText], { type: "image/svg+xml" });
const url = URL.createObjectURL(blob);
const img = new Image();
async function loadAudioSamples(context, directory, filenames) {
const audioFiles = filenames;
// Set the onload callback
img.onload = () => {
// Check if naturalWidth and naturalHeight are invalid
if (img.naturalWidth === 0 || img.naturalHeight === 0) {
// Set default dimensions if image is invalid (missing width/height in SVG)
img.width = defaultWidth;
img.height = defaultHeight;
}
const audioBuffers = {};
for (const file of audioFiles) {
const arrayBuffer = await fetchAudioData(`${directory}/${file}`);
const audioBuffer = await decodeAudioData(context, arrayBuffer);
const fileName = file.split("/").pop(); // Extract filename without path for reference
audioBuffers[fileName] = audioBuffer;
}
// Clean up the object URL to free memory
URL.revokeObjectURL(url);
return audioBuffers;
}
resolve(img);
};
async function fetchImageData(url) {
const response = await fetch(url);
const blob = await response.blob();
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onloadend = () => resolve(reader.result);
reader.onerror = reject;
reader.readAsDataURL(blob); // Convert to base64
// Handle image loading errors
img.onerror = (err) => {
URL.revokeObjectURL(url); // Clean up URL
reject(err);
};
img.src = url;
});
}
export async function loadInstruments() {
const instrumentsConfig = await loadInstrumentsConfig();
const context = new (window.AudioContext || window.webkitAudioContext)();
const instruments = {};
for (const [instrument, config] of Object.entries(instrumentsConfig)) {
const samples = await loadAudioSamples(
context,
config.directory,
config.filenames
);
const imageData = await fetchImageData(
const svgText = await fetchSvgText(
`${config.directory}/${config.image.filename}`
);
// Pass the width and height to the svgTextToImage function
const img = await svgTextToImage(
svgText,
config.image.width || 100,
config.image.height || 100
);
instruments[instrument] = {
samples: samples,
image: imageData,
yPos: config.image.yPos, // Store base64-encoded image data
image: img,
yPos: config.image.yPos,
midiChannelName: config["midi-channel-name"],
width: config.image.width,
height: config.image.width,
height: config.image.height,
};
}

View File

@ -5,27 +5,20 @@ export class Renderer {
currStaveNumber;
numStaves;
sheetWindow;
eventCanvas;
ctx;
canvases = [];
canvasWidth;
canvasHeight;
iconCache = []; // stores { image, x, y, width, height }
imageCache = new Map(); // memoized <svgString, HTMLImageElement>
iconCache = [];
iconScale = 0.25;
constructor() {
this.numStaves = 6;
this.currStaveNumber = 1;
this.timeIndicator = document.querySelector("#time-indicator");
this.timeIndicator = document.getElementById("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();
});
@ -36,9 +29,15 @@ export class Renderer {
}
});
// Render stave SVGs
for (let i = 1; i <= this.numStaves; i++) {
const staveWrapper = document.createElement("div");
const canvas = document.createElement("canvas");
canvas.id = `canvas-${i}`;
canvas.className = "event-canvas";
const ctx = canvas.getContext("2d");
this.canvases.push({ canvas, ctx });
staveWrapper.classList.add("stave-wrapper");
staveWrapper.setAttribute("id", `stave-wrapper-${i}`);
staveWrapper.style.position = "relative";
@ -48,17 +47,17 @@ export class Renderer {
staveObject.data = "assets/svg/stave.svg";
staveObject.classList.add("stave-svg");
staveWrapper.appendChild(canvas);
staveWrapper.appendChild(staveObject);
this.sheetWindow.appendChild(staveWrapper);
}
}
resizeCanvas() {
this.canvasWidth = this.sheetWindow.offsetWidth;
this.canvasHeight = this.sheetWindow.offsetHeight;
this.eventCanvas.width = this.canvasWidth;
this.eventCanvas.height = this.canvasHeight;
console.log("Canvas size:", this.canvasWidth, this.canvasHeight);
// Set canvas size after layout is applied
requestAnimationFrame(() => {
const rect = canvas.getBoundingClientRect();
canvas.width = rect.width;
canvas.height = rect.height;
});
}
}
async placeIcon(instrument) {
@ -68,112 +67,47 @@ export class Renderer {
const sheetLeft = this.sheetWindow.getBoundingClientRect().left;
const xPosition = rect.left + window.scrollX - sheetLeft;
const yPercent = parseFloat(instrument.yPos); // e.g., "55%" => 55
const y = (yPercent / 100) * this.canvasHeight;
const canvasEntry = this.canvases[this.currStaveNumber - 1];
const canvas = canvasEntry.canvas;
const ctx = canvasEntry.ctx;
// Handle image caching
let svgString = instrument.image;
if (svgString.startsWith("data:image/svg+xml;base64,")) {
const base64 = svgString.split(",")[1];
svgString = atob(base64);
}
const yPercent = parseFloat(instrument.yPos);
const y = (yPercent / 100) * canvas.height;
// Ensure SVG has dimensions
svgString = this.ensureSvgHasDimensions(
svgString,
instrument.width || 50,
instrument.height || 50
);
const width = instrument.width * this.iconScale;
const height = instrument.height * this.iconScale;
const img = await this.loadImageFromSVG(svgString);
ctx.drawImage(instrument.image, xPosition, y, width, height);
// Fallback if instrument.width/height not provided
const naturalWidth = img.naturalWidth || 50;
const naturalHeight = img.naturalHeight || 50;
const scale = 0.25;
const width = parseFloat(instrument.width || naturalWidth) * scale;
const height = parseFloat(instrument.height || naturalHeight) * scale;
this.ctx.drawImage(img, xPosition, y, width, height);
// Store for redrawing later
this.iconCache.push({
image: img,
image: instrument.image,
x: xPosition,
y,
width,
height,
});
console.log("Drawing position:", xPosition, y);
console.log("width", width, "height", height);
console.log(
"Canvas size:",
this.eventCanvas.width,
this.eventCanvas.height
);
console.log(
"Canvas offset size:",
this.eventCanvas.offsetWidth,
this.eventCanvas.offsetHeight
);
}
ensureSvgHasDimensions(svgString, defaultWidth = 50, defaultHeight = 50) {
const hasWidth = /<svg[^>]*\bwidth=/.test(svgString);
const hasHeight = /<svg[^>]*\bheight=/.test(svgString);
if (!hasWidth || !hasHeight) {
svgString = svgString.replace(
/<svg([^>]*)>/,
`<svg width="${defaultWidth}" height="${defaultHeight}"$1>`
);
}
return svgString;
}
async loadImageFromSVG(svgString) {
if (this.imageCache.has(svgString)) {
return this.imageCache.get(svgString);
}
return new Promise((resolve, reject) => {
const blob = new Blob([svgString], { type: "image/svg+xml" });
const url = URL.createObjectURL(blob);
const img = new Image();
img.onload = () => {
console.log("Image loaded:", img.width, img.height); // should no longer be 0
const scale = 0.25;
const width = instrument.width * scale || img.width * scale;
const height = instrument.height * scale || img.height * scale;
ctx.drawImage(img, xPosition, y, width, height);
URL.revokeObjectURL(url);
};
img.onerror = (err) => {
console.error("Image failed to load from SVG:", err);
reject(err);
};
img.src = url;
stave: this.currStaveNumber,
});
}
redrawIcons() {
this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
this.iconCache.forEach(({ image, x, y, width, height }) => {
this.ctx.drawImage(image, x, y, width, height);
this.canvases.forEach(({ ctx, canvas }) => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
});
this.iconCache.forEach(({ image, x, y, width, height, stave }) => {
const ctx = this.canvases[stave - 1].ctx;
ctx.drawImage(image, x, y, width, height);
});
}
cleanUpAndRestart(reload = false) {
this.iconCache = [];
this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
this.currStaveNumber = 1;
this.canvases.forEach(({ ctx, canvas }) => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
});
Conductor.setTitle();
if (reload) location.reload();
}

View File

@ -1,7 +1,6 @@
import { sendMidiMessage } from "./midi.js";
import { Conductor } from "./conductor.js";
import { DataStream } from "./socket.js";
import { playSound } from "./audio";
import { Renderer } from "./render.js";
document.addEventListener("DOMContentLoaded", async () => {
@ -21,17 +20,11 @@ document.addEventListener("DOMContentLoaded", async () => {
const selectedInstrument = instruments[keys[randomIndex]];
renderer.placeIcon(selectedInstrument);
playSound(selectedInstrument);
// **** add midi trigger +++++++++ ///
});
dataStream.init();
// let currStaveNumber = 1;
conductor.showMainContent();
// let strikeNumber = 0;
// setupWebSocketListeners(); // Setup the WebSocket listeners
// resetTimeout(); // Start the timeout timer
});

View File

@ -7,6 +7,8 @@ export class DataStream extends EventTarget {
this.client = null;
this.lastReceived = null;
this.connectionTimeout = null;
this.test = false;
this.testInterval = null;
}
async init() {
@ -38,25 +40,32 @@ export class DataStream extends EventTarget {
}
setupWebSocketListeners() {
this.client.removeAllListeners?.("data");
this.client.removeAllListeners?.("error");
this.client.removeAllListeners?.("close");
if (this.test === false) {
this.client.removeAllListeners?.("data");
this.client.removeAllListeners?.("error");
this.client.removeAllListeners?.("close");
this.client.on("data", (data) => {
this.lastReceived = Date.now();
this.resetTimeout();
this.client.on("data", (data) => {
this.lastReceived = Date.now();
this.resetTimeout();
this.dispatchEvent(new CustomEvent("strike", { detail: data }));
});
this.dispatchEvent(new CustomEvent("strike", { detail: data }));
});
this.client.on("error", (err) => {
console.error("WebSocket error:", err);
this.reconnectWebSocket();
});
this.client.on("error", (err) => {
console.error("WebSocket error:", err);
this.reconnectWebSocket();
});
this.client.on("close", () => {
console.log("WebSocket closed, attempting to reconnect...");
this.reconnectWebSocket();
});
this.client.on("close", () => {
console.log("WebSocket closed, attempting to reconnect...");
this.reconnectWebSocket();
});
} else {
clearInterval(this.testInterval);
this.testInterval = setInterval(() => {
this.dispatchEvent(new CustomEvent("strike", { detail: "fakeData" }));
}, 1000);
}
}
}

View File

@ -1,4 +1,3 @@
body {
display: flex;
flex-direction: column;
@ -8,6 +7,7 @@ body {
overflow: hidden;
}
object {
margin-bottom: 10px;
}
@ -74,14 +74,16 @@ html {
transition: opacity 1s ease;
}
#event-canvas {
.event-canvas {
background: rgba(255, 0, 0, 0.2); /* light red overlay */
position: absolute;
top: 0;
left: 0;
z-index: 1000;
}
width: 100%;
height:100%;
pointer-events: none;
z-index: 9999;
}
.logo {
color: #fff;
@ -96,10 +98,6 @@ html {
margin: 20px;
}
.event-icon {
width: 1vw;
min-width: 10px;
}
.instruments-key {
position: relative;
@ -174,6 +172,7 @@ html {
top: 30px;
width: 50%;
}
.title-title {
font-family: Helvetica;
color: #fff;
@ -191,42 +190,28 @@ html {
.stave-wrapper {
position: relative;
background-color: rgb(24,24,24);
border: 1px solid rgb(24,24,24);
border: 1px solid rgb(99,99,99);
padding-top: 30px;
padding-bottom: 20px;
z-index: 100;
}
.stave-svg {
position: relative;
z-index: 1;
}
.time-indicator {
top: 0;
left: 0;
position: absolute;
width: 2px;
/* height: 100%; */
animation: moveRight 10s linear infinite;
z-index: 999;
}
.stave-svg {
position: relative;
z-index: 1; /* Same here */
}
#event-canvas {
position: absolute;
top: 0;
left: 0;
pointer-events: none;
z-index: 999;
}
#time-indicator {
#time-indicator {
position: absolute;
top: 0;
bottom: 0;
width: 2px;
background-color: red;
z-index: 3;
z-index: 999;
animation: moveRight 10s linear infinite;
will-change: transform;
}
}
.fade-in {
opacity: 1;
@ -244,7 +229,8 @@ html {
to {
transform: rotate(360deg);
}
}
}
@keyframes moveRight {
0% {
left: 0;