def find_next(self): """Returns the index and chord of the next chord in the list of expected chords """ assert self.index + 1 < len(self.chords), "No more chords to walk" current_chord = self.chords[self.index] current_rn = roman.romanNumeralFromChord(current_chord, self.key) expected_chord_labels = get_expected_next(current_rn.figure, self.key.type == "major") assert len(expected_chord_labels) > 0, "No expected chords given" for i, c in enumerate(self.chords[self.index + 1:]): rn = roman.romanNumeralFromChord(c, self.key) if rn.figure in expected_chord_labels: return self.index + i + 1
def __next__(self): current_rn = roman.romanNumeralFromChord(self.chords[self.index], self.key) self.labeled_chords.append(current_rn.figure) new_index = self.find_next() if new_index != self.index + 1: raise ChordProgressionError(self.index - 1) self.index = new_index
def testTrecentoMadrigal(self): from music21 import corpus # c = corpus.parse('beethoven/opus18no1', 2).measures(1, 19) c = corpus.parse('PMFC_06_Giovanni-05_Donna').measures(1, 30) # c = corpus.parse('PMFC_06_Giovanni-05_Donna').measures(90, 118) # c = corpus.parse('PMFC_06_Piero_1').measures(1, 10) # c = corpus.parse('PMFC_06-Jacopo').measures(1, 30) # c = corpus.parse('PMFC_12_13').measures(1, 40) # fix clef fixClef = True if fixClef: startClefs = c.parts[1].getElementsByClass( 'Measure').first().getElementsByClass('Clef') if startClefs: clef1 = startClefs[0] c.parts[1].getElementsByClass('Measure').first().remove(clef1) c.parts[1].getElementsByClass('Measure').first().insert( 0, clef.Treble8vbClef()) cr = ChordReducer() # cr.printDebug = True p = cr.multiPartReduction(c, maxChords=3) # p = cr.multiPartReduction(c, closedPosition=True) from music21 import key, roman cm = key.Key('G') for thisChord in p.recurse().getElementsByClass('Chord'): thisChord.lyric = roman.romanNumeralFromChord( thisChord, cm, preferSecondaryDominants=True).figure c.insert(0, p) if self.show: c.show()
def comparePitches(self): ''' Single RN-slice comparison for pitches: do the chords reflect the pitch content of the score section in question? ''' if self.pitchFeedback is not None: # already done return self.pitchFeedback = [] pitchNumerator = 0 for comp in self.harmonicRanges: overall = 0 harmonicRangeLength = sum([round(sl.quarterLength, 2) for sl in comp.slices]) # Note: Avoid division by 0 for thisSlice in comp.slices: # NB: Rest slices handled already pitchesNameNoOctave = [x[:-1] for x in thisSlice.pitches] # Pitch only proportionSame = self._proportionSimilarity(comp, pitchesNameNoOctave) # weighedSimilarity = proportionSame * slice.beatStrength # TODO overall += thisSlice.quarterLength * proportionSame / harmonicRangeLength overall = round(overall, 2) compLength = comp.endOffset - comp.startOffset if overall >= self.tolerance: pitchNumerator += compLength else: # overall < self.tolerance: # Process feedback and reduce pitchScore. pitchNumerator += (compLength * overall) # Suggestions pl = [pList.pitches for pList in comp.slices] suggestions = [] for sl in comp.slices: chd = chord.Chord(sl.pitches) if (chd.isTriad() or chd.isSeventh()): rn = roman.romanNumeralFromChord(chd, comp.key) if rn.figure != comp.figure: suggestions.append([sl.measure, sl.beat, rn.figure, sl.pitches]) msg = f'Measure {comp.startMeasure}, beat {comp.startBeat}, {comp.figure} in {comp.key}, ' \ f'indicating the pitches {comp.pitches} ' \ f'accounting for successive slices of {pl}.' fb = Feedback(comp, msg) fb.matchStrength = f'Match strength estimated at {round(overall * 100, 2)}%.' fb.suggestions = [] if len(suggestions) > 0: for s in suggestions: fb.suggestions.append(f'm{s[0]} b{s[1]} {s[2]} for the slice {s[3]}') self.pitchFeedback.append(fb) self.pitchScore = round(pitchNumerator * 100 / self.totalLength, 2)
def compareBass(self): ''' Single RN-slice comparison for bass pitches (inversion). Creates feedback for cases where the bass pitch indicated by the Roman numeral's inversion does not appear as the lowest note during the span in question. ''' if self.bassFeedback is not None: # already done return self.bassFeedback = [] bassNumerator = 0 for comp in self.harmonicRanges: # bassPitchesWithOctave = [slice.pitches[0] for slice in comp.slices] bassPitchesWithOctave = [] # Sometimes the slice is a rest I guess? for slice in comp.slices: if len(slice.pitches) > 0: bassPitchesWithOctave.append(slice.pitches[0]) bassPitchesNoOctave = [p[:-1] for p in bassPitchesWithOctave] bassPitchesWithOctave = list(set(bassPitchesWithOctave)) if comp.bassPitch not in bassPitchesNoOctave: inversionSuggestions = [] for bassPitch in bassPitchesNoOctave: if bassPitch in comp.pitches: # already retrieved suggestedPitches = comp.pitches suggestedPitches.append(bassPitch + '0') # To ensure it is lowest suggestedChord = chord.Chord(suggestedPitches) rn = roman.romanNumeralFromChord(suggestedChord, comp.key) inversionSuggestions.append(f'm{comp.startMeasure} b{comp.startBeat} {rn.figure}') msg = f'Measure {comp.startMeasure}, beat {comp.startBeat}, {comp.figure} in {comp.key}, ' \ f'indicating the bass {comp.bassPitch} ' \ f'for lowest note(s) of: {bassPitchesWithOctave}.' fb = Feedback(comp, msg) # fb.matchStrength = N/A fb.suggestions = [] if len(inversionSuggestions) > 0: fb.suggestions = list(set(inversionSuggestions)) # Once for each suggestion self.bassFeedback.append(fb) else: # comp.bassPitch in bassPitchesNoOctave: compLength = comp.endOffset - comp.startOffset bassNumerator += compLength self.bassScore = bassNumerator / self.totalLength
def convert_harmonic_to_roman_numerals(self): ret = [] for c in self.harmony: if c == "-": ret.append("-") else: roman_numeral = roman.romanNumeralFromChord( c, self.expected_key) ret.append(simplify_roman_name(roman_numeral)) self.roman_numerals = ret return ret
def letter2roman(sym): # for now assume in key of C print '--- letter2roman', sym, try: chord_sym = harmony.ChordSymbol(sym) except: print 'WARNING: chord symbol does not exist' return None ch = chord.Chord(chord_sym.pitches) rn = roman.romanNumeralFromChord(ch, key.Key('C')) print rn.figure return rn.figure
def testTrecentoMadrigal(self): from music21 import corpus # c = corpus.parse('beethoven/opus18no1', 2).measures(1, 19) c = corpus.parse('PMFC_06_Giovanni-05_Donna').measures(1, 30) # c = corpus.parse('PMFC_06_Giovanni-05_Donna').measures(90, 118) # c = corpus.parse('PMFC_06_Piero_1').measures(1, 10) # c = corpus.parse('PMFC_06-Jacopo').measures(1, 30) # c = corpus.parse('PMFC_12_13').measures(1, 40) # fix clef fixClef = True if fixClef: startClefs = c.parts[1].getElementsByClass('Measure')[0].getElementsByClass('Clef') if startClefs: clef1 = startClefs[0] c.parts[1].getElementsByClass('Measure')[0].remove(clef1) c.parts[1].getElementsByClass('Measure')[0].insert(0, clef.Treble8vbClef()) cr = ChordReducer() #cr.printDebug = True p = cr.multiPartReduction(c, maxChords=3) #p = cr.multiPartReduction(c, closedPosition=True) from music21 import key, roman cm = key.Key('G') for thisChord in p.recurse().getElementsByClass('Chord'): thisChord.lyric = roman.romanNumeralFromChord(thisChord, cm, preferSecondaryDominants=True).figure c.insert(0, p) c.show()
from music21 import key from music21 import interval bci = corpus.chorales.Iterator(2, 371, numberingSystem = 'riemenschneider', numberList = [1,2,3,4,6,190,371], returnType = 'stream') chordProgressions = [] intervalSets = [] for chorale in bci: reduction = chorale.chordify() analyzedKey = chorale.analyze('key') chords = [] intervals = [] for c in reduction.flat.getElementsByClass('Chord'): c.closedPosition(forceOctave=4, inPlace = True) chordPitches = c.pitches chordSize = len(chordPitches) mainInterval = interval.notesToChromatic(chordPitches[0], chordPitches[chordSize-1]) rn = roman.romanNumeralFromChord(c, analyzedKey) chords.append(rn) intervals.append(mainInterval) chordProgressions.append(chords) intervalSets.append(intervals) for ch in chordProgressions: print ch print "//" for i in intervalSets: print i print "//"
def roman(chord, key): return chord_name(romanNumeralFromChord(chord, key).romanNumeral)
def chfyChordAndLabel(self, ignoreParts: int = 2, tonicizationsRemainInEffect: bool = False): ''' To use in the case of a partial analysis with chords and key information. Takes each successive chord and key/tonicization labelling lyric, works out the chord, and either returns that as data, or as appended to the score in question. Notes for the markup: changes of key remain in effect until the next marking, but changes of tonicizations don't by default (settable with tonicizationsRemainInEffect). That said, you may want to put in reminders of the prevailing key occasionally (after a tonicization, or indeed elsewhere); that's fine and doesn't make any different to the analysis. ''' self.deducedAnalysis = [] reduction = deepcopy(self.score) for x in range(ignoreParts): reduction.remove( reduction.parts[0]) # Top parts of the original score self.chfyScore = reduction.stripTies().chordify() self.chfyScore.partName = 'Roman' currentIndex = 0 # Index currentKey = 'FAKE KEY' # Initialise empty for inclusion of the first key currentTonicization = None if not self.annotationsAndLocations: self.getAnnotationsAndLocations() keyData = self.annotationsAndLocations lenKeyData = len(keyData) for ch in self.chfyScore.recurse().notes: startKey = currentKey if not tonicizationsRemainInEffect: currentTonicization = None # reset for each chord # Key Changes and Tonicization if lenKeyData > currentIndex and [ch.measureNumber, ch.beat ] == keyData[currentIndex][0:2]: stringInQuestion = keyData[currentIndex][ 2] # Before updating current index currentIndex += 1 if '/' in stringInQuestion: # Then it's a local tonicization rnFigureString, currentTonicization = stringInQuestion.split( '/') # TODO make use of any rnFigureString specified? # TODO support e.g. '/g' as well as (and converting it to) relative ('/ii') elif ':' in stringInQuestion: # modulation currentKey, rnFigureString = stringInQuestion.split(':') # TODO make use of any rnFigureString specified? else: x = re.search('[a-gA-G]', stringInQuestion[0]) if x: # modulation without RN. NB: Doesn't support Fr43 or Ger65 currentKey = stringInQuestion currentTonicization = None else: # TODO accept anything else as a full, user-defined Roman numeral? raise ValueError( f'Unrecognised entry {stringInQuestion} ' f'in measure {ch.measureNumber}, ' f'beat {ch.beat}') if not currentTonicization: rn = roman.romanNumeralFromChord(ch, key.Key(currentKey)) else: # currentTonicization localKey = getLocalKey(currentTonicization, currentKey) rn = roman.romanNumeralFromChord( ch, key.Key(localKey), # sixthMinor=roman.Minor67Default.CAUTIONARY, # seventhMinor=roman.Minor67Default.CAUTIONARY, ) # TODO: issues with sixth and seventh minor lyric = str(rn.figure) # Lyric modifications. Otherwise the lyric is unchanged if startKey != currentKey: lyric = currentKey + ': ' + lyric elif currentTonicization: lyric = lyric + stringInQuestion ch.lyric = lyric thisData = [ch.measureNumber, ch.beat, lyric] self.deducedAnalysis.append(thisData)
def determineDissonantIdentificationAccuracy(scoreIn, offsetList, keyStr=None): ''' runs comparison on score to identify dissonances, then compares to the user's offsetList of identified dissonances. The score is colored according to the results, and appropriate information is returned as a dictionary. See runPerceivedDissonanceAnalysis for full details and an example. *Color key* * Green: the user also recognizes this as a dissonant vertical slice GREEN * Red: the user did not recognize as a dissonant vertical slice RED * Blue: the user recognized it as a dissonant vertical slice BLUE >>> s = stream.Score() >>> p = stream.Part() >>> c1 = chord.Chord(['C3', 'E3', 'G3']) >>> c1.isConsonant() True >>> p.append(c1) >>> c2 = chord.Chord(['C3', 'B3', 'D#']) >>> c2.isConsonant() False >>> p.append(c2) >>> c3 = chord.Chord(['D3', 'F#3', 'A']) >>> c3.isConsonant() True >>> p.append(c3) >>> c4 = chord.Chord(['B-4', 'F#4', 'A-3']) >>> c4.isConsonant() False >>> p.append(c4) >>> p.makeMeasures(inPlace=True) >>> s.append(p) >>> aData = alpha.webapps.commands.determineDissonantIdentificationAccuracy(s, [2.3, 3.2]) >>> chords = aData['stream'].flat.getElementsByClass('Chord') >>> chords[0].style.color is None #BLACK (by default) True >>> chords[1].style.color #RED '#cc3300' >>> chords[2].style.color #BLUE '#0033cc' >>> chords[3].style.color #GREEN '#00cc33' ''' from music21 import roman ads = theoryAnalyzer.Analyzer() score = scoreIn.sliceByGreatestDivisor(addTies=True) vsList = ads.getVerticalities(score) user = len(offsetList) music21VS = 0 both = 0 romanFigureList = [] if keyStr is None: pieceKey = scoreIn.analyze('key') else: pieceKey = key.Key(keyStr) for (vsNum, vs) in enumerate(vsList): currentVSOffset = vs.offset(leftAlign=False) if vsNum + 1 == len(vsList): nextVSOffset = scoreIn.highestTime else: nextVSOffset = vsList[vsNum + 1].offset(leftAlign=False) if not vs.isConsonant(): #music21 recognizes this as a dissonant vertical slice music21VS += 1 if _withinRange(offsetList, currentVSOffset, nextVSOffset): vs.color = '#00cc33' # the user also recognizes this as a dissonant vertical slice GREEN both += 1 c = vs.getChord() romanFigureList.append(roman.romanNumeralFromChord(c, pieceKey).figure) else: vs.color = '#cc3300' #the user did not recognize as a dissonant vertical slice RED else: #music21 did not recognize this as a dissonant vertical slice if _withinRange(offsetList, currentVSOffset, nextVSOffset): vs.color = '#0033cc' #the user recognized it as a dissonant vertical slice BLUE score.insert(metadata.Metadata()) score.metadata.composer = scoreIn.metadata.composer score.metadata.movementName = scoreIn.metadata.movementName analysisData = {'stream': score, 'numUserIdentified': user, 'numMusic21Identified': music21VS, 'numBothIdentified': both, 'accuracy': both * 100 / music21VS if music21VS != 0 else 100, 'romans': romanFigureList, 'key': pieceKey} return analysisData
def determineDissonantIdentificationAccuracy(scoreIn, offsetList, keyStr=None): ''' runs comparison on score to identify dissonances, then compares to the user's offsetList of identified dissonances. The score is colored according to the results, and appropriate information is returned as a dictionary. See runPerceivedDissonanceAnalysis for full details and an example. *Color key* * Green: the user also recognizes this as a dissonant vertical slice GREEN * Red: the user did not recognize as a dissonant vertical slice RED * Blue: the user recognized it as a dissonant vertical slice BLUE >>> s = stream.Score() >>> p = stream.Part() >>> c1 = chord.Chord(['C3', 'E3', 'G3']) >>> c1.isConsonant() True >>> p.append(c1) >>> c2 = chord.Chord(['C3', 'B3', 'D#']) >>> c2.isConsonant() False >>> p.append(c2) >>> c3 = chord.Chord(['D3', 'F#3', 'A']) >>> c3.isConsonant() True >>> p.append(c3) >>> c4 = chord.Chord(['B-4', 'F#4', 'A-3']) >>> c4.isConsonant() False >>> p.append(c4) >>> p.makeMeasures(inPlace=True) >>> s.append(p) >>> aData = alpha.webapps.commands.determineDissonantIdentificationAccuracy(s, [2.3, 3.2]) >>> chords = aData['stream'].flat.getElementsByClass('Chord') >>> chords[0].style.color is None #BLACK (by default) True >>> chords[1].style.color #RED '#cc3300' >>> chords[2].style.color #BLUE '#0033cc' >>> chords[3].style.color #GREEN '#00cc33' ''' from music21 import roman ads = theoryAnalyzer.Analyzer() score = scoreIn.sliceByGreatestDivisor(addTies=True, inPlace=False) vsList = ads.getVerticalities(score) user = len(offsetList) music21VS = 0 both = 0 romanFigureList = [] if keyStr is None: pieceKey = scoreIn.analyze('key') else: pieceKey = key.Key(keyStr) for (vsNum, vs) in enumerate(vsList): currentVSOffset = vs.offset(leftAlign=False) if vsNum + 1 == len(vsList): nextVSOffset = scoreIn.highestTime else: nextVSOffset = vsList[vsNum + 1].offset(leftAlign=False) if not vs.isConsonant( ): #music21 recognizes this as a dissonant vertical slice music21VS += 1 if _withinRange(offsetList, currentVSOffset, nextVSOffset): vs.color = '#00cc33' # the user also recognizes this as a dissonant vertical slice GREEN both += 1 c = vs.getChord() romanFigureList.append( roman.romanNumeralFromChord(c, pieceKey).figure) else: vs.color = '#cc3300' #the user did not recognize as a dissonant vertical slice RED else: #music21 did not recognize this as a dissonant vertical slice if _withinRange(offsetList, currentVSOffset, nextVSOffset): vs.color = '#0033cc' #the user recognized it as a dissonant vertical slice BLUE score.insert(metadata.Metadata()) scoreInMetadata = scoreIn.metadata if scoreInMetadata: score.metadata.composer = scoreInMetadata.composer score.metadata.movementName = scoreInMetadata.movementName analysisData = { 'stream': score, 'numUserIdentified': user, 'numMusic21Identified': music21VS, 'numBothIdentified': both, 'accuracy': both * 100 / music21VS if music21VS != 0 else 100, 'romans': romanFigureList, 'key': pieceKey } return analysisData
def chfyChordAndLabel(self, ignoreParts: int = 2, tonicizationsRemainInEffect: bool = False): ''' To use in the case of a partial analysis with chords and key information. Takes each successive chord and key/tonicization labelling lyric, works out the chord, and either returns that as data, or as appended to the score in question. Notes for the markup: changes of key remain in effect until the next marking, but changes of tonicizations don't by default (settable with tonicizationsRemainInEffect). That said, you may want to put in reminders of the prevailing key occasionally (after a tonicization, or indeed elsewhere); that's fine and doesn't make any different to the analysis. ''' self.deducedAnalysis = [] reduction = deepcopy(self.score) for x in range(ignoreParts): reduction.remove( reduction.parts[0]) # Top parts of the original score self.chfyScore = reduction.stripTies().chordify() self.chfyScore.partName = 'Roman' chNotes = self.chfyScore.recurse().notes currentIndex = 0 # Index currentKey = 'FAKE KEY' # Initialise empty for inclusion of the first key currentTonicization = None if not self.annotationsAndLocations: self.getAnnotationsAndLocations() keyData = self.annotationsAndLocations lenKeyData = len(keyData) for ch in chNotes: startKey = currentKey if not tonicizationsRemainInEffect: # == False: currentTonicization = None # To reset for each chord. # Key Changes and Tonicization if lenKeyData > currentIndex: # Index from 0, len counts from 1. Gets all of keyData if [ch.measureNumber, ch.beat] == keyData[currentIndex][0:2]: stringInQuestion = keyData[currentIndex][ 2] # Before changing to new current index currentIndex += 1 if '/' in stringInQuestion: # Then it's a local tonicization currentTonicization = stringInQuestion[ 1:] # After the '/' # TODO support e.g. '/g' as well as (and converting it to) relative ('/ii') else: # Then it's a an actual modulation currentKey = stringInQuestion currentTonicization = None # Definitely reset for a key change # TODO add a condition to support m3-4 = m1-2 style annotation # TODO accept a roman.romanNumeral (not from chord) if not currentTonicization: rn = roman.romanNumeralFromChord(ch, key.Key(currentKey)) else: # currentTonicization localKey = getLocalKey(currentTonicization, currentKey) rn = roman.romanNumeralFromChord( ch, key.Key(localKey), # sixthMinor=roman.Minor67Default.CAUTIONARY, # seventhMinor=roman.Minor67Default.CAUTIONARY, ) # TODO: issues with sixth and seventh minor lyric = str(rn.figure) # Lyric modifications. Otherwise the lyric is unchanged if startKey != currentKey: lyric = currentKey + ': ' + lyric elif currentTonicization: lyric = lyric + stringInQuestion ch.lyric = lyric thisData = [ch.measureNumber, ch.beat, lyric] self.deducedAnalysis.append(thisData)
def main(): parser = argparse.ArgumentParser() parser.add_argument('-key', help='run Krumhansl algorithm on a music file', action="store_true") parser.add_argument('-rna', help='run roman numeral analysis on a music file', action="store_true") parser.add_argument('-rtc', help='run real-time chord naming', action="store_true") parser.add_argument('-out', help='read a file and then write it out again', action="store_true") args = parser.parse_args() if not (args.rtc): root = tk.Tk() root.withdraw() file_path = filedialog.askopenfilename() start = int(time.time()) score = converter.parse(file_path) read = int(time.time()) sys.stdout.write('file read in ' + str(read - start) + 'ms\n') if args.key or args.rna: key = analysis.discrete.analyzeStream(score, 'Krumhansl') keyTime = int(time.time()) sys.stdout.write( str(key) + ' established in ' + str(keyTime - read) + 'ms\n') if args.rna: delim = "" chordFilter = stream.filters.ClassFilter('Chord') sIter = score.recurse().iter sIter.addFilter(chordFilter) for a in sIter: root = a.root() rn = roman.romanNumeralFromChord(a, key) sys.stdout.write(delim + str(rn.romanNumeralAlone)) delim = " - " rnaTime = int(time.time()) sys.stdout.write(' determined in ' + str(rnaTime - read) + 'ms\n') if args.out: score.write('abc', 'abcout.abc') score.write('midi', 'midiout.mid') score.write('musicxml', 'xmlout.xml') write = int(time.time()) sys.stdout.write('file written in ' + str(write - read) + 'ms\n') if args.rtc: midi.init() controller = midi.Input(pygame.midi.get_default_input_id()) c = chord.Chord() while True: if (controller.poll()): midi_event = controller.read(5) notePitch = midi_event[0][0][1] eventType = midi_event[0][0][0] n = note.Note(notePitch) if eventType == 144: c.add(n) sys.stdout.write(str(n) + '\n') if c.isTriad(): sys.stdout.write(c.pitchedCommonName + '\n') if c.pitchedCommonName == 'A4-minor triad': break if eventType == 128: c.remove(n) c = chord.Chord(c)
def get_naive_chord_label(self, index): chord = self.chords[index] rn = roman.romanNumeralFromChord(chord, self.key) return rn.figure
def print_chords(chorale): chorale_key = chorale.analyze("key") chord_list = get_chords(chorale) for c in chord_list: rn = roman.romanNumeralFromChord(c, chorale_key) print(rn.figure)
bChords = b.chordify() #adds chordify as another voice to show better for c in bChords.flat: if 'Chord' not in c.classes: continue c.closedPosition(forceOctave=4, inPlace=True) #b.measures(0,2).show() stats = TransitionGraph() #adds roman numerals at the bottom for c in bChords.flat.getElementsByClass('Chord'): rn = roman.romanNumeralFromChord(c, key.Key('A')) stats.addNumeral(rn) c.addLyric(str(rn.figure)) #bChords.measures(0, 2).show() bChords.show('text') #prints the roman numerals, i.e. lyrics for c in bChords.measures(0,2).flat: if 'Chord' not in c.classes: continue print c.lyric, print "\n" stats.printTransitions()
def music21_extract(p): """ Takes in a Music21 score, and outputs dict """ parts = [] parts_times = [] parts_delta_times = [] parts_extras = [] parts_time_signatures = [] parts_key_signatures = [] c = p.chordify() ks = p.parts[0].stream().flat.keySignature parts_roman_chords = [] parts_chords = [] for this_chord in c.recurse().getElementsByClass('Chord'): parts_chords.append(this_chord.fullName) #print(this_chord.measureNumber, this_chord.beatStr, this_chord) rn = roman.romanNumeralFromChord(this_chord, ks.asKey()) parts_roman_chords.append(rn.figure) for i, pi in enumerate(p.parts): part = [] part_time = [] part_delta_time = [] part_extras = [] total_time = 0 ts = pi.stream().flat.timeSignature ks = pi.stream().flat.keySignature if len(ks.alteredPitches) == 0: parts_key_signatures.append([0]) else: parts_key_signatures.append([ks.sharps]) parts_time_signatures.append((ts.numerator, ts.denominator)) for n in pi.stream().flat.notesAndRests: if n.isRest: part.append(0) else: try: part.append(n.midi) except AttributeError: continue if n.tie is not None: if n.tie.type == "start": part_extras.append(1) elif n.tie.type == "continue": part_extras.append(2) elif n.tie.type == "stop": part_extras.append(3) else: print("another type of tie?") from IPython import embed; embed(); raise ValueError() elif len(n.expressions) > 0: print("trill or fermata?") from IPython import embed; embed(); raise ValueError() else: part_extras.append(0) part_time.append(total_time + n.duration.quarterLength) total_time = part_time[-1] part_delta_time.append(n.duration.quarterLength) parts.append(part) parts_times.append(part_time) parts_delta_times.append(part_delta_time) parts_extras.append(part_extras) return {"parts": parts, "parts_times": parts_times, "parts_delta_times": parts_delta_times, "parts_extras": parts_extras, "parts_time_signatures": parts_time_signatures, "parts_key_signatures": parts_key_signatures, "parts_chords": parts_chords, "parts_roman_chords": parts_roman_chords}