Skip to content

Commit

Permalink
Refactor beat visualization code. #27
Browse files Browse the repository at this point in the history
  • Loading branch information
sonph committed Apr 21, 2020
1 parent e68bbde commit 6179e6c
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 48 deletions.
7 changes: 7 additions & 0 deletions pug/body.pug
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,13 @@ div.row
a.btn.btn-default(v-on:click='metronomeTempoDouble()') ×2
hr.invisible

div.row
div.col
div#viz-container
canvas#viz

hr.invisible

div#chart.container-fluid.collapse(v-bind:class='{show: songChart.enabled}')
div.row
div.col
Expand Down
10 changes: 5 additions & 5 deletions pug/index.pug
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,18 @@ html(lang='en')

//- Bootstrap 4.4.1
link(rel='stylesheet' type='text/css' href='src/bootstrap/bootstrap.min.css')
script(src='src/bootstrap/jquery-3.5.0.min.js')
script(src='src/bootstrap/bootstrap.min.js')
script(defer src='src/bootstrap/jquery-3.5.0.min.js')
script(defer src='src/bootstrap/bootstrap.min.js')

//- Font awesome
link(rel='stylesheet' type='text/css' href='src/fontawesome/css/regular.min.css')
link(rel='stylesheet' type='text/css' href='src/fontawesome/css/fontawesome.min.css')

link(rel='stylesheet' type='text/css' href='src/css/app.css')

script(src='src/js/vue.js')
script(src='src/js/AudioContextMonkeyPatch.js')
script(src='dist/bundle.js')
script(defer src='src/js/vue.js')
script(defer src='src/js/AudioContextMonkeyPatch.js')
script(defer src='dist/bundle.js')

body
<div id="vueApp" v-cloak>
Expand Down
7 changes: 5 additions & 2 deletions src/css/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,12 @@ body {
display: none;
}

canvas#visualization {
height: 400px;
#viz-container {
width: 100%;
height: 4em;
border: 1px solid #c0c0c0;
padding: 0;
margin: 0;
}

.section.selected .section.name {
Expand Down
9 changes: 7 additions & 2 deletions src/js/metronome.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,11 @@ class Metronome {
}

scheduleNote(beatNumber, noteTime) {
// Append note in queue for visualization.
this.viz.appendNote({note: beatNumber, time: noteTime});
// beatNumber is 0 - 15, while we only need 0 - 3 for visualization purpose.
if (beatNumber % 4 == 0) {
// Append note in queue for visualization.
this.viz.appendNote({note: Math.floor(beatNumber / 4), time: noteTime});
}

if ((this.uiData.noteResolution == EIGHTH_NOTE) && (beatNumber % 2))
return; // we're not playing non-8th 16th notes
Expand Down Expand Up @@ -118,6 +121,7 @@ class Metronome {
// Set first note to be 0.5s from now (when user clicks).
this.nextNoteTime = this.audioContext.currentTime + delayMs;
this.timerWorker.postMessage('START');
this.viz.startDrawing();
}
}

Expand All @@ -130,6 +134,7 @@ class Metronome {
this.uiData.toggleLabel = 'START';
this.songChartSkippedFirstNote = false;
this.songChart.reset();
this.viz.stopDrawing();
}
}

Expand Down
133 changes: 94 additions & 39 deletions src/js/visualization.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,16 @@ class Viz {
this.audioContext = audio.getAudioContext();
this.window = window;
this.document = document;

this.canvas;
this.canvasContext;
this.ctx;

this.animationLoopId = -1;

this.positions = [];

// The last 'box' we drew on the screen
this.last16thNoteDrawn = -1;
this.lastNoteDrawn = 0;

// Notes that have been put into the web audio, and may or may not have
// played yet. {note, time}
Expand All @@ -23,62 +28,112 @@ class Viz {
}

initCanvas() {
this.canvas = this.document.createElement('canvas');
this.canvasContext = this.canvas.getContext('2d');
this.canvas.width = this.window.innerWidth;
this.canvas.height = this.window.innerHeight;
this.canvasContext.strokeStyle = '#ffffff';
this.canvasContext.lineWidth = 2;

/*
var container = this.document.createElement('div');
container.className = 'container';
container.appendChild(this.canvas);
this.document.body.appendChild(container);
*/

this.window.onorientationchange = () => { this.resetCanvas(); };
this.window.onresize = () => { this.resetCanvas(); };

// Starts the drawing loop.
this.window.requestAnimFrame(() => {this.draw()});
// TODO(#27): For some reason we still need this setTimeout() here, though
// the whole thing is already within a window.on('load', {}) listener.
this.window.setTimeout(() => {
console.log('initing canvas');

this.canvas = this.document.getElementById('viz');
this.resetCanvas();
// this.canvas = this.document.createElement('canvas');
this.ctx = this.canvas.getContext('2d');
this.ctx.strokeStyle = '#ffffff';
this.ctx.lineWidth = 2;

this.window.onorientationchange = () => { this.resetCanvas(); };
this.window.onresize = () => { this.resetCanvas(); };

this.calcSizeAndPosition();

this.draw(onload=true);
}, 500);
}

draw() {
var currentNote = this.last16thNoteDrawn;
/** Draws the visualization on screen.
* @param {bool} onload - If true, draw only once on page load.
*/
draw(onload=false) {
// Compare note time with current audioContext time for exact timing.
var currentTime = this.audioContext.currentTime;
var currentNote = this.lastNoteDrawn;

while (this.notesInQueue.length &&
this.notesInQueue[0].time < currentTime) {
this.notesInQueue[0].time <= currentTime) {
currentNote = this.notesInQueue[0].note;
this.notesInQueue.splice(0, 1); // remove note from queue
}

// We only need to draw if the note has moved.
if (this.last16thNoteDrawn != currentNote) {
var x = Math.floor(this.canvas.width / 18);
this.canvasContext.clearRect(0, 0, this.canvas.width, this.canvas.height);
for (var i = 0; i < 16; i++) {
this.canvasContext.fillStyle = (currentNote == i) ?
((currentNote % 4 === 0) ? 'red' : 'blue') :
'black';
this.canvasContext.fillRect(x * (i + 1), x, x / 2, x / 2);
if (onload || currentNote != this.lastNoteDrawn) {
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
let x = Math.floor(this.canvas.width / 18);
let width = x / 2;
let height = x / 2;
for (var i = 0; i < 4; i++) {
let p = this.positions[i];
if (i == currentNote) {
this.ctx.fillStyle = this.getColor(i);
this.ctx.fillRect(p.x, p.y, p.width, p.height);
} else {
this.ctx.strokeStyle = this.getColor(i);
this.ctx.strokeRect(p.x, p.y, p.width, p.height);
}
}
this.last16thNoteDrawn = currentNote;
this.lastNoteDrawn = currentNote;
}

// Set up to draw again
this.window.requestAnimFrame(() => {this.draw()});
if (!onload) {
this.animationLoopId = this.window.requestAnimationFrame(() => { this.draw(); });
}
}

getColor(noteNumber) {
if (noteNumber == 0) {
return '#E74C3C';
}
return '#95A5A6';
}

calcSizeAndPosition() {
this.positions = [];
let paddingRatio = 0.15; // 15% of width for padding on each side.
let boxSizeRatio = 0.1; // 10% of width.
let padding = this.canvas.width * 0.15;
let boxSize = Math.min(this.canvas.width * boxSizeRatio, 0.8 * this.canvas.height);
let paddingY = (this.canvas.height - boxSize) / 2;
let boxes = 4;
let spacing = Math.floor((this.canvas.width - padding * 2 - boxSize * 4) / (boxes - 1));
for (let i = 0; i < boxes; i++) {
this.positions.push({
x: Math.floor(padding + (boxSize + spacing) * i),
y: Math.floor(paddingY),
width: Math.floor(boxSize),
height: Math.floor(boxSize)
});
}
}

resetCanvas() {
// Resize canvas. This will also clears the canvas.
this.canvas.width = this.window.innerWidth;
this.canvas.height = this.window.innerHeight;
let d = this.document.getElementById('viz-container');
// TODO(#27): Without setTimeout() method in resetCanvas(),
// clientWidth for some reason will be 0, though this whole thing is already
// within window.on('load', {}) event.
this.canvas.width = d.clientWidth;
this.canvas.height = d.clientHeight;
}

startDrawing() {
this.animationLoopId = this.window.requestAnimationFrame(() => { this.draw(); });
}

// Scroll to the top left.
this.window.scrollTo(0, 0);
stopDrawing() {
if (this.animationLoopId != -1) {
this.window.cancelAnimationFrame(this.animationLoopId);
this.animationLoopId = -1;
this.draw(onload=true);
}
}
}

Expand Down

0 comments on commit 6179e6c

Please sign in to comment.