Quantcast
Channel: 音楽方丈記
Viewing all articles
Browse latest Browse all 339

iPhoneを振ってJavaScriptから加速度を検知してスレイベルを鳴らすサウンドWebアプリ [改良版]

$
0
0
ここんとこずっと時事ネタばかりで、自分発のネタをほとんど書いてなかったということで(といっても使いまわしなんですが)、去年作ったサウンド Web アプリ Sleigh Bells の改良版を公開します。

iOS 6以降の iPhone/iPod touch/iPad の Mobile Safari で動きます。
Mobile Safari 以外や Android では未確認。もしかしたら動くかもしれません。



 iPhone を振ってスレイベルをシャンシャン鳴らすだけの超シンプルなサウンド Web アプリで、JavaScript から Web Audio API と加速度センサーの値を取得する DeviceMotionEvent を使用しています。

スレイベル (Sleigh Bells)とは?

去年の記事と同じくまずはスレイベルの説明から。
スレイベルは木製の棒に鈴がたくさんついたパーカッションの一種です。
(輪っかに鈴がついた小型のタイプもあります)
名前や形は知らなくても、クリスマスの鈴の音といえば分かる人も多いのでは?



Sleigh Bells for iPhone の使い方


     

←左の QR コードをタップするか、iPhone のカメラで読み取って Sleigh Bells にアクセスしてください。
短縮 URL: http://goo.gl/72A8fF


使い方はいたってシンプルです。
  • まず「Sound Preview」ボタンで iPhone の音量を確認・調整してください。
  • iPhone を握って手首のスナップをきかせて振ってみてください。
  • 反応が悪い場合は振る速度を速くするか感度を低くしてみてください。
  振るときに iPhone がすっぽ抜けないように十分注意してください。
  (万が一 iPhone を落として壊してもこちらは責任は負いません)

プログラムの内容について

プログラムの処理内容に興味がない人はここから先はスルーしてください。
中身は去年作ったものとほぼ同じでサンプルレベルの短いものですが、細かい部分に少しだけ改良を加えました。

 UI 部分はお手軽な jQuery + jQuery UI の組み合わせで、タッチデバイス上のスライダーハンドルのドラッグ操作に対応するため jQuery プラグインの jquery-ui-touch-punch を利用しました。(ここは去年と同じ)

 今回は見た目も少し良くしようということで、MEDIALOOT で配布されているカスタマイズテーマの jQuery UI Theme: Retro のフリー版を元に、さらに少しだけカスタマイズを施しました。


 あと、特に必要というわけではないのですが、画面左上のアイコンをタッチすると画面が右にスライドして QR コードと短縮 URL を表示するようにしてあります。
 この部分の動作は jQuery プラグイン mmenu を利用しました。

↓JavaScript のサウンド関連部分だけ抜粋
var soundfile = 'sleigh-bells.mp3';
var locktime = 200;
var outputgain = 0.5;

var context = null;
var buffer = null;
var threshold = 0;
var valuemax = 3000;
var flashing = false;
var playing = true;

// Audio コンテキスト生成と音声ファイルの読み込み
try{
  var AudioContext = window.AudioContext || window.webkitAudioContext;
  context = new AudioContext();
  var request = new XMLHttpRequest();
  request.open('GET', soundfile, true);
  request.responseType = 'arraybuffer';
  request.onload = function(){
    context.decodeAudioData(request.response, function(data){
      buffer = data;
    });
  }
  request.send();
}catch(e){
  context = null;
}

// サウンド再生
function playSound(){
  if(playing) return;  // ロック中はキャンセル

  // 2度鳴り防止のロック時間を設定
  playing = true;
  window.setTimeout(function(){ playing = false; }, locktime);

 // サウンド再生
  var output = context.createGain();
  output.gain.value = outputgain;
  output.connect(context.destination);

  var src = context.createBufferSource();
  src.buffer = buffer;
  src.connect(output);
  src.noteOn(0);
}

// しきい値(感度)の設定
function setThreshold(value){
  threshold = value;
  var text = ["Sensitivity: ", parseInt(value / valuemax * 100), '%'].join('');
  $("#threshold-value").text(text);
}

$(function(){
  setThreshold(valuemax / 2);   // しきい値(感度)の初期値

  // Device Motion のイベントハンドラー指定
  $(window).on("devicemotion", function(e){
    if(Math.floor(e.originalEvent.acceleration.x * 100) > threshold){
      playSound();
    }
  });

  // Preview Sound ボタンのクリックイベント指定
  $("#preview").button().click(function(){
    playing  = false;
    playSound();
  }).focus();

  // しきい値(感度)のスライダーイベント指定
  $("#threshold-slider").slider({
    min: valuemax / 10, max: valuemax, range:'min',
    value: threshold,
    slide: function(event, ui){ setThreshold(ui.value); }
  }).draggable();
});

加速度センサーのイベント DeviceMotionEvent

iOS はバージョン 4.2 から加速度センサーの値を取得できる DeviceMotionEvent が追加されていて、JavaScript からイベントハンドラを指定してリアルタイムに現在値を取得することができます。

 » Safari Developer Library - DeviceMotionEvent Class Reference

window.addEventListener("devicemotion", function(e){
  // 傾きの加速度
  var accX = e.acceleration.x  // 左右方向
  var accY = e.acceleration.y  // 前後方向
  var accZ = e.acceleration.z  // 上下方向
  // 傾きの重力加速度
  var grvX = e.accelerationIncludingGravity.x
  var grvY = e.accelerationIncludingGravity.y
  var grvZ = e.accelerationIncludingGravity.z
  // 回転の加速度
  var rotZ = e.rotationRate.alpha  // z (上下方向)
  var rotX = e.rotationRate.beta    // x (左右方向)
  var rotY = e.rotationRate.gamma // y (前後方向)
});

// jQuery の .on(), .bind() で使う場合はプロパティの前に originalEvent を付加
$(window).on("devicemotion", function(e){
  var accX = e.originalEvent.acceleration.x
});

 Sleigh Bells では X 方向の加速度変化 (acceleration.x) だけを利用しています。X 方向は iPhone 本体正面から見て左右に動かしたときの移動で左方向がマイナス、右方向がプラスの値になります。
 今回の場合は握ったときに本体側面が対面になるので、前後に振る動作で変化する X 軸の値を利用します。

 指定した加速度に達したときに playSound() を呼び出して音を鳴らします。スレイベルを振る演奏操作的には振出しの動作と引きの動作の途中は鳴らずに、手前に強く押し出して止めたとき(加速度がマイナスからプラスの最大値に変化するとき)だけ鳴ればよいので、プラス方向でしきい値を超えたときだけ反応するようにしてあります。

 前回のバージョンでは、一振りで「シャシャン」と続けてトリガーされる2度鳴りが起こることがあったので、新たにトリガーロック時間を設けて指定時間内にトリガーがあった場合無視するようにしました。

Mobile Safari の Web Audio API

サウンドの再生は前回と同じく Mobile Safari で利用できる WebKit の Web Audio API を利用しています。

 去年初版を作ろうとした当初は HTML5 標準の Audio オブジェクトを使おうと思っていたのですが、Mobile Safari は Audio オブジェクトの play() を呼び出すたびに毎回律儀に音声ファイルをサーバーからロードしてしまって、再生が開始されるのが遅すぎて当初想定したレスポンスが得られないことが分かったので、今回も Web Audio API を採用しました。

 Safari Developer Library - Playing Sounds with the Web Audio API

 鈴の音の mp3 ファイルを Ajax 経由でバッファに読み込んでおいて、指定のタイミングで playSound() を呼ぶだけのシンプルな作りです。

// Audio コンテキスト生成と音声ファイルの読み込み
try{
  var AudioContext = window.AudioContext || window.webkitAudioContext;
  context = new AudioContext();
  var request = new XMLHttpRequest();
  request.open('GET', soundfile, true);
  request.responseType = 'arraybuffer';
  request.onload = function(){
    context.decodeAudioData(request.response, function(data){
      buffer = data;
    });
  }
  request.send();
}catch(e){
  context = null;
}

function playSound(){
  // ノードのルーティング
  var output = context.createGain();
  output.gain.value = outputgain;  // 出力ゲイン 0~1
  output.connect(context.destination);

  // バッファに読み込んだサウンドの再生
  var src = context.createBufferSource();
  src.buffer = buffer;
  src.connect(output);
  src.noteOn(0);
}

 あと、去年作っていて分かったことは、Mobile Safari は他のブラウザとちがって Web Audio API の再生はユーザーが画面上のボタンを押すなどの任意に画面操作を行うまでサウンドの再生が許されないという仕様で、ページロード後にいきなりサウンドを再生しても反応しない(オートプレイを禁止する)ようになっているということでした。

Apple Developer Library - Playing Sounds with the Web Audio API より抜粋

Note: On iOS, the Web Audio API requires sounds to be triggered from an explicit user action, such as a tap. Calling noteOn() from an onload event will not play sound.

 DeviceMotionEvent 経由で再生させる場合もこれ該当していて、ページロード直後に noteOn() を呼んでも反応しなかったため、苦肉の策として音量確認用という名目でプレビューボタンを設けました。
 一度任意で再生されると、後は Device Motion イベント経由でも再生できるようになります。

その他 Tips

 スライダーのようなドラッグ対応のコントロールを操作していると、まれに意図せず範囲選択状態になって鬱陶しいので、CSS に以下の宣言を追加して Mobile Safari の範囲選択を無効にしました。
* {
  -webkit-user-select: none;
}

 ページが画面内収まっている状態で余分なスクロールの押し戻しを無効にしたい場合は、HTML もしくは JavaScript で touchmove イベントを無効にします。
// HTML の場合
<body ontouchmove="event.preventDefault()">

// jQuery の場合
$(window).on("touchmove", function(e){ e.preventDefault(); });


ソースコードはサンプルレベルの短いものですが、JavaScript だけでもアイディア次第でこんなことができるサウンド Web アプリの一例として公開してみました。

他にもいくつか別のネタも作ってあるので後日公開予定です。

試すときはくれぐれも iPhone を落とさないように注意してください。

[関連サイト]
 jQuery
 jQuery UI
 jquery-ui-touch-punch
 MEDIALOOT - jQuery UI Theme: Retro
 mmenu
 Web Audio API
 Safari Developer Library




Viewing all articles
Browse latest Browse all 339

Trending Articles