1. Alphabet Project, Part 1
  2. Alphabet Project, Part 2
  3. Alphabet Project, Part 3
  4. Alphabet Project, Part 4
  5. Alphabet Project, Part 5

Step 4 - adding (even more) musicality

At the beginning of the last post, I listed the three timbral components that make up each note. In that post, we dealt with the first two: dynamic and pitch. Now, we’re left with the final component: phoneme sound/quality.

For the vowel lines, the natural sound of the letters provides a sustained sound rather than the more percussive consonants. To start providing some variation, the vowel parts (not including Y) will modulate back and forth between the long and short (American English) versions of the vowels. These are

vowel short long
A [æ] (cat, apple) [ei] (late, make)
E [e] (let, tell) [i:] (be, see)
I [i] (tip, pick) [ai] (ice, find)
O [o] (not, rock) [ou] (go, note)
U [ʌ] (cut, love) [u:] (rude, June)


For the consonants, let’s give them a bit of sustaining power, so they will modulate through the long vowel sounds: [ei], [i:], [ai], [ou], [u:].

In order to decide when each part modulates, we’re going back to the original coordinate sets we generated in the first post

[
  [0, 30], [1, 30], [2, 30], [3, 30], [4, 30], [5, 30], [6, 30], [7, 30], [8, 30], [9, 30], [10, 30],
  [11, 30], [12, 30], [13, 30], [14, 30], [15, 30], [16, 30], [17, 30], [18, 30], [19, 30], [20, 30],
  [21, 30], [22, 30], [23, 30], [24, 30], [25, 30], [26, 30], [27, 30], [28, 30], [29, 30], [30, 30],
  [31, 31], [32, 31], [33, 31], [34, 31], [35, 32], [36, 32], [37, 32], [38, 32], [39, 33], [40, 33],
  [41, 33], [42, 33], [43, 33], [44, 34], [45, 34], [46, 35], [47, 35], [48, 36], [49, 36], [50, 37],
  ...
]

To determine the inflection points, in English:

sort coordinates by Y value
select the coordinates at indices where rem(i, 20) == 0 # 10 total modulations
sort these coordinates by X value
attach a cycle of long and short vowel sounds
et voila! the inflection points

and in Elixir

def vowel_phoneme_modulation_points(letter) do
  @polyrhythm_generator.ordered_coordinates(letter)
  |> Enum.sort_by(fn {_, y} -> y end)
  |> Enum.with_index
  |> Enum.filter(fn { {x, y}, i} -> rem(i, 20) == 0 || x == 0 end)
  |> Enum.map(fn { { {x, _}, _}, vowel} -> {x, vowel} end)
  |> Enum.sort
  |> Enum.zip(Stream.cycle(Map.get(vowel_phoneme_pairs(), letter)))
end

so that

iex> vowel_phoneme_modulation_points("a")

gives us

[
  {0, "æ"}, {18, "ei"}, {45, "æ"}, {60, "ei"}, {68, "æ"}, {95, "ei"},
  {103, "æ"}, {124, "ei"}, {142, "æ"}, {162, "ei"}, {191, "æ"}, {192, "ei"}
]

So this means that the A part would begin by singing [æ] in the first measure, and by measure 19 (the list of measures above, like all lists in Elixir, is 0-indexed), the part would have modulated to singing [ei], and then would stay on [ei] until modulating back to [æ] by measure 46, and so on.

For the consonant parts, I want to do something a bit more involved, to make sure they’re not all modulating between vowels in the same order. To wit, I want it to work like this:

sort coordinates by Y value
select the coordinates at indices where rem(i, 20) == 0 # 10 total modulations
NEW: attach, in order, a cycle of the 5 vowel sounds
sort these coordinates by X value
et voila! the inflection points

This is almost identical to above, except that we assign vowel phonemes while they’re still sorted by Y value, so that each part, when sorted again by X value, would have its own ordering (or, at least, not a universal ordering) for the vowel additions.

As in the description above, in the Elixir code this is simply a matter of changing the order of operations:

def consonant_phoneme_modulation_points(letter) do
  @polyrhythm_generator.ordered_coordinates(letter)
  |> Enum.sort_by(fn {_, y} -> y end)
  |> Enum.with_index
  |> Enum.filter(fn { {x, y}, i} -> rem(i, 20) == 0 || x == 0 end)
  |> Enum.zip(Stream.cycle(["ei", "i:", "ai", "ou", "u:"]))
  |> Enum.map(fn { { {x, _}, _}, vowel} -> {x, vowel} end)
  |> Enum.sort
end

And as we can see, this does indeed provide us with some amount of phoneme variation between the consonant parts:

iex> consonant_phoneme_modulation_points("b")
[
  {0, "ei"}, {11, "i:"}, {21, "u:"}, {40, "ou"}, {62, "ai"}, {74, "i:"},
  {100, "ei"}, {133, "ou"}, {153, "u:"}, {160, "ai"}, {174, "ei"}, {184, "i:"}
]

iex> consonant_phoneme_modulation_points("c")
[
  {0, "ei"}, {21, "u:"}, {24, "ou"}, {36, "i:"}, {46, "ou"}, {66, "u:"},
  {74, "ai"}, {106, "ei"}, {124, "ai"}, {182, "i:"}, {191, "ei"}
]

Cool! Now let’s add it into the score.

defmodule ScoreGenerator do
  def letter_part_to_lily(letter, pulse) do
    {^letter, pitches} = Enum.find(measure_pitches(pulse), fn {l, _} ->
      l == letter
    end)
    modulation_map = phoneme_modulation_points(letter) |> Enum.into(Map.new)
    part = letter |> @dynamics_generator.measures(pulse)
    |> Enum.with_index |> Enum.map(fn { { {n, d}, notes}, i} ->
      pitch = Enum.at(pitches, i)
      # Make sure each note has the correct pitch
      notes = Enum.map(notes, fn n ->
        case n do
          "r8" -> "r8"
          note -> Regex.replace(~r/c/, note, pitch)
        end
      end)
      # if the measure is in the modulation map, attach the new phoneme
      # to the first note event
      notes = case Map.get(modulation_map, i) do
        nil -> notes
        phoneme ->
          [note|ns] = notes
          [note <> "^\\markup \"[#{phoneme}]\""|ns]
      end
      "\\tuplet #{n}/#{d} { #{Enum.join(notes, " ")} }"
    end) |> Enum.join("\n")
    write_lilypond_file(letter, part)
    {:ok, letter}
  end
end

Let’s look at a few sample pages

final/v1/page1 final/v1/page44 final/v1/page85

Great! This has come a long way from the first draft. With the musical elements, and the code that generates them, in place, it’s time to move on to some refactoring, both of the code and the resulting LilyPond output. Stay tuned!

The code I’ve written thus far, in almost exactly the same form it has appeared in in these posts, can be found here on GitHub. Future blog posts will include links to refactored code as appropriate.



Thanks for reading! If you liked this post, and want to know when the next one is coming out, follow me on Twitter (link below)!