Commit 4bfec6c4 authored by Tomáš Hübelbauer's avatar Tomáš Hübelbauer

Implement canvas based rendering

parent e626f0a1
......@@ -9,5 +9,9 @@
<body>
<input accept="audio/midi" id="fileInput" type="file" />
<h1 id="nameH1"></h1>
<input type="range" id="timeInput" value="0" style="width: 100%;" />
<button id="playButton"></button>
<br />
<canvas id="renderCanvas" style="background: black; padding: 5px;"></canvas>
</body>
</html>
window.addEventListener('load', async _ => {
const fileInput = document.getElementById('fileInput');
const nameH1 = document.getElementById('nameH1');
const timeInput = document.getElementById('timeInput');
const playButton = document.getElementById('playButton');
const renderCanvas = document.getElementById('renderCanvas');
let channelNotes = [];
fileInput.addEventListener('change', _ => {
const file = fileInput.files[0];
//nameH1.textContent = file.name;
nameH1.textContent = file.name;
const fileReader = new FileReader();
fileReader.addEventListener('load', _ => {
// TODO: Do this in a Web Worker and report progress to the UI.
// TODO: Rewrite this myself to not have to rely on a 3rd part library
const data = new SmfParser().parse(fileReader.result);
// TODO: What is this?
......@@ -17,19 +23,37 @@ window.addEventListener('load', async _ => {
//^ same as tracks[0] and tracks[1]
// Deadmau5 - Strobe from tilt1981 uses 4 tracks
console.log('tracks[0]', data.tracks[0]); // timeSignature & endOfTracks
console.log('tracks[1]', data.tracks[1]); // setTempo's
//console.log('tracks[0]', data.tracks[0]); // timeSignature & endOfTracks
//console.log('tracks[1]', data.tracks[1]); // setTempo's
const channel0Notes = [ ...getNotes(data.tracks[2]) ];
const channel1Notes = [ ...getNotes(data.tracks[3]) ];
timeInput.setAttribute('max', Math.max(channel0Notes[channel0Notes.length - 1].offTime, channel1Notes[channel1Notes.length - 1].offTime));
// https://www.youtube.com/watch?v=ppLnKRObtM0
renderChannelNotes(channel0Notes, length);
renderChannelNotes(channel1Notes, length);
//renderChannelNotes(channel0Notes, length);
//renderChannelNotes(channel1Notes, length);
channelNotes = [ channel0Notes, channel1Notes ];
render(renderCanvas, channelNotes, 0);
});
fileReader.readAsBinaryString(file);
});
let playbackInterval;
playButton.addEventListener('click', _ => {
playbackInterval = window.setInterval(() => {
timeInput.value = timeInput.valueAsNumber + 1;
render(renderCanvas, channelNotes, timeInput.valueAsNumber);
}, 1);
});
timeInput.addEventListener('input', _ => {
window.clearInterval(playbackInterval);
render(renderCanvas, channelNotes, timeInput.valueAsNumber);
});
});
// https://stackoverflow.com/a/23071105/2715716
......@@ -73,25 +97,54 @@ function* getNotes(track) {
}
}
const width = 10;
function renderChannelNotes(notes) {
// TODO: Do not use `100` for padding, use non-absolute positioning instead.
const limit = notes.slice(-1)[0].offTime + 100;
for (const note of notes) {
const boxDiv = document.createElement('div');
boxDiv.style.position = 'absolute';
boxDiv.style.background = `rgb(255, 255, ${note.no})`;
boxDiv.style.border = '1px solid black';
boxDiv.style.borderRadius = '10%';
// TODO: Give each channel a different color
// TODO: Fix first notes being off-frame
// TODO: Adapt `canvas` size to window (put in flex and use computed dimensions to set canvas context dimensions)
// TODO: Resize with `window.onresize`
function render(canvas, channels, time) {
const width = 768;
const height = 576;
boxDiv.style.top = `${limit - note.offTime}px`;
boxDiv.style.bottom = `${limit - note.onTime}px`;
canvas.width = 768;
canvas.height = 576;
boxDiv.style.left = `${note.no * width}px`;
const context = canvas.getContext('2d');
context.fillRect(0, 0, width, height);
boxDiv.style.width = `${width}px`;
const top = height + time;
const bottom = time;
boxDiv.textContent = note.no;
document.body.appendChild(boxDiv);
for (const notes of channels) {
// Draw all fill first so that they do not overlap any strokes
context.fillStyle = 'rgb(128, 128, 128)';
for (const note of notes) {
if (!isNoteInView(note)) continue;
const { x, y, w, h } = getNoteXYWH(note, 5, top);
context.fillRect(x, y, w, h);
}
// Draw all strokes second so they are not overlapped by any fills
let counter = 0;
context.strokeStyle = 'rgb(255, 255, 255)';
for (const note of notes) {
counter++;
if (!isNoteInView(note)) continue;
const { x, y, w, h } = getNoteXYWH(note, 5, top);
context.strokeRect(x, y, w, h);
context.fillText(`#${counter}: ${note.no}`, x + w, y + h);
}
}
context.fillStyle = 'red';
context.fillText(time, 0, height);
}
function isNoteInView(note, top, bottom) {
if (note.offTime < bottom) return false;
if (note.onTime > top) return false;
return true;
}
function getNoteXYWH(note, unit, top) {
return { x: note.no * unit, y: top - note.onTime, w: unit, h: note.offTime - note.onTime };
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment