【AfterEffects】エクステンションとWebSocketでMIDIのイベントを受け取って遊ぶ


PhotoshopをMIDIで操作する端末等を見ていたら面白そうだったので、とりあえずAfterEffectsもMIDIを受け取って制御してみることにします。

AE前提で書いていますが、基本的にはエクステンションの無いようなのでAE以外のAdobe製品でも使えます。

必要なもの

  • AfterEffects
  • Node.js v6以下 (fsまわりを使用しているMIDIライブラリの関係)
  • nanoKONTROL2 ¥4000円ぐらい

全体図

今回は、Node.jsでMIDIを聴いてイベントを出すwebSocketサーバーを立てて、それをエクステンションで受け取る形で実装します。

ソースはこちらで公開しています。
https://github.com/matsurai25/zxp-midi

server

const app    = require('http').createServer();
const io     = require('socket.io')(app);

app.listen(39393);
io.on('connection', function(socket) {
  // 接続された
  console.log('connection established');
});

Node.jsでサーバーを建てます。

const Korg   = require('./korg.js');
const korg   = new Korg();

let timer;
// 全イベントを聴く
korg.on('*', function(event, value) {

  // フェーダーのイベントが送られ過ぎないように、setTimeoutで
  // イベント後一定期間内に再度イベントがった場合はそのイベントを無視するようにしている。
  if (timer !== false) {
      clearTimeout(timer); // setTimeoutを解除
  }

  timer = setTimeout(function() {
    // webSocketでイベントを送信
    io.emit('midi-event', {
      time:moment().format('HH:mm:ss'),
      type:event.split(':')[0],
      channel:event.split(':')[1],
      value:value
    });
  }, 50);
})

MIDIイベントを受け取ってソケットで送ります。
korg.jsではkorg-nano を少々変更し、nanoKONTROL2のMIDIマッピングを受け取っています。
今回のフェーダーではイベントが送られすぎてしまってAE側の動作が重くなってしまったので、setTimeoutで短期間にイベントが送られすぎることを制限しています。

extension

AdobeのエクステンションではHTML+jsで色々作ることが出来ます。
つまりはフロントエンド領域の諸々が使えます。

とりあえずなんか良い感じに開発できるセットを作っているので、今回はこちらを使用します
https://github.com/matsurai25/adobe-html5-panel-template

jade/scss等のコンパイル、manifestファイルの設定、zxp署名と書き出しとか出来ます。

vue.js

今回はどんなイベントが来たかをサクッと見るために Vue.js を使用しています。
https://jp.vuejs.org/
日本語ドキュメントがあり、わかりやすいです。

const Vue = require('vue/dist/vue.common.js'); // Vue
const io = require('socket.io-client/dist/socket.io.js');

module.exports = () => {
  const socket = io('http://localhost:39393');
  var app = new Vue({
    el: '#app',
    data: {
      messages: []
    },
    created: function() {
      socket.on('midi-event', (data) => {
        console.log(data);
        this.$data.messages.unshift(data);
      });
    }
  });
};

new Vue()に色々渡してリストレンダリングとイベント設定をします。
createdで指定した関数がマウント完了時に実行されます。
socket.on(key, function)で、指定のkeyのソケットが送られてきた時の処理を行います。
これは受け取ったjsonをとりあえずunshiftmessage配列に入れていっています。DOM側は自動的に更新されます。

    #app
      .message(v-for='message in messages')
        .time
          | {{ message.time }}
        .type
          | {{ message.type }}
        .channel
          | {{ message.channel }}
        .value
          | {{ message.value }}

DOM側ではこんな感じに指定しておきます。
スタイルはscssで良い感じにあてます。

エクスプレッション


_sliderというコンポジションを作成し、layerというレイヤーを作成、そこにスライダー制御をいっぱい付けて、それをすべてのレイヤーで参照するようにします。

var s = comp('_slider').layer('slider').effect('slider:'+n)(1);

とかで値を取得できます。


とりあえず良い感じに見れるようにしたもの。

var k = comp('_slider').layer('slider').effect('slider:1')(1);
effect("色相/彩度")(7)+index*20 + 5*k;

適当に●をいっぱい置いて、色相エフェクトをフェーダーで制御してみます。

aepをこちらに置いておきますので、ご自由にご利用下さい(CC2014)
https://github.com/matsurai25/zxp-midi/blob/master/AE_MIDI_SLIDER_sample.aep

完成

感想

  • エクステンションまわりの記事少ない・・・。
  • manifest.xmlに不要なjsx読み込みが書いてあるとCSInterfaceが動かないという問題にしばらく悩まされました。
  • AE側でリアルタイムに値を制御したかったのですが、いちいちスクリプトを噛ませて実行するとやはり速度に難ありという印象。
  • 選択されているプロパティの数値を上げるとかのショートカットキーがあれば、そちらを再現してあげればよさそう。
  • スクリプトのショートカット割りあてが結構面倒な印象があるので、これとnanoPADを使って良い感じにすれば、結構業務効率向上しそうですね。
  • スクリプトはグローバルを共有しているので、EventEmitterとかをglobalに入れてsocketを通知してあげれば、他のスクリプトでも購読できて良さそう(by かれおばなさん)

以上です。