diff options
Diffstat (limited to 'sensfreq')
-rw-r--r-- | sensfreq/index.html | 95 | ||||
-rw-r--r-- | sensfreq/main.js | 114 |
2 files changed, 209 insertions, 0 deletions
diff --git a/sensfreq/index.html b/sensfreq/index.html new file mode 100644 index 0000000..7422b4a --- /dev/null +++ b/sensfreq/index.html @@ -0,0 +1,95 @@ +<!doctype html> +<html> +<head> +<title>SensFreq</title> +<script src="themer.js"></script> +<script src="main.js"></script> +<link rel="stylesheet" type="text/css" href="common.css"> +<link rel="stylesheet" type="text/css" href="theme0a.css" id="theme0a"> +<link rel="stylesheet" type="text/css" href="theme0b.css" id="theme0b"> +<link rel="stylesheet" type="text/css" href="theme1a.css" id="theme1a"> +<link rel="stylesheet" type="text/css" href="theme1b.css" id="theme1b"> +<link rel="stylesheet" type="text/css" href="theme2a.css" id="theme2a"> +<link rel="stylesheet" type="text/css" href="theme2b.css" id="theme2b"> +<link rel="stylesheet" type="text/css" href="theme3a.css" id="theme3a"> +<link rel="stylesheet" type="text/css" href="theme3b.css" id="theme3b"> +</head> +<body style="text-align:center;" onload="init()" class="TText"> +<h2>SensFreq</h2> +<table style="margin:auto;" class="TText"> +<tr> +<td>Note to look for</td> +<td> +<select id="note" class="TText"> +<option value="3">C</option> +<option value="4">C#</option> +<option value="5">D</option> +<option value="6">D#</option> +<option value="7">E</option> +<option value="8">F</option> +<option value="9">F#</option> +<option value="10">G</option> +<option value="11">G#</option> +<option value="0">A</option> +<option value="1">A#</option> +<option value="2">B</option> +</select> +</td> +</tr> +<tr> +<td>Number of notes</td> +<td> +<input type="number" value="1" min="1" max="10" id="minnnotes" class="TText"></input> +- +<input type="number" value="3" min="1" max="10" id="maxnnotes" class="TText"></input> +</td> +</tr> +<tr> +<td>Octave range</td> +<td> +<input type="number" value="-1" min="-5" max="5" id="minorange" class="TText"></input> +- +<input type="number" value="1" min="-5" max="5" id="maxorange" class="TText"></input> +</td> +</tr> +</table> +<input type="checkbox" id="arp" class="TText">Arpeggio</input><br> +<button id="gench" onclick="generate_challenge()" class="TText">Play something!</button><br> +<span id="prompt" class="TText"> </span> +<div style="width:100%;height:3em;"> +<span id="response"> +<style> +.yes { + background-color:#0c0; + border: 2px solid #0a0; + color: #fff; + padding: 8px; + margin: 4px; +} +.yes:hover { + background-color:#0e0; +} +.no { + background-color:#c00; + border: 2px solid #a00; + color: #fff; + padding: 8px; + margin: 4px; +} +.no:hover { + background-color:#e00; +} +</style> +<button onclick="check_yes()" class="yes TText">Yes</button> +<button onclick="check_no()" class="no TText">No</button> +</span> +</div> +<div> +<span id="stats" class="TText">Correct: * of *</span><br> +<button onclick="reset_stats()" class="TText">Reset statistics</button> +</div> +<div style="padding-top:1em;" class="TText"> +Disclaimer: this application cannot serve as a test for perfect pitch. Please do not go around and claim you have perfect pitch if you get 99.999% challenges right! +</div> +</body> +</html diff --git a/sensfreq/main.js b/sensfreq/main.js new file mode 100644 index 0000000..6dc431e --- /dev/null +++ b/sensfreq/main.js @@ -0,0 +1,114 @@ +const audioCtx = new (window.AudioContext || window.webkitAudioContext)(); + +e = s => document.getElementById(s); + +let ans = false; +let nq = 0; +let nc = 0; + +function init() +{ + e("gench").disabled = false; + e("response").style.display="none"; + reset_stats(); + loadTheme(); +} + +function play_notes(freq, t, arpt) +{ + const stop_notes=function(g, o) + { + o.stop(); + g.disconnect(audioCtx.destination); + o.disconnect(g); + } + const play_note=function(f) + { + const gn = audioCtx.createGain(); + gn.gain.setValueAtTime(.5/freq.length, audioCtx.currentTime); + gn.gain.linearRampToValueAtTime(0, audioCtx.currentTime + t / 1000.); + gn.connect(audioCtx.destination); + const osc = audioCtx.createOscillator(); + osc.type = "sine"; + osc.frequency.setValueAtTime(f, audioCtx.currentTime); + osc.connect(gn); + osc.start(); + setTimeout(stop_notes.bind(null, gn, osc), t); + } + let i=0; + freq.forEach(function(f){setTimeout(play_note.bind(null, f), arpt * (i++));}); +} + +get_freq = n => 440 * Math.pow(Math.pow(2, 1./12), n-69) + +function generate_challenge() +{ + const nmin=Number(e("minnnotes").value); + const nmax=Number(e("maxnnotes").value); + const omin=Number(e("minorange").value); + const omax=Number(e("maxorange").value); + if (omax < omin || nmax < nmin) + { + alert("?"); + return; + } + const nnotes = Math.floor(Math.random() * (nmax - nmin + 1)) + nmin; + const minrange = 60 + omin * 12; + const maxrange = 71 + omax * 12; + + let f = []; + let l = []; + ans = false; + + if (Math.random() < 0.3) + { + const o = Math.floor(Math.random() * (omax - omin + 1)) + omin; + let n = 60 + Number(e("note").value) + o * 12; + f.push(get_freq(n)); + l.push(n); + ans = true; + } + while (f.length < nnotes) + { + let n = Math.floor(Math.random() * (maxrange - minrange + 1)) + minrange; + if (l.indexOf(n)!=-1) + continue; + ans |= ((n % 12) == Number(e("note").value)); + f.push(get_freq(n)); + } + f.sort((a,b)=>a-b); + play_notes(f, 1000, e("arp").checked ? 50 : 0); + e("gench").disabled = true; + e("response").style.display=""; +} + +function update_stats() +{ + e("stats").innerHTML=`You got ${nc} of the ${nq} challenges correct.` +} + +function reset_stats() +{ + nq = nc = 0; + update_stats(); +} + +function check_yes() +{ + if (ans) + ++nc; + ++nq; + e("gench").disabled = false; + e("response").style.display="none"; + update_stats(); +} + +function check_no() +{ + if (!ans) + ++nc; + ++nq; + e("gench").disabled = false; + e("response").style.display="none"; + update_stats(); +} |