Last active
November 23, 2022 23:51
-
-
Save wilson428/5471336 to your computer and use it in GitHub Desktop.
A demo of my Javascript metronome.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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