Skip to content

Instantly share code, notes, and snippets.

@wilson428
Last active November 23, 2022 23:51
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save wilson428/5471336 to your computer and use it in GitHub Desktop.
Save wilson428/5471336 to your computer and use it in GitHub Desktop.
A demo of my Javascript metronome.
<!DOCTYPE html>
<!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
<!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
<!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]-->
<!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]-->
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>Javascript Metronome</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width">
<style>
.example {
font-family: Arial;
font-size: 12px;
}
.status {
width: 100px;
height: 200px;
padding: 10px;
overflow-y: auto;
}
.statusline {
font-size: 10px;
background-color: #E0E0E0;
margin: 3px 0;
padding: 2px;
}
.metr_input {
width: 40px;
margin-right: 10px;
text-align: center;
}
#count {
width: 50px;
display: inline-block;
text-align: right;
}
</style>
</head>
<body>
<table class="example">
<tr>
<td><div id="metronome_container"></div></td>
<td><div class="status"></div></td>
</tr>
<tr>
<td colspan=2 id="inputs"></td>
</tr>
</table>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/raphael/2.1.0/raphael-min.js"></script>
<script src="metronome.js"></script>
<script>
/*global $ Raphael soundManager metronome*/
function tick(t) {
$("<div />").html(t % 2 === 1 ? "Tick" : "Tock").addClass("statusline").appendTo(".status");
$("#count").html(t);
}
function done() {
$("<div />").html("Done!").addClass("statusline").css("background-color", "#FFFF99").appendTo(".status");
$("#startstop").html("start");
}
var paper = Raphael("metronome_container", 200, 200);
var m = metronome({
len: 200,
angle: 20,
tick: tick,
complete: done,
paper: paper,
audio: "https://github.com/wilson428/metronome/blob/master/tick.wav?raw=true"
});
m.make_input("#inputs");
m.shapes().outline.attr("fill", "#0962ba");
m.shapes().arm.attr("stroke", "#EEE");
</script>
</body>
</html>
var metronome = function(opts) {
//primary variables
var l = typeof opts.len !== "undefined" ? opts.len : 200, // length of metronome arm
r = typeof opts.angle !== "undefined" ? opts.angle : 20, //max angle from upright
w = 2 * l * Math.cos(r),
tick_func = typeof opts.tick !== "undefined" ? opts.tick : function() {}, //function to call with each tick
end_func = typeof opts.complete !== "undefined" ? opts.complete : function() {}, //function to call on completion
playSound = typeof opts.sound !== "undefined" ? opts.sound : true;
// initialize Raphael paper if need be
switch(typeof opts.paper) {
case "string": paper = Raphael(opts.paper, w, l + 20); break;
default: paper = Raphael(0, 0, w, l + 20); break;
}
// initialize audio if need be
if (playSound && opts.audio) {
// initialize audio
var sound = document.createElement('audio');
sound.setAttribute('src', opts.audio);
sound.setAttribute('id', 'tick');
document.body.appendChild(sound);
}
// derivative variables
var y0 = l * Math.cos(Math.PI * r / 180),
x0 = l * Math.sin(Math.PI * r / 180),
y = l + 10,
x = x0 + 10,
tick_count = 0;
var outline = paper.path("M"+x+","+y+"l-"+x0+",-"+y0+"a"+l+","+l+" "+2*r+" 0,1 "+2*x0+",0L"+x+","+y).attr({
fill: "#EEF",
'stroke-width': 0
});
var arm = paper.path("M" + x + "," + (y + 5) + "v-" + (l - 5)).attr({
'stroke-width': 5,
stroke: "#999"
}).data("id", "arm");
var weight = paper.path("M" + x + "," + (y-100) + "h12l-3,18h-18l-3-18h12").attr({
'stroke-width': 0,
fill: '#666'
}).data("id", "weight");
var vertex = paper.circle(x, y, 7).attr({
'stroke-width': 0,
fill: '#CCC'
}).data("id", "vertex");
var label = paper.text(x, y + 20, "").attr({
"text-anchor": "center",
"font-size": 14
});
var mn = paper.set(arm, weight);
Raphael.easing_formulas.sinoid = function(n) { return Math.sin(Math.PI * n / 2) };
function tick(obj, repeats) {
//Raphael summons the callback on each of the three objects in the set, so we
//have to only call the sound once per iteration by associating it with one of the objects.
//doesn't matter which one
if (obj.data("id") === "arm") {
tick_count += 1;
if (playSound) {
document.getElementById("tick").play();
}
tick_func(tick_count);
if (tick_count >= repeats) {
mn.attr("transform", "R0 " + x + "," + y);
end_func();
}
}
}
return {
start: function(tempo, repeats) {
tick_count = 0;
mn.attr("transform", "R-20 " + x + "," + y);
//2 iterations per animation * 60000 ms per minute / tempo
var interval = 120000 / tempo;
var animationDone = function() {
tick(this, repeats);
};
var ticktockAnimationParam = {
"50%": { transform:"R20 " + x + "," + y, easing: "sinoid", callback: animationDone },
"100%": { transform:"R-20 " + x + "," + y, easing: "sinoid", callback: animationDone }
};
//animation
var ticktock = Raphael.animation(ticktockAnimationParam, interval).repeat(repeats / 2);
arm.animate(ticktock);
weight.animateWith(arm, ticktockAnimationParam, ticktock);
},
stop: function() {
mn.stop();
mn.attr("transform", "R0 " + x + "," + y);
end_func();
},
shapes: function() {
return {
outline: outline,
arm: arm,
weight: weight,
vertex: vertex
}
},
make_input: function(el) {
$("<div />", {
html: "<span>tempo: </span>" +
"<input class='metr_input' type='text' id='tempo' value='100' />" +
"<span>ticks: </span>" +
"<input class='metr_input' type='text' id='ticks' value='8' />" +
"<button id='startstop'>start</button>" +
"<div id='count'>0</div>"
}).appendTo(el);
$('#startstop').click(function() {
// start animation
if ($(this).html() === "start") {
$(this).html("stop");
//get values for tempo and ticks and restrict
var tempo = parseInt($('#tempo').val(), 10);
if (!tempo) { tempo = 60; }
else if (tempo > 200) { tempo = 200; }
else if (tempo < 30) { tempo = 30; }
$("#tempo").val(tempo);
var ticks = parseInt($('#ticks').val(), 10);
if (!ticks) { ticks = 20; }
else if (ticks > 60) { ticks = 60; }
else if (ticks < 8) { ticks = 8; }
$("#ticks").val(ticks);
m.start(tempo, ticks);
} else {
$(this).html("start");
m.stop();
}
});
}
};
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment