DDJ-FLX4のPadでシーケンサーを作ろう

# 今回の目標
- Padでリズムパターンを入力可能にする
- リズムパターンでスネアを鳴らす

# 前提知識

https://qiita.com/t-yamanashi/items/94245aacd99931f0a54c

https://qiita.com/t-yamanashi/items/98533314876feeafc23e

今までのコラムの組み合わせでできます

# 仕様
- 非同期でパターンに沿ってスネアを鳴らす
- デッキ1のPad1〜Pad8のONの部分を鳴らす
- 初期はデッキ1のすべてのPadはOFF

# ソース

```elixir
defmodule Sequencer do
  @moduledoc """
  Documentation for `sequencer`.
  """
  @hi 42
  @bass_drum 36
  @snare 38

  @deck1 "97"
  @led_on "7F"
  @led_off "00"

  @pad1 "00"
  @pad2 "01"
  @pad3 "02"
  @pad4 "03"
  @pad5 "04"
  @pad6 "05"
  @pad7 "06"
  @pad8 "07"

  def pad_list, do: [@pad1, @pad2, @pad3, @pad4, @pad5, @pad6, @pad7, @pad8]
  def pad(no), do: pad_list() |> Enum.at(no)

  # メイン処理
  def start() do
    state = Enum.map(1..8, fn _ -> false end)
    Agent.start_link(fn -> state end, name: __MODULE__)

    {_, input} = PortMidi.open(:input, "DDJ-FLX4 MIDI 1")
    {_, output} = PortMidi.open(:output, "DDJ-FLX4 MIDI 1")

    pad_list()
    |> Enum.each(&pad_led(output, @deck1, &1, @led_off))

    {:ok, synth} = MIDISynth.start_link([])
    PortMidi.listen(input, self())
    Task.async(fn -> track(synth) end)
    midi_in(input, output)

    PortMidi.close(:input, input)
    PortMidi.close(:output, output)
  end

  # 入力処理
  def midi_in(input, output) do
    receive do
      # {^input, [{{144, 11, 0}, _}]} ->
      #   IO.inspect("end")

      {^input, [{{_, no, 127}, _}]} ->
        flg = get_button_state(no)
        set_button_state(no, !flg)

        get_state()
        |> IO.inspect()

        pad_led(output, @deck1, pad(no), led_sw(!flg))

        midi_in(input, output)

      _ ->
        midi_in(input, output)
    end
  end

  # リズムパターンで鳴らす
  def track(synth) do
    get_state()
    |> Enum.each(fn x ->
      drum(synth, 1, x)
      Process.sleep(50)
    end)

    track(synth)
  end

  # ここから音を鳴らす制御
  def drum(synth, 0, true), do: play(synth, @bass_drum)
  def drum(synth, 1, true), do: play(synth, @snare)
  def drum(synth, 2, true), do: play(synth, @hi)
  def drum(_, _, _), do: nil

  def play(synth, note), do: MIDISynth.Keyboard.play(synth, note, 50, 127, 9)
  # ここまで音を鳴らす制御

  # 　ここからPadボタンのON/OFF制御
  def get_button_state(index) do
    get_state()
    |> Enum.at(index)
  end

  def set_button_state(index, flg) do
    get_state()
    |> List.replace_at(index, flg)
    |> set_state()
  end

  def get_state, do: Agent.get(__MODULE__, & &1)
  def set_state(state), do: Agent.update(__MODULE__, fn _ -> state end)

  def pad_led(output, deck, pad, led_sw) do
    [deck, pad, led_sw]
    |> send_midi(output)
  end

  def hex_to_dec(hex), do: String.to_integer(hex, 16)

  def send_midi(v, output) do
    Enum.map(v, fn x -> hex_to_dec(x) end)
    |> List.to_tuple()
    |> then(&PortMidi.write(output, &1))
  end

  def led_sw(true), do: @led_on
  def led_sw(_), do: @led_off
  # ここまでPadボタンのON/OFF制御
end
```

# 動作動画
<iframe width="560" height="315" src="https://www.youtube.com/embed/VZaNBdzix-8?si=VxIuXh8jVO0O0CzW" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>