Task

Write a program to determine the note sounded, along with how many cents out of tune, of a string tuned to a given frequency and pressed down at a given point.

For the sake of simplicity, assume that the frequency of the sound produced and the length of the string to the right of where it is pressed are inversely proportional.

Note: this task deals solely in the fundamental tone, and not with overtones/other harmonics.

Input

Your program is given two pieces of data:

  • A string of arbitrary length, representing the string in question. This string will be marked with an X where the string is to be held down.

    [-----] is a string divided in six sections (five divisions).
    [--X--] is a string pressed at the exact center of the string.
    [X----] is a string pressed at 1/6 the length of the string. (Length used is 5/6)
    [-X--] is a string pressed at 2/5 of the length of the string. (Length used is 3/5)
    

    Assume the note is sounded using the part of the string to the right of the X.

  • A number (not necessarily an integer), signifying the frequency at which the string is tuned. The precision of this number will be at most four digits past the decimal.

It may be assumed that frequencies passed will lie between 10 Hz and 40000 Hz.

Input may be passed in a format of your choice. Please specify how input is accepted into your program in your answer.

Output

Your program must output both the closest note* in the twelve-tone equal temperament tuning system, and the number of cents away from the closest note that the sound denoted by the string would be (rounded to the nearest cent).

+n cents should be used to denote n cents sharp/above the note, and -n cents for flat/below the note.

The note should be outputted in scientific pitch notation. Assume A4 is tuned to 440Hz. Use b and # for flat/sharp notes. Note: Either sharp or flat may be used. For the note at 466.16Hz, either A# or Bb may be outputted for the note.

Format of output is up to you, as long as the output contains only the two previously specified pieces of information (i.e. printing every single possible output is not allowed).

*closest note refers to the note that is closest to the sound denoted by the input, measured in the number of cents (therefore, the note that is within 50 cents of the sound). If the sound is 50 cents away from two different notes (after rounding), then either of the two notes may be outputted.

Examples

Your program should work for all cases, not just the following examples.

Output             Input Frequency   Input String
A4,  +0  cents     220               [-----X-----]
A5,  +0  cents     220               [--------X--]
D5,  -2  cents     440               [--X--------]
B4,  -49 cents     440               [X----------]
A#4, +19 cents*    314.1592          [X-]
Eb9, +8  cents*    400               [-----------------------X]
Eb11,+8  cents*    100               [--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------X]
D#1, +49 cents*    10                [--X]
A0,  -11 cents     11.7103           [---X--]

*Either sharp or flat could have been outputted.

Potentially Helpful Links

This is so shortest answer wins.

  • I think your examples are somewhat inconsistent: According to the first one [--X--] the string is pressed in the middle of the division where the x is placed, while the last one [-X--] would be at 3/8 (not 2/5) when following this logic. Or do I understand something wrong? – flawr Aug 16 '14 at 20:57
  • @flawr for the last one, [-X--], the string is divided at 4 places (and therefore into 5 parts), and pressed at the second of these divisions. Thus, it is pressed at 2/5, and the length used is 3/5. – es1024 Aug 16 '14 at 21:25
  • Ah ok now I see, so each - basically represents the position of the divisions, thank you for explaining! – flawr Aug 16 '14 at 21:34
up vote 1 down vote accepted

BBC Basic, 161#

  REM get frequency and string. Store length of string in n for later.
  INPUT f,s$
  n=LEN(s$)

  REM store floating-point value of note in semitones above (C0-0.5). Store integer value in n% (offset effectively means it will be rounded not truncated.)
  n=LN(f*(n-1)/(n-INSTR(s$,"X"))/15.8861)/LN(2)*12
  n%=n

  REM format printing to whole numbers only
  @%=2^17

  REM Output note name and octave. Output cents (discrepancy between n and n%, minus the offset of 0.5)
  PRINT MID$("C C#D D#E F F#G G#A A#B ",n%MOD12*2+1,2);INT(n/12)'(n-0.5-n%)*100'

Score excludes comments. Not golfed yet.

Output

Performs correctly on all test cases except the two long ones. For Eb9 it seems there is one dash missing from the test case: There are 22 - and one X, which divides the string into 24 equal pieces. According to my manual calculations, this is 9600Hz, which is 37 cents above a D9. This is exactly what my program outputs. If I add another dash I get Eb9 + 8 cents. Unfortunately BBC Basic cannot handle strings over 255 characters, so the Eb11 case gives an error.

enter image description here

C, 179

main(n,d){float f;scanf("%*[^X]%nX%*[-]%n]%f",&n,&d,&f);f=log(f*d/(d-n))*17.3123-57.376;n=d=f+.5;n=n%12*7+784;printf("%c%d%c,%+2.0f cents\n",n/12,(d+9)/12,n%12/7*3+32,(f-d)*100);}

Receives the ascii picture on a line by itself, and the frequency on a separate line.

A few characters can be dropped by reducing the accuracy of the magic numbers 17.3123 and 57.376.

Without the golfing, the program looks like this:

main(n,d)
{
    float f; // 'float' and '%f' better than 'double' and '%lf'

    scanf("%*[^X]%nX%*[-]%n]%f", &n, &d, &f);
    // n is the number of chars before 'X'
    // d is the number of chars before ']'
    // f is the frequency

    // Calculate the tuned frequency
    f = f * d / (d - n);

    // Convert the frequency to logarithmic scale, relative to pitch A0
    f=log(f)*17.3123-57.376;
    // alternatively: f = log2(f / (440 / 16)) * 12;

    // Round to nearest integer
    n=d=f+.5;

    // Calculate the note name ('A', 'B', etc), multipled by 12 for convenience
    n=n%12*7+784;

    printf("%c%d%c,%+2.0f cents\n", // output example: B4 ,-49 cents
        n/12,        // note name
        (d+9)/12,    // octave number
        n%12/7*3+32, // if n%12 is large enough, it's '#' else ' ' (natural)
        (f-d)*100);  // number of cents; stdio rounds it to integer
}
  • 2
    +1 for the awesome scanf format string. I had no idea you could do that. I will check you output code later (I thought about doing this in C and although something similar occurred to me for the output, I couldn't see a way to do the whole thing competitively.) I assume d+9 is because you're indexed on note A so you have to adjust the octave number to the index on note C: I wonder if there is a way around that. – Level River St Aug 17 '14 at 16:37
  • Yes, the +9 compensates for the fact that octaves start at C. It's either that or making a similar fix to the calculation of note name. For note names, the circular shift could be implemented by a LUT, but I like the more "mathematical" way of calculating them. – anatolyg Aug 17 '14 at 18:38

JavaScript (199)

Call it e.g. as t('[X-]',314.1592)

t=(s,f)=>{l=s.length-1;p='C C# D D# E F F# G G# A B H'.split(' ');n=12*Math.log2(f*l/(l-s.indexOf('X'))/16.3515978);m=n+.5|0;return p[m%12]+(n/12|0)+' '+((n-m)*100+.5|0)}

Fixed. (Since I live in europe I used B instead of Bb and H instead of B=)

  • Flawr, are you German? I always thought of B and H as a German notation, not a European notation. UK and Ireland use Bb and B. Spain and Italy use SIb and SI (as in DO RE MI FA SOL LA SI.). Anyway it's only a saving of one character. – Level River St Aug 17 '14 at 10:40
  • Yes I am form a german speaking country, I was not aware that other european countries use that Doremi system (I only heard people use it in children education). Anyway it was primarly a joke since as you said it only saves 1 char and does not really meet the requirements=) – flawr Aug 17 '14 at 10:53
  • This seems to round the number of cents incorrectly if the number of cents is negative (for example, t('[---X--]',11.7103) (last example) gives -10 instead of -11 – es1024 Aug 18 '14 at 3:56
  • Using p="C0C#0D0D#0E0F0F#0G0G#0A0B0H".split(0) saves you an additional 2 characters. – Sean Latham Aug 18 '14 at 8:50
  • @es1024 Oh I should have known: It is because I implemented the round function by round(x) = x+.5|0 which is only correct for positive numbers, I'll fix that later. @ipi thanks! – flawr Aug 18 '14 at 11:17

Python 3: 175

import math
def t(b,s):l=len(s)-1;n=12*math.log2(b*l/(l-s.index("X"))/16.35);m=round(n);return"%s%s%+d"%(("C C# D D# E F F# G G# A A# B".split()*99)[m],m//12,round(100*(n-m)))

Ungolfed:

import math

c0 = 16.35

def tuning (base_frequency, string):
    return formatted (note_number (frequency (base_frequency, string)))

def formatted (note_number):
    return "{name}{octave:d}{cents:+d}".format (name=note_name (note_number),
                             octave=octave (note_number),
                             cents=cents_out (note_number))

def note_name (note_number):
    return ("C C# D D# E F F# G G# A A# B".split() * 99)[round (note_number)]

def note_number (frequency):
    return 12 * math.log2 (frequency / c0)

def octave (note_number):
    return round (note_number) // 12

def cents_out (note_number):
    return round (100 * (note_number - round (note_number)))

def frequency (base_frequency, string):
    string_length = len (string) - 1
    held_length = string_length - string.index ("X")
    return base_frequency * string_length / held_length

if "__main__" == __name__:

    print ("Testing functions against known values...")
    assert "A4+0"     == tuning (220,      "[-----X-----]")
    assert "A5+0"     == tuning (220,      "[--------X--]")
    assert "D5-2"     == tuning (440,      "[--X--------]")
    assert "B4-49"    == tuning (440,      "[X----------]")
    assert "A#4+19"   == tuning (314.1592, "[X-]")
    assert "D#9+8"    == tuning (400,      "[-----------------------X]")
    assert "D#11+8"   == tuning (100,      "[--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------X]")
    assert "D#1+49"   == tuning (10,       "[--X]")
    assert "A0-11"    == tuning (11.7103,  "[---X--]")
    print ("Tests passed.")

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

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