Pregunta

I am trying to write a program for getting the notes of a musical scale. This is what I've got up to now but it seems ridiculously complicated!! what am I missing? Is it supposed to be like that?

notes = [ "c", "c#", "d", "d#", "e", "f", "f#", "g", "g#", "a", "a#", "b" ]
major = [2,2,1,2,2,2] # semitone steps
root  = "f"
root_i= notes.index(root)

index = [root_i+i for i in [sum(major[:y]) for y in range(len(major)+1)]]
scale = [notes[i] if i < len(notes) else notes[i-len(notes)] for i in index]

I just need to increment the root_i by each "step" in major and restart when i reach the end of notes...

Thanks.

¿Fue útil?

Solución 2

The simplest?

scale = [notes[(y+root_i)%len(notes)] for y in [0,2,4,5,7,9,11]]

or even

scale = [notes[(y+notes.index(root))%len(notes)] for y in [0,2,4,5,7,9,11]]

you don't need root_i or index

Otros consejos

If you care about getting correct spelling of musical notes, you're going to need a more sophisticated approach. For instance, your F# major scale will read [F#, G#, A#, B, C#, D#, F], when what you really want is E# for the leading tone. Similarly, if you care about spelling, you'll need to implement flats as well. If you care about diatonic scales besides major scales (natural and harmonic minor, Lydian, etc.), you'll also need to decouple the note spacing from the desired scale spacing. What you'll instead want is something more complex like:

def getScale(root='C', mode='major')
    noteNames = ['C','D','E','F','G','A','B']
    noteSpacing = [2,2,1,2,2,2,1]
    if mode == 'natural minor':
        scaleSpacing = [2,1,2,2,1,2,2]
    elif mode == 'harmonic minor':
        scaleSpacing = [2,1,2,2,1,3,1]
    else:
        scaleSpacing = [2,2,1,2,2,2,1]

    startingIndex = noteNames.index(root[0])

    baseSemitoneOffset = root.count('#') - root.count('b')
    currentSemitones = 0
    correctSemitones = 0

    scale = [root]
    for noteDegree in range(1, 7):
        currentIndex = (startingIndex + noteDegree) % len(noteNames)
        currentSemitones += scaleSpacing[(noteDegree -1) % len(noteNames)]
        correctSemitones += noteSpacing[(currentIndex - 1) % len(noteNames)]
        currentSemitonesWithOffset = currentSemitones + baseSemitoneOffset
        thisNoteStep = noteNames[currentIndex]
        if currentSemitonesWithOffset < correctSemitones:
            thisNoteName = thisNoteStep + 'b' * (correctSemitones - currentSemitonesWithOffset)
        elif currentSemitonesWithOffset > correctSemitones:
            thisNoteName = thisNoteStep + '#' * (currentSemitonesWithOffset - correctSemitones)
        else:
            thisNoteName = thisNoteStep
        #print thisNoteName, currentSemitonesWithOffset, currentSemitones, correctSemitones
        scale.append(thisNoteName)

    return scale

Which for these values returns what you'd expect

print getScale('C')
print getScale('Ab')
print getScale('F#')

['C', 'D', 'E', 'F', 'G', 'A', 'B']
['Ab', 'Bb', 'C', 'Db', 'Eb', 'F', 'G']
['F#', 'G#', 'A#', 'B', 'C#', 'D#', 'E#']

And works for more obscure scales:

print getScale('C', mode='harmonic minor')
print getScale('Ab', mode='natural minor')
print getScale('Fb', mode='major')

['C', 'D', 'Eb', 'F', 'G', 'Ab', 'B']
['Ab', 'Bb', 'Cb', 'Db', 'Eb', 'Fb', 'Gb']
['Fb', 'Gb', 'Ab', 'Bbb', 'Cb', 'Db', 'Eb']

There's a real assumption that music theory is much easier than graphics or audio to implement in a computer...and it is, but not THAT much easier. Python programmers might be interested in the book Music for Geeks and Nerds by Pedro Kroger; or if you want to get into deeper music theory problems (melodic minor scales, which differ ascending and descending; non-octave repeating scales, etc.) you might (shameless plug for my own work) look at the music21 Python toolkit, especially the music21.scale module.

One way of doing it is using a deque, but there's nothing really wrong with the list based approach. I'd just tend to make it a bit more obvious as to what's going on by putting it in its own function...

from collections import deque

notes = [ "c", "c#", "d", "d#", "e", "f", "f#", "g", "g#", "a", "a#", "b" ]

def get_scale(seq, start):
    d = deque(seq)
    d.rotate(-seq.index(start)) 
    yield d[0]
    for idx in [2, 2, 1, 2, 2, 2]:
        d.rotate(-idx) # always bring element to index 0
        yield d[0] 

print list(get_scale(notes, 'c'))

And then, you might as well pre-compute the lot:

>>> scales = {k:list(get_scale(notes, k)) for k in notes}
>>> scales
{'a': ['a', 'b', 'c#', 'd', 'e', 'f#', 'g#'], 'c': ['c', 'd', 'e', 'f', 'g', 'a', 'b'], 'b': ['b', 'c#', 'd#', 'e', 'f#', 'g#', 'a#'], 'e': ['e', 'f#', 'g#', 'a', 'b', 'c#', 'd#'], 'd': ['d', 'e', 'f#', 'g', 'a', 'b', 'c#'], 'g': ['g', 'a', 'b', 'c', 'd', 'e', 'f#'], 'f': ['f', 'g', 'a', 'a#', 'c', 'd', 'e'], 'c#': ['c#', 'd#', 'f', 'f#', 'g#', 'a#', 'c'], 'd#': ['d#', 'f', 'g', 'g#', 'a#', 'c', 'd'], 'f#': ['f#', 'g#', 'a#', 'b', 'c#', 'd#', 'f'], 'g#': ['g#', 'a#', 'c', 'c#', 'd#', 'f', 'g'], 'a#': ['a#', 'c', 'd', 'd#', 'f', 'g', 'a']}
>>> scales['d']
['d', 'e', 'f#', 'g', 'a', 'b', 'c#']
>>> scales['c']
['c', 'd', 'e', 'f', 'g', 'a', 'b']
scale = [notes[i] if i < len(notes) else notes[i-len(notes)] for i in index]

can be written as

scale = [notes[i % len(notes)] for i in index]

The whole can be rewritten using itertools:

import itertools as it
notes = [ "c", "c#", "d", "d#", "e", "f", "f#", "g", "g#", "a", "a#", "b" ]
major = [2,2,1,2,2,2] # semitone steps
root  = "f"

note_iter = it.dropwhile(root.__ne__, it.cycle(notes))
scale = [list(it.islice(note_iter, m))[0] for m in major]

or a "one"-liner:

scale = [n for i, n in it.izip(it.chain.from_iterable(xrange(m) for m in major),
                               it.dropwhile(root.__ne__, it.cycle(notes)))
           if i == 0]
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top