Elixirで音を鳴らして遊ぼう

Elixirで音を鳴らすライブラリー　`midi_synth`
で遊びたいと思います

検証して動作に成功した環境はUbuntu20.04
同様のことを試してみましたが現時点ではmacOS 12はビルドが通らなかったです…（サポートされているはず）
Windows WSL2も同様ビルドが通らなかった（仮にビルド通ってもサウンドドライバー問題がきっとある…）

その為Ubuntu 20.04で動かすことを前提で書きました

ライブラリーはこちらを使いました

https://github.com/fhunleth/midi_synth


# 準備

FluidSynthをインストール
```sh
$ sudo apt install libfluidsynth-dev
```

# さっそく遊びましょう
まず、iexを起動する

```sh
$ iex
```

### Mix.install でライブラリーを読み込む

```elixir
iex(2)> Mix.install([{:midi_synth, "~> 0.4.0"}])
```

サウンドフォント（FluidR3_GM.sf2）も自動でダウンロードされました

実行結果
```elixir
Resolving Hex dependencies...
Dependency resolution completed:
New:
  elixir_make 0.6.3
  midi_synth 0.4.1
* Getting midi_synth (Hex package)
* Getting elixir_make (Hex package)
==> elixir_make
Compiling 1 file (.ex)
Generated elixir_make app
==> midi_synth
/usr/bin/make -C src all
make[1]: ディレクトリ '/home/user/.cache/mix/installs/elixir-1.13.4-erts-13.0.3/1481ec753c3c298f97311102a571a358/deps/midi_synth/src' に入ります
mkdir -p /home/user/.cache/mix/installs/elixir-1.13.4-erts-13.0.3/1481ec753c3c298f97311102a571a358/_build/dev/lib/midi_synth/priv
mkdir -p /home/user/.cache/mix/installs/elixir-1.13.4-erts-13.0.3/1481ec753c3c298f97311102a571a358/_build/dev/lib/midi_synth/obj
cc -c -O2 -Wall -Wextra -Wno-unused-parameter -std=c99 -D_GNU_SOURCE  -o /home/user/.cache/mix/installs/elixir-1.13.4-erts-13.0.3/1481ec753c3c298f97311102a571a358/_build/dev/lib/midi_synth/obj/midi_synth.o midi_synth.c
cc /home/user/.cache/mix/installs/elixir-1.13.4-erts-13.0.3/1481ec753c3c298f97311102a571a358/_build/dev/lib/midi_synth/obj/midi_synth.o  -L/usr/lib/x86_64-linux-gnu -lfluidsynth  -o /home/user/.cache/mix/installs/elixir-1.13.4-erts-13.0.3/1481ec753c3c298f97311102a571a358/_build/dev/lib/midi_synth/priv/midi_synth
curl -LO https://github.com/fhunleth/midi_synth/releases/download/v0.1.0/FluidR3_GM.sf2
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
100  141M  100  141M    0     0  6412k      0  0:00:22  0:00:22 --:--:-- 2864k
cp FluidR3_GM.sf2 /home/user/.cache/mix/installs/elixir-1.13.4-erts-13.0.3/1481ec753c3c298f97311102a571a358/_build/dev/lib/midi_synth/priv/FluidR3_GM.sf2
make[1]: ディレクトリ '/home/user/.cache/mix/installs/elixir-1.13.4-erts-13.0.3/1481ec753c3c298f97311102a571a358/deps/midi_synth/src' から出ます
Compiling 3 files (.ex)
Generated midi_synth app
:ok
```

### 音を鳴らしてみよう

```elixir
iex(3)> {:ok, synth} = MIDISynth.start_link([])
{:ok, #PID<0.295.0>}
```

ワーニングでますが、今回はそのままにしておきました
```
fluidsynth: warning: SDL2 not initialized, SDL2 audio driver won't be usable
                                                                                    fluidsynth: Using PulseAudio driver
                  fluidsynth: warning: Failed to set thread to high priority
```
MIDISynthのプロセスが立ち上がりsynthにpidが格納してます


ド（C4）を鳴らします
```elixir
iex(4)> MIDISynth.Keyboard.play(synth, 60, 100)
:ok
```
第1引数: MIDISynth.start_linkのpid
第2引数: ノート番号（音階）
第3引数: 長さ（ミリ秒）


### 1オクターブ音を鳴らしてみる
ノート番号としては60(C4)〜72（C5）を連続に鳴らします

```elixir
iex(4)> 60..72 |> Enum.each(fn note -> MIDISynth.Keyboard.play(synth, note, 100) end)
```

半音も含めて１オクターブ音が鳴りました

### ピコピコ鳴らしてみる

ノート番号の例
```
ド   C  60
　   C# 61	
レ   D  62	
　　 D# 63	
ミ　 E  64	
ファ F  65	
　　 F# 66	
ソ　 G  67	
　　 G# 68	
ラ　 A  69	
　　 A# 70
シ　 B  71
ド   C  72

```

```elixir
iex(9)> [60, 67] |> List.duplicate(10) |> List.flatten() |> Enum.each(fn note -> MIDISynth.Keyboard.play(synth, note, 100) end)
:ok
```

```elixir
iex(11)> [60, 67, 72, 64] |> List.duplicate(10) |> List.flatten() |> Enum.each(fn note -> MIDISynth.Keyboard.play(synth, note, 100) end)
:ok
```

ピアノでは面白くないのでシンセの音にします

プログラムチェンジで81で Lead 1 (square）にします

```elixir
iex(12)> MIDISynth.Keyboard.change_program(synth, 81)   
:ok
```

再度音を鳴らします
```elixir
iex(13)> [60, 67, 72, 64] |> List.duplicate(10) |> List.flatten() |> Enum.each(fn note -> MIDISynth.Keyboard.play(synth, note, 100) end)
:ok
```

# ドラムを鳴らしてみよう
MIDIはドラムのチャンネルは10チャンネルです（1〜16チャンネルの場合）

まず、MIDISynth.Keyboard.playのヘルプを確認してみる
チャンネルは0から始まるようなので、9がドラムのチャンネルになる

```elixir
iex(19)> h MIDISynth.Keyboard.play

        def play(server, note, duration, velocity \\ 127, channel \\ 0)         

  @spec play(
          GenServer.server(),
          MIDISynth.Command.note(),
          MIDISynth.Command.duration(),
          MIDISynth.Command.velocity(),
          MIDISynth.Command.channel()
        ) :: :ok

Play a note

This is a utility method for pressing a note down and then releasing it after a
duration.

## Example

iex> {:ok, synth} = MIDISynth.start_link([]) iex>
MIDISynth.Keyboard.play(synth, 60, 100) :ok iex> MIDISynth.Keyboard.play(synth,
60, 100, 80) :ok
```

ドラムのノート番号

```
36 バスドラム
38 スネアドラム
42 クローズハイハット
```

上記の前提で遊ぶ

まず、バスドラムを４つ打ち

```elixir
iex(23)> [36] |> List.duplicate(10) |> List.flatten() |> Enum.each(fn note -> MIDISynth.Keyboard.play(synth, note, 400, 127, 9) end)
:ok
```

裏拍にスネアを入れてみる
```elixir
iex(24)> [36, 38] |> List.duplicate(10) |> List.flatten() |> Enum.each(fn note -> MIDISynth.Keyboard.play(synth, note, 200, 127, 9) end)
:ok
```

ハイハットも追加してみる
```elixir
iex(27)> [36, 42, 38, 42] |> List.duplicate(10) |> List.flatten() |> Enum.each(fn note -> MIDISynth.Keyboard.play(synth, note, 200, 127, 9) end)
:ok
```

リズムマシンぽくなりました



同時に複数音鳴らすのはちょっと難易度が上がるのでこの記事では触れません

実行例
<iframe width="560" height="315" src="https://www.youtube.com/embed/8Qpnua52ZgQ" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>