forked from rjdmoore/fictrac
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1 from floesche/master
initial commit
- Loading branch information
Showing
6 changed files
with
1,012 additions
and
0 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
Large diffs are not rendered by default.
Oops, something went wrong.
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,332 @@ | ||
<!DOCTYPE html> | ||
<html> | ||
<!-- demo at https://CogNovo.eu/projects/project-17/pitchgrate --> | ||
|
||
<head> | ||
<meta charset="UTF-8"> | ||
<script src="/static/socket.io.slim.js"></script> | ||
<script> | ||
var socket = io(); | ||
socket.on('connect', function(){ | ||
socket.emit('my event', {data: 'I\'m connected!'}); | ||
}); | ||
socket.on('event', function(data){}); | ||
socket.on('disconnect', function(){}); | ||
</script> | ||
<style> | ||
html, | ||
body { | ||
padding: 0; | ||
margin: 0; | ||
overflow: hidden; | ||
} | ||
|
||
.box { | ||
background-color: #000; | ||
width: 5vw; | ||
height: 100vh; | ||
position: absolute; | ||
} | ||
|
||
#play { | ||
position: absolute; | ||
background-color: #CCCA; | ||
margin: 20px; | ||
padding: 20px; | ||
position: absolute; | ||
top: 50%; | ||
left: 50%; | ||
margin-right: -50%; | ||
transform: translate(-50%, -50%); | ||
} | ||
#play #btn { | ||
text-align:center; | ||
color: green; | ||
font-size: 5em; | ||
|
||
} | ||
</style> | ||
</head> | ||
|
||
<body> | ||
|
||
<div id="container"></div> | ||
<div id="play"> | ||
After a click on play you will hear sounds. So please turn down the volume. | ||
<div id="btn">Play</div></div> | ||
|
||
<script> | ||
window.AudioContext = window.AudioContext || window.webkitAudioContext; | ||
var audioContext = null; | ||
var isPlaying = false; | ||
var sourceNode = null; | ||
var counter = 500; | ||
|
||
var theBuffer = null; | ||
|
||
var buflen = 1024; | ||
var buf = new Float32Array(buflen); | ||
|
||
var rafID = null; | ||
|
||
(function () { | ||
audioContext = new AudioContext(); | ||
document.getElementById('play').addEventListener('click', function () { | ||
audioContext.resume().then(() => { | ||
console.log('Playback resumed successfully'); | ||
document.getElementById('play').hidden = true; | ||
//toggleOscillator(); | ||
toggleLiveInput(); | ||
}); | ||
}); | ||
|
||
cont = document.getElementById("container"); | ||
for (num = -1; num < 10; num++) { | ||
bx = document.createElement("div"); | ||
bx.id = "b" + num; | ||
bx.className = "box"; | ||
bx.style.left = num * 10 + "vw"; | ||
cont.appendChild(bx); | ||
} | ||
|
||
|
||
// var request = new XMLHttpRequest(); | ||
// request.open("GET", "./AudioSlideExample.ogg", true); | ||
// request.responseType = "arraybuffer"; | ||
// request.onload = function () { | ||
// audioContext.decodeAudioData(request.response, function (buffer) { | ||
// theBuffer = buffer; | ||
// }); | ||
// } | ||
// request.send(); | ||
|
||
// var interval = setInterval(runUpdate, 10); | ||
//toggleLiveInput(); | ||
// togglePlayback(); | ||
//toggleOscillator(); | ||
})(); | ||
|
||
// var CENTER = 2016; | ||
var CENTER = 1920; | ||
var SENSITIVITY = 10; | ||
function runUpdate(freq) { | ||
console.log(freq); | ||
console.log((CENTER-freq) / SENSITIVITY); | ||
if (freq < CENTER) { counter = counter - (CENTER - freq) / SENSITIVITY; } | ||
else { counter = counter + (freq - CENTER) / SENSITIVITY; } | ||
if (counter < 200 || counter > 1000) { | ||
counter = (counter % 100) + 500; | ||
} | ||
for (num = -1; num < 10; num++) { | ||
bx = document.getElementById("b" + num); | ||
bx.style.left = num * 10 + (counter % 100) / 10 + "vw"; | ||
} | ||
// counter = counter + 1; | ||
} | ||
|
||
function error() { | ||
alert('Stream generation failed.'); | ||
} | ||
|
||
function getUserMedia(dictionary, callback) { | ||
try { | ||
navigator.getUserMedia = | ||
navigator.getUserMedia || | ||
navigator.webkitGetUserMedia || | ||
navigator.mozGetUserMedia; | ||
navigator.getUserMedia(dictionary, callback, error); | ||
} catch (e) { | ||
alert('getUserMedia threw exception :' + e); | ||
} | ||
} | ||
|
||
function toggleLiveInput() { | ||
if (isPlaying) { | ||
//stop playing and return | ||
sourceNode.stop(0); | ||
sourceNode = null; | ||
analyser = null; | ||
isPlaying = false; | ||
if (!window.cancelAnimationFrame) | ||
window.cancelAnimationFrame = window.webkitCancelAnimationFrame; | ||
window.cancelAnimationFrame(rafID); | ||
} | ||
getUserMedia( | ||
{ | ||
"audio": { | ||
"mandatory": { | ||
"googEchoCancellation": "false", | ||
"googAutoGainControl": "false", | ||
"googNoiseSuppression": "false", | ||
"googHighpassFilter": "false" | ||
}, | ||
"optional": [] | ||
}, | ||
}, gotStream); | ||
} | ||
|
||
function toggleOscillator() { | ||
if (isPlaying) { | ||
//stop playing and return | ||
sourceNode.stop(0); | ||
sourceNode = null; | ||
analyser = null; | ||
isPlaying = false; | ||
if (!window.cancelAnimationFrame) | ||
window.cancelAnimationFrame = window.webkitCancelAnimationFrame; | ||
window.cancelAnimationFrame(rafID); | ||
return "play oscillator"; | ||
} | ||
sourceNode = audioContext.createOscillator(); | ||
var gainNode = audioContext.createGain(); | ||
gainNode.connect(audioContext.destination); | ||
sourceNode.connect(gainNode); | ||
gainNode.gain.setValueAtTime(-.95, audioContext.currentTime); | ||
for (i = 0; i < 10; i++) { | ||
sourceNode.frequency.setValueAtTime(1000 + 100 * i, audioContext.currentTime + i + 2); | ||
} | ||
for (i = 11; i < 20; i++) { | ||
sourceNode.frequency.setValueAtTime(1000 - 100 * (i - 11), audioContext.currentTime + i + 2); | ||
} | ||
for (i = 41; i < 60; i++) { | ||
sourceNode.frequency.setValueAtTime(Math.random() * 1950 + 50, audioContext.currentTime + i / 2 + 2); | ||
} | ||
|
||
analyser = audioContext.createAnalyser(); | ||
analyser.fftSize = 2048; | ||
sourceNode.connect(analyser); | ||
analyser.connect(audioContext.destination); | ||
sourceNode.start(0); | ||
isPlaying = true; | ||
isLiveInput = false; | ||
sourceNode.stop(audioContext.currentTime + 35); | ||
updatePitch(); | ||
|
||
return "stop"; | ||
} | ||
|
||
function togglePlayback() { | ||
if (isPlaying) { | ||
//stop playing and return | ||
sourceNode.stop(0); | ||
sourceNode = null; | ||
analyser = null; | ||
isPlaying = false; | ||
if (!window.cancelAnimationFrame) | ||
window.cancelAnimationFrame = window.webkitCancelAnimationFrame; | ||
window.cancelAnimationFrame(rafID); | ||
return "start"; | ||
} | ||
|
||
sourceNode = audioContext.createBufferSource(); | ||
sourceNode.buffer = theBuffer; | ||
sourceNode.loop = true; | ||
|
||
analyser = audioContext.createAnalyser(); | ||
analyser.fftSize = 2048; | ||
sourceNode.connect(analyser); | ||
analyser.connect(audioContext.destination); | ||
sourceNode.start(0); | ||
isPlaying = true; | ||
isLiveInput = false; | ||
updatePitch(); | ||
|
||
return "stop"; | ||
} | ||
|
||
|
||
|
||
|
||
function gotStream(stream) { | ||
// Create an AudioNode from the stream. | ||
mediaStreamSource = audioContext.createMediaStreamSource(stream); | ||
|
||
// Connect it to the destination. | ||
analyser = audioContext.createAnalyser(); | ||
analyser.fftSize = 2048; | ||
mediaStreamSource.connect(analyser); | ||
updatePitch(); | ||
} | ||
|
||
|
||
var MIN_SAMPLES = 0; // will be initialized when AudioContext is created | ||
var GOOD_ENOUGH_CORRELATION = 0.9; // this is the "bar" for how close a correlation needs to be | ||
|
||
function autoCorrelate(buf, sampleRate) { | ||
var SIZE = buf.length; | ||
var MAX_SAMPLES = Math.floor(SIZE / 2); | ||
var best_offset = -1; | ||
var best_correlation = 0; | ||
var rms = 0; | ||
var foundGoodCorrelation = false; | ||
var correlations = new Array(MAX_SAMPLES); | ||
|
||
for (var i = 0; i < SIZE; i++) { | ||
var val = buf[i]; | ||
rms += val * val; | ||
} | ||
rms = Math.sqrt(rms / SIZE); | ||
if (rms < 0.01) // not enough signal | ||
return -1; | ||
|
||
var lastCorrelation = 1; | ||
for (var offset = MIN_SAMPLES; offset < MAX_SAMPLES; offset++) { | ||
var correlation = 0; | ||
|
||
for (var i = 0; i < MAX_SAMPLES; i++) { | ||
correlation += Math.abs((buf[i]) - (buf[i + offset])); | ||
} | ||
correlation = 1 - (correlation / MAX_SAMPLES); | ||
correlations[offset] = correlation; // store it, for the tweaking we need to do below. | ||
if ((correlation > GOOD_ENOUGH_CORRELATION) && (correlation > lastCorrelation)) { | ||
foundGoodCorrelation = true; | ||
if (correlation > best_correlation) { | ||
best_correlation = correlation; | ||
best_offset = offset; | ||
} | ||
} else if (foundGoodCorrelation) { | ||
// short-circuit - we found a good correlation, then a bad one, so we'd just be seeing copies from here. | ||
// Now we need to tweak the offset - by interpolating between the values to the left and right of the | ||
// best offset, and shifting it a bit. This is complex, and HACKY in this code (happy to take PRs!) - | ||
// we need to do a curve fit on correlations[] around best_offset in order to better determine precise | ||
// (anti-aliased) offset. | ||
|
||
// we know best_offset >=1, | ||
// since foundGoodCorrelation cannot go to true until the second pass (offset=1), and | ||
// we can't drop into this clause until the following pass (else if). | ||
var shift = (correlations[best_offset + 1] - correlations[best_offset - 1]) / correlations[best_offset]; | ||
return sampleRate / (best_offset + (8 * shift)); | ||
} | ||
lastCorrelation = correlation; | ||
} | ||
if (best_correlation > 0.01) { | ||
// console.log("f = " + sampleRate/best_offset + "Hz (rms: " + rms + " confidence: " + best_correlation + ")") | ||
return sampleRate / best_offset; | ||
} | ||
return -1; | ||
// var best_frequency = sampleRate/best_offset; | ||
} | ||
|
||
function updatePitch(time) { | ||
var cycles = new Array; | ||
analyser.getFloatTimeDomainData(buf); | ||
var ac = autoCorrelate(buf, audioContext.sampleRate); | ||
// TODO: Paint confidence meter on canvasElem here. | ||
|
||
|
||
if (ac == -1) { | ||
// do something if not loud enough | ||
} else { | ||
runUpdate(ac); | ||
} | ||
|
||
if (!window.requestAnimationFrame) | ||
window.requestAnimationFrame = window.webkitRequestAnimationFrame; | ||
rafID = window.requestAnimationFrame(updatePitch); | ||
} | ||
// pitch detection from https://github.com/cwilso/PitchDetect/ | ||
</script> | ||
|
||
</body> | ||
|
||
</html> |
Oops, something went wrong.