0

Vibration APIでモールス信号を聞く

navigator.vibrate(ミリ秒)というのがあったので、モールス信号を再生してみた。
モールス信号ってかっこいいから覚えようとした事が何度かあったけど、まったく覚えれなかった。これなら覚えれそうな気がする。

デモ

http://shokai.github.io/web-morse-vibration/

トンを100ミリ秒振動、ツーを300ミリ秒振動として実装してる

ソースコード

https://github.com/shokai/web-morse-vibration



とりあえずモールス信号のローマ字(A-Zまで)だけ対応してる。


Vibration APIとは

ブラウザで端末をバイブレーションできる。AndroidのChromeで動いた。

Vibration API – Web developer guide | MDN
ボンバーマンとか、ゲーム用にあれば便利じゃね、とのこと

使い方

navigator.vibrate(100); // 100ミリ秒振動
navigator.vibrate([100,200,300]); // 100ミリ秒振動、200ミリ秒停止、300ミリ秒振動

navigator.vibrate(0); // 全ての振動止める

Mac版Chromeにもnavigator.vibrateあって呼び出せるけど、もちろん振動しない。ウィンドウが揺れればいいのでは

0

AndroidタブレットでWeb MIDI API

前:Node.jsとMIDIコントローラでHueを調光する

音楽やる気はないけどMIDIデバイスは入出力デバイスとして面白い。スライダーとかツマミとか色々あるし、MIDIで制御できるドラム叩きロボットとかもある。
とりあえず電子工作なしで買ってきたデバイス接続するだけでPCでもスマホでもWebブラウザから使えるというのは面白い。


Web MIDI API

Web MIDI APIはブラウザでMIDIデバイスが使えるHTML5のAPIで、現在はGoogle Chromeだけが対応している。
chrome://flags でWebMIDIを有効にし、MIDIデバイスを接続してからChromeを再起動する必要がある。
Chrome起動後にMIDIデバイスつなげても認識しないので注意。


試した

http://shokai.github.io/WebMidiAPIStudy/


Android版Chromeも対応しているので、USBホストケーブルでNexus7に接続して試してみた。
流れてきたデータを全部表示するだけだけど、Nodeのmidi npmで試した時とほぼ同じ手順でMIDIデバイスに接続できる。流れてくるデータもnode版はArrayでWebMIDIはUInt8Arrayなだけなので、MIDIデバイス用のラッパーライブラリ作るとしたらブラウザ・Nodeの両対応は簡単そう。



Mac版Chromeでも動く。Android版では取得できなかったデバイスのmanifacturerとnameプロパティがちゃんと取れた。


実装

https://github.com/shokai/WebMidiAPIStudyにある。

requestMIDIAccessはPromiseで使う
return if typeof navigator.requestMIDIAccess isnt 'function'

navigator.requestMIDIAccess()
.then (midi) ->
return new Promise (resolve, reject) ->
console.log "#{midi.inputs.size} input devices"
console.log "#{midi.outputs.size} output devices"
it = midi.inputs.values() # inputデバイスのリストがiteratorでくる
loop
input = it.next()
break if input.done
return resolve input.value # デバイス発見した
reject "input device not found"

.then (input) ->
console.log "Input Device found: #{input.manufacturer} - #{input.name}"
input.onmidimessage = (msg) ->
console.log msg.data # UInt8Arrayでデータが読める

.catch (err) ->
console.error err


気づいたこと


充電できない

AndroidではMIDIデバイスを使っているとUSBポートが埋まるので充電できない。Qiでなら充電できるかと思ったけどNexus5とNexus7ではMIDIと同時に充電は無理だった。


複数アプリから1つのMIDIデバイスを使える

Macでは複数のブラウザウィンドウで同時に1つのMIDIデバイスからのデータが読めた。またブラウザと同時にNode.jsからも使えた。
Macでは /System/Library/Frameworks/CoreMIDI.framework/MIDIServer が仲介しているっぽい。


参考になった

0

色々なPromiseライブラリで1つずつ順番に処理する

最初、Qでなぜかcatchでエラーが捕まえられないぞ?と書いていたけど、require(‘q’).Promiseを使うんだと教えてもらった。ありがとうございます。



色々なNodeのライブラリがそれぞれで好きなPromiseライブラリ使ってるけど、それらのライブラリ達をいっしょにアプリ内で混ぜてつなげても大丈夫なのか?ちゃんと相互にthenでチェインしたりエラー捕まえたりできるのか?
という事と、async.eachSeriesのようにURLのリストを1つずつ順番に処理完了するのを待ちながら処理していくのはPromiseでどうやって書くのかな?
というのが気になっていたのでちょっと調べた。


勉強用リポジトリ
https://github.com/shokai/promise-study

環境

node v0.10.29 + coffee-script v1.8.0
なのでまだPromiseが標準ライブラリに入ってないNode環境


試したPromiseライブラリ

このへんが有名そうだったのでREADMEやサンプルなどを読んで、試した。
でもdeferredはインタフェースがthenableじゃないっぽかったのですぐあきらめた。


結論

es6-promiseとbluebirdとQを混ぜこぜでthen/catchでチェインさせてもちゃんと動いた。

then/catchしたいだけならes6-promise使って、
async.jsでやるような高機能な並列・並行処理の制御がしたければbluebirdやQに付いている便利な関数を使えばいいと思う。


試したコード
## いろいろなPromiseライブラリを使ってみる

{Promise} = require 'es6-promise'
# {Promise} = require 'q'
# Promise = require 'bluebird'

checkOdd = (num) ->
return new Promise (resolve) ->
if typeof num isnt 'number'
throw new Error "#{num} is not number"
resolve num % 2 is 1

for i in [0,1,2,3,null,5,"かずどん",7,8]
do (i) ->
checkOdd i
.then (res) ->
if res
console.log "#{i} is odd"
else
console.log "#{i} is not odd"
.catch (err) ->
console.error err

実行結果


色々なPromiseライブラリをつなげる、1つずつ処理する

HTTP Getして、HTMLからtitleを取り出して、それをmacのsayコマンドで読む。
という3つのPromiseをそれぞれ別々のPromiseライブラリで作る。
そして3つ繋げて1セットの処理として、URLリストを順番に処理していく。
RSSを順番に見ていくクローラー的な処理を想定していて、1つ終わったら3秒待ってから次のURLを見に行くようにした。

試しに書いてみたコード
ちなみにURLリストを全部同時に処理するversionもある


下の方でBluebird.eachを使って1つずつURLを処理していく。
thenのチェインが最後まで走ったら3秒待ってから次のURLを処理するし、どこか途中でエラーが起きたらcatchして5秒待って次のURLの処理に行く。
とにかく同時に複数のHTTPリクエストは送らない。

こういうのをasync.eachSeriesで書くとけっこう頭が疲れるコードになると思うけどPromise使ったらすんなり書けた。

es6-promiseとbluebirdとQを混ぜて使っているけどちゃんと動いた。

## HTMLを(1つずつ)取得してtitleを取り出してsayで読み上げる
## いろいろなPromiseライブラリを混ぜて使ってみる
## HTTPリクエストするのは3000 msecごと
## 途中でエラーがあったら5000 msec待ってから、次のHTTPリクエストする

request = require 'request'
cheerio = require 'cheerio'
{exec} = require 'child_process'

process.env.DEBUG ||= '*'
debug = require('debug')('promise-study')


{Promise} = require 'es6-promise'
Q = require('q')
Bluebird = require 'bluebird'

urls = [
'http://shokai.org'
'そんなURLはない' # URLじゃない文字列。requestの例外を発生させるため
'https://github.com'
'https://github.com/robots.txt' # HTMLが返ってこないURL。titleタグ取得のエラーを起こすため
'https://google.co.jp'
]

# HTML本文を取得するPromise
# URLが間違っていたりすると失敗する
getHtml = (url) ->
debug "getHtml(#{url})"
return new Q.Promise (resolve, reject) -> # Qを使う
request url, (err, res, body) ->
if err or res.statusCode isnt 200
return reject(err or "statusCode: #{res.statusCode}")
resolve body

# HTMLからタイトルを取得するPromise
# HTMLじゃなければ失敗する
getTitle = (html) ->
debug "getTitle(html)"
return new Bluebird (resolve, reject) -> # Bluebirdを使う
$ = cheerio.load html
if title = $('title').text()
return resolve title
reject 'title not found'

# 音声読み上げするPromise
speech = (txt) ->
debug "speech(#{txt})"
return new Promise (resolve, reject) -> # es6-promiseを使う
exec "say #{txt}", (err, stdout, stderr) ->
return reject(txt) if err
resolve(txt)

# URLリストをBluebird.eachで1つずつ処理する
Bluebird.each urls, (url) ->
getHtml url
.then getTitle
.then speech
.then (title) ->
return new Q.Promise (resolve) -> # Qを使う
debug "wait 3000 msec"
# 3秒待ってから次のURLの処理へ
setTimeout ->
debug "wait done"
debug "!!OK #{url} - #{title}"
resolve()
, 3000
.catch (err) ->
return new Promise (resolve, reject) -> # es6-promiseを使う
debug "!!ERROR #{url} - #{err}"
debug "wait 5000 msec for Error"
# どこかでエラーあったら5秒待つ
setTimeout ->
debug "wait done"
resolve()
, 5000


実行結果

ちゃんと3秒/5秒待って次、と順番に処理できている。
requestにURLじゃない文字列を渡した時の例外も、try catch書かずにpromiseのcatchで捕捉できている。

0

ブラウザでtext to speech

ブラウザでテキストの音声読み上げができるというので試した。

http://shokai.github.io/text-to-speech-on-browser/



動いた / 動かない

Mac OSXとAndroid4.4とiOS7のSafariとChromeでは日本語が読み上げできた。
iOSはwebview(twitterアプリの内蔵ブラウザで試した)でも読み上げできたが、androidはwebviewでは無理っぽい。

古いOSは知らない。windowsだとどうなんだろう?

Firefoxには音声読み上げAPIが無い。


参考



コード

main.coffee
say = (str) ->
unless SpeechSynthesisUtterance
console.log "your browser does not support text-to-speech API"
return

msg = new SpeechSynthesisUtterance
voices = speechSynthesis.getVoices()
jp_voice = _.find voices, (v) -> /ja[-_]JP/.test v.lang

if jp_voice
console.log "say #{str} (#{jp_voice?.name})"
else
console.log "say #{str}"

msg.voice = jp_voice
msg.text = str
speechSynthesis.speak msg


$ ->
$("#btn_say").on 'click', ->
say $('#text').val()

say 'こんにちは'


細かいこと


voice プロパティ

Macはvoiceプロパティを設定してあげないとうんともすんとも言わない。getVoices()で取得して声を指定する。
逆にスマホ(android/iOS)だと、voiceプロパティは設定しなくても日本語をしゃべってくれた。英語とか日本語以外を喋らせるにはvoiceプロパティの設定が必要。

Macのchromeで1回目喋れない

なぜか1回目はgetVoices()が空配列を返してくる。
Macのsafariは問題ない。

iOSはユーザー操作後でないと読み上げできない

iOSはボタンを押すなどユーザー操作のイベントからでないと音声再生しない。

下の様にsetIntervalで喋らせた場合、何も操作しなくてもsay関数が呼ばれる。getVoicesもspeechSynthesis.speakも動くのに、音声が出ない。
一度でもボタン “#btn_say” の方を押せばsetInterval内の「こんにちは」も発声されるようになる。
$ ->
$("#btn_say").on 'click', ->
say $('#text').val()

setInterval ->
say 'こんにちは'
, 1000

0

lodash.throttleとarduinoで5秒に1回ドアのカギを開ける

throttleは「UIイベントの過剰な発火を間引く」とか説明されるけど、Arduinoなどのデバイスを使う際に「アクチュエーターが動いてる間命令を受け付けないようにする」のにも使えるよ
という話。


参考

javascriptで発生するイベントを間引く – 終わる世界とコンテンツ
lodashとunderscoreにあるthrottle・debounceという関数について説明している。
関数をthrottoleやdebounceに渡すと、無駄な連続呼び出しを無視するようにしてくれる。
実装を見るとthrottleはdebounceのラッパーになっている。


5秒に1回

nodeで制御してるarduinoでサーボモーターを回してドアのカギ(サムターンキー)を開けてるんだけど、連続でカギ回せ命令が来たら無視したい。

今までDate.now()で最後の呼び出し時刻を保存したりしてた。

lodashのthrottleで1つ目の命令だけ通して、以降5秒間は命令を無視するにはこうする。5秒たったらまた命令を受け付ける。
door_open = (onComplete = ->) ->
arduino.servoWrite 9, 0 # 開けて
setTimeout ->
arduino.servoWrite 9, 180 # 閉める
onComplete()
, 2000

door_open_throttled = _.throttle door_open, 5000, trailing: false

on '開けろや', ->
door_open_throttled ->
console.log "開けました"

{trailing: false}というオプションをthrottleに渡さないと、スロットルが閉じた時に最後の命令も通してしまう。
つまりカギ開けろ命令がたくさん来たら2個通してしまう。
trailing: falseすると最初の1つだけ通る。


nodeでのarduino制御にはarduino-firmata npmを使ってる。