Compare commits
15 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
28a2c31239 | |
|
|
e5eacbba0d | |
|
|
0e31c0ea1d | |
|
|
e6ed178aac | |
|
|
10b8111517 | |
|
|
9de15ba9f8 | |
|
|
f50bd236ca | |
|
|
8993615953 | |
|
|
75b66e245e | |
|
|
f776fe377a | |
|
|
be327165b8 | |
|
|
c6c659535e | |
|
|
17f1cbfa9b | |
|
|
ac58c0e8c3 | |
|
|
1393acdc42 |
|
|
@ -8,7 +8,13 @@
|
||||||
"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": "2.5%",
|
||||||
|
|
||||||
|
"width": "100",
|
||||||
|
"height": "100"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"Snare": {
|
"Snare": {
|
||||||
"directory": "/assets/instruments/snare",
|
"directory": "/assets/instruments/snare",
|
||||||
|
|
@ -17,19 +23,34 @@
|
||||||
"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": "7%",
|
||||||
|
"width": "100",
|
||||||
|
"height": "100"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"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": "conductorSurdo",
|
||||||
"image": { "filename": "surdo.svg", "yPos": "39%" }
|
"image": {
|
||||||
|
"filename": "surdo.svg",
|
||||||
|
"yPos": "10.5%",
|
||||||
|
"width": "100",
|
||||||
|
"height": "100"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"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": "12%",
|
||||||
|
"width": "100",
|
||||||
|
"height": "100"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"Timpani Large": {
|
"Timpani Large": {
|
||||||
"directory": "/assets/instruments/timpani-large",
|
"directory": "/assets/instruments/timpani-large",
|
||||||
|
|
@ -39,17 +60,28 @@
|
||||||
"timpani7b-hit-v5-rr2-main.mp3",
|
"timpani7b-hit-v5-rr2-main.mp3",
|
||||||
"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": "14%",
|
||||||
|
"width": "100",
|
||||||
|
"height": "100"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"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",
|
||||||
"timpani2-hit-v3-rr1-sum.mp3",
|
"timpani2-hit-v3-rr1-sum.mp3",
|
||||||
"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": "18%",
|
||||||
|
"width": "100",
|
||||||
|
"height": "100"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"Toms": {
|
"Toms": {
|
||||||
"directory": "/assets/instruments/toms",
|
"directory": "/assets/instruments/toms",
|
||||||
|
|
@ -59,6 +91,11 @@
|
||||||
"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": "42%",
|
||||||
|
"width": "100",
|
||||||
|
"height": "100"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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="200" height="200"><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 |
|
|
@ -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 |
|
|
@ -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 |
|
|
@ -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 |
|
|
@ -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 |
|
|
@ -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 |
|
|
@ -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 |
|
|
@ -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 |
57
index.html
|
|
@ -15,11 +15,6 @@
|
||||||
<div class="loading">
|
<div class="loading">
|
||||||
</div>
|
</div>
|
||||||
</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">
|
<div id="main-content">
|
||||||
|
|
||||||
|
|
@ -35,57 +30,7 @@
|
||||||
|
|
||||||
<div class="sheet-music-window" id="music-window">
|
<div class="sheet-music-window" id="music-window">
|
||||||
<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>
|
|
||||||
<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 Salford’s 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>
|
||||||
</div>
|
</div>
|
||||||
<script type="module" src="/src/script.js"></script>
|
<script type="module" src="/src/script.js"></script>
|
||||||
|
|
|
||||||
18
notes.txt
|
|
@ -1,18 +0,0 @@
|
||||||
stop zooming issue - resize rules
|
|
||||||
mishka gonna send an x icon for the back behaviour
|
|
||||||
look at memory leak??
|
|
||||||
make the sound and menu icons part of an 'app bar' along with the conductor logo
|
|
||||||
make the mid size the default size but double check the size on the phone isnt too small
|
|
||||||
refactor when it's all done
|
|
||||||
format the key as per the pdf
|
|
||||||
sort out the title size
|
|
||||||
sort out the blitzortung connection issue
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
make the text max size smaller
|
|
||||||
|
|
||||||
about page make title smaller
|
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
let audioContext;
|
||||||
|
let gainNode;
|
||||||
|
|
||||||
|
function startAudio() {
|
||||||
|
audioContext = new (window.AudioContext || window.webkitAudioContext)();
|
||||||
|
gainNode = audioContext.createGain();
|
||||||
|
gainNode.gain.value = 0;
|
||||||
|
gainNode.connect(audioContext.destination);
|
||||||
|
}
|
||||||
|
|
||||||
|
function muter() {
|
||||||
|
fadeOutVolume();
|
||||||
|
}
|
||||||
|
|
||||||
|
function unMute() {
|
||||||
|
startAudio();
|
||||||
|
fadeInVolume();
|
||||||
|
}
|
||||||
|
|
||||||
|
function fadeInVolume() {
|
||||||
|
gainNode.gain.setValueAtTime(0, audioContext.currentTime); // Start at 0
|
||||||
|
gainNode.gain.linearRampToValueAtTime(1, audioContext.currentTime + 2); // Fade to full volume
|
||||||
|
}
|
||||||
|
|
||||||
|
function fadeOutVolume() {
|
||||||
|
gainNode.gain.setValueAtTime(1, audioContext.currentTime); // Start at 1
|
||||||
|
gainNode.gain.linearRampToValueAtTime(0, audioContext.currentTime + 2); // Fade to 0
|
||||||
|
}
|
||||||
|
|
||||||
|
export function playSound(instrument) {
|
||||||
|
if (interacted) {
|
||||||
|
// const source = audioContext.createBufferSource();
|
||||||
|
const samples = instrument.samples;
|
||||||
|
const sampleKeys = Object.keys(samples);
|
||||||
|
const numSamples = sampleKeys.length;
|
||||||
|
const randomSampleNumber = Math.floor(Math.random() * (numSamples - 0));
|
||||||
|
// const randomKey =
|
||||||
|
// sampleKeys[randomSampleNumber];
|
||||||
|
sendMidiMessage(instrument.midiChannelName, randomSampleNumber);
|
||||||
|
|
||||||
|
// source.buffer = samples[`${randomKey}`];
|
||||||
|
// source.connect(gainNode);
|
||||||
|
// source.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,62 @@
|
||||||
|
import { loadInstruments } from "./instruments.js";
|
||||||
|
|
||||||
|
export class Conductor {
|
||||||
|
instruments;
|
||||||
|
|
||||||
|
constructor() {}
|
||||||
|
|
||||||
|
showingWhichContent;
|
||||||
|
interacted = true;
|
||||||
|
|
||||||
|
get instruments() {
|
||||||
|
return this.instruments;
|
||||||
|
}
|
||||||
|
|
||||||
|
static setTitle() {
|
||||||
|
const now = new Date();
|
||||||
|
const future = new Date(now.getTime() + 59 * 1000); // 59 seconds later
|
||||||
|
|
||||||
|
const formattedDate = `${Conductor.padZero(
|
||||||
|
now.getDate()
|
||||||
|
)}.${Conductor.padZero(now.getMonth() + 1)}.${now.getFullYear()}`;
|
||||||
|
const formattedTime = `${Conductor.padZero(
|
||||||
|
now.getHours()
|
||||||
|
)}:${Conductor.padZero(now.getMinutes())}:${this.padZero(
|
||||||
|
now.getSeconds()
|
||||||
|
)}`;
|
||||||
|
const formattedFutureTime = `${Conductor.padZero(
|
||||||
|
future.getHours()
|
||||||
|
)}:${Conductor.padZero(future.getMinutes())}:${Conductor.padZero(
|
||||||
|
future.getSeconds()
|
||||||
|
)}`;
|
||||||
|
|
||||||
|
title.innerHTML = `${formattedDate}<span class="tab-space"></span>${formattedTime} - ${formattedFutureTime} UTC`;
|
||||||
|
}
|
||||||
|
|
||||||
|
showMainContent() {
|
||||||
|
Conductor.setTitle();
|
||||||
|
this.fadeAndReplaceADiv("main-content", "loading-screen");
|
||||||
|
}
|
||||||
|
|
||||||
|
fadeAndReplaceADiv(innyDivId, outtyDivId) {
|
||||||
|
const outty = document.getElementById(outtyDivId);
|
||||||
|
const inny = document.getElementById(innyDivId);
|
||||||
|
outty.classList.remove("fade-in");
|
||||||
|
outty.classList.add("fade-out");
|
||||||
|
|
||||||
|
outty.style.display = "none";
|
||||||
|
inny.style.display = "flex";
|
||||||
|
inny.style.opacity = "0";
|
||||||
|
void inny.offsetWidth;
|
||||||
|
inny.classList.add("fade-in");
|
||||||
|
inny.style.opacity = "1";
|
||||||
|
}
|
||||||
|
|
||||||
|
static padZero(num) {
|
||||||
|
return num.toString().padStart(2, "0");
|
||||||
|
}
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
this.instruments = await loadInstruments();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,62 +3,67 @@ async function loadInstrumentsConfig() {
|
||||||
return await response.json();
|
return await response.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchAudioData(url) {
|
async function fetchSvgText(url) {
|
||||||
const response = await fetch(url);
|
const response = await fetch(url);
|
||||||
const arrayBuffer = await response.arrayBuffer();
|
return await response.text();
|
||||||
return arrayBuffer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function decodeAudioData(context, arrayBuffer) {
|
function svgTextToImage(svgText, defaultWidth = 100, defaultHeight = 100) {
|
||||||
return new Promise((resolve, reject) => {
|
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) {
|
// Set the onload callback
|
||||||
const audioFiles = filenames;
|
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 = {};
|
// Clean up the object URL to free memory
|
||||||
for (const file of audioFiles) {
|
URL.revokeObjectURL(url);
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
return audioBuffers;
|
resolve(img);
|
||||||
}
|
};
|
||||||
|
|
||||||
async function fetchImageData(url) {
|
// Handle image loading errors
|
||||||
const response = await fetch(url);
|
img.onerror = (err) => {
|
||||||
const blob = await response.blob();
|
URL.revokeObjectURL(url); // Clean up URL
|
||||||
return new Promise((resolve, reject) => {
|
reject(err);
|
||||||
const reader = new FileReader();
|
};
|
||||||
reader.onloadend = () => resolve(reader.result);
|
img.src = url;
|
||||||
reader.onerror = reject;
|
|
||||||
reader.readAsDataURL(blob); // Convert to base64
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function loadInstruments() {
|
export async function loadInstruments() {
|
||||||
const instrumentsConfig = await loadInstrumentsConfig();
|
const instrumentsConfig = await loadInstrumentsConfig();
|
||||||
const context = new (window.AudioContext || window.webkitAudioContext)();
|
|
||||||
const instruments = {};
|
const instruments = {};
|
||||||
|
|
||||||
for (const [instrument, config] of Object.entries(instrumentsConfig)) {
|
for (const [instrument, config] of Object.entries(instrumentsConfig)) {
|
||||||
const samples = await loadAudioSamples(
|
const numSamples = config.filenames.length;
|
||||||
context,
|
|
||||||
config.directory,
|
const svgText = await fetchSvgText(
|
||||||
config.filenames
|
|
||||||
);
|
|
||||||
const imageData = await fetchImageData(
|
|
||||||
`${config.directory}/${config.image.filename}`
|
`${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] = {
|
instruments[instrument] = {
|
||||||
samples: samples,
|
numSamples: numSamples,
|
||||||
image: imageData,
|
image: img,
|
||||||
yPos: config.image.yPos, // Store base64-encoded image data
|
yPos: config.image.yPos,
|
||||||
midiChannelName: config["midi-channel-name"],
|
midiChannelName: config["midi-channel-name"],
|
||||||
|
width: config.image.width,
|
||||||
|
height: config.image.height,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
53
src/midi.js
|
|
@ -1,30 +1,29 @@
|
||||||
export async function sendMidiMessage(midiChannelName, sampleNumber ) {
|
export async function sendMidiMessage(midiChannelName, sampleNumber, midiAccess) {
|
||||||
try {
|
try {
|
||||||
const midiAccess = await navigator.requestMIDIAccess();
|
console.log(midiChannelName, sampleNumber);
|
||||||
const availableMidiOutputs = midiAccess.outputs.values();
|
const availableMidiOutputs = midiAccess.outputs.values();
|
||||||
let midiOut;
|
let midiOut;
|
||||||
|
|
||||||
if (availableMidiOutputs.length === 0) {
|
if (availableMidiOutputs.length === 0) {
|
||||||
console.error("No MIDI output devices found.");
|
console.error("No MIDI output devices found.");
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
for ( const availableMidiOutput of availableMidiOutputs){
|
|
||||||
if (availableMidiOutput.name === midiChannelName){
|
|
||||||
midiOut = availableMidiOutput;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MIDI Note On (Play Note)
|
|
||||||
midiOut.send([0x90, 36 + sampleNumber, 127]);
|
|
||||||
|
|
||||||
// MIDI Note Off after 500ms
|
|
||||||
setTimeout(() => {
|
|
||||||
midiOut.send([0x80, 36 + sampleNumber, 0]); // Stop Note
|
|
||||||
}, 500);
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Failed to get MIDI access:", error);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const availableMidiOutput of availableMidiOutputs) {
|
||||||
|
if (availableMidiOutput.name === midiChannelName) {
|
||||||
|
midiOut = availableMidiOutput;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MIDI Note On (Play Note)
|
||||||
|
midiOut.send([0x90, 35 + sampleNumber, 127]);
|
||||||
|
|
||||||
|
// MIDI Note Off after 500ms
|
||||||
|
setTimeout(() => {
|
||||||
|
midiOut.send([0x80, 35 + sampleNumber, 0]); // Stop Note
|
||||||
|
}, 500);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to get MIDI access:", error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,135 @@
|
||||||
|
import { Conductor } from "./conductor.js";
|
||||||
|
|
||||||
|
export class Renderer {
|
||||||
|
timeIndicator;
|
||||||
|
currStaveNumber;
|
||||||
|
numStaves;
|
||||||
|
sheetWindow;
|
||||||
|
canvases = [];
|
||||||
|
iconCache = [];
|
||||||
|
iconScale = 0.20;
|
||||||
|
|
||||||
|
constructor(dataStream, strikeHandler) {
|
||||||
|
|
||||||
|
this.dataStream = dataStream;
|
||||||
|
this.strikeHandler = strikeHandler;
|
||||||
|
|
||||||
|
this.numStaves = 6;
|
||||||
|
this.currStaveNumber = 1;
|
||||||
|
|
||||||
|
this.timeIndicator = document.getElementById("time-indicator");
|
||||||
|
this.sheetWindow = document.getElementById("music-window");
|
||||||
|
|
||||||
|
window.addEventListener("resize", () => {
|
||||||
|
this.redrawIcons();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.timeIndicator.addEventListener("animationiteration", () => {
|
||||||
|
this.currStaveNumber++;
|
||||||
|
if (this.currStaveNumber > this.numStaves) {
|
||||||
|
this.dataStream.removeEventListener("strike", this.strikeHandler);
|
||||||
|
setTimeout( () =>{this.dataStream.addEventListener("strike", this.strikeHandler);
|
||||||
|
this.cleanUpAndRestart();}, 60000);
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
for (let i = 1; i <= this.numStaves; i++) {
|
||||||
|
const staveWrapper = document.createElement("div");
|
||||||
|
staveWrapper.classList.add("stave-wrapper");
|
||||||
|
staveWrapper.setAttribute("id", `stave-wrapper-${i}`);
|
||||||
|
staveWrapper.style.position = "relative";
|
||||||
|
|
||||||
|
const staveObject = document.createElement("object");
|
||||||
|
staveObject.type = "image/svg+xml";
|
||||||
|
staveObject.data = "assets/svg/stave.svg";
|
||||||
|
staveObject.className = "stave-svg"; // Fixed className
|
||||||
|
|
||||||
|
const canvas = document.createElement("canvas");
|
||||||
|
canvas.id = `canvas-${i}`;
|
||||||
|
canvas.className = "event-canvas";
|
||||||
|
|
||||||
|
const ctx = canvas.getContext("2d");
|
||||||
|
this.canvases.push({ canvas, ctx });
|
||||||
|
|
||||||
|
staveWrapper.appendChild(staveObject);
|
||||||
|
staveWrapper.appendChild(canvas);
|
||||||
|
this.sheetWindow.appendChild(staveWrapper);
|
||||||
|
|
||||||
|
// Your exact sizing method + minimal DPR adjustment
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
const dpr = window.devicePixelRatio || 1;
|
||||||
|
|
||||||
|
// Keep your offset measurements
|
||||||
|
const displayWidth = canvas.offsetWidth;
|
||||||
|
const displayHeight = canvas.offsetHeight;
|
||||||
|
|
||||||
|
// Apply to actual canvas buffer
|
||||||
|
canvas.width = Math.round(displayWidth * dpr);
|
||||||
|
canvas.height = Math.round(displayHeight * dpr);
|
||||||
|
|
||||||
|
// Maintain your display size
|
||||||
|
canvas.style.width = `${displayWidth}px`;
|
||||||
|
canvas.style.height = `${displayHeight}px`;
|
||||||
|
|
||||||
|
// Scale context to compensate
|
||||||
|
ctx.scale(dpr , dpr);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async placeIcon(instrument) {
|
||||||
|
if (this.currStaveNumber > this.numStaves) {
|
||||||
|
this.currStaveNumber = 1;
|
||||||
|
this.iconCache.length = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rect = this.timeIndicator.getBoundingClientRect();
|
||||||
|
const sheetLeft = this.sheetWindow.getBoundingClientRect().left;
|
||||||
|
const xPosition = rect.left + window.scrollX - sheetLeft;
|
||||||
|
|
||||||
|
const canvasEntry = this.canvases[this.currStaveNumber - 1];
|
||||||
|
const canvas = canvasEntry.canvas;
|
||||||
|
const ctx = canvasEntry.ctx;
|
||||||
|
|
||||||
|
const yPercent = parseFloat(instrument.yPos);
|
||||||
|
const y = (yPercent / 100) * canvas.height;
|
||||||
|
|
||||||
|
const width = instrument.width * this.iconScale;
|
||||||
|
const height = instrument.height * this.iconScale;
|
||||||
|
|
||||||
|
ctx.drawImage(instrument.image, xPosition, y, width, height);
|
||||||
|
|
||||||
|
this.iconCache.push({
|
||||||
|
image: instrument.image,
|
||||||
|
x: xPosition,
|
||||||
|
y,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
stave: this.currStaveNumber,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
redrawIcons() {
|
||||||
|
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.currStaveNumber = 1;
|
||||||
|
|
||||||
|
this.canvases.forEach(({ ctx, canvas }) => {
|
||||||
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
|
});
|
||||||
|
|
||||||
|
Conductor.setTitle();
|
||||||
|
if (reload) location.reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
310
src/script.js
|
|
@ -1,294 +1,44 @@
|
||||||
import { Client, BrowserSocketFactory } from "./blitz.js";
|
|
||||||
import { loadInstruments } from "./instruments.js";
|
|
||||||
import { sendMidiMessage } from "./midi.js";
|
import { sendMidiMessage } from "./midi.js";
|
||||||
|
import { Conductor } from "./conductor.js";
|
||||||
|
import { DataStream } from "./socket.js";
|
||||||
|
import { Renderer } from "./render.js";
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", async () => {
|
document.addEventListener("DOMContentLoaded", async () => {
|
||||||
let showing;
|
|
||||||
let audioContext;
|
|
||||||
let interacted = true;
|
|
||||||
let gainNode;
|
|
||||||
|
|
||||||
sendMidiMessage();
|
const midiAccess = await navigator.requestMIDIAccess();
|
||||||
|
const dataStream = new DataStream();
|
||||||
|
const conductor = new Conductor();
|
||||||
|
await conductor.init();
|
||||||
|
const instruments = await conductor.instruments;
|
||||||
|
let strikes = 0;
|
||||||
|
const strikeEvery = 2;
|
||||||
|
|
||||||
function startAudio() {
|
const strikeHandler = () => {
|
||||||
audioContext = new (window.AudioContext || window.webkitAudioContext)();
|
console.log(strikes);
|
||||||
gainNode = audioContext.createGain();
|
//** select instrument */
|
||||||
gainNode.gain.value = 0;
|
if (strikes === strikeEvery){
|
||||||
gainNode.connect(audioContext.destination);
|
|
||||||
}
|
|
||||||
|
|
||||||
function muter() {
|
const keys = Object.keys(instruments);
|
||||||
fadeOutVolume();
|
const randomIndex = Math.floor(Math.random() * keys.length);
|
||||||
}
|
const selectedInstrument = instruments[keys[randomIndex]];
|
||||||
|
|
||||||
function unMute() {
|
//** select sample */
|
||||||
startAudio();
|
const numSamples = selectedInstrument.numSamples;
|
||||||
fadeInVolume();
|
const randomSampleNumber = Math.floor(Math.random() * numSamples);
|
||||||
}
|
|
||||||
|
renderer.placeIcon(selectedInstrument);
|
||||||
function padZero(num) {
|
sendMidiMessage(selectedInstrument.midiChannelName, randomSampleNumber + 1, midiAccess);
|
||||||
return num.toString().padStart(2, "0");
|
strikes = 0;
|
||||||
}
|
|
||||||
|
|
||||||
function setTitle() {
|
|
||||||
const now = new Date();
|
|
||||||
const future = new Date(now.getTime() + 59 * 1000); // 59 seconds later
|
|
||||||
|
|
||||||
const formattedDate = `${padZero(now.getDate())}.${padZero(
|
|
||||||
now.getMonth() + 1
|
|
||||||
)}.${now.getFullYear()}`;
|
|
||||||
const formattedTime = `${padZero(now.getHours())}:${padZero(
|
|
||||||
now.getMinutes()
|
|
||||||
)}:${padZero(now.getSeconds())}`;
|
|
||||||
const formattedFutureTime = `${padZero(future.getHours())}:${padZero(
|
|
||||||
future.getMinutes()
|
|
||||||
)}:${padZero(future.getSeconds())}`;
|
|
||||||
|
|
||||||
title.innerHTML = `${formattedDate}<span class="tab-space"></span>${formattedTime} - ${formattedFutureTime} UTC`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function fadeInVolume() {
|
|
||||||
gainNode.gain.setValueAtTime(0, audioContext.currentTime); // Start at 0
|
|
||||||
gainNode.gain.linearRampToValueAtTime(1, audioContext.currentTime + 2); // Fade to full volume
|
|
||||||
}
|
|
||||||
function fadeOutVolume() {
|
|
||||||
gainNode.gain.setValueAtTime(1, audioContext.currentTime); // Start at 1
|
|
||||||
gainNode.gain.linearRampToValueAtTime(0, audioContext.currentTime + 2); // Fade to 0
|
|
||||||
}
|
|
||||||
|
|
||||||
function showMainContent() {
|
|
||||||
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();
|
|
||||||
});
|
|
||||||
|
|
||||||
fadeAndReplaceADiv("main-content", "loading-screen");
|
|
||||||
fadeAndReplaceADiv("buttons", "buttons");
|
|
||||||
showing = "main-content";
|
|
||||||
|
|
||||||
document.getElementById("menu-button").onclick = function () {
|
|
||||||
toggleInfo();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleInfo() {
|
|
||||||
if (showing === "main-content") {
|
|
||||||
fadeAndReplaceADiv("about-content", "main-content");
|
|
||||||
cleanUpAndRestart();
|
|
||||||
showing = "about-content";
|
|
||||||
} else {
|
|
||||||
fadeAndReplaceADiv("main-content", "about-content");
|
|
||||||
showing = "main-content";
|
|
||||||
}
|
}
|
||||||
}
|
strikes++;
|
||||||
|
|
||||||
function fadeAndReplaceADiv(innyDivId, outtyDivId) {
|
|
||||||
const outty = document.getElementById(outtyDivId);
|
|
||||||
const inny = document.getElementById(innyDivId);
|
|
||||||
outty.classList.remove("fade-in");
|
|
||||||
outty.classList.add("fade-out");
|
|
||||||
|
|
||||||
outty.style.display = "none";
|
|
||||||
inny.style.display = "flex";
|
|
||||||
inny.style.opacity = "0";
|
|
||||||
void inny.offsetWidth;
|
|
||||||
inny.classList.add("fade-in");
|
|
||||||
inny.style.opacity = "1";
|
|
||||||
}
|
|
||||||
|
|
||||||
const socketFactory = new BrowserSocketFactory();
|
|
||||||
let client = new Client(socketFactory);
|
|
||||||
client.connect();
|
|
||||||
let connectionTimeout; // Timeout for detecting inactivity
|
|
||||||
let lastReceived = Date.now(); // Tracks the last time data was received
|
|
||||||
|
|
||||||
function resetTimeout() {
|
|
||||||
clearTimeout(connectionTimeout);
|
|
||||||
connectionTimeout = setTimeout(() => {
|
|
||||||
// 15 seconds of inactivity
|
|
||||||
console.log("No data received in 15 seconds, reconnecting...");
|
|
||||||
reconnectWebSocket();
|
|
||||||
}, 15000);
|
|
||||||
}
|
|
||||||
|
|
||||||
function reconnectWebSocket() {
|
|
||||||
console.log("Reconnecting WebSocket...");
|
|
||||||
|
|
||||||
// Clean up the existing WebSocket connection
|
|
||||||
if (client) {
|
|
||||||
client.close(); // Ensure the current connection is closed
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a new client instance and re-establish the connection
|
|
||||||
client = new Client(new BrowserSocketFactory());
|
|
||||||
setupWebSocketListeners(); // Set up listeners for the new WebSocket
|
|
||||||
client.connect(); // Reconnect
|
|
||||||
|
|
||||||
// Reset timeout after reconnecting
|
|
||||||
resetTimeout(); // Ensure timeout is re-established after reconnection
|
|
||||||
}
|
|
||||||
|
|
||||||
function setupWebSocketListeners() {
|
|
||||||
client.removeAllListeners("data");
|
|
||||||
client.removeAllListeners("error");
|
|
||||||
client.removeAllListeners("close");
|
|
||||||
|
|
||||||
client.on("data", (strike) => {
|
|
||||||
lastReceived = Date.now(); // Update the last received timestamp
|
|
||||||
resetTimeout(); // Reset the timeout on any data received
|
|
||||||
|
|
||||||
// Process the strike (existing logic from your original code)
|
|
||||||
strikeNumber++;
|
|
||||||
if (strikeNumber == 1) {
|
|
||||||
const randomInstrumentNumber = Math.floor(
|
|
||||||
Math.random() * (Object.keys(instruments).length - 1)
|
|
||||||
);
|
|
||||||
const selectedInstrumentName =
|
|
||||||
Object.keys(instruments)[randomInstrumentNumber];
|
|
||||||
|
|
||||||
placeIcon(instruments[selectedInstrumentName]);
|
|
||||||
playSound(instruments[selectedInstrumentName]);
|
|
||||||
strikeNumber = 0;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
client.on("error", (message) => {
|
|
||||||
console.log("WebSocket error:", message);
|
|
||||||
reconnectWebSocket();
|
|
||||||
});
|
|
||||||
|
|
||||||
client.on("close", () => {
|
|
||||||
console.log("WebSocket closed, attempting to reconnect...");
|
|
||||||
reconnectWebSocket();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let currStaveNumber = 1;
|
|
||||||
|
|
||||||
const instruments = await loadInstruments();
|
|
||||||
const instNames = Object.keys(instruments);
|
|
||||||
|
|
||||||
showMainContent();
|
|
||||||
|
|
||||||
/***
|
|
||||||
* Add the instrument images and names into the about section
|
|
||||||
*/
|
|
||||||
|
|
||||||
const instrumentsKey = document.getElementById("instrument-key-div");
|
|
||||||
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 = instruments[instrument].image;
|
|
||||||
keyDiv.appendChild(keySvg);
|
|
||||||
keyDiv.appendChild(keyName);
|
|
||||||
keyDiv.classList.add("instrument-key");
|
|
||||||
instrumentsKey.appendChild(keyDiv);
|
|
||||||
}
|
|
||||||
|
|
||||||
const numStaves = 6;
|
|
||||||
const sheetWindow = document.getElementById("music-window");
|
|
||||||
|
|
||||||
for (let i = 1; i <= numStaves; i++) {
|
|
||||||
const staveWrapper = document.createElement("div");
|
|
||||||
staveWrapper.classList.add(`stave-wrapper`);
|
|
||||||
staveWrapper.setAttribute("id", `stave-wrapper-${i}`);
|
|
||||||
staveWrapper.style.position = "relative";
|
|
||||||
const staveObject = document.createElement("object");
|
|
||||||
staveObject.type = "image/svg+xml";
|
|
||||||
staveObject.data = "assets/svg/stave.svg";
|
|
||||||
staveObject.classList.add("stave-svg");
|
|
||||||
|
|
||||||
staveWrapper.appendChild(staveObject);
|
|
||||||
sheetWindow.appendChild(staveWrapper);
|
|
||||||
}
|
|
||||||
|
|
||||||
const timeIndicator = document.querySelector("#time-indicator");
|
|
||||||
|
|
||||||
timeIndicator.addEventListener("animationiteration", () => {
|
|
||||||
currStaveNumber++;
|
|
||||||
if (currStaveNumber > numStaves) {
|
|
||||||
cleanUpAndRestart();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const cleanUpAndRestart = (reload = false) => {
|
|
||||||
const icons = document.querySelectorAll(".event-icon");
|
|
||||||
const timeIndicator = document.getElementById("time-indicator");
|
|
||||||
icons.forEach((icon) => {
|
|
||||||
icon.classList.add("fade-out");
|
|
||||||
icon.addEventListener("transitionend", () => {
|
|
||||||
icon.remove();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
currStaveNumber = 1;
|
|
||||||
setTitle();
|
|
||||||
if (reload) location.reload();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const playSound = (instrument) => {
|
const renderer = new Renderer(dataStream, strikeHandler);
|
||||||
if (interacted) {
|
|
||||||
// const source = audioContext.createBufferSource();
|
|
||||||
const samples = instrument.samples;
|
|
||||||
const sampleKeys = Object.keys(samples);
|
|
||||||
const numSamples = sampleKeys.length;
|
|
||||||
const randomSampleNumber = Math.floor(Math.random() * (numSamples - 1));
|
|
||||||
// const randomKey =
|
|
||||||
// sampleKeys[randomSampleNumber];
|
|
||||||
sendMidiMessage(instrument.midiChannelName, randomSampleNumber);
|
|
||||||
|
|
||||||
// source.buffer = samples[`${randomKey}`];
|
dataStream.addEventListener("strike", strikeHandler);
|
||||||
// source.connect(gainNode);
|
|
||||||
// source.start();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const placeIcon = (instrument) => {
|
dataStream.init();
|
||||||
if (currStaveNumber > numStaves) currStaveNumber = 1;
|
|
||||||
// Select the staveWrapper in which to place the div
|
|
||||||
const staveWrapper = document.getElementById(
|
|
||||||
`stave-wrapper-${currStaveNumber}`
|
|
||||||
);
|
|
||||||
// Determine the position of the time indicator
|
|
||||||
const rect = timeIndicator.getBoundingClientRect();
|
|
||||||
const sheetLeft = sheetWindow.getBoundingClientRect().left;
|
|
||||||
const xPosition = rect.left + window.scrollX - sheetLeft;
|
|
||||||
|
|
||||||
// Create the icon div and give it its location data
|
conductor.showMainContent();
|
||||||
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}`;
|
|
||||||
|
|
||||||
// Create the icon
|
|
||||||
const eventIcon = document.createElement("object");
|
|
||||||
eventIcon.type = "image/svg+xml";
|
|
||||||
eventIcon.data = instrument.image;
|
|
||||||
|
|
||||||
// Append icon to div
|
|
||||||
newObject.appendChild(eventIcon);
|
|
||||||
|
|
||||||
// Append the div to the staveWrapper
|
|
||||||
staveWrapper.appendChild(newObject);
|
|
||||||
};
|
|
||||||
|
|
||||||
let strikeNumber = 0;
|
|
||||||
|
|
||||||
setupWebSocketListeners(); // Setup the WebSocket listeners
|
|
||||||
resetTimeout(); // Start the timeout timer
|
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
import { BrowserSocketFactory, Client } from "./blitz.js";
|
||||||
|
|
||||||
|
export class DataStream extends EventTarget {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.socketFactory = null;
|
||||||
|
this.client = null;
|
||||||
|
this.lastReceived = null;
|
||||||
|
this.connectionTimeout = null;
|
||||||
|
this.test = false;
|
||||||
|
this.testInterval = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
this.socketFactory = new BrowserSocketFactory();
|
||||||
|
this.client = new Client(this.socketFactory);
|
||||||
|
this.lastReceived = Date.now();
|
||||||
|
|
||||||
|
this.setupWebSocketListeners();
|
||||||
|
this.client.connect();
|
||||||
|
this.resetTimeout();
|
||||||
|
}
|
||||||
|
|
||||||
|
resetTimeout() {
|
||||||
|
clearTimeout(this.connectionTimeout);
|
||||||
|
this.connectionTimeout = setTimeout(() => {
|
||||||
|
console.log("No data received in 14 seconds, reconnecting...");
|
||||||
|
this.reconnectWebSocket();
|
||||||
|
}, 14999);
|
||||||
|
}
|
||||||
|
|
||||||
|
reconnectWebSocket() {
|
||||||
|
if (this.client) {
|
||||||
|
this.client.close();
|
||||||
|
}
|
||||||
|
this.client = new Client(new BrowserSocketFactory());
|
||||||
|
this.setupWebSocketListeners();
|
||||||
|
this.client.connect();
|
||||||
|
this.resetTimeout();
|
||||||
|
}
|
||||||
|
|
||||||
|
setupWebSocketListeners() {
|
||||||
|
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.dispatchEvent(new CustomEvent("strike", { detail: data }));
|
||||||
|
});
|
||||||
|
|
||||||
|
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();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
clearInterval(this.testInterval);
|
||||||
|
this.testInterval = setInterval(() => {
|
||||||
|
this.dispatchEvent(new CustomEvent("strike", { detail: "fakeData" }));
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
98
styles.css
|
|
@ -1,20 +1,20 @@
|
||||||
|
|
||||||
body {
|
body {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background-color: rgb(24, 24, 24);
|
background-color: rgb(0,0,0);
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
object {
|
object {
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
html {
|
html {
|
||||||
overflow-x: hidden;
|
overflow-x: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
* {
|
* {
|
||||||
|
|
@ -32,7 +32,7 @@ html {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transition: opacity 2s ease;
|
transition: opacity 2s ease;
|
||||||
overflow: hidden;
|
overflow: visible;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -74,6 +74,29 @@ html {
|
||||||
transition: opacity 1s ease;
|
transition: opacity 1s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.event-canvas {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: -10px;
|
||||||
|
height: 12vh;
|
||||||
|
width: 102%; /* Matches JS padding */
|
||||||
|
display: block;
|
||||||
|
pointer-events: none;
|
||||||
|
overflow: visible;
|
||||||
|
z-index: 9999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stave-wrapper {
|
||||||
|
position: relative;
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 12vh;
|
||||||
|
background-color: rgb(0,0,0);
|
||||||
|
padding-top: 30px;
|
||||||
|
padding-bottom: 20px;
|
||||||
|
overflow: visible;
|
||||||
|
z-index: 9995;
|
||||||
|
}
|
||||||
|
|
||||||
.logo {
|
.logo {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
|
|
@ -88,10 +111,6 @@ html {
|
||||||
margin: 20px;
|
margin: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.event-icon {
|
|
||||||
width: 1vw;
|
|
||||||
min-width: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.instruments-key {
|
.instruments-key {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
@ -121,8 +140,9 @@ html {
|
||||||
max-width: 700px;
|
max-width: 700px;
|
||||||
height: 70%;
|
height: 70%;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
background-color: rgb(24,24,24);
|
background-color: rgb(0,0,0);
|
||||||
margin-bottom: 100px;
|
margin-bottom: 100px;
|
||||||
|
overflow: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
.conductor-title{
|
.conductor-title{
|
||||||
|
|
@ -149,27 +169,30 @@ html {
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title{
|
|
||||||
top: 30px;
|
|
||||||
width: 100%;
|
|
||||||
margin: 30px;
|
|
||||||
text-align: center;
|
|
||||||
color: white;
|
|
||||||
background-color: rgb(24,24,24);
|
|
||||||
/* height: 20vh; */
|
|
||||||
font-family: Helvetica;
|
|
||||||
font-size: clamp(8px,1.1vw, 18px);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.about-title {
|
.about-title {
|
||||||
top: 30px;
|
top: 30px;
|
||||||
width: 50%;
|
width: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.title{
|
||||||
|
top: 30px;
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 30px;
|
||||||
|
margin-bottom: 80px;
|
||||||
|
text-align: center;
|
||||||
|
color: white;
|
||||||
|
background-color: rgb(0,0,0);
|
||||||
|
/* height: 20vh; */
|
||||||
|
font-family: Helvetica;
|
||||||
|
font-size: clamp(14px,1.1vw, 18px);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
.title-title {
|
.title-title {
|
||||||
font-family: Helvetica;
|
font-family: Helvetica;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-size: clamp(12px,1.5vw, 24px);
|
margin-top: 40px;
|
||||||
|
font-size: clamp(18px,1.5vw, 24px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.production-credits {
|
.production-credits {
|
||||||
|
|
@ -180,25 +203,25 @@ html {
|
||||||
content: '\00a0\00a0\00a0\00a0\00a0\00a0'; /* 6 non-breaking spaces */
|
content: '\00a0\00a0\00a0\00a0\00a0\00a0'; /* 6 non-breaking spaces */
|
||||||
}
|
}
|
||||||
|
|
||||||
.stave-wrapper {
|
|
||||||
|
.stave-svg {
|
||||||
position: relative;
|
position: relative;
|
||||||
background-color: rgb(24,24,24);
|
z-index: 1;
|
||||||
border: 1px solid rgb(24,24,24);
|
|
||||||
padding-top: 30px;
|
|
||||||
padding-bottom: 20px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.time-indicator {
|
|
||||||
top: 0;
|
#time-indicator {
|
||||||
left: 0;
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 2px;
|
top: 0;
|
||||||
/* height: 100%; */
|
bottom: 0;
|
||||||
|
width: 2px;
|
||||||
|
height: 92%;
|
||||||
|
background-color: rgb(0, 0, 0);
|
||||||
|
z-index: 0;
|
||||||
animation: moveRight 10s linear infinite;
|
animation: moveRight 10s linear infinite;
|
||||||
z-index: 2;
|
will-change: transform;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.fade-in {
|
.fade-in {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
@ -215,7 +238,8 @@ html {
|
||||||
to {
|
to {
|
||||||
transform: rotate(360deg);
|
transform: rotate(360deg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes moveRight {
|
@keyframes moveRight {
|
||||||
0% {
|
0% {
|
||||||
left: 0;
|
left: 0;
|
||||||
|
|
|
||||||