9
1

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 4

DDJ-FLX4のPadでシーケンシャルウインカーを作ろう

Posted at

PioneerDjのDDJ-FLX4でシーケンシャルウインカーを実装します

前提と基礎知識

の続編です

ElixirでPadのシーケンシャルウインカー

プログラム仕様

  • Pad1〜Pad8を使って表現します
  • 上段、下段のPadを使います
  • デッキ1、デッキ2のPadは内側から光ます

表示例

□□□□ □□□□ 
□□□□ □□□□
□□□■ ■□□□ 
□□□■ ■□□□
□□■■ ■■□□ 
□□■■ ■■□□
□■■■ ■■■□ 
□■■■ ■■■□
■■■■ ■■■■ 
■■■■ ■■■■

前回のコラムでDDJ-FLX4固有の部分をモジュール化しました
前回のコラムに通信仕様を書いた為説明は省略

lib/ddj.ex
defmodule Ddj do
  @moduledoc """
  Documentation for `Ddj`.
  """

  def led_on, do: "7F"
  def led_off, do: "00"

  def pad1, do: "00"
  def pad2, do: "01"
  def pad3, do: "02"
  def pad4, do: "03"
  def pad5, do: "04"
  def pad6, do: "05"
  def pad7, do: "06"
  def pad8, do: "07"

  def deck1, do: "97"
  def deck2, do: "99"

  def pad_list, do: [pad1(), pad2(), pad3(), pad4(), pad5(), pad6(), pad7(), pad8()]

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

  def close(output) do
    PortMidi.close(:output, output)
  end

  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 hex_to_dec(hex), do: String.to_integer(hex, 16)

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

ウインカープログラムの本体です

lib/turn_signal.ex
defmodule TurnSignal do
  @moduledoc """
  Documentation for `TurnSignal`.
  """
  import Ddj

  @doc """
  左側のウインカーの光るリスト
  """
  def deck1_signal do
    [
      {pad4(), pad8()},
      {pad3(), pad7()},
      {pad2(), pad6()},
      {pad1(), pad5()}
    ]
  end

  @doc """
  右側のウインカーの光るリスト
  """
  def deck2_signal do
    # 左側と逆になる
    deck1_signal()
    |> Enum.reverse()
  end

  def start() do
    output = open()

    # ウインカーは5回
    1..5
    |> Enum.each(fn _ -> task_deck(output) end)

    close(output)
  end

  @doc """
  左右ウインカー1回分
  """
  def task_deck(output) do
    # 非同期で左ウインカーを処理
    task_deck1 =
      Task.async(fn ->
        deck1_signal()
        |> signal_process(deck1(), output)
      end)

    # 非同期で右ウインカーを処理
    task_deck2 =
      Task.async(fn ->
        deck2_signal()
        |> signal_process(deck2(), output)
      end)

    # 左右のウインカーを処理が終わるまで待機
    Task.await(task_deck1)
    Task.await(task_deck2)
  end

  @doc """
  片方のウインカーを処理
  """
  def signal_process(pattern, deck, output) do
    pattern
    |> Enum.each(&signal_on(&1, deck, output))

    Process.sleep(300)

    pattern
    |> Enum.each(&signal_off(&1, deck, output))

    Process.sleep(300)
  end

  @doc """
  上段、下段のPadの表示/非表示
  """
  def signal({pad_up, pad_down}, deck, led_sw, output) do
    pad_led(output, deck, pad_up, led_sw)
    pad_led(output, deck, pad_down, led_sw)
  end

  @doc """
  上段、下段のPadの表示
  表示後100ミリ秒待つ
  """
  def signal_on({pad_up, pad_down}, deck, output) do
    signal({pad_up, pad_down}, deck, led_on(), output)
    Process.sleep(100)
  end

  @doc """
  上段、下段のPadの非表示
  """
  def signal_off({pad_up, pad_down}, deck, output),
    do: signal({pad_up, pad_down}, deck, led_off(), output)
end

補足
左右のウインカーは、非同期で動きます
但し、左右のウインカーが終わるまで次の処理を実行しません
データを作るのがめんどくさいので非同期処理しました(手抜き)

実行

$ mix run --eval "TurnSignal.start()"

動作結果

9
1
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
9
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?