Prelude, 468 324 bytes
v4 7- + v
vv3v8- 1-(1-(1-(1-(1-(1-(1-))#7-0))))v++^ vv^^vvvv (!
?
6 9+ 4+ 4+ 4+ 4+ 4+ 0 # #v# ( v(0 )0) v
? vv03+ 4+ # ^ #v #0 # #^ #0 v#v^^vv^^^^(!0)#9!)
^ 8(1-)## 7^+# ( ^(0 )0)^^ # ^
Try it online or run the test suite. Input is an uppercase letter possibly followed by an accidental (#
or b
). Output is enharmonically correct. Inputs of Fb
, B#
, or other non-standard keys that require double sharps or double flats are not supported.
Explanation
Prelude is a 2D language inspired by music. Each line, known as a voice, has its own stack. Instructions in the same column are executed simultaneously, representing notes sounding simultaneously in different voices. Loops, demarcated by ()
pairs, run in parallel across all voices. There are no conditionals but testing for equality to zero is possible with loops.
Each chord name consists of two characters: a letter name (e.g. C
) and an accidental (e.g. #
). (Natural notes have an 'empty' accidental.) Let the codepoints of these characters be \$c_j\$ and \$a_j\$, respectively, with \$j = \text{I, IV, or V}\$. In Prelude, our goal in this challenge is to find all the \$c_j\$ and \$a_j\$.
Chord I is easy: the ?
instructions in Voices 3 and 5 read \$c_\text{I}\$ and \$a_\text{I}\$ directly from STDIN. (If there is no accidental, \$a_\text{I}\$ is read as \$0\$ (EOF), which will prove to be convenient later.) Three tasks remain:
- Find \$c_\text{IV}\$ and \$c_\text{V}\$.
- Find \$a_\text{IV}\$ and \$a_\text{V}\$.
- Print the 12 bar blues progression.
1. Finding \$c_\text{IV}\$ and \$c_\text{V}\$
Focus first on the \$c_\text{IV}\$ calculation, which takes place in Voice 2. Observe that
$$c_\text{IV} = \begin{cases}c_\text{I}+3\text{ if }c_\text{I}<69,\\
c_\text{I}-4\text{ if }c_\text{I}\ge69.
\end{cases}$$
Our strategy, therefore, is to get \$+3\$ or \$-4\$ on the stack as appropriate, then add this to \$c_\text{I}\$. Because there is no direct way to test whether \$c_\text{I}<69\$ in Prelude, we have to resort to a series of nested loops that function like conditional branches. The code between dashed lines below is only executed if \$c_\text{I}\ge69\$.
3 Push 3. Stack: [3]
v Push cI from voice below. Stack: [3, cI]
8- (In loop) 8 times, subtract 8. Stack: [3, cI-64]
1-( If cI-65 = 0, skip ahead to the matching ). Stack: [3, cI-65] -+
1-( If cI-66 = 0, skip ahead to the matching ). Stack: [3, cI-66] |
. . |
. . etc. |
. . |
------------------------------------------------------------- |
1-)) End of loops. If here, cI >= 69. Stack: [3, 0] |
# Discard top of stack. Stack: [3] |
7- Subtract 7. Stack: [-4] |
0 Push 0. Stack: [-4, 0] |
------------------------------------------------------------- |
)))) End of loops. Stack: [3, 0] or [-4, 0] <-----------------------+
v Push cI from voice below. Stack: [3, 0, cI] or [-4, 0, cI]
+ Pop top two elements and push their sum. Stack: [3, cI] or [-4, cI]
+ Pop top two elements and push their sum. Stack: [cIV]
Notice that in the inner section, we have to do #7-0
rather than just 4-
because in the latter case, the stack would be [3, -4]
and the loop would not terminate. (Loops terminate only if \$0\$ is on top of the stack.)
Here is another way to visualise how the nested loops work, with the letter names corresponding to \$c_\text{I}\$ included. At each (
, we effectively test whether \$c_\text{I}\$ exactly corresponds to the indicated letter. If it does, we go no deeper. Thus, we see that the top branch is followed if \$c_\text{I}\$ corresponds to a letter between A and D (inclusive); the bottom branch is followed otherwise.
A B C D
1-(1-(1-(1-(1- )))) -> cIV = cI+3
(1-(1-))#7-0 -> cIV = cI-4
E F
Up in Voice 1, we perform an analogous calculation for \$c_\text{V}\$:
$$c_\text{V} = \begin{cases}c_\text{I}+4\text{ if }c_\text{I}<68,\\
c_\text{I}-3\text{ if }c_\text{I}\ge68.
\end{cases}$$
The code looks much simpler because we are piggybacking on the nested loops/conditionals in Voice 2, which apply across all voices.
2. Finding \$a_\text{IV}\$ and \$a_\text{V}\$
Observe that
$$ a_\text{IV} \ne a_\text{I}\text{ if and only if }c_\text{I}=70,\\
a_\text{V} \ne a_\text{I}\text{ if and only if }c_\text{I}=66.$$
Musically speaking, there are only four keys for which we cannot simply copy the accidental from chord I to chords IV and V. These keys are F
(chord IV Bb
), F#
(chord IV B
), B
(chord V F#
), and Bb
(chord IV F
).
The \$a_\text{IV}\$ and \$a_\text{V}\$ calculations partly involve piggybacking on the loops in Voice 2, this time to pre-load values onto the stacks in Voices 4 and 6 that are then manipulated further. Let's look at how \$a_\text{V}\$ is determined in Voices 5 and 6. (The procedure for \$a_\text{IV}\$ is similar in Voices 4 and 5.) The accidental calculation itself is performed by this snippet:
#v #0 #
( ^(0 )0)^
There are three execution branches depending on the values of \$c_\text{I}\$ and \$a_\text{I}\$ and it's easiest to consider these branches separately. In each case, the stack in Voice 5 (V5) is pre-loaded with two copies of \$a_\text{I}\$. The value on top of the stack in Voice 6 (V6) when the snippet ends is \$a_\text{V}\$.
Note that for the following explanations the code runs vertically, with Voice 5 in the left column and Voice 6 in the right column. (This vertical format is used to maximise readability of the comments, but isn't a valid way to write Prelude code.)
- If \$c_\text{I}\ne 66\$ (i.e. the input is not
B
or Bb
), the stack in V6 is cleared during the pre-load stage, leaving \$0\$ as the top element. The outer loop in V6 is not entered, and a copy of \$a_\text{I}\$ (whose value may be \$0\$, \$35\$, or \$98\$) is simply yanked (^
) from V5.
V5 stack: [aI, aI], V6 stack: [0]
( Top of stack is 0 so skip ahead to the matching ). -+
# |
v^ |
( |
#0 |
0 |
) |
0 |
) <---------------------------------------------------+
#^ Discard top of stack in V5, but only after yanking it into V6. V5 stack: [aI], V6 stack: [0, aV = aI]
- If \$c_\text{I} = 66\$, we push \$35\$ (the codepoint of
#
) onto the stack in V6 during the pre-load stage. If \$a_\text{I}=0\$ (i.e. the input is B
) then we need \$a_\text{V}=35\$. After a bit of back and forth between the voices, we see that \$35\$ does indeed end up back on top of the stack in V6 at the end of the snippet.
V5 stack: [0, 0], V6 stack: [35]
( Top of stack is 35 so enter the loop.
# Discard top of stack in V5. V5 stack: [0], V6 stack: [35]
v^ Swap top stack values between V5 and V6. V5 stack: [35], V6 stack: [0]
( Top of stack is 0 so skip ahead to the matching ). -+
#0 |
0 |
) <---------------------------------------------------+
0 Push 0 in V6. V5 stack: [35], V6 stack: [0, 0]
) End loop.
#^ Discard top of stack in V5, but only after yanking it into V6. V5 stack: [0], V6 stack: [0, 0, aV = 35]
- If \$c_\text{I} = 66\$, we push \$35\$ onto the stack in V6 during the pre-load stage. If \$a_\text{I}=98\$ (i.e. the input is
Bb
) then we need \$a_\text{V}=0\$, which is indeed the value on top of the stack in V6 at the end of the snippet.
V5 stack: [98, 98], V6 stack: [35]
( Top of stack is 35 so enter the loop.
# Discard top of stack in V5. V5 stack: [98], V6 stack: [35]
v^ Swap top stack values between V5 and V6. V5 stack: [35], V6 stack: [98]
( Top of stack is 98 so enter the loop.
#0 Discard top of stack in V5. Push 0 in V6. V5 stack: [0], V6 stack: [98, 0]
0 Push 0 in V5. V5 stack: [0, 0], V6 stack: [98, 0]
) End loop.
0 Push 0 in V6. V5 stack: [0, 0], V6 stack: [98, 0, 0]
) End loop.
#^ Discard top of stack in V5, but only after yanking it into V6. V5 stack: [0], V6 stack: [98, 0, 0, aV = 0]
3. Printing the progression
Printing the progression is an exercise in stack management. The goal is to get all of the \$c_j\$ onto one stack (Voice 2) and all of the \$a_j\$ onto another (Voice 5), then churn them all out in a single loop. There's more than one way to do this, and the code went through several iterations before I settled on the current order of voices.
The print loop is spread across two voices and is notable for the fact that the starting (
and ending )
are not in the same voice (a byte-saving optimisation). The !
in Voice 2 prints each of the letters \$c_j\$. The accidentals \$a_j\$ require special treatment. For any \$a_j=0\$ (corresponding to a natural note) no character should be printed, but a bare !
will print a null byte. The way around this is to use (!0)#
to print only if \$a_j\ne0\$. Finally, a separator is needed. It's cheap to push and print a tab (codepoint \$9\$) on demand.
Fugue and Prelude are dual languages: Fugue uses musical notation to encode Prelude instructions. I'm not sure how it should be scored (no pun intended) but I'd suggest that the byte count of its Prelude twin (above) is a reasonable measure.
The musical score was generated from the Prelude source code using a custom Ruby script to metaprogram the LilyPond engraving. You can listen to the MIDI or view the PDF score, Ruby script, and LilyPond source on Github. There is a very old Fugue compiler that reads MIDI files as source code, but I was unable to get it to work.
The piece, titled Fugacity, is scored for trumpet, electric guitar, tenor sax (two-note cameo), double bass, and piano. (The choice of instruments is artistic and arbitrary as far as Fugue is concerned.) How does it sound? Not at all bluesy, but I've definitely heard worse.
Generating the score
To get a basic score to begin with, all no-ops (spaces) are converted to crotchet rests and most Prelude instructions are converted to crotchets. There are two exceptions:
Push commands are converted to a pair of quavers. The first quaver represents the push instruction itself (ascending or descending third) and the second quaver represents the value being pushed.
Some effort is taken to keep the parts within the playing ranges of the instruments. To this end, jumps of 10 semitones or more (which are no-ops) are automatically added. These range-limiting jumps become the second of a pair of quavers, with the first quaver being the instruction that triggered the range limiter.
All voices are padded to the same length with rests, which isn't necessary for Fugue but does make the score a bit nicer to read.
With this basic score to work from, some artistic embellishments are introduced for increased musicality. The following adjustments occur automatically:
- A crotchet followed by one, two, or three crotchet rests in the same bar is replaced by a minim, dotted minim, or semibreve with \$50\;\%\$ probability.
- Consecutive rests are consolidated.
- An accent is added to each note with \$25\;\%\$ probability.
- A dynamic change is added to each run of notes with \$50\;\%\$ probability. An initial forte dynamic is applied to all voices. Louder dynamics are favoured over softer ones.
- A crescendo or decrescendo hairpin is added between runs of at least three notes with \$50\;\%\$ probability.
- Slurs are added between runs of at least two non-unison notes with \$50\;\%\$ probability.
The first note in each voice is arbitrary. I toyed with several ideas but finally settled on a unison C. It's interesting to hear how the parts diverge from a common starting point.
C C C C Cxx# Cxx# C C Cxxx# Cxx# C C
? :) \$\endgroup\$Db
instead ofC#
\$\endgroup\$Cbb
,Ex#
, andFbbbbbbbbbbb
? \$\endgroup\$