ElixirでText形式で曲を書いて鳴らす

Elixirで「メロディ」を鳴らすの続編です

https://qiita.com/t-yamanashi/items/07e31c1c180580f020ab


# 前提条件
- OS Ubuntu 22.04

# 仕様
- テキストで曲を書けるようにする
- 1行1音とする
- １行のデーターは「a4 8」の形式で「a4」は「ラで高さ4」　、「8」八分音符とする
- R0は休符とする
- c4+の「+」は半音上げるとする、同様のルールで「-」で半音さげるとする

# ソースを書く
```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

  @bpm 200

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

  @doc """
  Hello world.

  ## Examples

      iex> Sox.hello()
      :ok

  """
  def hello do
    part_a = """
    c4 16
    r4 16
    c4 16
    d4 16
    c4 16
    d4 16
    e4 16
    d4 16
    e4 16
    g4 16
    e4 16
    g4 16
    e4 16
    g4 16
    a4 16
    g4 16
    a4 16
    c5 16
    g4 16
    a4 16
    c5 16
    a4 16
    c5 16
    d5 16
    e5 2
    r5 4
    """

    part_b = """
    a4 8
    r0 16
    a4- 8
    a4 8
    c5 8
    r0 16
    b4 8
    r0 16
    a4- 8
    a4 8
    r0 16
    a4- 8
    r0 16
    a4 16
    d4 8
    r0 16
    d4+ 8
    r0 16
    e4 8
    """

    part_c = """
    a4 8
    r0 16
    a4- 8
    r0 16
    g4 16
    g4- 2
    g4 8
    r0 16
    g4- 8
    r0 16
    f4 16
    e4 2

    d4 8
    r0 16
    d4+ 8
    r0 16
    e4 16
    f4 8
    r0 16
    f4+ 8
    r0 16
    g4 16
    g4+ 8
    r0 16
    a4 8
    r0 16
    b4 16
    c5 8
    r0 16
    c5+ 8
    r0 16
    d5 16
    """

    main_part = String.duplicate(part_b, 2) <> part_c

    (part_a <> String.duplicate(main_part, 2))
    |> text_to_play()

    :ok
  end

  def text_to_play(text) do
    text
    |> String.split("\n")
    |> Enum.reject(&(&1 == ""))
    |> Enum.map(&create_play_syntax(&1))
    |> Enum.each(&play(&1))
  end

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

  def note_shift({0, time}, _), do: {0, time}
  def note_shift({note, time}, shift), do: {note + shift, time}

  def note_shift(notes, shift) when is_list(notes) do
    notes
    |> Enum.map(&note_shift(&1, shift))
  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 play({note, time}) do
    [alphabet | number] = note |> String.split("") |> Enum.reject(&(&1 == ""))
    note_no = get_note_no(alphabet) + get_octaval_and_semitone(number)
    play(note_no, time)
  end

  def play(0, time) do
    sec = get_sec(time)
    play_cmd(0, sec)
  end

  def play(note, time) do
    sec = get_sec(time)

    note_no_to_frequency(note)
    |> play_cmd(sec)
  end

  def play_cmd(frequency, time), do: System.cmd("play", ~w"-n synth #{time} sin #{frequency}")

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

  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
```

<iframe width="1280" height="720" src="https://www.youtube.com/embed/SRJTmGLvME4" title="ElixirでText形式で曲を書いて鳴らす" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

# ソース(github)

https://github.com/ymn-t-yamanashi/sox/releases/tag/v0.04