diff --git a/assets/instruments/instruments.json b/assets/instruments/instruments.json
index 4dfbf0b..4ad71d1 100644
--- a/assets/instruments/instruments.json
+++ b/assets/instruments/instruments.json
@@ -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"
+ }
}
}
diff --git a/assets/instruments/orchestral-bass/bass.svg b/assets/instruments/orchestral-bass/bass.svg
index 30e63e3..334fb75 100644
--- a/assets/instruments/orchestral-bass/bass.svg
+++ b/assets/instruments/orchestral-bass/bass.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/assets/instruments/snare/snare.svg b/assets/instruments/snare/snare.svg
index f8cba7e..651ad5c 100644
--- a/assets/instruments/snare/snare.svg
+++ b/assets/instruments/snare/snare.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/assets/instruments/surdo-napa/surdo-napa.svg b/assets/instruments/surdo-napa/surdo-napa.svg
index d90e032..7c3f7fe 100644
--- a/assets/instruments/surdo-napa/surdo-napa.svg
+++ b/assets/instruments/surdo-napa/surdo-napa.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/assets/instruments/surdo/surdo.svg b/assets/instruments/surdo/surdo.svg
index bccb271..a9707fa 100644
--- a/assets/instruments/surdo/surdo.svg
+++ b/assets/instruments/surdo/surdo.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/assets/instruments/timpani-large/timpani-large.svg b/assets/instruments/timpani-large/timpani-large.svg
index 17c3f6c..d26bcda 100644
--- a/assets/instruments/timpani-large/timpani-large.svg
+++ b/assets/instruments/timpani-large/timpani-large.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/assets/instruments/timpani-small/timpani-small.svg b/assets/instruments/timpani-small/timpani-small.svg
index e12a085..d43b15c 100644
--- a/assets/instruments/timpani-small/timpani-small.svg
+++ b/assets/instruments/timpani-small/timpani-small.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/assets/instruments/toms/toms.svg b/assets/instruments/toms/toms.svg
index 8bec501..5a6f04b 100644
--- a/assets/instruments/toms/toms.svg
+++ b/assets/instruments/toms/toms.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/assets/svg/stave.svg b/assets/svg/stave.svg
index 277660e..a7f40fc 100644
--- a/assets/svg/stave.svg
+++ b/assets/svg/stave.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/index.html b/index.html
index 5229146..681e5bb 100644
--- a/index.html
+++ b/index.html
@@ -15,11 +15,6 @@
-
-
-
-
-
@@ -34,59 +29,8 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
- The Conductor translates live lightning strikes from around the world into a graphic score for seven percussion
- instruments.
-
-
-
-
-
- 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.
-
-
- 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.
-
-
- Click here
- to read more about the project. Download a copy of the artist's graphic score here.
-
-
-
-
-
- Project Conception and Design by Mishka Henner.
-
- Data Capture, Web Design and Coding by Joe Gibson.
-
- Lightning data from Blitzortung.org
-
-
- 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.
-
-
-
+ id="time-indicator">
diff --git a/notes.txt b/notes.txt
index 0c50a07..4d00686 100644
--- a/notes.txt
+++ b/notes.txt
@@ -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
+
diff --git a/planeNotes.txt b/planeNotes.txt
new file mode 100644
index 0000000..a4ea469
--- /dev/null
+++ b/planeNotes.txt
@@ -0,0 +1,11 @@
+placeIcon method:
+ Now img is an 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.
+
+
+
diff --git a/src/conductor.js b/src/conductor.js
index 901a6a5..1bf9843 100644
--- a/src/conductor.js
+++ b/src/conductor.js
@@ -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);
- }
}
}
diff --git a/src/instruments.js b/src/instruments.js
index 9afed55..20ebfd8 100644
--- a/src/instruments.js
+++ b/src/instruments.js
@@ -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,
};
}
diff --git a/src/render.js b/src/render.js
index 06403ab..57bc39a 100644
--- a/src/render.js
+++ b/src/render.js
@@ -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
+ 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 = /