5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ElixirAdvent Calendar 2024

Day 19

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

Posted at

今回の目標

  • Padでリズムパターンを入力可能にする
  • リズムパターンでスネアを鳴らす

前提知識

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

仕様

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

ソース

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

動作動画

5
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?