호두나무 공방/Exercism in Elixir

Scale Generator - Exercism in Elixir

2022. 10. 13. 23:13

문제 보기

음계 계산과 관련된 여러 함수를 구현하는 문제였다. 특정 음에서 1도~3도 떨어진 음은 무엇인가, 특정 음에서 시작하는 1옥타브를 플랫 또는 샵 표현으로 나타내거나 등등. 마지막 함수는 앞에서 구현한 함수들을 모두 활용해야 하는 문제였어서 과제가 유기적으로 연결된 듯 하여 재미있게 풀었다.

defmodule ScaleGenerator do
  @spec step(scale :: list(String.t()), tonic :: String.t(), step :: String.t()) :: String.t()
  def step(scale, tonic, step) do
    tonic_index = scale |> Enum.find_index(fn s -> s == tonic end)

    index = case step do
      "m" -> 1
      "M" -> 2
      "A" -> 3
    end
    |> Kernel.+(tonic_index)
    |> Kernel.rem(length(scale))

    Enum.at(scale, index)
  end

  @spec chromatic_scale(tonic :: String.t()) :: list(String.t())
  def chromatic_scale(tonic \\ "C") do
    get_scale(~w(C C# D D# E F F# G G# A A# B), tonic)
  end

  @spec flat_chromatic_scale(tonic :: String.t()) :: list(String.t())
  def flat_chromatic_scale(tonic \\ "C") do
    get_scale(~w(A Bb B C Db D Eb E F Gb G Ab), tonic)
  end

  defp get_scale(total_notes, tonic) do
    tonic = case String.length(tonic) do
      1 -> String.upcase(tonic)
      2 -> String.at(tonic, 0) |> String.upcase() |> Kernel.<>(String.at(tonic, 1))
    end

    start_index = Enum.find_index(total_notes, fn n -> n == tonic end)

    Enum.slice(total_notes, start_index..-1//1) ++ Enum.slice(total_notes, 0..start_index//1)
  end

  @spec find_chromatic_scale(tonic :: String.t()) :: list(String.t())
  def find_chromatic_scale(tonic) when tonic in ~w(F Bb Eb Ab Db Gb d g c f bb eb) do
    flat_chromatic_scale(tonic)
  end

  def find_chromatic_scale(tonic) do
    chromatic_scale(tonic)
  end

  @spec scale(tonic :: String.t(), pattern :: String.t()) :: list(String.t())
  def scale(tonic, pattern) do
    chromatic_scale = find_chromatic_scale(tonic)

    pattern
    |>String.graphemes()
    |> Enum.reduce([hd(chromatic_scale)], fn step, [h | _] = acc ->
      [step(chromatic_scale, h, step) | acc]
    end)
    |> Enum.reverse()
  end
end