kanejaku.org

夕立

17 Sep 2018

午後六時半。九月も半ばを過ぎて辺りはもう真っ暗になっている。

先ほどから雷音と一緒に強い雨が降ってきた。あ、夕立だ、と思った。

夕立という言葉は季節感があって好きだ。最近よく耳にするゲリラ豪雨という言葉には無い含意がある。

夕立というには陽もとっくに暮れているけれど、自分にとってはこれは夕立。

Splatoon2 の漢字フォント

29 Jul 2018

スプラトゥーン2は1に引き続きフォントを自作しているそうだ。

スプラトゥーンのイカしたUIはこうして作られた─担当デザイナーが語る秘話 | 超ゲームウォーカー!

ゲームによくマッチしているし、特徴的でかっこいいフォントだと思う。

自作といっても全部作るのはさすがに無理っぽくて、漢字は既存のフォントを使っているそう。

採用情報:仕事を読み解くキーワード - 世の中にないフォントを作る

じゃあ漢字部分のフォントは何だろう、と思って調べてみた。

スプラトゥーン2の公式アプリであるイカリング2にアクセスすると、Web フォントが二つロードされる。78 KBのフォントと 587 KBのフォント。サイズから言って後者のフォントが漢字を含んでいるフォントだろう。後者のメタデータを見ればフォント名が分かるんではないか。

サイズの大きいフォントをshowttfで見てみる。NAME テーブルは見づらいので CFF テーブルのほうを見るとこんな感じだった。

...
Dump of top dictionary for KurokaneStd-EB
 Version=0 .notdef
 Notice=393 Copyright 2008-2013 Fontworks Inc. All Rights Reserved.
 fullname=394 KurokaneStd-EB
 familyname=395 KurokaneStd
 weight=396 EB
...

というわけで、漢字の部分はフォントワークスの「くろかね EB」というフォントかな?

くろかね EB|書体見本|FONTWORKS | フォントワークス

ちなみに前者のフォントには「Splatoon2」という名前がついていた。ひねりなしの素直な名前。

最後にお約束事だけど、商用フォントの不正利用はご法度です。

世界時計 Web アプリ

13 Jul 2018

ちょっと前から iPhone / Android みたいな世界時計の Web 版があるといいな、と思って作っている。

https://tokei.kanejaku.org

たまに海外に出ることがある。海外にいると大抵今日本は何時だっけ、ええと、となる。日本にいるときも海外のある都市の時間を知りたくなるときがある。

これまではスマホの世界時計を使って知りたい場所の時間を調べていた。だけど PC 上で作業しているときにスマホを取り出すのは PC 世代の自分には若干かったるい。ぱっとブラウザのタブを開いて興味のある都市の時間を調べたい。

要件定義は次の 3 つ。

  1. メインの時計は現在位置の時間を表示する。
  2. 任意の都市の現地時間を追加/削除できるようにする。追加する際は都市名で検索できるようにする。
  3. 追加した都市の時間は再度タブを開いたときも表示する。

1 は JS の API を使うだけ。3 もブラウザが提供する localStorage / IndexedDB で実装できる。2 に関しては Google Place API を使うことにした。

とりあえず欲しいものはできたけど、もう少しいじろうと思っている。レイアウトの調整と直感的でない操作の改善がひとつ。あとは React や Vue みたいなフレームワークを使ってみる、とか。

Emscripten Modularize

4 Jul 2018

Emscripten で出力した JS/wasm を Web アプリの一部として組み込む場合、これらのリソースの読み込みをできるだけ遅延させたいことがある。例えばユーザーがあるボタンをクリックしたときのみ wasm が提供する機能を使いたいとする。ボタンが押されるまでは wasm の読み込みは避けたい。

Emscripten はデフォルトでは Web アプリに組み込んだり遅延ロードさせるにはあまり適さない JS/wasm を出力する。Module オブジェクトはシングルトンとして生成されるし、wasm の場所もランタイムの JS と同じパスにあると仮定されてしまう。後者はランタイムの JS を読み込む前に適宜 locateFile とかを設定すればよいのだけど読み込み順序に依存するコードはアプリに組み込むには使いづらい。

Emscripten のフロントエンドである emcc に -s MODULARIZE=1 を渡すと Web アプリに組み込みやすい形で JS/wasm を出力してくれるようになる。このオプションを渡すと Module はシングルトンオブジェクトではなくコンストラクタ関数として生成される。これにより JS/wasm の読み込みタイミングと Module オブジェクトの生成タイミングを分離できて、例えばランタイムの JS は他の JS とバンドリングしておいて、wasm のロードは後で行う、といったことが可能になる。

以下は -s MODULARIZE=1 で生成した wasm をボタンが押されたタイミングでロード/実行する例:

function onWasmBinaryReady(wasmBinary) {
  return new Promise(resolve => {
    let mod = null;
    const args = {
      wasmBinary: wasmBinary,
      onRuntimeInitialized: () => {
        resolve(mod);
      }
    };
    mod = new Module(args);
  });
}

const wasmPath = "a.out.wasm";

const button = document.getElementById("button");
button.addEventListener("click", () => {
  fetch(wasmPath)
    .then(res => res.arrayBuffer())
    .then(buf => new Uint8Array(buf))
    .then(wasmBinary => onWasmBinaryReady(wasmBinary))
    .then(mod => {
      // Call mod.ccall(...)
    });
});

WebAssembly でフォントコンバータを作った

24 Jun 2018

Wasm で何かウェブアプリでも作ろうと思い、フォントのコンバータを作った。OpenType, WOFF, WOFF2 を相互に変換できる。

https://kombu.kanejaku.org/

WOFF2 のデコード/エンコードに google/woff2 を Emscripten で wasm 化したものを使っている。

大きめのフォント、例えば NotoSansCJKjp とかを WOFF2 に変換すると手元のマシンだと 2 分くらいかかる。最初は Emscripten のランタイムか wasm のオーバーヘッドが大きいのかと邪推したけど、WSL 上でコンパイルしたネイティブの woff2_compress でも 1 分ぐらいかかることが判明した。google/woff2 のエンコード自体があまり大きいフォントに最適化されていないようだ。

Wasm のサイズは-Ozを指定してコンパイルすると 925KB になった。Emscripten のランタイムを含む JS のほうは 26KB。このくらいならなんとか実用に使えるサイズ感な気がする。ランタイムのほうは使っていない部分が結構あるのでもっと小さくできそう。

こまごまとしたアップデートを今後やっていこうと思う。PWA 化とかしてみたい。

Audio Worklet で遊ぶ

2 Jun 2018

元ネタ:ドラクエで学ぶファミコン音色の時間変化

この動画で解説されている呪文の効果音を AudioWorklet を使って生成してみた。エンベロープの計算など細かいところは省いたがそれっぽく聞こえると思う。

コードは Github に置いた。

Chrome Web Audio Samples に AudioWorklet のサンプルがいくつかあるけど、ソースノードを作る例があまりない。この記事では入力を受け取らない、ソースノードを書くときに調べたことをちょっと紹介してみようと思う。

AudioWorklet

AudioWorklet は JS で書かれた音の生成ロジックをオーディオスレッド上で実行してしまおう、というもの。メインスレッド上で実行される ScriptProcessorNode が持っていた問題点を解決する目的で導入された。最終的に音を生成するのはオーディオスレッドなので、その上で JS 走らせられれば遅延や同期ずれを軽減できるんじゃないか、というのがアイデア。

AudioWorklet を使うには独自の AudioWorkletNode と AudioWorkletProcessor を定義しなければならず、ScriptProcessorNode と比較して書くのがかったるい。だけど、オーディオグラフ (AudioNode たちをつなげたもの) はメインスレッドの実行コンテキスト上で管理されるから、「オーディオスレッド上で動くロジック」をメインスレッド側で表現するノードが必要になる。その、メインスレッド側でオーディオグラフを構成する役割をもつのが AudioWorkletNode であり、その内部処理を担うのが AudioWorkletProcessor となる。

方針

今回作るのはある種のカスタム OscillatorNode みたいなもの。基本は OscillatorNode で作れる矩形波なのだけどファミコンの矩形波が持つ3つのモードを周期的に切り替えるような波形を作りたい。だとしたら OscillatorNode とできるだけ使用感を統一できるとよさそう、という方針を立てた。

OscillatorNode は生成する波の周波数を frequency パラメータで受け取る。frequency パラメータの型は AudioParam であり、これは時間経過とともに変更できるパラメータとなっている。また、OscillatorNode は start() と stop() メソッドを持っていて、開始と終了のタイミングを指定できる。

これらの振る舞いに似せた AudioWorketNode/AudioWorkletProcessor を作る。

周波数を設定する

OscillatorNode に周波数を指定する時のコードはこんな感じになる。

// |osc| is an OscillatorNode
osc.frequency.value = 440; // A4, 440Hz

frequency は AudioParam の一種で時間経過とともに変わりうるパラメータ。例えば frequency.setValueAtTime(880, t) を設定すれば時刻 t が来たタイミングで周波数が440Hzから880Hzに切り替わる。これと似たようなパラメータを AudioWorklet で定義したい。

時刻とともに変化するパラメータを定義するには AudioWorkletProcessor に parameterDescriptors() を指定する。

// processor.js
class FooWorkletProcessor extends AudioWorketProcessor {
  static get parameterDescriptors() {
    return [
      { name: 'frequency', defaultValue: 440 }
    ]
  }
}

上記のように定義するとメインスレッド側から以下のように渡すことができて、

// main.js
node.parameters.get('frequency').value = 220; // A3, 220Hz

プロセッサ側では 音を生成するときに呼ばれる process() の parameters 引数から参照することができる。

// processor.js
class FooWorkletProcessor extends AudioWorkletProcessor {
  process(inputs, outputs, parameters) {
    let output = outputs[0][0];
    for (let i = 0; i < output.length; i++) {
      const freq = parameters.frequency[i];  // <-- 220
    }
  }
}

時間と共に変わるのを表現するため、parameters.frequency は出力バッファのサイズと同じ数を持った配列としてわたってくる。

start() と stop()

Web Audio API で定義されている Audio Node のいくつかは AudioScheduledSourceNode を継承していて、OscillatorNode もその一つ。このインターフェースが start() と stop() を提供しているが、AudioWokrletNode はこれを継承していない。その代わりに MessageChannel を使って同等の機能を提供してね、というのが AudioWorklet の設計者の意図らしい。

MessageChannel は実行コンテキストが異なるオブジェクト間での通信手段を提供するもの。AudioWorkletNode と AudioWorkletProcessor はそれぞれメインスレッドとオーディオスレッド上で動くので、インタラクティブに音の生成を制御する場合には MessageChannel が必要になる。AudioParam も動的に変化する情報を渡すのに使えるけれど、こちらは例えばユーザが停止ボタンを押したら音の生成を止める、みたいな用途には向かない。

AudioWorkletNode/AudioWorkletProcessor はそれぞれ MessageChannel のエンドポイントである MessagePort を持っていて、一方の MessagePort から postMessage() するともう片方の onmessage ハンドラで受け取ることができる。

// main.js
class FooWorkletNode extends AudioWorkletNode {
  start(when) {
    this.port.postMessage({ action: 'start', when: when });
  }
}

// processor.js
class FooWorkletProcessor extends AudioWorkletProcessor {
  this.port.onmessage = (e) => {
    if (e.data.action === 'start') {
      // logic to start
    }
  };
}

上のコード片はメインスレッドからオーディオスレッド側へ情報を送る例。逆も同じように書ける。

時間変化を伴わないパラメータを渡したい

一度設定したら変化しないパラメータを指定したい時はどうしたらいいんだろうか。parameterDescriptors() で指定するパラメータは毎回 process() に渡されるものだけど、今回作った AudioWorkletProcessor ではメインスレッドから渡されるパラメータで時間と共に変わるものは使っていない。だとしたらコンストラクタでパラメータを受け取れば多少効率が良さそうに思える。

AudioWorkletNode のコンストラクタ引数には省略可能な options パラメータがある。その一部に processorOptions というのがあってこれにオブジェクトを渡すと AudioWorkletProcessor の options の processorOptions として受け取れる。具体的にはこう。

// main.js
class FooWorkletNode extends AudioWorkletNode {
  constructor(context) {
    const options = {
      // Passed as |options.processorOptions| in AudioWorkletProcessor.
      processorOptions: {
        barProp: 'bar'
      }
    }
    super(context, 'foo-processor', options);
  }
}

// processor.js
class FooWorkletProcessor {
  constructor(options) {
    options.processorOptions.barProp; // <-- 'bar'
  }
}

今回は OscillatorNode に使い勝手を寄せたのでこのやり方は使わなかったが、AudioWorkletProcessor の生存期間の間に不変のパラメータを定義したい場合はこちらを使うとよいかもしれない。

参考文献