21
\$\begingroup\$

Quick musical refresher:

The piano keyboard consists of 88 notes. On each octave, there are 12 notes, C, C♯/D♭, D, D♯/E♭, E, F, F♯/G♭, G, G♯/A♭, A, A♯/B♭ and B. Each time you hit a 'C', the pattern repeats an octave higher.

enter image description here

A note is uniquely identified by 1) the letter, including any sharps or flats, and 2) the octave, which is a number from 0 to 8. The first three notes of the keyboard, are A0, A♯/B♭ and B0. After this comes the full chromatic scale on octave 1. C1, C♯1/D♭1, D1, D♯1/E♭1, E1, F1, F♯1/G♭1, G1, G♯1/A♭1, A1, A♯1/B♭1 and B1. After this comes a full chromatic scale on octaves 2, 3, 4, 5, 6, and 7. Then, the last note is a C8.

Each note corresponds to a frequency in the 20-4100 Hz range. With A0 starting at exactly 27.500 hertz, each corresponding note is the previous note times the twelfth root of two, or roughly 1.059463. A more general formula is:

enter image description here

where n is the number of the note, with A0 being 1. (More information here)

The Challenge

Write a program or function that takes in a string representing a note, and prints or returns the frequency of that note. We will use a pound sign # for the sharp symbol (or hashtag for you youngins) and a lowercase b for the flat symbol. All inputs will look like (uppercase letter) + (optional sharp or flat) + (number) with no whitespace. If the input is outside of the range of the keyboard (lower than A0, or higher than C8), or there are invalid, missing or extra characters, this is an invalid input, and you do not have to handle it. You can also safely assume you will not get any weird inputs such as E#, or Cb.

Precision

Since infinite precision isn't really possible, we'll say that anything within one cent of the true value is acceptable. Without going into excess detail, a cent is the 1200th root of two, or 1.0005777895. Let's use a concrete example to make it more clear. Let's say your input was A4. The exact value of this note is 440 Hz. Once cent flat is 440 / 1.0005777895 = 439.7459. Once cent sharp is 440 * 1.0005777895 = 440.2542 Therefore, any number greater than 439.7459 but smaller than 440.2542 is precise enough to count.

Test cases

A0  --> 27.500
C4  --> 261.626
F#3 --> 184.997
Bb6 --> 1864.66
A#6 --> 1864.66
A4  --> 440
D9  --> Too high, invalid input.
G0  --> Too low, invalid input.
Fb5 --> Invalid input.
E   --> Missing octave, invalid input
b2  --> Lowercase, invalid input
H#4 --> H is not a real note, invalid input.

Keep in mind that you don't have to handle the invalid inputs. If your program pretends they are real inputs and prints out a value, that is acceptable. If your program crashes, that it also acceptable. Anything can happen when you get one. For the full list of inputs and outputs, see this page

As usual, this is code-golf, so standard loopholes apply, and shortest answer in bytes wins.

\$\endgroup\$
9
  • 9
    \$\begingroup\$ "H#4 --> H is not a real note, invalid input." Except in Europe. \$\endgroup\$
    – Lui
    Commented Jan 20, 2016 at 2:37
  • 6
    \$\begingroup\$ @Lui what's this thing about Europe as if the whole of Europe uses H? H meaning B is AFAIK only used in German speaking countries. (where B means Bb by the way.) What the British and Irish call B is called Si or Ti in Spain and Italy, as in Do Re Mi Fa Sol La Si. \$\endgroup\$ Commented Jan 20, 2016 at 2:45
  • 3
    \$\begingroup\$ I've played a B♯2 on a viola before, it's a perfectly reasonable note and not weird at all. \$\endgroup\$
    – Neil
    Commented Jan 20, 2016 at 13:14
  • 4
    \$\begingroup\$ @steveverrill H is used in Germany, Czech Republic, Slovakia, Poland, Hungary, Serbia, Denmark, Norway, Finland, Estonia and Austria, according to Wikipedia. (I can also confirm it for Finland myself.) \$\endgroup\$ Commented Jan 20, 2016 at 15:51
  • 6
    \$\begingroup\$ @Neil It was probably just accidental. ;) \$\endgroup\$
    – beaker
    Commented Jan 20, 2016 at 16:05

11 Answers 11

23
\$\begingroup\$

Japt, 41 37 35 34 bytes

I finally have a chance to put ¾ to good use! :-)

55*2pU¬®-1¾ª"C#D EF G A B"bZ /C} x

Try it online!

How it works

          // Implicit: U = input string, C = 12
U¨    }  // Take U, split into chars, and map each item Z by this function:
-1¾       //  Subtract 1.75 from Z. This produces NaN for non-digits.
ª"..."bZ  //  If the result is falsy (NaN), instead return the index of Z in this string.
          //  C produces 0, D -> 2, E -> 4, F -> 5, G -> 7, A -> 9, B -> 11.
          //  # -> 1, and b -> -1, so we don't need to calculate them separately.
/C        //  Divide the index by 12.
x         // Sum.
2p        // Take 2 to the power of the result.
55*       // Multiply by 55.

Test cases

All the valid test cases come through fine. It's the invalid ones where it gets weird...

input --> output       (program's reasoning)
A0  --> 27.5           (Yep, I can do that for you!)
C4  --> 261.625565...  (Yep, I can do that for you!)
F#3 --> 184.997211...  (Yep, I can do that for you!)
Bb6 --> 1864.6550...   (Yep, I can do that for you!)
A#6 --> 1864.6550...   (Yep, I can do that for you!)
A4  --> 440            (Yep, I can do that for you!)
D9  --> 9397.27257...  (Who says that's too high?)
G0  --> 24.49971...    (I've heard that note before.)
Fb5 --> 659.25511...   (Wait, Fb isn't supposed to be a note?)
E   --> 69.295657...   (I'm gonna guess that the missing octave is 1¾.)
b2  --> 61.735412...   (I assume that b means Cb...)
H#4 --> 261.625565...  (H# is C!)
\$\endgroup\$
4
  • 16
    \$\begingroup\$ +¾ for using ¾ :) \$\endgroup\$
    – anatolyg
    Commented Jan 20, 2016 at 20:50
  • 1
    \$\begingroup\$ Isn't this actually 38 bytes? \$\endgroup\$ Commented Jan 21, 2016 at 0:07
  • \$\begingroup\$ @PatrickRoberts This is 38 bytes in UTF-8, but Japt uses the ISO-8859-1 encoding, in which each character is exactly one byte. \$\endgroup\$ Commented Jan 21, 2016 at 1:11
  • \$\begingroup\$ F flat is E tho. NOT weird! (I know music) \$\endgroup\$
    – Someone
    Commented Jan 7, 2021 at 17:15
8
\$\begingroup\$

Pyth, 46 44 43 42 39 35 bytes

*55^2tsm.xsdc-x"C D EF GbA#B"d9 12z

Try it online. Test suite.

The code now uses a similar algorithm to ETHproductions's Japt answer, so credit to him for that.

Explanation

                                            implicit: z = input
       m                          z         for each character in input:
          sd                                  try parsing as number
        .x                                    if that fails:
               "C D EF GbA#B"                   string "C D EF GbA#B"
              x              d                  find index of character in that
             -                9                 subtract 9
            c                   12              divide by 12
      s                                     sum results
     t                                      decrement
   ^2                                       get the correct power of 2
*55                                         multiply by 55 (frequency of A1)

Old version (42 bytes, 39 w/ packed string)

*55^2+tsezc+-x"C D EF G A B"hz9-}\#z}\bz12

Explanation

<pre>
                                         implicit: z = input
                                     GET OFFSET BY NOTE:
              ."…"                       string "C@D@EF@G@A@B" packed
             x    hz                     get index of first char in input
            -       9                    subtract 9
                                     PARSE #/b:
           +          }\#z               add 1 if input contains "#"
                     -    }\bz           subtract 1 if input contains "b"
                                     CONVERT OFFSET TO OCTAVES:
          c                   12         divide by 12
                                     FIND OCTAVE IN INPUT:
        ez                               last char of input
       s                                 convert to number
                                     GET FINAL RESULT:
      t                                  decrement the octave
     +                                   add the octave and the note offset
   ^2                                    get the correct power of 2
*55                                      multiply by 55 (frequency of A1)
</pre>

\$\endgroup\$
2
  • \$\begingroup\$ This is interesting. How does Pyth pack strings? \$\endgroup\$
    – Luis Mendo
    Commented Jan 20, 2016 at 12:50
  • \$\begingroup\$ @LuisMendo You can find information on that in the docs. Basically, it finds the smallest base to convert the data to and then encodes the result in base 256. \$\endgroup\$ Commented Jan 20, 2016 at 15:45
7
\$\begingroup\$

Mathematica, 77 bytes

2^((Import[".mid"~Export~Sound@SoundNote@#,"RawData"][[1,3,3,1]]-69)/12)440.&

Explanation:

The main idea of this function is to convert the string of note to its relative pitch, and then calculate its frequency.

The method I use is export the sound to midi and import the raw data, but I suspect that there is a more elegant way.


Test cases:

f=%; (* assign the function above to f *)
f["A4"]    (* 440.    *)
f["A5"]    (* 880.    *)
f["C#-1"]  (* 8.66196 *)
f["Fb4"]   (* 329.628 *)
f["E4"]    (* 329.628 *)
f["E"]     (* 329.628 *)
\$\endgroup\$
1
  • 2
    \$\begingroup\$ Usually I'm sad to see Mathematica builtins that trivially solve problems, but this is actually a pretty inspired way to do it. \$\endgroup\$ Commented Jan 20, 2016 at 22:41
4
\$\begingroup\$

JavaScript ES7, 73 70 69 bytes

x=>[...x].map(c=>t+=c-1.75||"C#D EF G A B".search(c)/12,t=0)&&2**t*55

Uses the same technique as my Japt answer.

\$\endgroup\$
4
\$\begingroup\$

MATL, 56 53 50 49 48 bytes

Hj1)'C D EF G A B'=f22-'#b'"G@m]-s+ 12/G0)U+^55*

Uses current release (10.1.0), which is earlier than this challenge.

Try it online!

Explanation

H                   % push 2
j1)                 % get input string. Take first character (note)
'C D EF G A B'=f    % find index of note: 1 for C, 3 for D...
22-                 % subtract 22. This number comes from three parts:
                    % 9; 1 for 0-based indexing; 12 to subtract 1 octave
'#b'"G@m]-s         % For loop. Gives 1 if input contains '#', -1 if 'b', 0 otherwise
+                   % add to previous number. Space needed to separate from next literal
12/                 % divide by 12
G0)                 % push input and get last character (octave)
U+                  % convert to number and add to previous number
^                   % raise 2 (that was initially pushed) to accumulated number 
55*                 % multiply by 55 (=27.5*2). Implicitly display
\$\endgroup\$
3
\$\begingroup\$

Ruby, 69 65

->n{2**((n.ord*13/8%12-n.size+(n=~/#/?7:5))/12.0+n[-1].to_i)*55/4}

Ungolfed in test program

f=->n{
  2**(                    #raise 2 to the power of the following expression:
   (
     n.ord*13/8%12-       #note name C..B maps to number 0..11 calculated from the ascii code of n[0] 
     n.size+(n=~/#/?7:5)  #Correction for flat: length of n is 1 more for flat (or sharp) than for natural. Then apply correction for sharp
                          #now we have a number 3..14 for C..B (so 12 for A, will be a whole number when divided)
   )/12.0+                #divide by 12 to convert into a fraction of an octave

  n[-1].to_i              #add the octave number, last character in n
  )*                      #end of power expression, now we have A0=2,A1=4,A2=4 etc

  55/4                    #multiply to get correct frequency, this is shorter than 13.75 or 440/32                      
}

#complete octave test case
puts %w{A0 A#0 Bb0 B0 C1 C#1 Db1 D1 D#1 Eb1 E1 F1 F#1 Gb1 G1 G#1 Ab1 A1 A#1}.map{|e|[e,f[e]]}

#test case per OP
puts %w{A0 C4 F#3 Bb6 A#6}.map{|e|[e,f[e]]}

Output

A0
27.5
A#0
29.13523509488062
Bb0
29.13523509488062
B0
30.867706328507758
C1
32.70319566257483
C#1
34.64782887210901
Db1
34.64782887210901
D1
36.70809598967595
D#1
38.890872965260115
Eb1
38.890872965260115
E1
41.20344461410875
F1
43.653528929125486
F#1
46.2493028389543
Gb1
46.2493028389543
G1
48.999429497718666
G#1
51.91308719749314
Ab1
51.91308719749314
A1
55.0
A#1
58.27047018976123
A0
27.5
C4
261.6255653005986
F#3
184.9972113558172
Bb6
1864.6550460723593
A#6
1864.6550460723593
\$\endgroup\$
2
\$\begingroup\$

ES7, 82 bytes

s=>55*2**(+s.slice(-1)+("C D EF G A B".search(s[0])+(s[1]<'0')-(s[1]>'9')-21)/12)

Returns 130.8127826502993 on input of "B#2" as expected.

Edit: Saved 3 bytes thanks to @user81655.

\$\endgroup\$
5
  • \$\begingroup\$ @user81655 2*3**3*2 is 108 in Firefox's browser console, which agrees with 2*(3**3)*2. Also note that that page also says that ?: has higher precedence than = but they actually have equal precedence (consider a=b?c=d:e=f). \$\endgroup\$
    – Neil
    Commented Jan 20, 2016 at 15:30
  • \$\begingroup\$ Ah, ok. My Firefox doesn't have ** so I've never been able to test it. I think ?: does have a higher precedence than = though because in your example a is set to the result of the ternary, rather than b, then executing the ternary. The other two assignments are enclosed in the ternary so they're a special case. \$\endgroup\$
    – user81655
    Commented Jan 20, 2016 at 15:41
  • \$\begingroup\$ @user81655 How is e=f inside the ternary? \$\endgroup\$
    – Neil
    Commented Jan 20, 2016 at 20:32
  • \$\begingroup\$ Consider a=b?c=d:e=f?g:h. If they were the same precedence and the first ternary ended at the = after e, it would cause an invalid left-hand assignment error. \$\endgroup\$
    – user81655
    Commented Jan 20, 2016 at 20:56
  • \$\begingroup\$ @user81655 But that would also be a problem if ?: had higher precedence than = anyway. The expression needs to group as if it was a=(b?c=d:(e=(f?g:h))). You can't do that if they don't have the same precedence. \$\endgroup\$
    – Neil
    Commented Jan 20, 2016 at 21:17
2
\$\begingroup\$

C, 123 bytes

float d(char*s){int n=*s++,m=(n*12+(n<67?90:6))/7,o=*s++,a=o^35?o^98?0:-1:1;return exp((m+(a?*s++:o)*12+a)/17.3123-37.12);}

Usage:

#include <stdio.h>
#include <math.h>

float d(char*s){int n=*s++,m=(n*12+(n<67?90:6))/7,o=*s++,a=o^35?o^98?0:-1:1;return exp((m+(a?*s++:o)*12+a)/17.3123-37.12);}

int main()
{
    printf("%f\n", d("A4"));
}

The calculated value is always about 0.8 cents less than the exact value, because I cut as many digits as possible from the floating-point numbers.

Overview of the code:

float d(char*s){
    int n=*s++,        // read the letter
        m=(n*12+       // multiply by 12/7 to convert from A...G to 0...11
        (n<67?90:6)    // if A or B, add 1 octave; also add some fix-up rounding value
        )/7,

        o=*s++,        // read next char: the octave digit or accidental

        a=o^35?o^98?0:-1:1; // if accidental, convert it into +1 or -1; else 0

        return exp((m+ // I adjusted the factors to use exp instead of pow
            (a?*s++:o) // if was accidental, now read the octave digit
            *12+a)/
            17.3123-   // a more exact value is 17.3123404447
            37.12);    // a more exact value is 37.1193996632
}
\$\endgroup\$
1
\$\begingroup\$

R, 157 150 141 136 bytes

f=function(x){y=strsplit(x,"")[[1]];55*2^(as.double(y[nchar(x)])-1+(c(10,12,1,3,5,6,8)[LETTERS==y[1]]-switch(y[2],"#"=9,"b"=11,10))/12)}

With indent and newlines:

f=function(x){
     y=strsplit(x,"")[[1]]
     55 * 2^(as.double(y[nchar(x)]) - 1 + 
         (c(10,12,1,3,5,6,8)[LETTERS==y[1]] - 
         switch(y[2],"#"=9,"b"=11,10))/12)
     }

Usage:

> f("A0")
[1] 27.5
> f("C8")
[1] 4186.009
> sapply(c("C4","Bb6","A#6","A4"),f)
       C4       Bb6       A#6        A4 
 261.6256 1864.6550 1864.6550  440.0000 
\$\endgroup\$
1
\$\begingroup\$

Python, 97 95 bytes

def f(n):a,*b,c=n;return 2**(int(c)+('C@D@EF@G@A@B'.find(a)-(21,(22,20)['#'in b])[b>[]])/12)*55

Based on Pietu1998's (and others') old approach of looking for the index of the note in the string 'C@D@EF@G@A@B' for some blank char or another. I use iterable unpacking to parse the note string without conditionals. I did a little bit of algebra at the end to simplify the conversion expression. Don't know if I can get it shorter without changing my approach.

\$\endgroup\$
2
  • 1
    \$\begingroup\$ I think b==['#'] could be shortened to '#'in b, and not b to b>[]. \$\endgroup\$
    – Zgarb
    Commented Jan 20, 2016 at 21:31
  • \$\begingroup\$ Nice points! Works for my test suite, thanks. I think I can improve on golfing down conditionals in Python a fair bit, thanks. \$\endgroup\$
    – Ogaday
    Commented Jan 21, 2016 at 0:59
1
\$\begingroup\$

Wolfram Language (Mathematica), 69 bytes

ToExpression@("Music`"<>StringReplace[#,{"b"->"flat","#"->"sharp"}])&

Using the music package, with which simply entering a note as an expression evaluates to its frequency, like this:

 In[1]:= Eflat3
Out[1]:= 155.563

To save bytes by avoiding to import the package with <<Music, I'm using the fully qualified names: Music`Eflat3. However, I still have to replace b with flat and # with sharp to match the input format of the question, which I do with a simple StringReplace.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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