Macでマックのティロリ音が聞けるコマンドを作った

2023年1月17日にマクドナルドの公式Twitterよりポテトを揚げる音、通称「ティロリサウンド」が配布されました。

https://twitter.com/McDonaldsJapan/status/1615280373637779456

ツイートを見る感じだと好きに使って良さそうなので、何か面白いことに使えないかな...と考えていた所、こんなアイディアを思いつきました。

- ターミナルからコマンド経由で「ティロリ♪ ティロリ♪...」と流す
- マックのポテトが食べたくなる
- リモートワークで家に引きこもってるけど、マックに行かねばならなくなる
- 外出することで運動になる

風が吹けば桶屋が儲かるという言葉もあるので、さっそくelixirでティロリサウンドを再生するコマンドを作ってみました。

## 作ったもの
まずは完成品をご覧ください。  

<iframe width="600" height="415" src="https://www.youtube.com/embed/TeUMfQgI2X4" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>

ターミナルに「poteto」と打ち込むと「ティロリ♪ティロリ♪...」とサウンドが再生されます。そのまま、30秒程度、サウンドが再生し終わるまで放置するか、「stop」と打ち込むことで再生を停止させることが出来ます。

では、どのように作ったかを順に紹介していきます。

## プロジェクト作成
まずは`mix`を使って新しいプロジェクトを作成します。今回はコマンド名に合わせて「poteto」としました。

```bash
$ mix new poteto
* creating README.md
* creating .formatter.exs
:
Your Mix project was created successfully.
You can use "mix" to compile it, test it, and more:

    cd poteto
    mix test

Run "mix help" for more commands.
```

## 処理の実装
では`lib/poteto.ex`にメインの処理を記述していきます。今回は`escript`を使って、ビルドしてコマンドとして実行できるようにしたいので、main関数を定義します。
main関数は１つ引数を受け取る必要がありますが、今回はターミナルからコマンドを受け取る予定はありませんので、アンダーラインを付与して使用しない変数だと明示しておきます。

```elixir:lib/poteto.ex
defmodule Poteto do
  def main(_args) do
  end
end
```

### サウンドの再生
調査の結果、自力で頑張ってサウンドを再生させるのはかなり大変...ということが分かりました。
「なんとかならないかな？」と思案していた所、Macには`afplay`という指定したファイルパスの音声ファイルを再生するコマンドがあることを発見しました。

https://blog.kimizuka.org/entry/2020/07/10/174935

このコマンドを`Sysytem.cmd`経由で呼び出します。再生を停止させる場合は同様に`System.cmd`経由で`killall afplay`で停止させます。欠点として`afplay`で再生した音声が全て停止してしまうという問題がありますが、今回は目を瞑ります。。。

```elixir
def play_sound(sound_path) do
  System.cmd("afplay", [sound_path])
end

def stop_sound do
  System.cmd("killall", ["afplay"])
end
```

### OSによる処理の分岐
Macの場合には`afplay`コマンドを使用しますが、他のOSでは別のコマンドを使用することを想定して、パターンマッチで処理を分岐できるようにしました。
もし興味があれば、[レポジトリを公開](https://github.com/okabe-yuya/poteto-ex)しているので、別OSの実装のPRを投げて頂けると嬉しいです。

```elixir:lib/poteto.ex
defp command_exec({ :win32, _ }, _function, _args), do: :pass
defp command_exec({ :unix, :darwin }, function, args), do: apply(OS.Darwin, function, args)
defp command_exec(_, _function, _args), do: :pass
```

モジュールはマッチしてOSの情報に合わせて、対応するモジュールの関数を動的に呼び出すようにしました。今回はdarwinに対応する`OS.Darwin`が使用されます。
```elixir:lib/os/darwin.ex
defmodule OS.Darwin do
  def play_sound(sound_path) do
    System.cmd("afplay", [sound_path])
  end

  def stop_sound do
    System.cmd("killall", ["afplay"])
  end
end
```

あとは「stop」させるためにplay_sound関数の呼び出しを別プロセスで行い、ユーザー入力を受け付けるreceiver関数を定義してあげれば出来上がりです。

```elixir:lib/poteto.ex
defmodule Poteto do
  def main(_args) do
    spawn(fn -> command_exec(:os.type(), :play_sound, ["/Users/takamizawa46/sounds/poteto_sounds.mp3"]) end)

    IO.puts("🍟「再生を停止したい場合は 'stop' と入力して下さい」")
    receiver()
  end

  defp receiver do
    case IO.gets("") do
      "stop\n" -> command_exec(:os.type(), :stop_sound, [])
      _ -> receiver()
    end
  end

  defp command_exec({ :win32, _ }, _function, _args), do: :pass
  defp command_exec({ :unix, :darwin }, function, args), do: apply(OS.Darwin, function, args)
  defp command_exec(_, _function, _args), do: :pass

end
```

### 実行ファイルの作成
音声ファイルのファイルパスを絶対パスにしたのはビルド後にPATHを通した際に、ファイルが参照出来なくなるエラーを確認したためです。合わせてビルド出来ないか試しましたが、そういったオプションは自分が調べた限りでは発見出来ませんでしたので、この方法を採用しました。

あとは`mix escript.build`を実行して、実行ファイル(poteto)を作成します。これでターミナルから`./poteto`とすればティロリ音が再生されます。お好みでPATHを通して、どこからでも実行できるようにすれば完成です。

```bash
bashᕙ( ˙-˙ )ᕗ takamizawa46@ ~/elixir/poteto  $ ./poteto
🍟「再生を停止したい場合は 'stop' と入力して下さい」
stop
```

https://wemo.tech/3949

コードの全体像はレポジトリをご覧ください。

https://github.com/okabe-yuya/poteto-ex

## 最後に
思いつきのネタでしたが、無事に実装出来てよかったです。
特にサウンドを再生するということはやったことがなかったので良い勉強になりました。今回は`afplay`という手段を選択しましたが、もし他にアイディアや知識をお持ちの方がいたら、ご教授頂ければと思います。

普段は個人ブログにリモートワークやチームビルディング、プログラミング言語などの記事を書いていますので、ぜひご覧ください。

https://www.okb-shelf.work/

では、ティロリ音を聞いてポテトが食べたくなったので、マックに行ってきます🏃‍♂️🏃‍♂️🏃‍♂️