음계 계산과 관련된 여러 함수를 구현하는 문제였다. 특정 음에서 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
'호두나무 공방 > Exercism in Elixir' 카테고리의 다른 글
Transpose - Exercism in Elixir (0) | 2022.10.25 |
---|---|
Sieve - Exercism in Elixir (0) | 2022.10.24 |
Diamond - Exercism in Elixir (0) | 2022.10.12 |
Palindrome Products - Exercism in Elixir (0) | 2022.10.11 |
Affine Cipher - Exercism in Elixir (0) | 2022.10.10 |