names = list('CDEFGAB')
solfege = ['Do', 'Re', 'Mi', 'Fa', 'So', 'La', 'Ti']
offsets = {'C': 0, 'D': 2, 'E': 4, 'F': 5, 'G': 7, 'A': 9, 'B': 11}
offsets.update({note + '#': off + 1 for note, off in offsets.items()})
offsets.update({note + 'b': off - 1 for note, off in offsets.items() if len(note) == 1})
def clean_name(note_name):
"""Cancel out sharp/flat pairs, and change a double sharp to the standard x symbol."""
base = note_name[0]
offset = note_name.count('#') - note_name.count('b')
return base + ('' if offset == 0 else '#' if offset == 1 else 'x' if offset == 2 else 'b' * -offset)
def note(scale, position):
base_name = scale[0]
suffix = scale[1:]
start = names.index(base_name)
# Wrap to start the scale in the right place
scale_names = [name + suffix for name in names[start:] + names[:start]]
offset = offsets[scale]
# expected offsets is just the standard 0,2,4,5,7,9,11
expected_offsets = sorted({v for k, v in offsets.items() if len(k) == 1})
# Find our current offsets, based on the note names
true_offsets = [(offsets[note] - offset) % 12 for note in scale_names]
# See how we need to add accidentals to get a proper major scale
adjustments = [expected - true for expected, true in zip(expected_offsets, true_offsets)]
scale_names = [clean_name(name + ('#' if adj == 1 else 'b' if adj == -1 else '')) for name, adj in
zip(scale_names, adjustments)]
return scale_names[solfege.index(position)]
if __name__ == '__main__':
print(note('C', 'Do')) # C
print(note('C', 'Re')) # D
print(note('C', 'Mi')) # E
print(note('D', 'Mi')) # F#
print(note('Bb', 'Fa')) # Eb
print(note('A#', 'Fa')) # D#
print(note('A#', 'Mi')) # Cx (C##)
print(note('Fb', 'Fa')) # Bbb
bmFtZXMgPSBsaXN0KCdDREVGR0FCJykKc29sZmVnZSA9IFsnRG8nLCAnUmUnLCAnTWknLCAnRmEnLCAnU28nLCAnTGEnLCAnVGknXQpvZmZzZXRzID0geydDJzogMCwgJ0QnOiAyLCAnRSc6IDQsICdGJzogNSwgJ0cnOiA3LCAnQSc6IDksICdCJzogMTF9Cm9mZnNldHMudXBkYXRlKHtub3RlICsgJyMnOiBvZmYgKyAxIGZvciBub3RlLCBvZmYgaW4gb2Zmc2V0cy5pdGVtcygpfSkKb2Zmc2V0cy51cGRhdGUoe25vdGUgKyAnYic6IG9mZiAtIDEgZm9yIG5vdGUsIG9mZiBpbiBvZmZzZXRzLml0ZW1zKCkgaWYgbGVuKG5vdGUpID09IDF9KQoKCmRlZiBjbGVhbl9uYW1lKG5vdGVfbmFtZSk6CiAgICAiIiJDYW5jZWwgb3V0IHNoYXJwL2ZsYXQgcGFpcnMsIGFuZCBjaGFuZ2UgYSBkb3VibGUgc2hhcnAgdG8gdGhlIHN0YW5kYXJkIHggc3ltYm9sLiIiIgogICAgYmFzZSA9IG5vdGVfbmFtZVswXQogICAgb2Zmc2V0ID0gbm90ZV9uYW1lLmNvdW50KCcjJykgLSBub3RlX25hbWUuY291bnQoJ2InKQogICAgcmV0dXJuIGJhc2UgKyAoJycgaWYgb2Zmc2V0ID09IDAgZWxzZSAnIycgaWYgb2Zmc2V0ID09IDEgZWxzZSAneCcgaWYgb2Zmc2V0ID09IDIgZWxzZSAnYicgKiAtb2Zmc2V0KQoKCmRlZiBub3RlKHNjYWxlLCBwb3NpdGlvbik6CiAgICBiYXNlX25hbWUgPSBzY2FsZVswXQogICAgc3VmZml4ID0gc2NhbGVbMTpdCiAgICBzdGFydCA9IG5hbWVzLmluZGV4KGJhc2VfbmFtZSkKICAgICMgV3JhcCB0byBzdGFydCB0aGUgc2NhbGUgaW4gdGhlIHJpZ2h0IHBsYWNlCiAgICBzY2FsZV9uYW1lcyA9IFtuYW1lICsgc3VmZml4IGZvciBuYW1lIGluIG5hbWVzW3N0YXJ0Ol0gKyBuYW1lc1s6c3RhcnRdXQogICAgb2Zmc2V0ID0gb2Zmc2V0c1tzY2FsZV0KICAgICMgZXhwZWN0ZWQgb2Zmc2V0cyBpcyBqdXN0IHRoZSBzdGFuZGFyZCAwLDIsNCw1LDcsOSwxMQogICAgZXhwZWN0ZWRfb2Zmc2V0cyA9IHNvcnRlZCh7diBmb3IgaywgdiBpbiBvZmZzZXRzLml0ZW1zKCkgaWYgbGVuKGspID09IDF9KQogICAgIyBGaW5kIG91ciBjdXJyZW50IG9mZnNldHMsIGJhc2VkIG9uIHRoZSBub3RlIG5hbWVzCiAgICB0cnVlX29mZnNldHMgPSBbKG9mZnNldHNbbm90ZV0gLSBvZmZzZXQpICUgMTIgZm9yIG5vdGUgaW4gc2NhbGVfbmFtZXNdCiAgICAjIFNlZSBob3cgd2UgbmVlZCB0byBhZGQgYWNjaWRlbnRhbHMgdG8gZ2V0IGEgcHJvcGVyIG1ham9yIHNjYWxlCiAgICBhZGp1c3RtZW50cyA9IFtleHBlY3RlZCAtIHRydWUgZm9yIGV4cGVjdGVkLCB0cnVlIGluIHppcChleHBlY3RlZF9vZmZzZXRzLCB0cnVlX29mZnNldHMpXQogICAgc2NhbGVfbmFtZXMgPSBbY2xlYW5fbmFtZShuYW1lICsgKCcjJyBpZiBhZGogPT0gMSBlbHNlICdiJyBpZiBhZGogPT0gLTEgZWxzZSAnJykpIGZvciBuYW1lLCBhZGogaW4KICAgICAgICAgICAgICAgICAgIHppcChzY2FsZV9uYW1lcywgYWRqdXN0bWVudHMpXQogICAgcmV0dXJuIHNjYWxlX25hbWVzW3NvbGZlZ2UuaW5kZXgocG9zaXRpb24pXQoKCmlmIF9fbmFtZV9fID09ICdfX21haW5fXyc6CiAgICBwcmludChub3RlKCdDJywgJ0RvJykpICAjIEMKICAgIHByaW50KG5vdGUoJ0MnLCAnUmUnKSkgICMgRAogICAgcHJpbnQobm90ZSgnQycsICdNaScpKSAgIyBFCiAgICBwcmludChub3RlKCdEJywgJ01pJykpICAjIEYjCiAgICBwcmludChub3RlKCdCYicsICdGYScpKSAgIyBFYgogICAgcHJpbnQobm90ZSgnQSMnLCAnRmEnKSkgICMgRCMKICAgIHByaW50KG5vdGUoJ0EjJywgJ01pJykpICAjIEN4IChDIyMpCiAgICBwcmludChub3RlKCdGYicsICdGYScpKSAgIyBCYmI=