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

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

Posted at

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

前提条件

  • OS Ubuntu 22.04

仕様

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

ソースを書く

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

実行

$ mix test

ソース(github)

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