音の鳴るブログ

鳴らないこともある

prominence - node.jsコールバックをPromise化するやつ

と、いうのを作りました。

node.jsの非同期APIはコールバックでエラーと結果を受け取りますが、それをPromiseベースのAPIに変換します。

こうやって使う。

var fs = require("fs");
var prominence = require("prominence");

// prominence するとそのオブジェクトで使える全メソッドが Promise 仕様になる
prominence(fs).readFile("hoge.txt", "utf-8").then(function(text) {
  console.log(text);
}).catch(console.error.bind(console));

// こういう書き方もできる
prominence(fs, "readFile", [ "hoge.txt", "utf-8" ]).then(function(text) {
  console.log(text);
}).catch(console.error.bind(console));

// 中でやっていること
new Promise(function(resolve, reject) {
  fs.readFile("hoge.txt", "utf-8", function(err, text) {
    if (err) {
      return reject(err);
    }
    return resolve(text);
  });
}).then(function(text) {
  console.log(text);
}).catch(console.error.bind(console));

prominence というのは凄く書きづらいですね。

類似のやつも紹介しておきます。

画像を点字にする

ごめんな。Windowsでは点字が表示できないんだ。

2年くらい前にこの記事を見て、真似してブラウザで動くやつを作ったのだけどすっかり忘れて放置していた。

僕の書いたのは二値化した単色点字で、元になったほうを改めて見るとクオリティが全然違っててビックリするのだけど、今週になってもうちょっと便利にならんか?という問い合わせをいただいたので、せっかくだしnpmモジュール化しました。(ブラウザのほうは修正するのが面倒そうだった)

こういう感じで使う。

$ seurat image/lena.jpg

f:id:mohayonao:20150310220043p:plain

他にサイズや二値化の具合を調整したり、白黒反転させたりできる。

$ seurat --help

Usage: seurat [options] path/to/image

  -w, --width Number      width(cols) of the converted text
  -h, --height Number     height(rows) of the converted text
  -t, --threshold Number  threshold for binarization
  -i, --invert            invert to negative
  -o, --output String     write the converted text to this file
  -p, --print             print out the converted text
  -v, --version           show version
  --help                  show help

勢いで作ってみたは良いものの問い合わせをくれたのは技術系の人ではなくて node.js が使えないとのことなのでどう連絡しようか悩んでいるところです。

ES6の書き方が定まらない

最近ちょっとしたコードは ES6 で書くようにしているのだけど、慣れていないせいか書き方が定まらない。

例えば ArrowFunction の => の前後にスペースを入れるかどうか

let foo = ()=>{};   // 最初はこうしていたけど
let foo = ()=> {};  // これを経て
let foo = () =>{};
let foo = () => {}; // 結局これになった

ArrowFunction の {} を省略するかどうか

let foo = array.map(x => x * 2); // 単純な演算なら省略する

let foo = array.map((x) => { // メソッド呼び出しが入っていたら
  return x.valueOf();        // {} は 省略しないようにしていたけど
});
let foo = array.map(x => x.valueOf()); // 結局は省略するようになった

// でも(感覚的に)長いときや入れ子になっているときは省略しない
let foo = array.map((x) => {
  return x.slice(10).concat(x.slice(0, 10).reverse().map(x => x.valueOf()));
});
// {} を使うときは引数の () も省略しない (これはまだ悩んでいる)

// 関係ないけど、よく括弧を書き忘れる
let a = aray.map x => x.clone();

export のタイミングとか

class Foo {}
export default Foo; // 最初はファイルの最後にこうしていたけど

export default class Foo {} // 良く考えたらこっちの方が良いと思う
export class Bar {}        // 上の書き方の場合、default 以外が書きにくくなる

上の書き方だとこういうインポートをする羽目になる

import Foo, {Bar} from "./foobar"; // なんか格好悪い

今、気が付いたけどこう書けばよかったのかも

export class Foo {} // export はその場で書く
export class Bar {}

export default Foo; // export default はファイルの最後に書く
// 格好良い
import Foo from "./foobar";
import { Foo, Bar } from "./foobar";

// こっちの方が分かりやすいのかも知れない..
import * as foobar from "./foobar";

あと var と let を使い分けたり..

let a = () => { // 一番外側は let
  var a = 10;   // 関数の最初は var
  {
    let a = 5;  // ブロックの中は let
  }
  return a;
};

// 今は var はいっさい使わない

本当に最初のときはすごく乱暴にこういう感じだった

export default new (class Bar extends require("./foo") {
})();

ES6、こういう感じで書くのが良いよというのがあったら教えてください!!

Promiseの使い方が良く分からなくなってきた

先に言っておくと疑問文で終わる雑な記事です。

Promise、正常系の処理は比較的簡単に書けるのだけど、何か間違いがあったときに Promise 自体のエラーハンドリングが強力すぎて良く分からなくなる。

readFile というファイルを読み込む Promise ベースの良くできた関数があるとして、

読み込んだファイルを処理するとき

readFile("foo.txt").then(function(text) {
  doSomething(text);
});

ファイルが読み込めない場合を想定するとき

readFile("not-found.txt").then(function(text) {
  doSomething(text);
}, function() {
  readFailed();
});

この時、catch を使うのは良くなくて、ファイルの読み込みは成功したのに readFailedが実行される場合が考えられる。

readFile("存在するファイル.txt").then(function(text) {
  doBrokenSomething(text); // ここでエラーが発生する
}).catch(function() {
  readFailed();
});

これを回避するにはこう書く。というか Promise ベースの API を使ったら最後に必ず catch しておく必要があると思う。

readFile("foo.txt").then(function(text) {
  doComplexSomething(text); // ここでエラーが発生するかも知れない
}, function() {
  readFailed();
}).catch(console.error.bind(console));
// ^^ doComplexSomething/ readFailed でエラーがあったとき

と、いうのが Promise の基本的な使い方だと思っているのだけど、、

これだとエラーがあったことは分かるけど、どこでエラー発生したか等の情報がいまいち分かりにくくて、デバッグしづらい。よくよく考えると Promise は非同期で実行されるフローを分かりやすく書くことが目的なので、すべてのおぜん立てが揃った最後の then の中で複雑な処理を書くのがそもそもの間違いな気がする。その段階では Promise のエラーハンドリング機能は邪魔でしかないと思う。Promise の呪縛から逃れるには 大きなスコープ を使うか setTimeout を使うというのが考えられるのだけど、本当にこんなので良いのでしょうか?

// 大きなスコープを使う
var text = null;

readFile("foo.txt").then(function(_text) {
  text = _text;
});

button.on("click", function() {
  if (text !== null) {
    doComplexSomething(text);
  } else {
    readFailed();
  }
});
// setTimeout を使う
button.on("click", function() {
  readFile("foo.txt").then(function(text) {
    setTimeout(function() {
      doComplexSomething(text);
    }, 0);
  }, function() {
    setTimeout(function() {
      readFailed();
    }, 0);
  });
});

追記:

ということをブツブツ書いていたら、有益な情報教えていただきました!!

Promise を返す decodeAudioData をつくった

draft Web Audio API shim ブームということで、先日つくった StereoPannerNode の shim に引き続いて Promise を返す decodeAudioData をつくった。

decodeAudioData というのはオーディオファイルのバイナリ(ArrayBuffer)をAudioBufferに変換するメソッドでサンプリングベースのなんかそういうのをあれこれするときに使います。

今はこういうインターフェースをしている。

callback DecodeSuccessCallback = void (AudioBuffer decodedData);
callback DecodeErrorCallback = void ();

interface AudioContext : EventTarget {
  void decodeAudioData(
    ArrayBuffer audioData,
    DecodeSuccessCallback successCallback,
    optional DecodeErrorCallback errorCallback
  );
}

で、将来的にはPromiseだろうという感じで、古いインターフェースとの互換性を保ちつつ戻り値をPromiseにして、ついでにエラーコールバックのときに引数がつくような変更が予定されています。

callback DecodeSuccessCallback = void (AudioBuffer decodedData);
callback DecodeErrorCallback = void (DOMException error);

interface AudioContext : EventTarget {
  Promise<AudioBuffer> decodeAudioData(
    ArrayBuffer audioData,
    optional DecodeSuccessCallback successCallback,
    optional DecodeErrorCallback errorCallback
  );
}

Promiseベースのインターフェースは古いインターフェースとの互換性があるので今と同じ使い方を続けるというのもありだけど、他の非同期処理と同期をとりたいとき(Promise.all)などPromiseの文脈で書きたいときに、いちいち調べたり自前でラッピングせずに便利に使えます。

使い方

<script src="/path/to/promise-decode-audio-data.js"></script>

これで AudioContext.prototype.decodeAudioData がPromiseベースに上書きされます。

仕組み

こういう感じ。単純にPromiseラッピングして後方互換用のコールバックを登録しているだけです。

var decodeAudioData = AudioContext.prototype.decodeAudioData;
AudioContext.prototype.decodeAudioData = function(audioData, success, error) {
  var audioContext = this;
  // Promiseでラッピング
  var promise = new Promise(function(resolve, reject) {
    decodeAudioData.call(audioContext, audioData, resolve, reject);
  });
  // 後方互換用のコールバックを登録
  promise.then(success, error);
  // Promiseを返す
  return promise;
};

StereoPannerNode の shim をつくった

なんかカンファレンスとかあって一部で非常に盛り上がっているっぽいウェブオーディオ界隈ですが、そろそろ Chrome あたりに StereoPannerNode というのがやってきそうな気配があります。

今のウェブオーディオで音の定位を扱うには PannerNode というのがあるのですが、なぜか3D空間に音を配置するみたいな超高級な仕様で、パンといえばミキサーについているつまみくらいの感覚の一般市民には使いにくかったりします。

で、新しくステレオ定位を扱う StereoPannerNode というのが検討されています。

http://webaudio.github.io/web-audio-api/#the-stereopannernode-interface

interface StereoPannerNode : AudioNode {
  readonly attribute AudioParam pan;
};

見てのとおりインターフェースは単純で、pan 属性があるのみ。-1 で左、0 で真ん中、+1 で右という按配です。 なるほど分かった簡単だし便利そうだ。ですが、代替できる仕組みがないとすべてのブラウザに実装されるまで使いたくないというのもあるので、今のウェブオーディオだけで作ってみた。

使い方

<script src="/path/to/stereo-panner-shim.js"></script>

これで AudioContext.prototype.createStereoPanner が使えるようになります。

var audioContext = new AudioContext();
var audioElement = document.getElementById("audioElement");

var mediaSource = audioContext.createMediaElementSource(audioElement);
var autoPanRate = audioContext.createOscillator();
var autoPanAmount = audioContext.createGain();
var stereoPanner = audioContext.createStereoPanner(); // これが新しく使える

autoPanRate.frequency.value = 0.05;
autoPanRate.start(audioContext.currentTime);

mediaSource.connect(stereoPanner); // 普通に接続して使います
autoPanRate.connect(autoPanAmount);
autoPanAmount.connect(stereoPanner.pan);
stereoPanner.connect(audioContext.destination);

仕組み

既存のオーディオノードをごちゃごちゃ組み合わせてやっています。詳細はここの README を参照してください。

mohayonao/stereo-panner-node · GitHub

JSタイマー三種盛りとそのインターフェース

最近タイマーAPIを書くブームが来ていて、とりあえず3つほど書いたので順番に紹介していきます。

ブラウザのタイマーはタブの裏に入ると精度が悪くなる。それを解消する方法として WebWorker でタイマーを作動させてメッセージ送信→コールバック関数を実行するやり方がある。ウェブオーディオとか見えなくてもちゃんと処理してほしい時に使える。

setInterval で 100 を指定すると、だいたい 100ms 単位でコールバック関数が呼ばれるのだけど、これは 100ms ± 10% くらいのアバウトな間隔でコールバックされるやつ。"酔う"感じとかは設定で変えられる。

sinon の FakeTimer などのように、手動で時間を進められるやつ。テストに使う。


いずれもネイティブと同じインターフェースで setInterval とか setTimeout を呼んで使う。例えば、n回タイマー処理をしたい場合、以下のようなインターフェースにしておくと、状況に応じてタイマーを切り替えられるという算段です。

var nTimesInterval = (n, callback, delay, timerAPI = global) => {
    var count = 0;
    var timerId = timerAPI.setInterval(() => {
      callback(count++);
      if (count >= n) {
        timerAPI.clearInterval(timerId);
      }
    }, delay);
};

// 普通のとき
nTimesInterval(10, callback, 100);

// バックグラウンドでも絶対に実行したいとき
nTimesInterval(10, callback, 100, WorkerTimer);

// ぐだぐだな感じにしたいとき
nTimesInterval(10, callback, 100, DrunkTimer);

// テストのとき
nTimesInterval(10, callback, 100, TickableTimer);

APIを引数で受け取ると良いというのは前回書いた記事と同じ。

乱数を使う関数のテスト - 音の鳴るブログ

新しいタイマーを作るぞ!というときにオブジェクティブな感じで new して start / stop みたいなインターフェースにするのではなくて、ネイティブのに合わせておくと使用性が上がる。

具体的には関数名を同じにするのと、出来たらメソッドでなくて関数にする。例えば乱数を生成するのは Math.random と同じように random という名前でアクセスできて、fn = foo.random; fn() ができた方が良い。