Elixirで音を鳴らすライブラリー midi_synth
で遊びたいと思います
検証して動作に成功した環境はUbuntu20.04
同様のことを試してみましたが現時点ではmacOS 12はビルドが通らなかったです…(サポートされているはず)
Windows WSL2も同様ビルドが通らなかった(仮にビルド通ってもサウンドドライバー問題がきっとある…)
その為Ubuntu 20.04で動かすことを前提で書きました
ライブラリーはこちらを使いました
準備
FluidSynthをインストール
$ sudo apt install libfluidsynth-dev
さっそく遊びましょう
まず、iexを起動する
$ iex
Mix.install でライブラリーを読み込む
iex(2)> Mix.install([{:midi_synth, "~> 0.4.0"}])
サウンドフォント(FluidR3_GM.sf2)も自動でダウンロードされました
実行結果
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
音を鳴らしてみよう
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)を鳴らします
iex(4)> MIDISynth.Keyboard.play(synth, 60, 100)
:ok
第1引数: MIDISynth.start_linkのpid
第2引数: ノート番号(音階)
第3引数: 長さ(ミリ秒)
1オクターブ音を鳴らしてみる
ノート番号としては60(C4)〜72(C5)を連続に鳴らします
iex(4)> 60..72 |> Enum.each(fn note -> MIDISynth.Keyboard.play(synth, note, 100) end)
半音も含めて1オクターブ音が鳴りました
ピコピコ鳴らしてみる
ノート番号の例
ド 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
iex(9)> [60, 67] |> List.duplicate(10) |> List.flatten() |> Enum.each(fn note -> MIDISynth.Keyboard.play(synth, note, 100) end)
:ok
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)にします
iex(12)> MIDISynth.Keyboard.change_program(synth, 81)
:ok
再度音を鳴らします
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がドラムのチャンネルになる
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 クローズハイハット
上記の前提で遊ぶ
まず、バスドラムを4つ打ち
iex(23)> [36] |> List.duplicate(10) |> List.flatten() |> Enum.each(fn note -> MIDISynth.Keyboard.play(synth, note, 400, 127, 9) end)
:ok
裏拍にスネアを入れてみる
iex(24)> [36, 38] |> List.duplicate(10) |> List.flatten() |> Enum.each(fn note -> MIDISynth.Keyboard.play(synth, note, 200, 127, 9) end)
:ok
ハイハットも追加してみる
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
リズムマシンぽくなりました
同時に複数音鳴らすのはちょっと難易度が上がるのでこの記事では触れません
実行例