Metronome
by YH Park
HTML
<h1>Metronome</h1>
<h3>Tempo</h3>
<input type="range" min="10" max="200" step="1" style="width: 500px" oninput="fnObj.rangeChange()">
<br>
<input type="button" name="tempo-up" value="+" onclick="fnObj.addTempo(true)">
<input type="button" name="tempo-down" value="-" onclick="fnObj.addTempo(false)">
<input type="button" name="taptap" value="tap tempo" onclick="fnObj.tap()">
<br>
<form action="javascript: fnObj.tempoChange(Number($('#cur-tempo').val()))">
<input type="text" id="cur-tempo" autocomplete="off">
<input type="submit" value="change">
</form>
<h3>Beat</h3>
<select name="beat" onchange="fnObj.changeBeat()">
<option>1</option>
<option>2</option>
<option>3</option>
<option selected>4</option>
<option>5</option>
<option>6</option>
<option>7</option>
<option>8</option>
</select>
1<input type="radio" name="divide" value="1" onchange="fnObj.split = 1" checked>
2<input type="radio" name="divide" value="2" onchange="fnObj.split = 2">
3<input type="radio" name="divide" value="3" onchange="fnObj.split = 3">
4<input type="radio" name="divide" value="4" onchange="fnObj.split = 4">
6<input type="radio" name="divide" value="6" onchange="fnObj.split = 6">
<br><br>
<div id="visualization-area"></div>
<br><br>
<input type="button" name="begin" value="begin" onclick="fnObj.begin()">
<input type="button" name="stop" value="stop" onclick="fnObj.stop()">
CSS
#visualization-area {
width: max-content;
}
.circle {
width: 50px;
height: 50px;
display: inline-block;
margin-right: 50px;
border-radius: 50%;
}
.circle.dis {
background: #555;
opacity: 0.2;
position: relative;
}
.circle.act {
background: #0f0;
position: absolute;
transition-property: margin-left;
transition-timing-function: ease-out;
transition-delay: 0s;
}
JavaScript
var beep = [
new Audio('https://www.soundjay.com/buttons/beep-29.wav'),
new Audio('https://www.soundjay.com/buttons/beep-07a.wav'),
new Audio('https://www.soundjay.com/buttons/beep-21.wav'),
new Audio('https://www.soundjay.com/buttons/beep-21.wav'),
new Audio('https://www.soundjay.com/buttons/beep-21.wav'),
new Audio('https://www.soundjay.com/buttons/beep-21.wav'),
new Audio('https://www.soundjay.com/buttons/beep-21.wav')
]
beep[1].volume = 0.5;
var fnObj = {
tempo: 100, //metronome tempo
cnt: 0, //beep counter
intv: 4, //loud beep interval
split: 1, //beat split number
// init before begin
init: function() {
//set tempo
this.tempoChange(this.tempo);
//set visual area (circle)
$('#visualization-area').empty();
$('#visualization-area').append('<div class="circle act"></div>');
for (var i = 0; i < this.intv; i++)
$('#visualization-area').append('<div class="circle dis"></div>');
},
//metronome begin
begin: function() {
this.stop();
this.recur();
},
//function for each beep sound
recur: function() {
//play standard beep sound
if ((this.cnt %= this.intv) == 0)
beep[1].play();
else
beep[0].play();
//move green circle to next gray circle
$('.circle.act').css('transition-duration', Math.round(60000/this.tempo)+'ms').css('margin-left', (100*this.cnt)+'px');
this.cnt++;
//call next recursive function
this.timeoutFunc = setTimeout(function() {
fnObj.recur()
}, 60000 / this.tempo);
//if there is a bit split, plays a small beep
for (let i = 1; i < this.split; i++)
setTimeout(function() {
beep[i+1].play();
}, 60000 / this.tempo / this.split * i);
},
//metronome end
stop: function() {
this.cnt = 0;
clearInterval(this.timeoutFunc);
},
//change tempo between 10 ~ 200
tempoChange: function(val) {
if (val < 10)
val = 10;
else if (val > 200)
val = 200;
this.tempo = val;
...