Note to those without experience in music: Through making an attempt to solve this problem, you may find that music and computer programming are similar in the ways that they implement rules and syntax. With some help from Wikipedia, you can likely solve this problem with no prior knowledge of music theory.

Write a program that can take a string representing any valid key signature (ex: C, D#, Ab) and return the appropriate chord progression for a 12 bar blues in that key.

Many aspiring jazz musicians begin their musical journey by learning the 12 bar blues. It is a popular chord progression that consists of the I, IV, and V chords of a key.

There are a few variations of the 12 bar blues, we will use the following:

I   I   I   I
IV  IV  I   I
V   IV  I   I

Where each numeral represents the chords of the given key.



Input:    C

Output:   C   C   C   C
          F   F   C   C
          G   F   C   C


Input:    Eb

Output:   Eb  Eb  Eb  Eb
          Ab  Ab  Eb  Eb
          Bb  Ab  Eb  Eb


Input:    F#

Output:   F#  F#  F#  F#
          B   B   F#  F#
          C#  B   F#  F#

Note: In music, you can write some notes two different ways. (ex: F# is equivalent to Gb). Consequently, a given input may have multiple valid answers. An answer is correct as long as it outputs chords that are correct, regardless of how they are represented.

Example - F# Alternative solution

Input:    F#

Output:   Gb  Gb  Gb  Gb
          Cb  Cb  Gb  Gb
          Db  Cb  Gb  Gb

Answers should be returned in one of the following formats:

Examples - Valid Formatting (Given input C)

Valid output 1 (multi-line string):
C   C   C   C
F   F   C   C
G   F   C   C

Valid output 2 (single line string):

Valid output 3 (1d list/array):
['C', 'C', 'C', 'C', 'F', 'F', 'C', 'C', 'G', 'F', 'C', 'C']

Valid Output 4 (2d list/array):
[['C', 'C', 'C', 'C'],
 ['F', 'F', 'C', 'C'],
 ['G', 'F', 'C', 'C']]

This is : The solution with the fewest bytes wins.

Python 3 - Sample Code, not Golfed

def twelve_bar_blues(k_input):

	chords = ['Ab', 'A', 'Bb', 'B', 'C', 'Db', 'D', 'Eb', 'E', 'F', 'Gb', 'G']*2

	if k_input not in chords and k_input[1] == '#':
		k_index = chords.index(k_input[0]) + 1
		k = chords[k_index]
		k = k_input

	I_index = chords.index(k)
	I = chords[I_index]

	IV_index = I_index + 5
	IV = chords[IV_index]

	V_index = I_index + 7
	V = chords[V_index]

	return f'{I} {I} {I} {I} {IV} {IV} {I} {I} {V} {IV} {I} {I}'

Try it online!

  • 2
    \$\begingroup\$ While you could claim that G♭ is the dominant of B, you should prefer to use F♯ wherever possible. \$\endgroup\$
    – Neil
    Feb 4 at 7:55
  • 5
    \$\begingroup\$ Can I say C C C C Cxx# Cxx# C C Cxxx# Cxx# C C? :) \$\endgroup\$
    – l4m2
    Feb 4 at 9:55
  • 3
    \$\begingroup\$ Must we handle all enharmonic equivalents of a given input, or can we assume, e.g., it will always be Db instead of C# \$\endgroup\$
    – pxeger
    Feb 4 at 10:18
  • 3
    \$\begingroup\$ The lack of an answer in C# (as I write this) is disturbing. \$\endgroup\$
    – Chris H
    Feb 4 at 14:19
  • 3
    \$\begingroup\$ Please clarify the meaning of "any valid key signature." You mentioned D#, but D# major is not among the 15 key signatures that are generally labeled on the circle of fifths (e.g. as shown in the diagram on the Wiki page you linked). If D# is "valid" as a key signature, then what about Cbb, Ex#, and Fbbbbbbbbbbb? \$\endgroup\$
    – Max
    Feb 5 at 7:23

13 Answers 13


Python 3, 113 110 bytes

l='C Db D Eb E F Gb G Ab A Bb B'.split()
def f(a):c,*a=l.index(a),a,a;I=[l[c-7]];return a*2+I*2+a+[l[c-5]]+I+a

Doesn't accept # ones. Uses flats only.

Thanks to @ovs and @l4m2 for -3 bytes.

Try it online!

Python 3, 129 bytes

l='C Db D Eb E F Gb G Ab A Bb B'.split()
def f(a):b="#"in a;c,*a=l.index(a[:~b+3])+b,a,a;I=[l[c-7]];return a*2+I*2+a+[l[c-5]]+I+a

This version accepts # keys. Additional 19 bytes :(

Try it online!

  • \$\begingroup\$ 112 bytes by shortening a=[a]*2 with iterable unpacking \$\endgroup\$
    – ovs
    Feb 4 at 9:13
  • \$\begingroup\$ -2 \$\endgroup\$
    – l4m2
    Feb 4 at 10:14
  • 2
    \$\begingroup\$ Doesn't need to work for #? \$\endgroup\$
    – Axuary
    Feb 4 at 15:09
  • \$\begingroup\$ @Axuary Added another solution to work with # \$\endgroup\$ Feb 4 at 19:05

Vyxal, 34 bytes

«⟑,HǎẆṀ,‹¹β»Ẇ→| e׫⌈:?ḟ»Ǎ‹ǒr$»fḢ+İ

Try it Online!

Takes input as notes in lowercase (db not Db or C#, this doesn't allow sharps)

Thanks to Unrelated String for saving a byte on the numbers ocmpression

«...«              # Compressed string `c db d eb e f gb g ab a bb b`
     ⌈:            # Split on spaces and make a copy
       ?ḟ          # Find the index of the input in those
         »...»fḢ   # The list [0,0,0,0,5,5,0,0,7,5,0,0]
                   # Note: This is surprisingly difficult to compress. The leading and trailing zeroes resist base compression, the high numbers make base compression inefficient, but attempting to index into [0,5,7] is worse.
                +İ # Add those numbers to the input's number and modularly index into the notes.
  • \$\begingroup\$ »×[Ḃ⟨Ǐ»9τ‹ -> »Ǎ‹ǒr$»fḢ? \$\endgroup\$ Feb 4 at 4:04
  • \$\begingroup\$ @UnrelatedString Oh, nice! \$\endgroup\$
    – emanresu A
    Feb 4 at 4:30

Excel, 75 bytes (44 formula, 31 data)

=INDEX(G:G,MOD(B6:E8+MATCH(A1,G:G,)-1,12)+1) (44 bytes) 

Link to Spreadsheet

B6:E8 are blank for the cells corresponding to the I chord; are 5 for the IV chord; and 7 for the V chord. (4 bytes) G:G contains the list of keys both sharp and flat. (27 bytes)

  • \$\begingroup\$ This is really cool: well golfed. However, the list of keys should really be 34 bytes (for 88 bytes total), because the 7 empty currently-uncounted cells in the range G13:G23 are an essential part of the data... \$\endgroup\$ Feb 4 at 17:55
  • \$\begingroup\$ I am not sure what to count them as since they are blank with 0 length. I am looking at it in the same light as referencing the default value of an array where nothing has been assigned. If I need to add something for G13:G23, then I would also need to add bytes for the blank cells in B6:E8. \$\endgroup\$
    – Axuary
    Feb 4 at 22:14

x86-16 machine code, 77 71 68 bytes

00000000: adbb fc00 d780 fc23 7c06 7502 2c0a 040b  .......#|.u.,...
00000010: 92be 3701 33c9 acd4 108a cce3 1902 c2d4  ..7.3...........
00000020: 0cbb 4401 bd48 204b 4d38 077f fa95 7402  ..D..H KM8....t.
00000030: b423 f3ab ebe0 c340 2520 1715 2000 0203  .#.....@% .. ...
00000040: 0507 080a                                ....


AD              LODSW                   ; AH = note accidental, AL = note name  
BB 00FD         MOV  BX, OFFSET SLT-'A' ; BX = semitone table (ASCII offset index)
D7              XLAT                    ; convert to semitones 
80 FC 23        CMP  AH, '#'            ; is it sharp? 
7C 06           JL   START_SONG         ; if has no accidental, let's jam
75 02           JNZ  IS_FLAT            ; if not, it's a flat 
2C 0A           SUB  AL, 10             ; "sharp" the note up one semitone
04 0B           ADD  AL, 11             ; "flat" the note up 11 semitones 
92              XCHG AX, DX             ; DL = root note (key) relative to A
BE 0138         MOV  SI, OFFSET SNG     ; SI = song pattern data 
33 C9           XOR  CX, CX             ; clear CX for counter
AC              LODSB                   ; AL = length and note packed nibbles
D4 10           AAM  16                 ; AH = # of repeats, AL = note
8A CC           MOV  CL, AH             ; CX = bar repeat counter 
E3 1A           JCXZ SONG_DONE          ; end, if the song is over 
02 C2           ADD  AL, DL             ; transpose note to correct key 
D4 0C           AAM  12                 ; AL = output note (mod 12 to fix wraparound)
BB 0144         MOV  BX, OFFSET SLT+7   ; BX = end of semitone table 
BD 2048         MOV  BP, 02048H         ; H=' ', L='H' (G+1 since loop pre-decrements)
4B              DEC  BX                 ; loop backwards through semitone table 
4D              DEC  BP                 ; walk down notes starting from G
38 07           CMP  BYTE PTR[BX], AL   ; compare note to semitone   
7F F9           JG   NOTE_LOOP          ; if note is higher than target, keep looping 
93              XCHG AX, BP             ; move output to AX
74 02           JZ   WRITE_NOTE         ; if note is exactly target, it's natural 
B4 23           MOV  AH, '#'            ; otherwise, it's sharp (ouch!) 
F3 AB           REPZ STOSW              ; write to output string (repeatedly)
EB DF           JMP  SONG_LOOP          ; jump to next song pattern 
C3              RET                     ; return to caller 

; packed song data: 
;   high nibble = repeats, low nibble = note interval 
SNG DB  40H, 25H, 20H, 17H, 15H, 20H 
; semitone lookup table 
;       A  B  C  D  E  F  G 
SLT DB  0, 2, 3, 5, 7, 8, 10

Callable function. Input string at [SI], output to [DI]. Accepts sharps or flats in input.


The first character of input string pointer is converted to a semitone scale relative to A, based on the table [0,2,3,5,7,8,10]. The pointer of this table is actually offset by 0x41 (ASCII 'A') so that the index is the ASCII value, eliminating the need to ASCII convert to 0-based index in the program. The second character's ASCII value is checked and if it's less than a '#' (ASCII 0x23) (white space, CR/LF, null, etc), the note is natural. If it is a '#', 10 is subtracted from the value followed by adding 11, for a net +1 (this is a machine code optimization to eliminate the need for a branch/jump). If it's a flat, the subtraction of 10 is skipped and only 11 is added, for a (relative) net -1. The remaining number is the input note's number of semitones relative to A -- the key, effectively.

Next, the "song" is loaded from a packed array of bytes [0x40,0x25,0x20,0x17,0x15,0x20] where the first nibble is the number of times the bar repeats and the second is the relative number of semitones up from the tonic. That number is added with the key from earlier and mod 12 to handle wraparound, leaving the root note of the chord transposed to the correct key.

Last, the note is compared to the semitone table again. Searching from the end, if it matches, the note is natural. If it's lower, the note is one note name lower and sharped. Output is then written as an array of two byte strings to the buffer passed in [DI].

enter image description here

enter image description here

Jam on!


05AB1E, 35 31 bytes


Port of @emanresuA's Vyxal answer. It also only uses lowercase inputs without sharps; and outputs (in lowercase) as single list.

Try it online or verify all test cases.


A                 # Push the lowercase alphabet
 7£               # Only leave the first 7 letters: "abcdefg"
   ÀÀ             # Rotate it right twice: "cdefgab"
     S            # Convert it to a list of characters
      D           # Duplicate it
       ¤          # Push the last character (without popping): "b"
        «         # Append it to each in the copy:
                  #  ["cb","db","eb","fb","gb","ab","bb"]
         s.ι      # Swap, and interleave the lists:
                  #  ["cb","c","db","d","eb","e","fb","f","gb","g","ab","a","bb","b"]
            ¦     # Remove the first ("cb")
             „fbK # As well as the "fb"
                  #  ["c","db","d","eb","e","f","gb","g","ab","a","bb","b"]
D                 # Duplicate it
 Ik               # Get the index of the input in this list
   •íƒò=Z•        # Push compressed integer 1000055007500
          ¦       # Remove the leading 1: "000055007500"
           S      # Convert it to a list of digits
            +     # Add the earlier index to each
             è    # Use that to 0-based modular index into the list of notes
                  # (after which the result is output implicitly)

See this 05AB1E tip of mine (section How to compress large integers?) to understand why •íƒò=Z• is 1000055007500.


Excel, 125 123 bytes (doesn't use helper data)

Saved 2 bytes thanks to Axuary

=LET(n,"G GbF E EbD DbC B BbA Ab",f,FIND(LEFT(A1),n),MID(n&n,IFERROR(MAX(f,FIND(A1,n)),f-2)+{0,0,0,0;7,7,0,0;5,7,0,0}*2,2))
  • n,"G GbF E EbD DbC B BbA Ab" stores the string of notes in reverse order.
  • f,FIND(LEFT(A1),n) finds the first character of the note. This is why the string is in reverse order so we find "G " instead of "Gb".
  • IFERROR(MAX(f,FIND(A1,n)),f-2) adjusts for sharps and flats.
  • {0,0,0,0;7,7,0,0;5,7,0,0}*2 array to add to the FIND() position from earlier.
  • MID(n&n,IFERROR(~)+{~},2) pulls out all the character pairs based on FIND() and offset by the array of fixed values.


Screenshot shows the 125 bytes version of the formula.

  • 1
    \$\begingroup\$ Nice. -2 bytes if you use {0,0,0,0;7,7,0,0;5,7,0,0}*2. \$\endgroup\$
    – Axuary
    Feb 4 at 22:55

Subleq, 112 bytes

 0:-1 6 -1  : Load first Character to [6]
 3:5 6 -28  : [6] = [6] + 28
 6:0 27 9   : Load address in 6 to 27
 9:-1 32 21 : Load second Character to [32]: if nil goto 21:
12:55 32 18 : if [32] < 66 goto 18:
15:17 27 4  : [27] = [27] - 4: move 2 notes down
18:20 27 -2 : [27] = [27] + 2: move one note up
21:27 29 24 : [29] = [29] - [27] : [29] = -[27]
24:29 30 -1 : [30] = [30] - [29] : [30] = 1 - [29]
27:0 -1 0   : print base chord
30:1 -1 0   : print flat or space
33:0 39 -1  : [39] = [39] + 1
36:0 42 -1  : [42] = [42] + 1
39:99 27 -1 : [27] = [27] - [99*] : if <=0 exit
42:99 30 -1 : [30] = [30] - [99*]
45:54 -1 -1 : print space
48:18 18 27 : [18] = 0 goto 27:

51:65 98 65 32 : Ab & A
55:66 98 66 32 : Bb & B
59:67 32       : C
61:68 98 68 32 : Db & D
65:69 98 69 32 : Eb & E
69:70 32       : F
71:71 98 71 32 : Gb & G
75:65 98 65 32 : Ab & A
79:66 98 66 32 : Bb & B
83:67 32       : C
87:68 98 68 32 : Db & D
91:69 98 69 32 : Eb & E
95:-53 -57 -59 -63 -67 -69 -73      : Negative Starting Key addresses
100:0 0 0 -10 0 10 0 -14 4 10 0 127 : Chord changes

Link to Subleq emulator spreadsheet

Got just one instruction; that's all I can use.

Don't have no functions; I'm very confused.

Coding reduction; I've got the Subleq Blues.


Python 3, 201 bytes

def b(m):
	if m not in c and m[1]=='#':k=c[c.index(m[0])+1]
	return 4*[I]+[F,F,I,I,V,F,I,I]

Try it online!

  • 1
    \$\begingroup\$ 171 bytes - you can use a .split() to create the array; you can change the if/else to a ternary, which also makes variable k obsolete; and m[1]=='#' can be and'$'>m[1]; and return 4*[I] can be return[I]*4. \$\endgroup\$ Feb 4 at 8:33

JavaScript (ES6), 106 bytes

-4 bytes thanks to @l4m2

Expects a string in [A-G][#b]? format. Returns a list of strings in the same format.


Try it online!

  • \$\begingroup\$ Needn't it handle input C#? \$\endgroup\$
    – l4m2
    Feb 4 at 9:53
  • \$\begingroup\$ @l4m2 According to the 4th revision of the question, I guess that it should -- which makes most other answers also invalid. \$\endgroup\$
    – Arnauld
    Feb 4 at 11:11
  • \$\begingroup\$ @l4m2 Now fixed. \$\endgroup\$
    – Arnauld
    Feb 4 at 11:48
  • \$\begingroup\$ 109? \$\endgroup\$
    – l4m2
    Feb 4 at 17:15
  • \$\begingroup\$ @l4m2 I think we can safely remove BC, as you first suggested. \$\endgroup\$
    – Arnauld
    Feb 4 at 17:20

Retina 0.8.2, 109 bytes


(.+ )(.+)(¶.+)
$2 $2 $2 $2¶$1$1$2 $2$3 $1$2 $2

Try it online! Link includes test cases. Enharmonic-free, but uses ## instead of x. Explanation:


Make two copies of the input, with space and newline as separators.


Transpose the first copy down a fifth.


But F transposes to B♭.


Transpose the last copy up a fifth.


But B transposes to F♯.


Cancel out the sharps and flats if transposing F♯ to B or B♭ to F.

(.+ )(.+)(¶.+)
$2 $2 $2 $2¶$1$1$2 $2$3 $1$2 $2

Generate the desired chord layout.


Charcoal, 52 bytes


Try it online! Link is to verbose version of code. Outputs all chords on one line. Enharmonic-free, but uses ## instead of x. Explanation:


Get the list of fifths.


Split the compressed string 111100112011 into digits, decrement each, and add on the index of the input in the list of fifths, adding 7 for each #, and subtracting 7 for each b. Loop over the resulting list.


Output the appropriate fifth by cyclic indexing.


Output the appropriate number of sharps or flats.

Leave a space between chords.


Ruby, 89 bytes


Try it online!

An anonymous function taking a flat, sharp or natural key signature as an argument n and outputting the following chords in order to keep with the convention of naming major chords as flats to avoid double sharps. (Chords are listed here as a circle of 5ths: B E A D G C F Bb Eb Ab Db Gb; note all the natural notes come first with the flats together at the end.)

n.ord*2%7 finds the letter in the input. The ASCII code for F is 70 so this becomes 0. G is 2 steps further round the cycle of 5ths and becomes 2, while C fills in the gap with the value 1

n[-1].ord/32*7 adjusts for flats and sharps. If the last character of the input is an uppercase letter (i.e. same as the first) it evaluates to 2*7. If it is a lowercase b it evaluates to 3*7 and if it is a symbol like # it evaluates to 1*7 . A few bytes could be saved if only handling flats is required, because it will enable a count of the length of the string to be used instead of checking for the difference between # and b

Internally, B is assigned the value 0, F is 6 and Bb is 7. To take advantage of Ruby's wraparound index on the string BEADGCF, -7 is subtracted from all values when indexing the string.

Commented code

->n{"444455443544".bytes.map{|i| #convert the magic string into bytes and iterate through the 12 chords
"BEADGCF"[-7+                    # when indexing the output string, use wraparound index -7 =B through 0 =Bb to 4 =Gb
   x=(n[-1].ord/32*7             #7 for sharp, 14 for natural, 21 for flat
      -n.ord*2%7                 #subtract note letter as a position in the cycle of 5ths
      +i                         #in order to make the chord changes, adjust by the value in the first magic string              
   )%12                          #take the note value mod 12 to get into a nice 12-note range
   ?b*x/=7}                      #divide x by 7. Then add x flat symbols ?b. x is either 0 or 1.

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.


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:

  1. Find \$c_\text{IV}\$ and \$c_\text{V}\$.
  2. Find \$a_\text{IV}\$ and \$a_\text{V}\$.
  3. 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.)

  1. 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]
  1. 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]
  1. 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.


enter image description here

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:

  1. 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.

  2. 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.


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.