Fukuoka.rb 第256回でLT登壇するまでの話

Fukuoka.rbのLTで久々に登壇をした。

Fukuoka.rb 0x100 回 LT 大会 (#256) - connpass

まじめなasync/awaitの話をするように見せかけて、ポニョの歌を使った並列処理の勉強会ライブコーディングをするというネタを仕込んでの登壇。「まさかそうくるとは」的な期待通りの反応もあり、ちょっと嬉しかった。

さて、反省記事。

きっかけ

なんかTwitterでFukuoka.rbの256回がLT大会だというのが流れているのが見えた。

LTは過去に何度か社内勉強会でやって毎度時間をはみ出してしまって、非常に苦手という自負はあった。ただ、「まぁFukuoka.rbならなんとかなるやろ」(意味不明w)てきなノリで、とりあえず申し込んでみた。

ようするに、登壇理由はほぼない。久しぶりにちょっと喋りたかっただけ、くらいのノリ。

着想

話すネタは、登壇2日前からようやく考え始めた。

ブラウザの自動操作について話そうかと思ったが、とてもじゃないが5分では何も話しきらない。

ぜんぜん別の方面で、socketry/async というライブラリの話をしようかとも思ったが、これもとてもじゃないがLTで話すようなものではない。

そうこうしているとテーマを決めてねとアナウンスがあったのでとりあえず回答。

そうはいっても、この時点では本当にまだ何もできていなかった。

さて、Async gemを説明するとして、「そもそもなぜAsync gemを説明したいか」というのをまず考える。すると、JSのPromiseやasync/awaitをRubyで表現するときの方法が実はあまりQiitaとかZennとかで記事になっていないし、Threadベースのconcurrent-rubyは順序性の担保が大変だということはほとんど知られていない。という課題に気づく。

こうしてLT登壇テーマは決まった

PuppeteerとかPlaywrightのJSコードには、async/awaitが非常に多く使われていて、RubyRailsで使いたいときにポーティングに困る、という自分の過去の実体験にも紐付けることができて、それなりに良いテーマだなぁと自己納得しつつ、資料を一気に作り上げた。これが登壇前日の夜の出来事w

"音"の着想

さて、資料を夜更しして作って、寝て起きてみたら、自分のLT発表が全く面白くないことに気づいた。

concurrent-rubyとAsync gemそれぞれの使い方を軽く説明し、concurrent-rubyはスレッドベースなので順序が前後しやすい弱点を説明して、軽くデモしておしまい、デモの内容は、sleepとputsだけのコンソールプログラム。

順序性の乱れをputsとsleepで表現するのは、エンジニアならわからなくはないが、聴く側としてはおそらくとてもつまらない。

ここでふと、rukawa(ワークフローエンジン)で音を鳴らしている人がいたなーと思い出す。(思い出すといっても、その発表自体は自分は知らなくて、前職の先輩がエンジニアブログの記事の片隅に書いていたなー、というのを思い出した)

並列処理を音で表現すれば、順序性の乱れが音の乱れとなって聞こえてくるのでは?!

さっそくやってみることにした。

Rubyでどうやって音を鳴らせばいいのか

Googleで検索してみても、音をならすというと、BGMを鳴らす系のものが多く出てくる。知りたいのは、MIDIなのか、音声合成なのか、そういう系のものなのだけど、なかなかキーワードがわからず苦戦した。

いろいろ調べてみた結果、どうもSonic Pi というものがあって、そいつにむかってUDPパケットを投げつけると音が鳴るっぽいことはわかった。

/run-code みたいなやつ。日本語記事もふくめいろいろ出てくるんだけど、トレンドが過ぎているせいか割とみんな情報が古くて(UDPポート番号が違ってる記事が多い)、一番役に立った情報はこれ。

in-thread.sonic-pi.net

require 'osc-ruby'

def play(sound)
  client = OSC::Client.new('localhost', 51235)
  client.send(OSC::Message.new('/run-code', 'DEMO', "play #{sound}"))
end

こんな感じでメソッド定義すれば、 play 60 で「ドー♪」とピアノ音が鳴る。(当然、あらかじめSonic Piを起動しておく必要はある)

音の表現を使ってLT発表する

音で非同期処理を表現できることはわかった。時刻はLT当日の午前10時。平日なので、お仕事の時間になったw

資料は修正が間に合わないので、あきらめてそのまま。LT発表は口頭でフォローしつつ、ライブコーディング形式でいかざるをえなくなった。

順序性の乱れは音で表現できなかった

この時点で、1つ問題に気づいていた。concurrent-rubyの順序性の乱れは音で表現できないということだ。

人間の耳というのはとてもよくできていて、数ミリ秒の音の前後はうまく吸収して和音と認識してしまう。つまり、もともと説明しようとしていたことは無理だということだ。

それならもう、「音で並列処理を表現する」単なるconcurrent-rubyやAsyncの使い方の説明だけにふりきってしまおうと、楽しむ方向性でいくことにした。

音で楽しませつつライブコーディングに困らない選曲

幸いにして、自分はもともとピアノ弾きなので、任意の曲を音符に起こすことにはそんなに困らない。

最初に作ったのは「かえるのうた」の輪唱。

def kaeru
  Async do |t|
    # かーえーるーのーうーたーがーー
    play(60)
    t.sleep 0.4
    play(62)
    t.sleep 0.4
    play(64)
    t.sleep 0.4
    play(65)
    t.sleep 0.4
    play(64)
    t.sleep 0.4
    play(62)
    t.sleep 0.4
    play(60)
    t.sleep 0.8

    # きーこーえーてーくーるーよーー
    play(64)
    t.sleep 0.4
    play(65)
    t.sleep 0.4
    play(67)
    t.sleep 0.4
    play(69)
    t.sleep 0.4
    play(67)
    t.sleep 0.4
    play(65)
    t.sleep 0.4
    play(64)
    t.sleep 0.8

    # ぐわーx4
    4.times {
      play(60)
      t.sleep 0.8
    }

    # げげげげげげげげぐわぐわぐわ
    play(60)
    t.sleep 0.2
    play(60)
    t.sleep 0.2
    play(62)
    t.sleep 0.2
    play(62)
    t.sleep 0.2
    play(64)
    t.sleep 0.2
    play(64)
    t.sleep 0.2
    play(65)
    t.sleep 0.2
    play(65)
    t.sleep 0.2
    play(64)
    t.sleep 0.4
    play(62)
    t.sleep 0.4
    play(60)
    t.sleep 0.8
  end
end

1メソッドを実行するだけだと当然に単旋律だが、↓こんな感じで並列処理すれば輪唱になる。

Async {
  bar = Async::Barrier.new
  bar.async { kaeru.wait }
  bar.async { |t| t.sleep 3.2 ; kaeru.wait }
  bar.async { |t| t.sleep 6.4 ; kaeru.wait }
  bar.wait

  kaeru.wait
}

が、このデモには1つ問題がある。長い

かえるのうたを全パート鳴らしきるのに30秒くらいかかるのだ。LT時間は5分しかないので却下。

なんとなくジブリ系の音楽を片っ端から想像して、最終的に採用したのが、ポニョのあれ。「ポーニョポーニョポニョさかなのこ」は2〜3秒くらいだけど、聞いた瞬間からそれとわかる。和音で奏でることもできる。

ベースパートは、Rubyらしくループで

def base_async
  Async do |t|
    8.times do
      play(53)
      t.sleep 0.2
    end
    4.times do
      play(58)
      t.sleep 0.2
    end
    play(53)
  end
end

主旋律と副旋律はてきとうにこんな感じで

def main_async
  Async do |t|
    play(72)
    t.sleep 0.4
    play(69)
    t.sleep 0.2
    play(65)
    t.sleep 0.4
    play(60)
    t.sleep 0.2
    play(60)
    t.sleep 0.2
    play(60)
    t.sleep 0.2
    play(62)
    t.sleep 0.2
    play(65)
    t.sleep 0.2
    play(70)
    t.sleep 0.2
    play(74)
    t.sleep 0.2
    play(72)
    t.sleep 0.4
  end
end

def sub1_async
  Async do |t|
    play(69)
    t.sleep 0.4
    play(65)
    t.sleep 0.2
    play(65)
    t.sleep 0.4
    play(60)
    t.sleep 0.2
    play(57)
    t.sleep 0.2
    play(60)
    t.sleep 0.2
    play(58)
    t.sleep 0.2
    play(62)
    t.sleep 0.2
    play(58)
    t.sleep 0.2
    play(62)
    t.sleep 0.2
    play(65)
  end
end

全部重ねあわせると、いいかんじのポニョになる。

ライブコーディングでいちいちこのsleepとかplayは書いてられないので、切り貼りできるようにgistに上げておいた。

fukuoka.rb 0x100 LT demo · GitHub

ZOOMで音を届けるための準備

LT当日16:50、仕事を早めに切り上げて(というか、本当に体調が悪くて早退して)ZOOMでポニョを鳴らすためのいろいろを調べた。

画面共有の設定でかんたんにできることはわかったのだが、ぶっつけ本番はこわいので自分のスマホとPCでZOOMを実際に通話させて実験。この予行練習は本当にやっておいて正解で、「音がならない場合にはSonic Piをとりあえず再起動すればよい」というのは予行練習で発見したバッドノウハウだ。

ZOOMで音を届けるための設定は↓ここにノウハウをまとめたので、本記事では書かない。 https://zenn.dev/yusukeiwaki/articles/eb13bda3462b0f#%E3%82%AA%E3%83%B3%E3%83%A9%E3%82%A4%E3%83%B3%E9%96%8B%E5%82%AC%E3%81%AE%E5%8B%89%E5%BC%B7%E4%BC%9A%E3%81%A7%E9%9F%B3%E3%82%92%E9%B3%B4%E3%82%89%E3%81%99

まさか音を届けるとは思わないだろう、作戦

登壇タイトルはいかにも真面目そうなasync/awaitの説明に見えるので、LTだしギャップを楽しんでもらいたい。そんなわけで資料はあえて事前公開して、まさか音を鳴らすデモがあるとは発表時までわからないようにしてみた。

当日のTwitterの反応を見るに、そこそこ反響はあったのかなと想像。(自己満足?w)

ふりかえり

  • 資料を作るのが直前だった割にはどうにかなった。"音"は偉大。
  • プライベートPCをリプレイスしたばかりでKeynote入れてなかったのでGoogle Slideで作ったが、LT発表くらいのコンテンツであればちょうど使いやすかった
  • LT苦手と自覚しておきながら、5分でおさめる努力が足りなさすぎた
    • オンライン登壇ならやっぱり事前練習を2〜3回やらないといかんね...