# Guidelines

Given two notes, inputted as strings or lists/arrays, calculate how many semitones apart they are (inclusive of the notes themselves), outputting as a number.

Explanation of a semitone:

A semitone is one step up or down the keyboard. An example is C to C#. As you can see below the note C is on a white note and C# is the black note just one above it. Semitones are the leaps from a black note to the next white note, up or down, except for:

• B to C
• C to B
• E to F
• F to E

### Examples

'A, C' -> 4

'G, G#' -> 2

'F#, B' -> 6

'Bb, Bb' -> 13

### Rules

• The largest distance between two notes is 13 semitones.
• The second inputted note will always be above the first inputted note.
• You can take input as either a string, or an array/list. If you take it as a string, the notes will be comma-separated (e.g. String -> 'A, F', Array -> ['A', 'F']).
• You can assume that you will always be given two valid notes.
• Sharps will be denoted as # and flats will be denoted as b
• Your code must support enharmonic equivalents (e.g. It must support both F# and Gb)
• Your code does not need to support notes that are named with, but can be named without a sharp or flat (i.e. You do not need to support E#, or Cb). Bonus points if your code does support it though.
• Your code does not need to support double sharps or double flats.
• You can assume that if you get the both the same notes, or same pitch (e.g. 'Gb, Gb' or 'A#, Bb'), the second not will be exactly one octave above the first.
• This is code golf so the answer with the least amount of bytes wins.
• I get 2 for G -> G# because they're both included. Feb 20, 2018 at 7:05
• @HyperNeutrino Yep sorry. Mistake on my behalf. Feb 20, 2018 at 7:06
• Do we have to cater for notes like Cb or E#? What about double sharps/flats?
– Sok
Feb 20, 2018 at 8:20
• @Sok No, your code does not need to support notes such as E# or Cb, and it does not need to support double sharps or flats. I've updated the question to make it clearer. Sorry about any confusion. Feb 20, 2018 at 11:37
• Just to be clear, when talking from a music theory sense distance in semitones does not include the note you start on. In math it wold be represented as (X, Y] so C to C# is 1 semitone and C to C is 12 semitones.
– Dom
Feb 21, 2018 at 4:48

# Python 2, 66 bytes

r=1
for s in input():r=cmp(s[1:]+s,s)-ord(s[0])*5/3-r
print-r%12+2


Try it online!

Python 2, 68 bytes

lambda s,t:13-(q(s)-q(t))%12
q=lambda s:ord(s[0])*5/3+cmp(s,s[1:]+s)


Try it online!

• Extra points for being able to handle notes like B# and Fb, while still remaining the shortest so far. Feb 20, 2018 at 11:44

# JavaScript (ES6), 78 bytes

Saved 1 byte thanks to @Neil

Takes the notes in currying syntax (a)(b).

a=>b=>((g=n=>'0x'+'_46280ab_91735'[parseInt(n+3,36)*2%37%14])(b)-g(a)+23)%12+2


### Test cases

let f =

a=>b=>((g=n=>'0x'+'_46280ab_91735'[parseInt(n+3,36)*2%37%14])(b)-g(a)+23)%12+2

console.log(f('A')('C'))   // 4
console.log(f('G')('G#'))  // 2
console.log(f('F#')('B'))  // 6
console.log(f('Bb')('Bb')) // 13

### Hash function

The purpose of the hash function is to convert a note into a pointer in a lookup table containing the semitone offsets (C = 0, C# = 1, ..., B = 11), stored in hexadecimal.

We first append a '3' to the note and parse the resulting string in base-36, leading to an integer N. Because '#' is an invalid character, it is simply ignored, along with any character following it.

Then we compute:

H(N) = ((N * 2) MOD 37) MOD 14


Below is a summary of the results.

 note | +'3' | parsed as | base 36->10 |   *2  | %37 | %14 | offset
------+------+-----------+-------------+-------+-----+-----+--------
C   |  C3  |    c3     |         435 |   870 |  19 |   5 |  0x0
C#  |  C#3 |    c      |          12 |    24 |  24 |  10 |  0x1
Db  |  Db3 |    db3    |       17247 | 34494 |  10 |  10 |  0x1
D   |  D3  |    d3     |         471 |   942 |  17 |   3 |  0x2
D#  |  D#3 |    d      |          13 |    26 |  26 |  12 |  0x3
Eb  |  Eb3 |    eb3    |       18543 | 37086 |  12 |  12 |  0x3
E   |  E3  |    e3     |         507 |  1014 |  15 |   1 |  0x4
F   |  F3  |    f3     |         543 |  1086 |  13 |  13 |  0x5
F#  |  F#3 |    f      |          15 |    30 |  30 |   2 |  0x6
Gb  |  Gb3 |    gb3    |       21135 | 42270 |  16 |   2 |  0x6
G   |  G3  |    g3     |         579 |  1158 |  11 |  11 |  0x7
G#  |  G#3 |    g      |          16 |    32 |  32 |   4 |  0x8
Ab  |  Ab3 |    ab3    |       13359 | 26718 |   4 |   4 |  0x8
A   |  A3  |    a3     |         363 |   726 |  23 |   9 |  0x9
A#  |  A#3 |    a      |          10 |    20 |  20 |   6 |  0xa
Bb  |  Bb3 |    bb3    |       14655 | 29310 |   6 |   6 |  0xa
B   |  B3  |    b3     |         399 |   798 |  21 |   7 |  0xb


Below is the proof that this hash function ensures that a note followed by a '#' gives the same result than the next note followed by a 'b'. In this paragraph, we use the prefix @ for base-36 quantities.

For instance, Db will be converted to @db3 and C# will be converted to @c (see the previous paragraph). We want to prove that:

H(@db3) = H(@c)


Or in the general case, with Y = X + 1:

H(@Yb3) = H(@X)


@b3 is 399 in decimal. Therefore:

H(@Yb3) =
@Yb3 * 2 % 37 % 14 =
(@Y * 36 * 36 + 399) * 2 % 37 % 14 =
((@X + 1) * 36 * 36 + 399) * 2 % 37 % 14 =
(@X * 1296 + 1695) * 2 % 37 % 14


1296 is congruent to 1 modulo 37, so this can be simplified as:

(@X + 1695) * 2 % 37 % 14 =
((@X * 2 % 37 % 14) + (1695 * 2 % 37 % 14)) % 37 % 14 =
((@X * 2 % 37) + 23) % 37 % 14 =
((@X * 2 % 37) + 37 - 14) % 37 % 14 =
@X * 2 % 37 % 14 =
H(@X)


A special case is the transition from G# to Ab, as we'd expect Hb in order to comply with the above formulae. However, this one also works because:

@ab3 * 2 % 37 % 14 = @hb3 * 2 % 37 % 14 = 4

• @Neil Thanks! Your optimization saves more bytes than mine. Feb 20, 2018 at 10:09
• Huh, I actually found the reverse with my Batch solution...
– Neil
Feb 20, 2018 at 11:33
• @Neil Because the sign of the modulo in Batch is the sign of the divisor, I guess? Feb 20, 2018 at 11:42
• No, it's the sign of the dividend, as in JS, but it turned out to be a slightly golfier of correcting for the sign of the result which had been inverted due to an earlier golf.
– Neil
Feb 20, 2018 at 12:18

# Perl, 39 32 bytes

Includes +1 for p

Give the start and end notes as two lines on STDIN

(echo "A"; echo "C") | perl -pe '$\=(/#/-/b/-$\+5/3*ord)%12+$.}{'; echo  Just the code: $\=(/#/-/b/-$\+5/3*ord)%12+$.}{

• Command line flags are free now Feb 20, 2018 at 16:39
• @wastl So I've been told. I'd like to know which meta post though so I can go there and disagree :-) Feb 20, 2018 at 18:06
• My comment is a link. Feel free to click it. Feb 20, 2018 at 18:32
• Looks like this works very similarly to mine - but awesomely short for Perl, +1 Feb 21, 2018 at 23:55
• @LevelRiverSt well, this is Ton Hospel. Feb 25, 2018 at 17:01

# Japt, 27 bytes

®¬x!b"C#D EF G A"ÃrnJ uC +2


Test it online! Takes input as an array of two strings.

Also works for any amount of sharps or flats on any base note!

### Explanation

®¬x!b"C#D EF G A"ÃrnJ uC +2   Let's call the two semitones X and Y.
®                Ã            Map X and Y by
¬                              splitting each into characters,
x                             then taking the sum of
!b"C#D EF G A"               the 0-based index in this string of each char.
C -> 0, D -> 2, E -> 4, F -> 5, G -> 7, A -> 9.
# -> 1, adding 1 for each sharp in the note.
b -> -1, subtracting 1 for each flat in the note.
B also -> -1, which happens to be equivalent to 11 mod 12.
The sum will be -2 for Bb, 2 for D, 6 for F#, etc.
Now we have a list of the positions of the X and Y.
rnJ         Reduce this list with reversed subtraction, starting at -1.
This gets the difference Y - (X - (-1)), or (Y - X) - 1.
uC      Find the result modulo 12. This is 0 if the notes are 1
semitone apart, 11 if they're a full octave apart.
+2   Add 2 to the result.


# Perl 5 + -p, 66 bytes

s/,/)+0x/;y/B-G/013568/;s/#/+1/g;s/b/-1/g;$_=eval"(-(0x$_-1)%12+2"


Try it online!

Takes comma-separated values. Does also work for Cb, B#, E#, Fb and multiple #/b.

Explanation:

# input example: 'G,G#'
s/,/)+0x/; # replace separator with )+0x (0x for hex) => 'G)+0xG#'
y/B-G/013568/; # replace keys with numbers (A stays hex 10) => '8)+0x8#'
s/#/+1/g; s/b/-1/g; # replace accidentals with +1/-1 => '8)+0x8+1'
$_ = eval # evaluate => 2 "(-(0x$_-1)%12+2" # add some math => '(-(0x8)+0x8+1-1)%12+2'


Explanation for eval:

(
- (0x8) # subtract the first key => -8
+ 0x8 + 1 # add the second key => 1
- 1 # subtract 1 => 0
) % 12 # mod 12 => 0
+ 2 # add 2 => 2
# I can't use % 12 + 1 because 12 (octave) % 12 + 1 = 1, which is not allowed


# Ruby, 56 bytes

->a{a.map!{|s|s.ord*5/3-s[-1].ord/32}
13-(a[0]-a[1])%12}


Try it online!

The letters are parsed according to their ASCII code times 5/3 as follows (this gives the required number of semitones plus an offset of 108)

A    B    C    D    E    F    G
108  110  111  113  115  116  118


The last character (#, b or the letter again) is parsed as its ASCII code divided by 32 as follows

# letter (natural) b
1  { --- 2 --- }   3


This is subtracted from the letter code.

Then the final result is returned as 13-(difference in semitones)%12

# Stax, 25 24 bytes

╝─°U┤ƒXz☺=≡eA╕δ┴╬\¿☺zt┼§


Run and debug it online

The corresponding ascii representation of the same program is this.

{h9%H_H32/-c4>-c9>-mrE-v12%^^


Effectively, it calculates the keyboard index of each note using a formula, then calculates the resulting interval.

1. Start from the base note, A = 2, B = 4, ... G = 14
2. Calculate the accidental offset 2 - code / 32 where code is the ascii code of the last character.
4. If the result is > 4, subtract 1 to remove B#.
5. If the result is > 7, subtract 1 to remove E#.
6. Modularly subtract the two resulting note indexes, and add 1.
• ["F#","B"] should be 6. Feb 26, 2018 at 5:33
• Thanks. I changed one half of the calculation without adjusting the other. It's fixed. Feb 26, 2018 at 5:58

## Batch, 136 135 bytes

@set/ac=0,d=2,e=4,f=5,g=7,a=9,r=24
@call:c %2
:c
@set s=%1
@set s=%s:b=-1%
@set/ar=%s:#=+1%-r
@if not "%2"=="" cmd/cset/a13-r%%12


Explanation: The substitutions in the c subroutine replace # in the note name with +1 and b with -1. As this is case insensitive, Bb becomes -1-1. The variables for C...A (also case insensitive) are therefore chosen to be the appropriate number of semitones away from B=-1. The resulting string is then evaluated, and @xnor's trick of subtracting the result from the value gives the desired effect of subtracting the note values from each other. Edit: Finally I use @Arnauld's trick of subtracting the modulo from 13 to achieve the desired answer, saving 1 byte.

# Python 3, 95 bytes

lambda a,b:(g(b)+~g(a))%12+2
g=lambda q:[0,2,3,5,7,8,10][ord(q[0])-65]+" #".find(q.ljust(2)[1])


Try it online!

-14 bytes thanks to user71546

• -8 bytes with ord(q[0])-65 replacing "ABCDEFG".find(q[0]) ;) Feb 20, 2018 at 7:18
• Oh, -6 more bytes with (g(b)+~g(a))%12+2 replacing 1+((g(b)-g(a))%12or 12) Feb 20, 2018 at 7:20
• @user71546 oh cool, thanks! Feb 20, 2018 at 12:26

# Jelly, 28 bytes

O64_ṠH$2¦ḅ-AḤ’d5ḅ4µ€IḞṃ12FṪ‘  A monadic link accepting a list of two lists of characters and returning an integer. ### How? Performs some bizarre arithmetic on the ordinals of the input characters to map the notes onto the integers zero to twelve and then performs a base-decompression as a proxy for modulo by twelve where zero is then replaced by 12 then adds one. O64_ṠH$2¦ḅ-AḤ’d5ḅ4µ€IḞṃ12FṪ‘ - Main link, list of lists    e.g. [['F','#'],['B']]  ...or [['A','b'],['G','#']]
µ€         - for €ach note list          e.g.  ['F','#'] ['B']          ['A','b'] ['G','#']
O                            - { cast to ordinal (vectorises)    [70,35]   [66]           [65,98]   [71,35]
64                          -   literal 64
_                         -   subtract (vectorises)           [-6,29]   [-2]           [-1,-34]  [-7,29]
¦                    -   sparse application...
2                     -   ...to indices: [2] (just index 2)
$- ...do: last two links as a monad: Ṡ - sign [-6,1] [-2] [-1,-1] [-7,1] H - halve [-6,-0.5] [-2] [-1,-0.5] [-7,0.5] ḅ- - convert from base -1 5.5 -2 0.5 7.5 A - absolute value 5.5 2 0.5 7.5 Ḥ - double 11.0 4 1.0 15.0 ’ - decrement 10.0 3 0.0 14.0 d5 - divmod by 5 [2.0,2.0] [0,3] [0.0,0.0] [2.0,4.0] ḅ4 - convert from base 4 10.0 3 0.0 12.0 - } --> [10.0,3] [0.0,12.0] I - incremental differences [-7.0] [12.0] Ḟ - floor (vectorises) [-7] [12] ṃ12 - base decompress using [1-12] [[5]] [[1,12]] F - flatten [5] [1,12] Ṫ - tail 5 12 ‘ - increment 6 13  ### Also at 28 bytes... A (not so direct) port of xnor's Python 2 answer... O×5:3z60_Ṡ¥2¦60U1¦Fḅ-‘N%12+2  Try all possible cases # CJam, 67 bytes l',/~)@)@{ciW*50+_z/}_@\~@@~-@@{ci65- 2*_9>-_3>-}_@\~@@~12+\- 12%+)  Online interpreter: http://cjam.aditsu.net/ # Raku, 121 bytes I write quite a lot of music in EDOs other than 12 so I implemented an option to choose a particular EDO. {1+(round($^c*log2(3))*([-](map {m:P5/([A-G])(\#*)(b*)/;4613502.comb[$0.ord-65]+7*($1.comb-$2.comb)},($^b,$^a)))%$c||\$c)}


No TIO link because TIO has an outdated Rakudo, so here are some examples:

f("A", "C", 12) # => 4
f("Bb", "Bb", 12) # => 13
f("F#", "B", 19) # => 9
f("G", "G#", 31) # => 3
f("C", "Dbb", 31) # => 2