cuthbertLab / music21

music21 is a Toolkit for Computational Musicology
https://www.music21.org/
Other
2.09k stars 395 forks source link

Unexpected relation between pitch attributes and frequency or pitchspace? #96

Closed chtenb closed 9 years ago

chtenb commented 9 years ago

My apologies if this question is inappropriate here, but I couldn't find a mailing list or the like.

Consider the following testing code.

from music21 import pitch

C0 = 16.35

for f in [261, 130, 653, 64, 865]:
    p = pitch.Pitch()
    p.frequency = f

    # Compare manual frequency with music21 frequency
    f1 = p.frequency
    f2 = C0 * pow(2, p.octave) * pow(2, p.pitchClass / 12) * pow(2, p.microtone.cents / 1200)
    print(f, f1, f2)

    # Compare manual pitchspace with music21 pitchspace
    ps1 = p.ps
    ps2 = 12 * (p.octave + 1) + p.pitchClass + p.microtone.cents / 100
    print(ps1, ps2)
    print()

The output of this is

261 260.99999402174154 521.9489797003519
59.958555 71.95855499999999

130 129.99999854289362 259.974590631057
47.892097 59.892097

653 653.0000144741496 652.9362051837928
75.834954 75.834954

64 63.999998381902046 65.86890433005668
35.623683 36.123683

865 864.9999846113213 890.2594167561009
80.702359 81.202359

There is often a difference between my manual computation of the frequency resp. the pitch space and the music21 value. Note that sometimes this difference can be about an octave (like the first two C note frequencies), but mostly it is about one tone. Another weird thing is that for the third testing frequency the pitchspace values are the same while the frequencies are not.

Are my manual formulas wrong, or is music21 using the attributes in an unexpected way?

mscuthbert commented 9 years ago

Hi Chiel92 -- welcome. There is a mailing list at music21list@googlegroups.com; in the future this would be a good place to start, but you've also discovered some bugs in the system worth using GitHub for.

I thought at first that the small bugs would be from using C0 = 16.35 and not the closer approximation 16.351597831287375, and this explains the differences between 653 and 652.936; but this turns out to be insufficient for the rest. (Note: this example is in Python 3; substitute 12.0 and 1200.0 for Python 2 division) The difference between f and f1 are floating point rounding errors (we convert frequency to a pitch and microtone and then convert back to get the frequency, so 261 and 260.9999994 are as close as we can get).

One thing that could've caused some problems in the system is in conflating octave which is a fact of DIATONIC space (note names, etc.) and pitchClass which, like .ps, comes from CHROMATIC space. For instance, this is NOT a bug:

p = pitch.Pitch("C4")
p2 = pitch.Pitch("B#3")
p.ps == p2.ps  # True
p.pitchClass == p2.pitchClass # True
p.octave == p2.octave # FALSE!!!!!!

This is not a bug, because octave designations are often used in layout decisions, staff splitting etc., and the presence of an accidental (even a triple sharp, etc.) is defined never to change the octave of the note. So two notes could have the same frequency and have different octaves.

What you've found is that .pitchClass and .microtone seem to interact in strange ways. The bug is that your 216, 130, and 64 examples all have pitchClass 12. That should never be allowed to exist. They should be pitchClass 0. This is the bug that will be fixed.

The problem that arises is that pitchClass for non-12ET pitches is undefined in the literature. What should C-half-sharp's pitch class be? 0 [a type of C], 0.5 [halfway between 0 and 1], or 1 [0.5 rounds to 1]? What we have been doing is rounding to the nearest pitch, so that pitchClass is always an integer, as it is in most situation it is expected. (A C half sharp can be created with the symbol "C~"; half-flat with "C`")

The microtone, however, is an adjustment off of the displayed accidental. So for instance, C + 90c is a possible designation. Its pitchClass is 1 because it is much closer to C# than to C. However, its microtonal adjustment is 90 cents up, not 10 cents down. So by adjusting upwards from the rounded number, the difference between pitchClass and actual frequency becomes compounded, not alleviated.

Thus, it's really complicated to compute frequency manually from pitches. There are pitches defined as "7th harmonic of X" that are even more complex and "neutral" accidentals [not fully implemented or documented] which take their accidental from the prevailing key signature or context. The plan is also for .frequency to take into account transposition context (An "A4" played by a Bb clarinet is not 440 hertz if the score is defined as a transposing score). In these cases, .freq440 (currently just a synonym for .frequency) will return the naive (and fast) frequency of a note.

The bug fix will fix the pitchClass == 12 bugs, but will leave the (expected) behavior of the other calculations alone.

Thanks! I'll post this exchange to the music21list so that others can benefit from it.

mscuthbert commented 9 years ago

Fixed! Feel free to reopen or comment w/ more suggestions.

chtenb commented 9 years ago

Thanks a lot for your fast and clear reply!

mscuthbert commented 9 years ago

Thank you for bringing attention to the bug and giving me the chance to write some better documentation (to go with the next release)!