ブラウザARもどきテルミンもどき

chromeのabout:flagsでmedia-streamを有効化するとWebカメラが使える。
そこでjavascriptオンリーのブラウザARテルミンをやってみた。


仕組みはマーカーの位置を検出し、x軸とy軸をピッチと音量に割り当てた単純なものです。
マーカーを検出するライブラリはjs-arucoを使用しています。
音声処理はweb audio apiです。


やはり作ってみるとなかなか重いので、画面サイズを640*480から320*240にして、音で遊ぶものなので余計な描画は全てカットしました。
またモノフォニックなテルミンなので複数検出機能も捨てています。
すると、、、けっこうきれいに音が出て満足しています。工夫すればドラムマシーンのようなものもできるかも。

demo

こんな感じになる写真(スマフォはマーカー印刷するのめんどくさかったから使ってる)

以下ソース

<html>
<head>
<title>AR Theremin</title>
<script type="text/javascript" src="polyfill.js"></script> 
<script type="text/javascript" src="cv.js"></script> 
<script type="text/javascript" src="aruco.js"></script> 
<script>
var video, canvas, context, imageData, detector;
var sampleRate = 48000;
var bufferSize = 2048;
var freq = 440;
var amp = 0.0;
var audiocontext = new webkitAudioContext();
audiocontext.sampleRate = sampleRate;
var node = audiocontext.createJavaScriptNode(bufferSize, 0, 1); 
var phase = 0;
node.onaudioprocess = function (event) {
    var data = event.outputBuffer.getChannelData(0);
    for (var i = 0; i < data.length; i++) {
        phase += freq/sampleRate;
	phase = phase >= 1 ? -1 : phase;
        data[i] = phase < 0 ? -1*amp : 1*amp;
    }
};
function freqampCorners(markers){
    var corners,corner;
    corners = markers[0].corners;
    corner = corners[0];
    freq = 8000 * (corner.x / parseFloat(canvas.width));
    amp = corner.y / parseFloat(canvas.height);
}

function onLoad(){
    video = document.getElementById("video");
    canvas = document.getElementById("canvas");
    context = canvas.getContext("2d");
    
    canvas.width = parseInt(canvas.style.width);
    canvas.height = parseInt(canvas.style.height);
      
    navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia;
    if (navigator.getUserMedia){
        navigator.getUserMedia("video", successCallback, errorCallback);
        
        function successCallback(stream){
          if (window.webkitURL) {
            video.src = window.webkitURL.createObjectURL(stream);
          } else {
            video.src = stream;
          }
        }
        
        function errorCallback(error){
        }
        
        detector = new AR.Detector();

        requestAnimationFrame(tick);
    }
    node.connect(audiocontext.destination);
}
    
function tick(){
    requestAnimationFrame(tick);
      
    if (video.readyState === video.HAVE_ENOUGH_DATA){
        snapshot();
        var markers = detector.detect(imageData);
	freqampCorners(markers);
    }
}

function snapshot(){
    context.drawImage(video, 0, 0, canvas.width, canvas.height);
    imageData = context.getImageData(0, 0, canvas.width, canvas.height);
}

window.onload = onLoad;
</script>
</head>
<body>
<center>
  <div>AR-Theremin.html</div>
  <video id="video" autoplay="true" style="display:none;"></video>
  <canvas id="canvas" style="width:320px; height:240px;"></canvas>
</center>
</body>
</html>