ElixirでText形式の曲を書いて鳴らす 〜 曲データを一括変換後に再生＆変換時に分散処理〜

「ElixirでText形式の曲を書いて鳴らす 〜 パート宣言をして再利用可能にする〜」の続編です

https://qiita.com/t-yamanashi/items/56478a65cb5769a06252

# 前提条件
- OS Ubuntu 22.04
- SOXがインストール済み

# 仕様　前回からの差分
- 曲データの一括変換
- Task.asyncを使って変換時に分散処理
- 変換時には/dev/shm/(RAMディスク)を使う

# ソースを書く
```diff_elixir:lib/sox.ex
defmodule Sox do
  @a4_frequency 440

  @c4_note_no 60
  @d4_note_no 62
  @e4_note_no 64
  @f4_note_no 65
  @g4_note_no 67
  @a4_note_no 69
  @b4_note_no 71
  @r_note_no 0
  @dotted_note 1.5

  @bpm 150
+ @tmp_dir "/dev/shm/sox_ymn"
+ @play_file_name "ymn.wav"

  @moduledoc """
  Documentation for `Sox`.
  """

  @doc """
  Hello world.

  ## Examples

      iex> Sox.hello()
      :ok

  """
  def hello do
+   File.rm_rf(@tmp_dir)
+   File.mkdir(@tmp_dir)

    File.read!("music.txt")
    |> text_to_play()

    :ok
  end

  def text_to_play(text) do
    [parts | main] =
      text
      |> String.split("main ")

    part_map =
      get_list_part(parts)
      |> Enum.map(&create_part_keyword(&1))

    main
    |> List.first()
    |> String.split(" ")
    |> Enum.map(&Keyword.get(part_map, String.to_atom(&1)))
    |> List.flatten()
    |> Enum.map(&create_play_syntax(&1))
+   |> Enum.with_index(1)
+   |> Enum.map(&sox(&1))
+   |> Enum.map(&Task.await(&1))

+   sox_merge_cmd()
+   aplay_cmd()
  end

  def get_list_part(parts) do
    parts
    |> String.split("part")
    |> Enum.reject(&(&1 == ""))
  end

  def create_part_keyword(part) do
    [part_name | part_data] =
      part
      |> String.split("\n")
      |> Enum.reject(&(&1 == ""))

    {String.to_atom(part_name), part_data}
  end

  def create_play_syntax(line) do
    [note, time] = line |> String.split(" ")
    {note, time}
  end

  def note_no_to_frequency(@a4_note_no), do: @a4_frequency

  def note_no_to_frequency(note) when note < @a4_note_no,
    do: @a4_frequency / :math.pow(2, 1 / 12 * (@a4_note_no - note))

  def note_no_to_frequency(note) when note > @a4_note_no,
    do: @a4_frequency * :math.pow(2, 1 / 12 * (note - @a4_note_no))

+ def sox({{note, time}, index}) do
+   file_name =
+     index
+     |> Integer.to_string()
+     |> String.pad_leading(10, "0")
+     |> then(&"#{@tmp_dir}/#{&1}.wav")

    [alphabet | number] = note |> String.split("") |> Enum.reject(&(&1 == ""))
    note_no = get_note_no(alphabet) + get_octaval_and_semitone(number)
+   sox(note_no, time, file_name)
  end

+ def sox(0, time, file_name) do
+   sec = get_sec(time)
+   Task.async(fn -> sox_cmd(0, sec, file_name) end)
+ end

+ def sox(note, time, file_name) do
+   sec = get_sec(time)

+   frequency = note_no_to_frequency(note)
+   Task.async(fn -> sox_cmd(frequency, sec, file_name) end)
+ end

+ def sox_cmd(frequency, time, file_name),
+   do: System.cmd("sox", ~w"-n #{file_name} synth #{time} sin #{frequency}")

+ def sox_merge_cmd(), do: System.cmd("sox", ~w"#{@tmp_dir}/*.wav #{@tmp_dir}/#{@play_file_name}")
+ def aplay_cmd(), do: System.cmd("aplay", ~w"#{@tmp_dir}/#{@play_file_name}")

  def get_sec("1"), do: get_sec("4") * 4
  def get_sec("1."), do: get_sec("1") * @dotted_note
  def get_sec("2"), do: get_sec("4") * 2
  def get_sec("2."), do: get_sec("2") * @dotted_note
  def get_sec("4"), do: 60 / @bpm
  def get_sec("4."), do: get_sec("4") * @dotted_note
  def get_sec("8"), do: get_sec("4") / 2
  def get_sec("8."), do: get_sec("8") * @dotted_note
  def get_sec("16"), do: get_sec("4") / 4
  def get_sec("16."), do: get_sec("16") * @dotted_note

  def get_note_no("c"), do: @c4_note_no
  def get_note_no("d"), do: @d4_note_no
  def get_note_no("e"), do: @e4_note_no
  def get_note_no("f"), do: @f4_note_no
  def get_note_no("g"), do: @g4_note_no
  def get_note_no("a"), do: @a4_note_no
  def get_note_no("b"), do: @b4_note_no
  def get_note_no("r"), do: @r_note_no

  def get_octaval_and_semitone(number) when length(number) == 2 do
    [octaval | semitone] = number
    semitone = List.first(semitone)
    get_octaval_and_semitone(octaval) + get_semitone(semitone)
  end

  def get_octaval_and_semitone(number) when length(number) == 1 do
    List.first(number)
    |> get_octaval_and_semitone()
  end

  def get_octaval_and_semitone(number) do
    String.to_integer(number)
    |> get_octaval()
  end

  def get_octaval(octaval), do: (octaval - 4) * 12
  def get_semitone("+"), do: 1
  def get_semitone("-"), do: -1
  def get_semitone("#"), do: 1
end

```

# 実行

```sh
$ mix test
```

# ソース(github)

https://github.com/ymn-t-yamanashi/sox/releases/tag/V0.07