def getMicroDestination(currentChord, startDistance, direction=1):

    # could also just use defineChord()
    pitches = currentChord.getPitches()

    # get starting pitch
    startPitch = dtp.distanceToPitch(startDistance)

    # remove starting pitch from pitches list - is this necessary?
    #pitches.remove(startPitch)

    # pick a random pitch remaining in the chord
    # TODO: consider not making this random
    chosenPitch = random.choice(pitches)
    # convert to tonal
    chosenTonal = ptt.pitchToTonal(chosenPitch)

    # start at bass
    chosenDistance = ptd.pitchToDistance(chosenPitch, 0)
    # reset to 0
    chosenDistance -= 24
    # move into range - assumes
    while chosenDistance < startDistance:
        chosenDistance += 12
    # fix if direction is -1
    if direction == -1:
        chosenDistance -= 12

    return chosenTonal, chosenDistance, chosenPitch
def transposeCellDiatonically(oldCell,
                              newChord,
                              newNextChord,
                              direction=1,
                              newDestinationDistance=None):

    notes = []

    # set up
    if newDestinationDistance is not None and newDestinationDistance != 88:
        if oldCell.destination < newDestinationDistance:
            direction = 1
        else:
            direction = -1
    # don't make this an else to the above if because we need to set a correct direction for transposeDistance
    if newDestinationDistance is None:
        # set a new destination distance based on the old cell's destination
        newDestinationDistance = td.transposeDistance(oldCell.destination,
                                                      oldCell.chord, newChord,
                                                      direction)
        #print("newDestinationDistance", newDestinationDistance)
        #newDestinationTonal = dtt.distanceToTonal(newDestinationDistance)

    # first get distance between old chord and new chord
    distanceBetweenTonal = newChord.root - oldCell.chord.root
    if direction == 1 and distanceBetweenTonal < 0:
        distanceBetweenTonal += 7
    elif direction == -1 and distanceBetweenTonal > 0:
        distanceBetweenTonal -= 7
    #print('distanceBetweenTonal', distanceBetweenTonal)

    for oldNote in oldCell.notes:

        # adjust pitch
        newVal = (ptt.pitchToTonal(oldNote.pitch) + distanceBetweenTonal -
                  1) % 7 + 1  # TODO: doublecheck the -1 and +1
        newPitch = ttp.tonalToPitch(newVal)
        # adjust distance
        # TODO: think diatonically, consider minor keys, etc - use pitchToDistance()
        newDistance = td.transposeDistance(oldNote.distance, oldCell.chord,
                                           newChord, direction)

        #print('transposing:', oldNote.pitch, ptt.pitchToTonal(oldNote.pitch), oldNote.distance, '--->', newPitch, newVal, newDistance)

        # add everything else
        notes.append(
            mo.Note(newPitch, newDistance, oldNote.rhythm, oldNote.tied,
                    newChord.root, oldNote.tonality, oldNote.seventh,
                    oldNote.inversion, oldNote.secondary,
                    oldNote.secondaryRoot, oldNote.key, oldNote.major,
                    oldNote.timesig))

    newCell = mo.Cell(newChord, newNextChord, oldCell.beats, notes,
                      newDestinationDistance, oldCell.voice)

    return newCell
Beispiel #3
0
def orchestrate(music, chordArray, maxVoices, species = 1):

    if maxVoices == 3:
        bass = 0
        tenor = 1
        soprano = 2
    else:
        bass = 0
        tenor = 1
        soprano = 2
        alto = 3  # keeping as 3 instead of swapping with soprano - easier to add into current code

    beats1234 = [1,2,3,4]
    beats12 = [1,2]
    beats34 = [3,4]

    measures = len(chordArray)
    # create finalMTX - a 2D array of 1D arrays (lists) of Cells - because each measure can hold 1-4 Cells
    finalMTX = np.empty((measures, maxVoices), dtype=object)

    ################################################################
    # orchestrate the first chord
    ################################################################
    # bass
    finalMTX[0][bass] = []
    for chord in range(len(chordArray[0])):
        # if 1 chord in the measure
        if len(chordArray[0]) == 1:
            finalMTX[0][bass].append(mo.Cell(chordArray[0][0], chordArray[1][0], beats1234, [mo.Note(ttp.tonalToPitch(chordArray[0][0].root), ttd.tonalToDistance(chordArray[0][0].root), 1, False, chordArray[0][0].root)], None, bass))
    # TODO: add other species

    chordNotes = [1, 3, 5]  # create array for random to choose from below

    # alto/tenor
    finalMTX[0][tenor] = []
    for chord in range(len(chordArray[0])):
        # if 1 chord in the measure
        if len(chordArray[0]) == 1:
            pitch = ttp.tonalToPitch(random.choice(chordNotes))  # pick 1, 3, or 5
            finalMTX[0][tenor].append(mo.Cell(chordArray[0][0], chordArray[1][0], beats1234, [mo.Note(pitch, ptd.pitchToDistance(pitch, tenor), 1, False, chordArray[0][0].root)], None, tenor))
    # TODO: add other species

    # soprano
    finalMTX[0][soprano] = []
    if finalMTX[0][tenor][0].notes[0].pitch == music.key:  # if 2 roots, must pick 3rd
        pitch = finalMTX[0][tenor][0].chord.getPitches()[1] # 3rd of chord
    elif finalMTX[0][tenor][0].notes[0].pitch == mo.Chord(ptt.pitchToTonal(music.key)).getPitches()[1]:  # if root and 3rd, can pick 1 or 5
        chordNotes = [1, 5]
        pitch = ttp.tonalToPitch(random.choice(chordNotes))
    else:  # if root and 5th, must pick 3rd
        pitch = finalMTX[0][tenor][0].chord.getPitches()[1]  # 3rd of chord
    finalMTX[0][soprano].append(mo.Cell(chordArray[0][0], chordArray[1][0], beats1234, [mo.Note(pitch, ptd.pitchToDistance(pitch, soprano), 1, False, chordArray[0][0].root)], None, soprano))
    # TODO: add other species


    ################################################################
    # orchestrate the remaining chords
    ################################################################
    # NOTE: check for parallel 5ths, parallel octaves, tri-tones - redo a chord that fails check
    attempts = 0
    totalFailures = 0

    # save for overwrite prevention
    i64 = (chordArray[13][0].root == 1) # .inversion == 2

    i = 1
    while i < measures:
        """NOTE: this used to be a for loop from 1 to 'measures', but i changed to a while loop so we 
        could add checks for tritones and reset 'i' to re-write any 'bad' measures"""

        # bass
        notes = []
        for s in range(species):
            # TODO: CONTINUE FROM HERE *******make getNextNote() return Note classes instead of ints*********
            notes.append(gnn.getNextNote(music, chordArray, finalMTX, i, measures, bass, maxVoices))
        finalMTX[i][0][0] = mo.Cell(blah)

        # manually set last measure's bass to 1
        if i == measures - 1:
            finalMTX[0][i][0] = str(noteMTX[i][4])

        # fill out inversion column for the other voices to follow rules
        if int(finalMTX[0][i][0]) == noteMTX[i][4]:
            noteMTX[i][7] = 0
            finalMTX[0][i][7] = 0
        elif int(finalMTX[0][i][0]) == noteMTX[i][4]+2 or int(finalMTX[0][i][0]) == noteMTX[i][4]-5:  # wrapping 1-7
            #print('1st')
            noteMTX[i][7] = 1
            finalMTX[0][i][7] = 1
        elif int(finalMTX[0][i][0]) == noteMTX[i][4]+4 or int(finalMTX[0][i][0]) == noteMTX[i][4]-3:  # wrapping 1-7
            #print('2nd')
            noteMTX[i][7] = 2
            finalMTX[0][i][7] = 2
        else:
            #print('3rd')
            noteMTX[i][7] = 3
            finalMTX[0][i][7] = 3  # 7th chords have 3rd inversion


        # manually reset 164 if it was overwritten
        if i == 13 and i64:
            #print('overwriting')
            noteMTX[13][0] = str(noteMTX[0][4]+4)
            if int(noteMTX[13][0]) > 7:
                noteMTX[13][0] = str(int(noteMTX[13][0]) - 7)
            finalMTX[0][13][0] = noteMTX[13][0]
            for j in range(maxVoices):
                finalMTX[i][j][0].chord.inversion = 2

        # soprano
        finalMTX[2][i][0] = str(gnn.getNextNote(music, noteMTX, finalMTX, i, measures, 2, maxVoices))  # soprano

        # alto/tenor:
        finalMTX[1][i][0] = str(gnn.getNextNote(music, noteMTX, finalMTX, i, measures, 1, maxVoices))  # alto/tenor

        # check for tritones, parallel 5ths, and parallel octaves. if any are found, rewrite the whole measure
        if (int(finalMTX[0][i][0]) == 4 and int(finalMTX[0][i - 1][0]) == 7) or \
            (int(finalMTX[0][i][0]) == 7 and int(finalMTX[0][i - 1][0]) == 4) or \
            (int(finalMTX[1][i][0]) == 4 and int(finalMTX[1][i - 1][0]) == 7) or \
            (int(finalMTX[1][i][0]) == 7 and int(finalMTX[1][i - 1][0]) == 4) or \
            (int(finalMTX[2][i][0]) == 4 and int(finalMTX[2][i - 1][0]) == 7) or \
            (int(finalMTX[2][i][0]) == 7 and int(finalMTX[2][i - 1][0]) == 4) or \
            (int(finalMTX[0][i][0]) == (int(finalMTX[1][i][0]) + 3) % 7 + 1) and (
        int(finalMTX[0][i - 1][0]) == (int(finalMTX[1][i - 1][0]) + 3) % 7 + 1) or \
            (int(finalMTX[0][i][0]) == (int(finalMTX[2][i][0]) + 3) % 7 + 1) and (
        int(finalMTX[0][i - 1][0]) == (int(finalMTX[2][i - 1][0]) + 3) % 7 + 1) or \
            (int(finalMTX[1][i][0]) == (int(finalMTX[2][i][0]) + 3) % 7 + 1) and (
        int(finalMTX[1][i - 1][0]) == (int(finalMTX[2][i - 1][0]) + 3) % 7 + 1) or \
            (int(finalMTX[0][i][0]) == int(finalMTX[1][i][0]) and int(finalMTX[0][i - 1][0]) == int(finalMTX[1][i - 1][0])) or \
            (int(finalMTX[0][i][0]) == int(finalMTX[2][i][0]) and int(finalMTX[0][i - 1][0]) == int(finalMTX[2][i - 1][0])) or \
            (int(finalMTX[1][i][0]) == int(finalMTX[2][i][0]) and int(finalMTX[1][i - 1][0]) == int(finalMTX[2][i - 1][0])):

            # increment
            attempts += 1
            totalFailures += 1

            if totalFailures < 40:  # only try to go back if we're not caught in an endless loop
                i -= attempts  # rewrite the last few measures, the more failures, the farther back we go
            elif totalFailures >= 40:  # otherwise we probably ended up in an endless loop so try changing noteMTX
                print("Changing the matrix. Measure "+str(i + 1)+" is now a I chord.")
                noteMTX[i][4] = 1
                i -= 1  # reattempt current measure with new noteMTX
                totalFailures = 0

            if i < 0: # just in case we go back too far or get caught in a loop, start at measure 2
                i = 0
                attempts = 0
            #print("Attempt #" + str(attempts) + ". Rewriting measure " + str(i + 1) + ".")

        else:
            attempts = 0  # reset
            # set all columns for i-th row of finalMTX using noteMTX
            #   12 note data types: pitch, duration, direction, interval, chord root,
            #       7th chord, tonality, inversion, prev chord root, distance, beat, measure
            for voice in range(3):
                finalMTX[voice][i][1] = chordsPerMeasure    # duration

                # voice 0 interval
                if finalMTX[voice][i][0] == '1' or finalMTX[voice][i][0] == '2':    # interval, must check for 'wrapping'
                    if finalMTX[voice][i-1][0] == '6' or finalMTX[voice][i-1][0] == '7':
                        temp = int(finalMTX[voice][i][0]) - int(finalMTX[voice][i-1][0])
                        if temp < 0:
                            temp += 7
                        finalMTX[0][i][3] = temp
                    else:
                        finalMTX[voice][i][3] = finalMTX[voice][i][0] - finalMTX[voice][i-1][0]
                elif finalMTX[voice][i][0] == '6' or finalMTX[voice][i][0] == '7':
                    if finalMTX[voice][i-1][0] == '1' or finalMTX[voice][i-1][0] == '2':
                        temp = int(finalMTX[voice][i][0]) - int(finalMTX[voice][i-1][0])
                        if temp > 0:
                            temp -= 7
                        finalMTX[voice][i][3] = temp
                    else:
                        finalMTX[voice][i][3] = int(finalMTX[voice][i][0]) - int(finalMTX[voice][i-1][0])
                else:
                    finalMTX[voice][i][3] = int(finalMTX[voice][i][0]) - int(finalMTX[voice][i-1][0])

                if finalMTX[voice][i][3] == 0:                              # direction
                    finalMTX[voice][i][2] = 0
                elif finalMTX[voice][i][3] > 0:
                    finalMTX[voice][i][2] = 1
                else:
                    finalMTX[voice][i][2] = -1

                finalMTX[voice][i][4] = noteMTX[i][4]                       # chord root
                finalMTX[voice][i][5] = noteMTX[i][5]                       # 7th chord
                finalMTX[voice][i][6] = noteMTX[i][6]                       # tonality
                finalMTX[voice][i][7] = noteMTX[i][7]                       # inversion
                finalMTX[voice][i][8] = noteMTX[i-1][4]                     # prev chord root
                finalMTX[voice][i][9] = 0                                   # distance, none in bass
                finalMTX[voice][i][10] = noteMTX[i][10]                     # beat
                finalMTX[voice][i][11] = noteMTX[i][11]                     # measure

        i += 1  # move on to the next measure

    #print(finalMTX)
    return finalMTX
Beispiel #4
0
def getNextNote(music, chordArray, finalMTX, measure, measures, voice, maxVoices, species = 1):

    if maxVoices == 3:
        bass = 0
        tenor = 1
        soprano = 2
    else:
        bass = 0
        tenor = 1
        soprano = 2
        alto = 3  # keeping as 3 instead of swapping with soprano - easier to add into current code

    # previous note
    prevNote = int(ptt.pitchToTonal(finalMTX[measure - 1][voice][-1].notes[-1].pitch))

    # TODO: fix [measure][0] for species with more than 1 chord per measure
    currentRoot = chordArray[measure][0].root        # current chord root
    nextChord = 1                                    # next chord root, currently unused
    if measure < measures - 1:                       # avoid index out of range error on last measure
        nextChord = chordArray[measure + 1][0].root
    seventh = chordArray[measure][0].seventh         # seventh chord
    tonality = chordArray[measure][0].tonality       # tonal root
    inversion = chordArray[measure][0].inversion     # inversion

    keepGoing = True
    direction = -2

    # TODO: need to add nextChord considerations!!
    #   especially for 7th chords because of 3rd inversion linear descents,
    #   but also for picking 2nd inversions, which should be rare if it isn't linear motion

    # generate random number - PocketBach's soul...
    num1 = random.random()

    # track chord tones
    chordVec = chordArray[measure][0].getPitches()


    ################################################################
    # bass
    ################################################################
    # RULES:
    # 7th chords with only 3 voices cannot be in 2nd inversion (5th in bass)
    # bass line has high chance to pick root, even if there's a jump
    # NOTE: use actual data to derive percentages based on each composer's preferences
    # TO DO: need to consider repeated pitches
    if voice == 0:
        #print(chordVec)

        # return 5th for I-6/4 chords in bass
        if currentRoot == 1 and inversion == 2:
            nextNote = currentRoot + 4
            if nextNote > 7:
                nextNote -= 7
            keepGoing = False
            #return nextNote

        # if on the 2nd to last measure force a V chord
        elif measure == measures - 2 and keepGoing:
            nextNote = 5
            keepGoing = False
            #return 5

        # 7th chords with only 3 voices cannot be in 2nd inversion (5th in bass)
        elif seventh and maxVoices == 3 and keepGoing:
            if inversion == 0:
                del chordVec[2]
            elif inversion == 1:
                del chordVec[1]
            elif inversion == 2:
                del chordVec[0]
            else:
                del chordVec[3]

        if keepGoing:
            if num1 < 0.2:    # 20% chance to repeat bass note (technically less than 20%,
                                # because we check to see if prevNote is a chord tone)
                #print('staying the same')
                if ttp.tonalToPitch(prevNote, music.key) in chordVec:  # repeat prevNote if you can
                    nextNote = prevNote
                else:  # otherwise just go with root
                    nextNote = currentRoot
            elif num1 < 0.4:   # 20% chance to move up linearly (technically less than 20%,
                                # because we check to see if prevNote + 1 is a chord tone)
                #print('moving up')
                if prevNote == 7:  # if prevNote is 7, can't use prevNote + 1
                    if ttp.tonalToPitch(1, music.key) in chordVec:
                        nextNote = 1
                    else:  # otherwise just go with root
                        nextNote = currentRoot
                else:  # if prevNote isn't 7, can use prevNote + 1
                    if ttp.tonalToPitch(prevNote + 1, music.key) in chordVec:
                        nextNote = prevNote + 1
                    else:  # otherwise just go with root
                        nextNote = currentRoot
            elif num1 < 0.6:   # 20% chance to move down linearly (technically less than 20%,
                    # because we check to see if prevNote - 1 is a chord tone)
                #print('moving down')
                if prevNote == 1:  # if prevNote is 1, can't use prevNote - 1
                    if ttp.tonalToPitch(7, music.key) in chordVec:
                        nextNote = 7
                    else:  # otherwise just go with root
                        nextNote = currentRoot
                else:  # if prevNote isn't 1, can use prevNote - 1
                    if ttp.tonalToPitch(prevNote - 1, music.key) in chordVec:
                        nextNote = prevNote - 1
                    else:  # otherwise just go with root
                        nextNote = currentRoot
            else:  # 40% chance to simply pick root
                nextNote = currentRoot

    ################################################################
    # soprano
    ################################################################
    # RULES:
    # can't be 3rd+3rd
    # can't be 5th+5th
    # 7th chords with 3 voices cannot have a 5th
    # 7th chords with 3 voices cannot be root+root
    # 7th chords with 3 voices cannot be 3rd+3rd
    # TO DO: no parallel 5ths or octaves
    # NOTE: use actual data to derive percentages based on each composer's preferences
    elif voice == 2:
        # enforce rules above by changing chordVec!!!
        # check for 7th chords first

        # first of all... if on the last measure, just return tonic
        if chordArray[measure][measures-1] == measures:
            nextNote = 1
            keepGoing = False

        elif keepGoing:
            if not seventh:  # if not a 7th chord
                # can't be 3rd+3rd
                if inversion == 1:  # if inversion = 1
                    del chordVec[0]
                # can't be 5th+5th
                elif inversion == 2:  # if inversion = 2
                    del chordVec[0]
            else:  # if a 7th chord
                # 7th chords with 3 voices cannot have a 5th
                if maxVoices == 3:
                    # 7th chords with 3 voices cannot be root+root
                    if inversion == 0:  # if inversion = 0
                        chordVec = [chordVec[1], chordVec[3]]
                    # 7th chords with 3 voices cannot be 3rd+3rd
                    elif inversion == 1:  # if inversion = 1
                        chordVec = [chordVec[2], chordVec[3]]
                    # elif inversion == 2:  # NOTE: this should never happen with only 3 voices...
                    else:  # inversion == 3:  # if inversion = 3
                        # can only be root or 3rd
                        chordVec = [chordVec[1], chordVec[2]]
                else:
                    # TO DO: 4-voice rules
                    pass

            # all percentages are multiplied by 2.5/7 = 0.36 because there's only about a 2.5/7 chance
            #   for the prevNote or prevNote +1/-1 to be a chord tone
            # TO DO: right now it checks for upward motion first, so percentages of upward/downward
            #   motion aren't equal... need to equalize
            if num1 < 0.83:         # 83% * 0.36 = 30% chance to move up or down
                if prevNote == 7:   # if prevNote is 7, can't use prevNote + 1
                    if ttp.tonalToPitch(1, music.key) in chordVec:
                        nextNote = 1
                        keepGoing = False
                        direction = 1
                else:  # if prevNote isn't 7, can use prevNote + 1
                    if ttp.tonalToPitch(prevNote + 1, music.key) in chordVec:
                        nextNote = prevNote + 1
                        keepGoing = False
                        direction = 1
                if prevNote == 1 and keepGoing:  # if prevNote is 1, can't use prevNote - 1
                    if ttp.tonalToPitch(7, music.key) in chordVec:
                        nextNote = 7
                        keepGoing = False
                        direction = -1
                elif keepGoing:  # if prevNote isn't 1, can use prevNote - 1
                    if ttp.tonalToPitch(prevNote - 1, music.key) in chordVec:
                        nextNote = prevNote - 1
                        keepGoing = False
                        direction = -1
            else:      # 100% * 0.36 = 36% * 0.7 (30% less chance to make it through first if) = 25% chance to repeat note
                if ttp.tonalToPitch(prevNote, music.key) in chordVec:
                    nextNote = prevNote
                    keepGoing = False
                    direction = 0

            # there's a high chance the note will fall through the if-elifs above,
            #   so don't put this line into "else:"
            if keepGoing:
                nextNote = ptt.pitchToTonal(chordVec[random.randint(0, len(chordVec) - 1)], music.key)
                keepGoing = False

    ################################################################
    # alto/tenor
    ################################################################
    # RULES:
    # if root+root, must be 3rd
    # if root+3rd, can be root or 5th
    # if root+5th, must be 3rd
    # if 3rd+root, can be root or 5th
    # can't be 3rd+3rd
    # if 3rd+5th, must be root
    # if 5th+root, must be 3rd
    # if 5th+3rd, must be root
    # can't be 5th+5th
    # can't be 7th+7th
    # 7th chords with 3 voices cannot have a 5th
    # 7th chords with 3 voices cannot be root+root
    # 7th chords with 3 voices cannot be 3rd+3rd
    # 7th chords with 3 voices with root+3rd, must be 7th
    # 7th chords with 3 voices with root+7th, must be 3rd
    # 7th chords with 3 voices with 3rd+root, must be 7th
    # 7th chords with 3 voices with 3rd+7th, must be 7th
    # 7th chords with 3 voices with 7th+root, must be 3rd
    # 7th chords with 3 voices with 7th+3rd, must be root
    # TO DO: no parallel 5ths or octaves
    # NOTE: use actual data to derive percentages based on each composer's preferences
    # TO DO: need to check for note chosen in voice 0 and 1...
    else:
        # enforce rules above by changing chordVec!!!
        # check for 7th chords first
        if not seventh:  # if not a 7th chord
            if inversion == 0:  # if inversion = 0
                # if root+root, must be 3rd
                if finalMTX[measure][soprano][0].notes[0].pitch == chordVec[0]:
                    nextNote = ptt.pitchToTonal(chordVec[1], music.key)
                    keepGoing = False
                # if root+3rd, can be root or 5th
                elif finalMTX[measure][soprano][0].notes[0].pitch == chordVec[1]:
                    del chordVec[1]
                # if root+5th, must be 3rd
                else:
                    nextNote = ptt.pitchToTonal(chordVec[1], music.key)
                    keepGoing = False
            elif inversion == 1:  # if inversion = 1
                # if 3rd+root, can be root or 5th
                if finalMTX[measure][soprano][0].notes[0].pitch == chordVec[2]:
                    del chordVec[0]
                # elif # can't be 3rd+3rd
                else:  # if 3rd+5th, must be root
                    nextNote = ptt.pitchToTonal(chordVec[2], music.key)
                    keepGoing = False
            elif inversion == 2:  # if inversion = 2
                # if 5th+root, must be 3rd
                if finalMTX[measure][soprano][0].notes[0].pitch == chordVec[1]:
                    nextNote = ptt.pitchToTonal(chordVec[2], music.key)
                    keepGoing = False
                # elif # can't be 5th+5th
                else:  # if 5th+3rd, must be root
                    nextNote = ptt.pitchToTonal(chordVec[1], music.key)
                    keepGoing = False
        else:  # if a 7th chord
            # 7th chords with 3 voices cannot have a 5th
            if maxVoices == 3:
                if inversion == 0:  # if inversion = 0
                    # 7th chords with 3 voices cannot be root+root
                    # 7th chords with 3 voices with root+3rd, must be 7th
                    if finalMTX[measure][soprano][0].notes[0].pitch == chordVec[1]:
                        nextNote = ptt.pitchToTonal(chordVec[3], music.key)
                        keepGoing = False
                    # elif # 7th chords with 3 voices cannot have a 5th
                    # 7th chords with 3 voices with root+7th, must be 3rd
                    else:
                        nextNote = ptt.pitchToTonal(chordVec[1], music.key)
                        keepGoing = False

                elif inversion == 1:  # if inversion = 1
                    # 7th chords with 3 voices with 3rd+root, must be 7th
                    if finalMTX[measure][soprano][0].notes[0].pitch == chordVec[3]:
                        nextNote = ptt.pitchToTonal(chordVec[2], music.key)
                        keepGoing = False
                    # 7th chords with 3 voices cannot be 3rd+3rd
                    # 7th chords with 3 voices cannot have a 5th
                    # 7th chords with 3 voices with 3rd+7th, must be root
                    else:
                        nextNote = ptt.pitchToTonal(chordVec[3], music.key)
                        keepGoing = False
                # 7th chords with 3 voices cannot have a 5th, so no 2nd inversion
                else:  # if inversion = 3
                    # 7th chords with 3 voices with 7th+root, must be 3rd
                    if finalMTX[measure][soprano][0].notes[0].pitch == chordVec[1]:
                        nextNote = ptt.pitchToTonal(chordVec[2], music.key)
                        keepGoing = False
                    # 7th chords with 3 voices with 7th+3rd, must be root
                    else:
                        nextNote = ptt.pitchToTonal(chordVec[1], music.key)
                        keepGoing = False
                    # 7th chords with 3 voices cannot have a 5th
                    # can't be 7th+7th

            # not relevant at the moment so skipping them...
            else:
                # TO DO: many possible combinations and rules for 4 voices below...
                pass

        if keepGoing:
            if num1 < 0.4:  # 40% chance to repeat note (technically less)
                            # because we check to see if prevNote is a chord tone)
                direction = 0
                if ttp.tonalToPitch(prevNote, music.key) in chordVec:
                    nextNote = prevNote
                else:
                    nextNote = ptt.pitchToTonal(chordVec[random.randint(0, len(chordVec) - 1)], music.key)
            elif num1 < 0.7:    # 30% chance to move up linearly (technically less than 30%,
                                # because we check to see if prevNote + 1 is a chord tone)
                direction = 1
                if prevNote == 7:  # if prevNote is 7, can't use prevNote + 1
                    if ttp.tonalToPitch(1, music.key) in chordVec:
                        nextNote = 1
                    else:
                        nextNote = ptt.pitchToTonal(chordVec[random.randint(0, len(chordVec) - 1)], music.key)
                else:  # if prevNote isn't 7, can use prevNote + 1
                    if ttp.tonalToPitch(prevNote + 1, music.key) in chordVec:
                        nextNote = prevNote + 1
                    else:
                        nextNote = ptt.pitchToTonal(chordVec[random.randint(0, len(chordVec) - 1)], music.key)
            else:   # 30% chance to move down linearly (technically less than 30%,
                    # because we check to see if prevNote - 1 is a chord tone)
                direction = -1
                if prevNote == 1:  # if prevNote is 1, can't use prevNote - 1
                    if ttp.tonalToPitch(7, music.key) in chordVec:
                        nextNote = 7
                    else:
                        nextNote = ptt.pitchToTonal(chordVec[random.randint(0, len(chordVec) - 1)], music.key)
                else:  # if prevNote isn't 1, can use prevNote - 1
                    if ttp.tonalToPitch(prevNote - 1, music.key) in chordVec:
                        nextNote = prevNote - 1
                    else:
                        nextNote = ptt.pitchToTonal(chordVec[random.randint(0, len(chordVec) - 1)], music.key)


    # get direction if it hasn't been chosen yet. for now 50/50 up/down
    # TODO: pick a better method than this
    if direction == -2:
        if nextNote == prevNote:
            direction = 0
        else:
            num1 = random.random()
            if num1 > 0.5:
                direction = 1
            else:
                direction = -1

    # get distance
    distance = ttd.tonalToDistance(nextNote, direction, finalMTX[measure - 1][voice][-1].notes[-1].distance,
                                   voice, music.key, music.major)
    #TODO: if distance out of range for the voice, change the direction and distance

    # convert to a Note class
    nextNote2 = mo.Note(ttp.tonalToPitch(nextNote), distance, species, False, currentRoot)

    return nextNote2
Beispiel #5
0
def getNotesFugue(currentChord, nextChord, beatsArr, startDistance = None, destinationDistance = None, voice = 0, episode = False, previousCell = None, key = 'c', major = True, timesig = None):

    if timesig is None:
        timesig = [4,4]

    # any measure where a voice first enters or re-enters after a rest and no start is specified
    if not startDistance:

        # pick a random index from options
        options = dc.defineChord(currentChord)
        startPitch = random.choice(options[0])
        startTonal = ptt.pitchToTonal(startPitch)
        # pick a distance based on voice
        startDistance = ptd.pitchToDistance(startPitch, voice)
        #print('start:', startTonal, startPitch, startDistance)

    # if we are given a start
    else:

        # convert start to 0-7
        startPitch = dtp.distanceToPitch(startDistance)
        startTonal = ptt.pitchToTonal(startPitch)
        #print('start:', startTonal, startPitch, startDistance)


    # if no destination given, make one
    if not destinationDistance:

        # pick a random destination
        options = dc.defineChord(nextChord)
        destinationPitch = random.choice(options[0])
        destinationTonal = ptt.pitchToTonal(destinationPitch)
        # pick a distance based on voice
        destinationDistance = ptd.pitchToDistance(destinationPitch, voice)
        #print('destination:', destinationTonal, destinationPitch, destinationDistance)

    # if we are given a destination
    else:

        # convert destination to 0-7
        destinationPitch = dtp.distanceToPitch(destinationDistance)
        destinationTonal = ptt.pitchToTonal(destinationPitch)
        #print('destination:', destinationTonal, destinationPitch, destinationDistance)


    notes = []

    # calculate distance and direction
    if startDistance == destinationDistance:
        direction = 0
        distanceBetweenTonal = 0
    elif startDistance < destinationDistance:
        direction = 1
        if startTonal < destinationTonal: # 1 to 7 = 7 - 1 = 6, 3 to 5 = 5 - 3 = 2
            distanceBetweenTonal = max(destinationTonal, startTonal) - min(destinationTonal, startTonal)
        else: # 7 to 1 = 1 - 7 + 7 = 1, 5 to 3 = 3 - 5 + 7 = 5
            distanceBetweenTonal = min(destinationTonal, startTonal) - max(destinationTonal, startTonal) + 7
    else:
        direction = -1
        if startTonal < destinationTonal: # 1 to 7 = 1 - 7 + 7 = 1, 3 to 5 = 3 - 5 + 7 = 5
            distanceBetweenTonal = min(destinationTonal, startTonal) - max(destinationTonal, startTonal) + 7
        else: # 7 to 1 = 7 - 1 = 6, 5 to 3 = 5 - 3 = 2
            distanceBetweenTonal = max(destinationTonal, startTonal) - min(destinationTonal, startTonal)


    print('start: ' + str(startTonal) + '\tdestination: ' + str(destinationTonal) + '\tdistanceBetweenTonal: ' + str(distanceBetweenTonal) + '\tdirection: ' + str(direction))


    #######################################################################
    # FOR NON-EPISODES
    #######################################################################

    if not episode:

        num1 = random.random()
        #print(num1)

        #####################################################
        # LINEAR MOTION
        #####################################################

        # TODO: find good percentage. starting high for debugging
        # 80% chance to move linearly - technically less if distance is out of range
        # NOTE: tested these values with testGetRhythms.py
        if num1 < 0.8 and ( (len(beatsArr) == 4 and distanceBetweenTonal > 2 and distanceBetweenTonal < 9) or (len(beatsArr) == 2 and distanceBetweenTonal > 0 and distanceBetweenTonal < 8) ):

            # create rhythms with a length equal to distance
            # TODO: update and reuse randRhythm() from old getRhythmsChorale.py rather than rely on while loop... wasted cycles
            lenR = -1
            while lenR != distanceBetweenTonal:
                rhythms = grf.getRhythmsFugue(beatsArr, timesig)
                lenR = len(rhythms)

            #print('distance:', distance, '\tlenR:', lenR)

            # TODO: only give linear motion an X% chance. (100-X)% chance to move differently
            # if dist up or dist down is same as number of rhythms, and that distance is < 5
            if lenR == distanceBetweenTonal and direction == 1:
                nextNote = int(startTonal)
                for note in rhythms:
                    if nextNote == 8:
                        nextNote = 1
                    notes.append(nextNote)
                    nextNote += 1
            elif lenR == distanceBetweenTonal and direction == -1:
                # same as above in other direction
                nextNote = int(startTonal)
                for note in rhythms:
                    if nextNote == 0:
                        nextNote = 7
                    notes.append(nextNote)
                    nextNote -= 1
            else:
                print('error under linear motion in getNotes.py')


        #####################################################
        # COMMON PATTERNS
        #####################################################

            # # repeated pitch patterns
            # elif abs(distance) == 0:
            #     pass

            # # 2nd jump patterns
            # elif abs(distance) == 1:
            #     pass

            # # 3rd jump patterns
            # elif abs(distance) == 2:
            #     pass

            # # 4th jump patterns
            # elif abs(distance) == 3:
            #     pass

            # # 5th jump patterns
            # elif abs(distance) == 4:
            #     pass

            # # 6th jump patterns
            # elif abs(distance) == 5:
            #     pass

            # # 7th jump patterns
            # elif abs(distance) == 6:
            #     pass

            # # octave jump patterns
            # elif abs(distance) == 7:
            #     pass

        #####################################################
        # MICRO-DESTINATION
        #####################################################

        else:

            # call getRhythmsFugue to generate rhythms - this is now our number of notes needed
            rhythms = grf.getRhythmsFugue(beatsArr, timesig)
            lenR = len(rhythms)

            # TODO: change this. don't just repeat.
            for note in rhythms:
                notes.append(startTonal)

            # check for accented beats after the first rhythm, create new micro-destinations
            # if len(beatsArr) > 2:
            #     for r in rhythms[1:]:
            #         if r[-1]:  # if accented
            #             # find a new micro-dest with a distance between startDistance and destinationDistance
            #             # move linearly if possible
            #             # else move be step
            #             microDestinationTonal, x, y = gmd.getMicroDestination(currentChord, startDistance, direction)
            #         else:
            #             pass

    #######################################################################
    # FOR EPISODES
    #######################################################################

    else:
        #print('episode')

        # if previousCell is None, it's the first cell of the episode
        if not previousCell:
            rhythms = grf.getRhythmsFugue(beatsArr, timesig)

            # TODO: change this. don't just repeat.
            for note in rhythms:
                notes.append(startTonal)

        # otherwise try to match intervals/rhythms from previousCell
        else:
            rhythms = grf.addRhythmData(previousCell.notes)

            # TODO: change this. don't just repeat.
            for note in rhythms:
                notes.append(startTonal)



    #######################################################################
    # CONVERT TO MUSIC OBJECTS BEFORE RETURNING
    #######################################################################
    #print('notes: ', notes)
    #print('rhythms: ', rhythms)
    #print('destinationDistance:', destinationDistance)

    # convert notes and rhythms to Note classes
    newNotes = []
    for i in range(len(notes)):
        tied = (rhythms[i][0][-1] == '.')
        if tied:
            rhythms[i][0] = rhythms[i][0][:-1]
        ##########################################################################
        # TODO: FIX DISTANCE BELOW! this is where you decide where to move
        ##########################################################################
        if i == 0:
            fixedDistance = int(startDistance)
        else:
            # no octave jumps yet, so this fixes the repeated note problem
            if notes[i] == notes[i-1]:
                fixedDistance = ttd.tonalToDistance(notes[i], 0, fixedDistance)
            else:
                fixedDistance = ttd.tonalToDistance(notes[i], direction, fixedDistance)
                # check range
                if newNotes[-1].distance > fixedDistance and (newNotes[-1].distance - fixedDistance) > 9:
                    fixedDistance += 12
                elif newNotes[-1].distance < fixedDistance and (fixedDistance - newNotes[-1].distance) > 9:
                    fixedDistance += 12
        # if voice == blah, check bounds so distance doesn't go too far
        newNotes.append(mo.Note(ttp.tonalToPitch(notes[i]), fixedDistance, int(rhythms[i][0]), tied, currentChord.root,
                                currentChord.tonality, currentChord.seventh, currentChord.inversion, currentChord.secondary,
                                currentChord.secondaryRoot, key, major, timesig))

    return newNotes, destinationDistance