def testJoinPartStaffsB(self): ''' Gapful first PartStaff, ensure <backup> in second PartStaff correct ''' from music21 import layout from music21 import note s = stream.Score() ps1 = stream.PartStaff() ps1.insert(0, note.Note()) # Gap ps1.insert(3, note.Note()) ps2 = stream.PartStaff() ps2.insert(0, note.Note()) s.append(ps1) s.append(ps2) s.insert(0, layout.StaffGroup([ps1, ps2])) root = self.getET(s) notes = root.findall('.//note') forward = root.find('.//forward') backup = root.find('.//backup') amountToBackup = ( int(notes[0].find('duration').text) + int(forward.find('duration').text) + int(notes[1].find('duration').text) ) self.assertEqual(int(backup.find('duration').text), amountToBackup)
def testMultipleInstrumentsPiano(self): ps1 = stream.PartStaff([ stream.Measure( [instrument.ElectricPiano(), note.Note(type='whole')]), stream.Measure( [instrument.ElectricOrgan(), note.Note(type='whole')]), stream.Measure([instrument.Piano(), note.Note(type='whole')]), ]) ps2 = stream.PartStaff([ stream.Measure([instrument.Vocalist(), note.Note(type='whole')]), stream.Measure([note.Note(type='whole')]), stream.Measure([note.Note(type='whole')]), ]) sg = layout.StaffGroup([ps1, ps2]) s = stream.Score([ps1, ps2, sg]) scEx = ScoreExporter(s) tree = scEx.parse() self.assertEqual( [el.text for el in tree.findall('.//instrument-name')], ['Electric Piano', 'Voice', 'Electric Organ', 'Piano']) self.assertEqual(len(tree.findall('.//measure/note/instrument')), 6)
def testJoinPartStaffsE(self): ''' Measure numbers existing only in certain PartStaffs: don't collapse together ''' from music21 import corpus from music21 import layout sch = corpus.parse('schoenberg/opus19', 2) s = stream.Score() ps1 = stream.PartStaff() ps2 = stream.PartStaff() s.append(ps1) s.append(ps2) s.insert(0, layout.StaffGroup([ps1, ps2])) m1 = sch.parts[0].measure(1) # RH m2 = sch.parts[1].measure(2) # LH m3 = sch.parts[0].measure(3) # RH ps1.append(m1) ps1.append(m3) ps2.insert(m1.offset, m2) root = self.getET(s) m1tag, m2tag, m3tag = root.findall('part/measure') self.assertEqual({staff.text for staff in m1tag.findall('note/staff')}, {'1'}) self.assertEqual({staff.text for staff in m2tag.findall('note/staff')}, {'2'}) self.assertEqual({staff.text for staff in m3tag.findall('note/staff')}, {'1'})
def writeScoreWithAnalysis(self, outPath: str = '.', outFile: str = 'Analysis_on_score', feedback: bool = True, lieder: bool = True): ''' Mostly to combine an off-score analysis with the corresponding score and write to disc. Option (feedback=True, default) to include '***' above relevant moments. Error raised in the case of a call on score with analysis already on there (i.e. with analysisLocation = 'On score' in the init) and feedback as false. Nothing to add in that case. Additional presentation option (lieder=True, default) for returning the Mensurstrich brace to the piano part of the lieder. Don't worry if that doesn't mean anything to you. ''' if self.analysisLocation == 'On score': if not feedback: msg = 'This method is for combining a score with ' \ 'an analysis hosted separately, and / or for ' \ 'flagging up moments for which there is feedback available.\n' \ 'You have called this on a score with the analysis already on there, ' \ 'and declined feedback, so this method has nothing to offer.' raise ValueError(msg) else: self.scoreWithAnalysis = self.score else: self.scoreWithAnalysis = deepcopy(self.score) analysis = deepcopy(self.analysis) analysis.partName = 'Analysis' measureDiff = self.scoreMeasures - self.analysisMeasures if measureDiff > 0: for x in range(measureDiff): analysis.append(stream.Measure()) self.scoreWithAnalysis.insert(0, analysis) if lieder: # If lieder option is set to true and ... if len(self.scoreWithAnalysis.parts) == 4: # there are 4 parts inc. the analysis staffGrouping = layout.StaffGroup([self.scoreWithAnalysis.parts[1], self.scoreWithAnalysis.parts[2] ], name='Piano', abbreviation='Pno.', symbol='brace') staffGrouping.barTogether = 'Mensurstrich' self.scoreWithAnalysis.insert(0, staffGrouping) # self._feedbackOnScore() if outFile == 'Analysis_on_score': if self.name: outFile = self.name + '_with_analysis_onscore', self.scoreWithAnalysis.write('mxl', fp=f'{os.path.join(outPath, outFile)}.mxl')
def testStaffGroupsB(self): from music21 import layout p1 = stream.Part() p1.repeatAppend(note.Note(), 8) p2 = stream.Part() p3 = stream.Part() p4 = stream.Part() p5 = stream.Part() p6 = stream.Part() p7 = stream.Part() p8 = stream.Part() sg1 = layout.StaffGroup([p1, p2], symbol='brace', name='marimba') sg2 = layout.StaffGroup([p3, p4], symbol='bracket', name='xlophone') sg3 = layout.StaffGroup([p5, p6], symbol='line', barTogether=False) sg4 = layout.StaffGroup([p5, p6, p7], symbol='line', barTogether=False) s = stream.Score() s.insert([ 0, p1, 0, p2, 0, p3, 0, p4, 0, p5, 0, p6, 0, p7, 0, p8, 0, sg1, 0, sg2, 0, sg3, 0, sg4 ]) # deprecated insert usage #s.show() raw = fromMusic21Object(s) match = '<group-symbol>brace</group-symbol>' self.assertEqual(raw.find(match) > 0, True) match = '<group-symbol>bracket</group-symbol>' self.assertEqual(raw.find(match) > 0, True) # TODO: order of attributes is not assured; allow for any order. match = '<part-group number="1" type="start">' self.assertEqual(raw.find(match) > 0, True) match = '<part-group number="1" type="stop"/>' self.assertEqual(raw.find(match) > 0, True) match = '<part-group number="2" type="start">' self.assertEqual(raw.find(match) > 0, True) match = '<part-group number="2" type="stop"/>' self.assertEqual(raw.find(match) > 0, True)
def testJoinPartStaffsC(self): ''' First PartStaff longer than second ''' from music21 import layout from music21 import note s = stream.Score() ps1 = stream.PartStaff() ps1.repeatAppend(note.Note(), 8) ps1.makeNotation(inPlace=True) # makeNotation to freeze notation s.insert(0, ps1) ps2 = stream.PartStaff() ps2.repeatAppend(note.Note(), 4) ps2.makeNotation(inPlace=True) # makeNotation to freeze notation s.insert(0, ps2) s.insert(0, layout.StaffGroup([ps1, ps2])) root = self.getET(s) measures = root.findall('.//measure') notes = root.findall('.//note') self.assertEqual(len(measures), 2) self.assertEqual(len(notes), 12)
def testMeterChanges(self): from music21 import layout from music21 import meter from music21 import note ps1 = stream.PartStaff() ps2 = stream.PartStaff() sg = layout.StaffGroup([ps1, ps2]) s = stream.Score([ps1, ps2, sg]) for ps in ps1, ps2: ps.insert(0, meter.TimeSignature('3/1')) ps.repeatAppend(note.Note(type='whole'), 6) ps.makeNotation(inPlace=True) # makes measures ps[stream.Measure][1].insert(meter.TimeSignature('4/1')) root = self.getET(s) # Just two <attributes> tags, a 3/1 in measure 1 and a 4/1 in measure 2 self.assertEqual(len(root.findall('part/measure/attributes/time')), 2) # Edge cases -- no expectation of correctness, just don't crash ps1[stream.Measure].last().number = 0 # was measure 2 root = self.getET(s) self.assertEqual(len(root.findall('part/measure/attributes/time')), 3)
def testJoinPartStaffsD(self): ''' Same example as testJoinPartStaffsC but switch the hands: second PartStaff longer than first ''' from music21 import layout from music21 import note s = stream.Score() ps1 = stream.PartStaff() ps1.repeatAppend(note.Note(), 8) ps1.makeNotation(inPlace=True) # makeNotation to freeze notation ps2 = stream.PartStaff() ps2.repeatAppend(note.Note(), 4) ps2.makeNotation(inPlace=True) # makeNotation to freeze notation s.insert(0, ps2) s.insert(0, ps1) s.insert(0, layout.StaffGroup([ps2, ps1])) root = self.getET(s) measures = root.findall('.//measure') notes = root.findall('.//note') # from music21.musicxml.helpers import dump # dump(root) self.assertEqual(len(measures), 2) self.assertEqual(len(notes), 12)
cello.remove(n) cello.makeMeasures(inPlace=True) # -------------------------------------------------------------------------------------------------- # Build final score # -------------------------------------------------------------------------------------------------- piano_right_hand = tools.convert_stream(piano_right_hand, stream.Part) piano_left_hand = tools.convert_stream(piano_left_hand, stream.Part) cello = tools.convert_stream(cello, stream.Part) cello.partName = "Vc." score = tools.merge_streams( cello, piano_right_hand, piano_left_hand, stream_class=stream.Score ) piano_staff_group = layout.StaffGroup( [piano_right_hand, piano_left_hand], name="Piano", abbreviation="Pno.", symbol="brace", ) score.insert(0, piano_staff_group) score.metadata = metadata.Metadata() score.metadata.title = "Liturgie de Cristal" score.metadata.composer = "Olivier Messiaen" # -------------------------------------------------------------------------------------------------- # Output xml file and show score in MuseScore # -------------------------------------------------------------------------------------------------- score.write(fp="olivier_messiaen_quatuor.xml") score.show()
def makeLiederExercise(score, leaveRestBars: bool = True, quarterLengthOfRest=2, leaveBassLine: bool = False, addition=None, # Options: 'transferTune' and 'chordHints' quarterLength=1, writeFile: bool = False, outPath: str = '~/Desktop/', title: str = ''): """ Removes the piano part of an input song to create an exercise. Optionally leaves in: 1. the piano part for bars with a vocal part rest of a specified, minimum length; 2. the bass line (piano LH); Optionally then adds one of the following to the piano RH as a draft solution: A. the vocal melody (notes and rests); B. new chords based on leaps in the vocal line within the harmonic rhythm (rate) specified by quarterLength variable. """ score = deepcopy(score) # Bassline line Y / N. i.e. Select both piano parts, or just RH. parts = [1] # For cutting out RH (partNumber[1]) if not leaveBassLine: parts.append(2) # For also cutting out LH topPart = score.parts[0] NumMeasures = len(score.parts[0].getElementsByClass('Measure')) # Find vocal rests restBars = [] # Remains empty if not leaveRestBars if leaveRestBars: for listIndex in range(0, NumMeasures - 1): # NB list is 0 to N; measures is 1 to (N-1) count = 0 thisMeasure = topPart.getElementsByClass('Measure')[listIndex] # NB Above for item in thisMeasure.recurse().notesAndRests: if item.isRest: count += item.quarterLength if count >= thisMeasure.quarterLength: # Special case for anacrustic pieces. restBars.append(listIndex + 1) # = measure number break # No need to look at this measure any further elif count >= quarterLengthOfRest: restBars.append(listIndex + 1) # = measure number break # No need to look at this measure any further measuresToDo = [x for x in range(1, NumMeasures) if x not in restBars] # Removals for measureNumber in measuresToDo: for partNo in parts: for x in score.parts[partNo].measure(measureNumber).recurse().notesAndRests: score.parts[partNo].remove(x, recurse=True) # Additions validAdditions = [None, 'transferTune', 'chordHints'] if addition not in validAdditions: raise ValueError(f'Invalid addition type: must be one of {validAdditions}.') elif addition == 'transferTune': tempScore = transferClefs(score, measuresToDo) score = transferTune(tempScore, measuresToDo) elif addition == 'chordHints': tempScore = transferClefs(score, measuresToDo) score = addChords(tempScore, quarterLength=quarterLength) # Scrub lyrics inherited into piano part for item in score.parts[1].recurse().notesAndRests: if item.lyric: item.lyric = None if not title: title = score.metadata.title staffGrouping = layout.StaffGroup([score.parts[1], score.parts[2]], name='Piano', abbreviation='Pno.', symbol='brace') staffGrouping.barTogether = 'Mensurstrich' score.insert(0, staffGrouping) if writeFile: score.write('mxl', fp=f'{outPath}Exercise-{title}.mxl') return score