Compare commits

...

2 Commits

Author SHA1 Message Date
Joe 8993615953 nearly sorted with the rendering 2025-04-23 12:32:44 +01:00
Joe 75b66e245e this is garbage from chatty mc fuckin chat face 2025-04-18 14:35:30 +01:00
18 changed files with 213 additions and 282 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": "15vw", "height": "15vw" }
"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": "15vw", "height": "15vw" }
"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": "15vw", "height": "15vw"}
"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": "15vw", "height": "15vw"
} },
"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": "15vw", "height": "15vw"
} },
"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": "15vw", "height": "15vw"}
"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": "15vw", "height": "15vw"}
"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,61 +29,10 @@
<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>
</div>
</div>
<script type="module" src="/src/script.js"></script>
</body>

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;
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;
// 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;
}
return audioBuffers;
}
// Clean up the object URL to free memory
URL.revokeObjectURL(url);
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
resolve(img);
};
// 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 drawn icons
imageCache = new Map(); // for memoizing loaded images
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,92 +47,67 @@ 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;
// Set canvas size after layout is applied
requestAnimationFrame(() => {
const rect = canvas.getBoundingClientRect();
canvas.width = rect.width;
canvas.height = rect.height;
});
}
}
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;
const newObject = document.createElement("div");
newObject.classList.add("event-icon");
newObject.style.position = "absolute";
newObject.style.left = `${xPosition}px`;
newObject.style.top = instrument.yPos;
newObject.style.width = instrument.width;
newObject.style.height = instrument.height;
const canvasEntry = this.canvases[this.currStaveNumber - 1];
const canvas = canvasEntry.canvas;
const ctx = canvasEntry.ctx;
// ✅ 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);
}
const yPercent = parseFloat(instrument.yPos);
const y = (yPercent / 100) * canvas.height;
const parser = new DOMParser();
const svgDoc = parser.parseFromString(svgString, "image/svg+xml");
const svgElement = svgDoc.documentElement;
const width = instrument.width * this.iconScale;
const height = instrument.height * this.iconScale;
// ✅ Handle parsing error
if (svgElement.nodeName === "parsererror") {
console.error("Failed to parse SVG:", svgString);
return;
}
ctx.drawImage(instrument.image, xPosition, y, width, height);
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;
this.iconCache.push({
image: instrument.image,
x: xPosition,
y,
width,
height,
stave: this.currStaveNumber,
});
}
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
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,6 +40,7 @@ export class DataStream extends EventTarget {
}
setupWebSocketListeners() {
if (this.test === false) {
this.client.removeAllListeners?.("data");
this.client.removeAllListeners?.("error");
this.client.removeAllListeners?.("close");
@ -58,5 +61,11 @@ export class DataStream extends EventTarget {
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,9 +74,16 @@ html {
transition: opacity 1s ease;
}
#event-canvas {
background-color: rgba(255, 255, 255, 0.02); /* lightly visible canvas for debugging */
}
.event-canvas {
background: rgba(255, 0, 0, 0.2); /* light red overlay */
position: absolute;
top: 0;
left: 0;
width: 100%;
height:100%;
pointer-events: none;
z-index: 9999;
}
.logo {
color: #fff;
@ -91,10 +98,6 @@ html {
margin: 20px;
}
.event-icon {
width: 1vw;
min-width: 10px;
}
.instruments-key {
position: relative;
@ -169,6 +172,7 @@ html {
top: 30px;
width: 50%;
}
.title-title {
font-family: Helvetica;
color: #fff;
@ -186,38 +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;
}
.time-indicator {
top: 0;
left: 0;
position: absolute;
width: 2px;
/* height: 100%; */
animation: moveRight 10s linear infinite;
z-index: 3;
.stave-svg {
position: relative;
z-index: 1;
}
#event-canvas {
position: absolute;
top: 0;
left: 0;
pointer-events: none;
z-index: 2;
}
#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;
@ -235,7 +229,8 @@ html {
to {
transform: rotate(360deg);
}
}
}
@keyframes moveRight {
0% {
left: 0;