def getDocumentationFiles(runOne=False): ''' returns a list of namedtuples for each module that should be run >>> from music21.test import testDocumentation >>> testDocumentation.getDocumentationFiles() [ModTuple(module='index.rst', fullModulePath='...music21/documentation/autogenerated/index.rst', moduleNoExtension='index', autoGen=False), ..., ModTuple(module='usersGuide_03_pitches.rst', fullModulePath='...music21/documentation/autogenerated/usersGuide/usersGuide_03_pitches.rst', moduleNoExtension='usersGuide_03_pitches', autoGen=True), ...] ''' from music21 import common music21basedir = common.getRootFilePath() builddocRstDir = os.path.join(music21basedir, 'documentation', 'source') if not os.path.exists(builddocRstDir): raise Music21Exception( "Cannot run tests on documentation because the rst files " + "in documentation/source do not exist") allModules = [] for root, unused_dirnames, filenames in os.walk(builddocRstDir): for module in filenames: fullModulePath = os.path.join(root, module) if not module.endswith('.rst'): continue if module.startswith('module'): # we have this already... continue if module in skipModules: continue if runOne is not False: if not module.endswith(runOne): continue with io.open(fullModulePath, 'r', encoding='utf-8') as f: incipit = f.read(1000) if 'AUTOMATICALLY GENERATED' in incipit: autoGen = True else: autoGen = False moduleNoExtension = module[:-4] modTuple = ModTuple(module, fullModulePath, moduleNoExtension, autoGen) allModules.append(modTuple) return allModules
def extractHarmonies(music21Stream): # noinspection PyShadowingNames ''' Takes in a :class:`~music21.stream.Stream` and returns a dictionary whose values are the voice leading moments of the :class:`~music21.stream.Stream` and whose keys are (offset, endTime) pairs delimiting their duration. The voice leading moments are spelled out from the first or highest :class:`~music21.stream.Part` to the lowest one. >>> from music21 import corpus >>> score = corpus.parse('corelli/opus3no1/1grave').measures(1, 3) >>> #_DOCS_SHOW score.show() .. image:: images/figuredBass/corelli_grave.* :width: 700 >>> from music21.figuredBass import checker >>> allHarmonies = checker.extractHarmonies(score) >>> for (offsets, notes) in sorted(allHarmonies.items()): ... print("{0!s:15}[{1!s:23}{2!s:23}{3!s:22}]".format(offsets, notes[0], notes[1], notes[2])) (0.0, 1.5) [<music21.note.Note C> <music21.note.Note A> <music21.note.Note F> ] (1.5, 2.0) [<music21.note.Note C> <music21.note.Note A> <music21.note.Note F> ] (2.0, 3.0) [<music21.note.Note B-> <music21.note.Note G> <music21.note.Note G> ] (3.0, 3.5) [<music21.note.Note A> <music21.note.Note F> <music21.note.Note A> ] (3.5, 4.0) [<music21.note.Note A> <music21.note.Note F> <music21.note.Note B->] (4.0, 6.0) [<music21.note.Note G> <music21.note.Note E> <music21.note.Note C> ] (6.0, 6.5) [<music21.note.Note A> <music21.note.Note F> <music21.note.Note A> ] (6.5, 7.0) [<music21.note.Note B-> <music21.note.Note F> <music21.note.Note A> ] (7.0, 7.5) [<music21.note.Note C> <music21.note.Note F> <music21.note.Note A> ] (7.5, 8.0) [<music21.note.Note C> <music21.note.Note E> <music21.note.Note A> ] (8.0, 8.5) [<music21.note.Note C> <music21.note.Note D> <music21.note.Note B->] (8.5, 9.0) [<music21.note.Note F> <music21.note.Note D> <music21.note.Note B->] (9.0, 9.5) [<music21.note.Note B-> <music21.note.Note D> <music21.note.Note B->] (9.5, 10.0) [<music21.note.Note B-> <music21.note.Note G> <music21.note.Note B->] (10.0, 10.5) [<music21.note.Note B-> <music21.note.Note E> <music21.note.Note C> ] (10.5, 11.0) [<music21.note.Note B-> <music21.note.Note C> <music21.note.Note C> ] (11.0, 11.5) [<music21.note.Note A> <music21.note.Note F> <music21.note.Note D> ] (11.5, 12.0) [<music21.note.Note A> <music21.note.Note F> <music21.note.Note A> ] ''' allParts = music21Stream.getElementsByClass(stream.Part) if len(allParts) < 2: raise Music21Exception( 'There must be at least two parts to extract harmonies') allHarmonies = createOffsetMapping(allParts[0]) for music21Part in allParts[1:]: allHarmonies = correlateHarmonies(allHarmonies, music21Part) return allHarmonies
def readPickleGzip(filePath: Union[str, pathlib.Path]) -> Any: ''' Read a gzip-compressed pickle file, uncompress it, unpickle it, and return the contents. ''' with gzip.open(filePath, 'rb') as pickledFile: try: uncompressed = pickledFile.read() newMdb = pickle.loads(uncompressed) except Exception as e: # pylint: disable=broad-except # pickle exceptions cannot be caught directly # because they might come from pickle or _pickle and the latter cannot # be caught. raise Music21Exception('Cannot load file ' + str(filePath)) from e return newMdb
def slashMixedToFraction(valueSrc: str) -> Tuple[NumDenomTupleList, bool]: ''' Given a mixture if possible meter fraction representations, return a list of pairs. If originally given as a summed numerator; break into separate fractions and return True as the second element of the tuple >>> meter.tools.slashMixedToFraction('4/4') ([(4, 4)], False) >>> meter.tools.slashMixedToFraction('3/8+2/8') ([(3, 8), (2, 8)], False) >>> meter.tools.slashMixedToFraction('3+2/8') ([(3, 8), (2, 8)], True) >>> meter.tools.slashMixedToFraction('3+2+5/8') ([(3, 8), (2, 8), (5, 8)], True) >>> meter.tools.slashMixedToFraction('3+2+5/8+3/4') ([(3, 8), (2, 8), (5, 8), (3, 4)], True) >>> meter.tools.slashMixedToFraction('3+2+5/8+3/4+2+1+4/16') ([(3, 8), (2, 8), (5, 8), (3, 4), (2, 16), (1, 16), (4, 16)], True) >>> meter.tools.slashMixedToFraction('3+2+5/8+3/4+2+1+4') Traceback (most recent call last): music21.exceptions21.MeterException: cannot match denominator to numerator in: 3+2+5/8+3/4+2+1+4 ''' pre = [] post = [] summedNumerator = False value = valueSrc.strip() # rem whitespace value = value.split('+') for part in value: if '/' in part: tup = slashToTuple(part) if tup is None: raise TimeSignatureException( 'cannot create time signature from:', valueSrc) pre.append([tup.numerator, tup.denominator]) else: # its just a numerator try: pre.append([int(part), None]) except ValueError: raise Music21Exception( 'Cannot parse this file -- this error often comes ' + 'up if the musicxml pickled file is out of date after a change ' + 'in musicxml/__init__.py . ' + 'Clear your temp directory of .p and .p.gz files and try again...; ' + f'Time Signature: {valueSrc} ') # when encountering a missing denominator, find the first defined # and apply to all previous for i in range(len(pre)): if pre[i][1] is not None: # there is a denominator post.append(tuple(pre[i])) else: # search ahead for next defined denominator summedNumerator = True match = None for j in range(i, len(pre)): if pre[j][1] is not None: match = pre[j][1] break if match is None: raise MeterException( f'cannot match denominator to numerator in: {valueSrc}') pre[i][1] = match post.append(tuple(pre[i])) return post, summedNumerator
def slashMixedToFraction(valueSrc: str) -> t.Tuple[NumDenomTuple, bool]: ''' Given a mixture if possible meter fraction representations, return a tuple of two elements: The first element is a tuple of pairs of numerator, denominators that are implied by the time signature. The second element is False if the value was a simple time signature (like 4/4) or a composite meter where all numerators had their own denominators. >>> meter.tools.slashMixedToFraction('4/4') (((4, 4),), False) >>> meter.tools.slashMixedToFraction('3/8+2/8') (((3, 8), (2, 8)), False) >>> meter.tools.slashMixedToFraction('3+2/8') (((3, 8), (2, 8)), True) >>> meter.tools.slashMixedToFraction('3+2+5/8') (((3, 8), (2, 8), (5, 8)), True) >>> meter.tools.slashMixedToFraction('3+2+5/8+3/4') (((3, 8), (2, 8), (5, 8), (3, 4)), True) >>> meter.tools.slashMixedToFraction('3+2+5/8+3/4+2+1+4/16') (((3, 8), (2, 8), (5, 8), (3, 4), (2, 16), (1, 16), (4, 16)), True) >>> meter.tools.slashMixedToFraction('3+2+5/8+3/4+2+1+4') Traceback (most recent call last): music21.exceptions21.MeterException: cannot match denominator to numerator in: 3+2+5/8+3/4+2+1+4 >>> meter.tools.slashMixedToFraction('3.0/4.0') Traceback (most recent call last): music21.exceptions21.TimeSignatureException: Cannot create time signature from "3.0/4.0" Changed in v7 -- new location and returns a tuple as first value. ''' pre: t.List[t.Union[NumDenom, t.Tuple[int, None]]] = [] post: t.List[NumDenom] = [] summedNumerator = False value = valueSrc.strip() # rem whitespace value = value.split('+') for part in value: if '/' in part: try: tup = slashToTuple(part) except MeterException as me: raise TimeSignatureException( f'Cannot create time signature from "{valueSrc}"') from me pre.append((tup.numerator, tup.denominator)) else: # its just a numerator try: pre.append((int(part), None)) except ValueError: raise Music21Exception( 'Cannot parse this file -- this error often comes ' + 'up if the musicxml pickled file is out of date after a change ' + 'in musicxml/__init__.py . ' + 'Clear your temp directory of .p and .p.gz files and try again...; ' + f'Time Signature: {valueSrc} ') # when encountering a missing denominator, find the first defined # and apply to all previous for i in range(len(pre)): if pre[i][1] is not None: # there is a denominator intNum = pre[i][0] # this is all for type checking intDenom = pre[i][1] if t.TYPE_CHECKING: assert isinstance(intNum, int) and isinstance(intDenom, int) post.append((intNum, intDenom)) else: # search ahead for next defined denominator summedNumerator = True match: t.Optional[int] = None for j in range( i, len(pre) ): # this O(n^2) operation is easily simplified to O(n) if pre[j][1] is not None: match = pre[j][1] break if match is None: raise MeterException( f'cannot match denominator to numerator in: {valueSrc}') preBothAreInts = (pre[i][0], match) post.append(preBothAreInts) return tuple(post), summedNumerator