Prelude, 468468 324 bytes
?8(1-)# ^v v4 7- ! !! !! !! !v! ^!v! ^!! !! ! + ^!v! ^!! !!
7^ 8vv3v8- 1-(1-(1-(1-(1-(1-(#1-))##7-0))))#4-^ +
7 ^ 1- 1- #v++^ #3- vv^^vvvv ^+(!
?
6 9+ 9+ 9+4+ 8+4+ 4+ 4+ 4+ 0 # 0 # # 7(1-)##v# ( v(0 )0)vv #(!) v(!)v v ^! # (!) v(!)v(!)
? v v vv03+ 4+ # ^ #v #0 # #^ #0 #(!)^(!)^v#v^^vv^^^^(!0)^(#9!)^
^ #(!)^(!)v8(!1-)^
^ # 3v+### 7^+# ( ^(0 )0)^
^^ 4+ # ^
Try it onlineTry it online or run the test suiterun 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
The code hasEach chord name consists of two partscharacters: an initialisation stagea letter name (e.g. C
) and an output stageaccidental (e. I'm not sure how optimal the code is but I did manage to golf 282 bytes off my first working versiong. #
). (Most ofNatural notes have an 'empty' accidental.) Let the savings came from finding ways to shorten Voices 2codepoints of these characters be \$c_j\$ and \$a_j\$, 3respectively, with \$j = \text{I, IV, or V}\$. In Prelude, our goal in this challenge is to find all the \$c_j\$ and 7\$a_j\$.)
Initialisation
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}\$
The initialisation stage runs from column 1 until just before theFocus first on the !
\$c_\text{IV}\$ calculation, which takes place in Voice 12. Each of the seven voices creates and/or stores (the codepoint of) one of the seven characters needed for output: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\$.
I3 letter (cI) Push 3. Stack: [3]
IV letterv (cIV) Push cI from voice below. Stack: [3, cI]
V letter 8- (cVIn loop) 8 times, subtract 8. Stack: [3, cI-64]
IV accidental 1-(aIV If cI-65 = 0, skip ahead to the matching ). Stack: [3, cI-65] -+
I accidental 1-(aI If cI-66 = 0, skip ahead to the matching ). Stack: [3, cI-66] |
V accidental (aV . . |
. . etc. |
. . |
------------------------------------------------------------- |
1-)) End of loops. If here, cI >= 69. Stack: [3, 0] |
space (s # 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]
For convenience, I'll use the short namesNotice that in parentheses above to referthe inner section, we have to these codepoints. Flats and sharps are stored as \$98\$ and \$35\$ (the codepoints ofdo b#7-0
andrather than just #4-
) because in the latter case, respectivelythe stack would be [3, -4]
and the loop would not terminate. Naturals are stored as(Loops terminate only if \$0\$, both because this is the EOF value returned by the read instruction (?
) ifon top of the input contains only one character and because it facilitates conditional printing laterstack. The codepoints are generated as follows:)
Voices 1 and 5: \$c_\text{I}\$ and \$a_\text{I}\$ are read from STDIN.
Voice 7: \$s\$ is generated in a simple loop:
8(1-)
in Voice 1 combined with4+
in Voice 7 leaves \$32\$ on the seventh stack.Voice 2: \$ c_\text{IV} = \begin{cases}c_\text{I}+3,\;c_\text{I}<69;\\ c_\text{I}-4,\;c_\text{I}\ge69. \end{cases}\$
The strategy is therefore to get \$+3\$ or \$-4\$ on the stack as appropriate, then add this to \$c_\text{I}\$. The code is a little complicated because (i) there is no direct way to test whether \$c_\text{I}<69\$ and (ii) even narrowing \$c_\text{I}\$ down to a range of values isn't sufficient; we actually need to do different things here and elsewhere depending on the exact value of \$c_\text{I}\$. To make the nested loops in the code below a bit clearer, I've labelled them A-G corresponding to the value of \$c_\text{I}\$ needed to enter each loop:
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.
7 Push 7
^ Push cI from voice above
8- SubtractA 64 (8B times 8)C from cID
1-(1-(1-(1-(1-(1-(#))#)))) Repeatedly subtract 1, skipping inner loops once the result becomes 0.
A B C D E F G Leave 0 on the stack if)))) cI-> <cIV 69.= cI+3
Pop 0 from the stack (leaving 7 uppermost1-(1-) otherwise.
# Pop top of stack
4)#7- Subtract 40 from top of stack, yielding-> 3cIV if= cI < 69 and -4 otherwise
^+ E Add to cI,F giving cIV
Voice 3: \$c_\text{V}\$ is found analogously to \$c_\text{IV}\$. Read \$c_\text{V}\$, \$+4\$, \$-3\$, and \$68\$ for \$c_\text{IV}\$, \$+3\$, \$-4\$, and \$69\$ in the description for Voice 2.
Voice 4: \$a_\text{IV}\$ differs from \$a_\text{I}\$ only if \$c_\text{I}\$ corresponds to the letter F. In that case, we arrange to have \$98\$ and \$0\$ on the stack (\$0\$ uppermost). In every other case, we ensure that there is only one (unimportant) value on the stack. Now pop the top value. From this point the code involves a bit of juggling between Voices 4 and 5 to keep copies of values that we need, but the basic idea is as follows:
If the new top value in Voice 4 is \$98\$, \$a_\text{IV}\$ differs from \$a_\text{I}\$ so we enter the
( v(0 )0)
loops.- If \$a_\text{I}\ne0\$, the input was
F#
so chord IV is B (i.e. \$a_\text{IV}=0\$). Enter the inner loop and push \$0\$ onto the stack. - If \$a_\text{I}=0\$, the input was
F
so chord IV is Bb. Leave \$98\$ on the stack.
- If \$a_\text{I}\ne0\$, the input was
Otherwise, the stack is empty, so \$a_\text{IV}=a_\text{I}\$. The loops are bypassed and \$a_\text{I}\$ is simply copied from the voice below.
Voice 6: \$a_\text{V}\$ is found analogously to \$a_\text{IV}\$. Read \$a_\text{V}\$, B, \$35\$,
( ^(0 )0)
,Bb
, F,B
, and F# for \$a_\text{IV}\$, F, \$98\$,( v(0 )0)
,F#
, B,F
, and Bb in the description for Voice 4.
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.
Output
2. Finding \$a_\text{IV}\$ and \$a_\text{V}\$
The remainder ofObserve 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 code handles outputaccidental from chord I to chords IV and V. The print instructionThese keys are !F
converts codepoints to characters by default (which is what we wantchord IV Bb
). Printing isn't entirely straightforward because there is no way to print without popping and no way to duplicate the top of a stack. Instead, we have to use adjacent voices as temporary storageF#
(chord IV B
), pulling B
(chord V ^F#
), and Bb
(chord IV F
).
The \$a_\text{IV}\$ and v
)\$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 clearing6 that are then manipulated further. Let's look at how \$a_\text{V}\$ is determined in Voices 5 and 6. (#
) values as necessaryThe 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)^
AccidentalsThere 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
orBb
), 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 isB
) 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 handlingtreatment. For any \$a_i\$ that is \$0\$\$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_i\ne0\$\$a_j\ne0\$. A side effectFinally, a separator is that it's not possibleneeded. It's cheap to stack multiple copies of flats or sharps inpush and print a voice becausetab (!)
will print all of them at once. Instead, they must be yanked from an adjacent voice(codepoint \$9\$) on demand.
The musical score was generated from the Prelude source code using a custom RubyRuby script to metaprogram the LilypondLilyPond engraving. You can listen to the MIDI or view the Lilypond source, PDF score, and Ruby script (rather atrociously hacked together), and LilyPond source on GithubGithub. There is a very old Fugue compiler hereFugue compiler that reads MIDI files as source code, but I was unable to get it to work.
The piece, titled Revolution 12Fugacity, is a septetscored for violintrumpet, violaelectric guitar, cellotenor sax (two-note cameo), double bass, clarinet, bassoon, and hornpiano. (Seven voices are required but theThe choice of instruments is artistic and arbitrary as far as Fugue is concerned.) As a starting point, most of the Prelude instructions were converted to crotchets andHow does it sound? Not at all no-ops (spaces) to crotchet rests. The exception was push commands, which were converted to a pair of quavers. The first quaver represents the push instruction itself and the second quaver represents the value being pushed. In this way, simultaneous commands were kept synchronised. All voices were padded to the same length with restsbluesy, which isn't necessary for Fugue but does make the score a bit easier to readI'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 adjustments were madeembellishments are introduced for increased musicality. To keep all pitches within the playing ranges of the instruments, no-op jumps of a seventh or an octave were manually inserted in various places, either by replacing a crotchet with a pair of quavers or by replacing a crotchet rest with a crotchet. (My apologies to the double bass player.) The following adjustments were automatedoccur automatically:
- A crotchet followed by one, two, or three crotchet rests in the same bar wasis replaced by a minim, dotted minim, or semibreve with \$50\;\%\$ probability.
- Consecutive rests wereare consolidated.
- An accent wasis added to each note with \$25\;\%\$ probability.
- A dynamic change wasis added to each noterun of notes with \$10\;\%\$\$50\;\%\$ probability and louder dynamics favoured over softer ones. (AnAn initial forte dynamic wasis applied to all voices.) Louder dynamics are favoured over softer ones.
- A crescendo or decrescendo hairpin wasis added between runs of at least three notes with \$50\;\%\$ probability.
- Slurs wereare added between runs of at least two non-unison notes with \$50\;\%\$ probability.
The first note in each voice is arbitrary. I toyed with a walking bass line staggered across the voicesseveral ideas but didn't like the result andfinally settled on a simple V-IV-I progressionunison C. It's interesting to hear how the parts diverge from a common starting point.