diff --git a/music21/base.py b/music21/base.py index f93918fd54..227d069749 100644 --- a/music21/base.py +++ b/music21/base.py @@ -2820,11 +2820,12 @@ def write( raise Music21ObjectException(f'cannot support output in this format yet: {fmt}') formatWriter = scClass() return formatWriter.write(self, - regularizedConverterFormat, + fmt=regularizedConverterFormat, fp=fp, subformats=subformats, **keywords) + def _reprText(self, **keywords): ''' Return a text representation possible with line @@ -2873,7 +2874,7 @@ def show(self, fmt=None, app=None, **keywords): # pragma: no cover if common.runningUnderIPython(): try: fmt = environLocal['ipythonShowFormat'] - except environment.EnvironmentException: + except (environment.EnvironmentException, KeyError): fmt = 'ipython.musicxml.png' else: fmt = environLocal['showFormat'] diff --git a/music21/braille/basic.py b/music21/braille/basic.py index e4c612e3a8..bc6f035c2e 100644 --- a/music21/braille/basic.py +++ b/music21/braille/basic.py @@ -9,6 +9,35 @@ # Copyright: Copyright © 2011-22 Michael Scott Asato Cuthbert # License: BSD, see license.txt # ------------------------------------------------------------------------------ +r''' +Basic tools for working with Braille in `music21`. Most users will not need to +use these. Simply call `.show('braille')` or `.show('braille.ascii')` + +>>> soprano = corpus.parse('bwv66.6').parts[0] +>>> #_DOCS_SHOW soprano.show('braille.ascii') +>>> print(converter.toData(soprano, 'braille.ascii')) # _DOCS_HIDE + %%%.C + #J .DJ [W?>> data = converter.toData(soprano, 'braille') +>>> print(data) + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠩⠩⠩⠨⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ⠼⠚⠀⠨⠙⠚⠀⠪⠺⠹⠣⠇⠫⠀⠹⠺⠪⠣⠇⠹⠀⠊⠚⠳⠻⠣⠇⠪⠀⠺⠺⠻⠫⠀⠪⠺⠹⠣⠇⠹ + ⠀⠀⠐⠪⠺⠹⠪⠀⠳⠻⠗⠣⠇⠀⠟⠻⠻⠈⠉⠀⠻⠛⠩⠋⠻⠣⠇⠣⠅ + +Any :class:`~music21.base.Music21Object` which cannot be transcribed in +:mod:`~music21.braille.basic` returns a braille literary question mark +and outputs a warning to the console, rather than raising an exception. +This is so that a transcription of a :class:`~music21.stream.Stream` in +:class:`~music21.braille.translate` is completed as thoroughly as possible. +''' + + from __future__ import annotations import typing as t @@ -52,15 +81,6 @@ # ------------------------------------------------------------------------------ # music21Object to braille unicode methods -# noinspection PyStatementEffect -''' -Any :class:`~music21.base.Music21Object` which cannot be transcribed in -:mod:`~music21.braille.basic` returns a braille literary question mark -and outputs a warning to the console, rather than raising an exception. -This is so that a transcription of a :class:`~music21.stream.Stream` in -:class:`~music21.braille.translate` is completed as thoroughly as possible. -''' - def barlineToBraille(music21Barline): r''' Takes in a :class:`~music21.bar.Barline` and returns its representation @@ -1449,18 +1469,16 @@ def brailleUnicodeToBrailleAscii(brailleUnicode): translates a braille UTF-8 unicode string into braille ASCII, which is the format compatible with most braille embossers. - .. note:: The function works by corresponding braille symbols to ASCII symbols. The table which corresponds to said values can be found `here `_. Because of the way in which the braille symbols translate, the resulting ASCII string will look to a non-reader as gibberish. Also, the eighth-note notes - in braille - music are one-off their corresponding letters in both ASCII and written braille. + in braille music are one-off their corresponding letters + in both ASCII and written braille. The written D is really a C eighth-note, the written E is really a D eighth note, etc. - >>> from music21.braille.basic import brailleUnicodeToBrailleAscii, noteToBraille >>> brailleUnicodeToBrailleAscii('\u2800') ' ' @@ -1621,6 +1639,9 @@ def add_letter(inner_letter: str): add_letter(letter.lower()) elif letter == '.': wordTrans.append(symbols['dot']) + elif letter == 'ß': + add_letter('s') + add_letter('s') else: try: add_letter(letter) @@ -1646,6 +1667,9 @@ def add_letter(inner_letter: str): add_letter(letter.lower()) elif letter.isdigit(): wordTrans.append(lookup.numbersUpper[int(letter)]) + elif letter == 'ß': + add_letter('s') + add_letter('s') else: add_letter(letter) diff --git a/music21/braille/lookup.py b/music21/braille/lookup.py index 5fa4558333..b887c3bb6b 100644 --- a/music21/braille/lookup.py +++ b/music21/braille/lookup.py @@ -346,7 +346,7 @@ def makePitchNameToNotes(): ']': _B[2356] + _B[3], '*': _B[35] + _B[35], } -alphabet.update({str(k): v for k, v in numbersUpper.items()}) +alphabet |= {str(k): v for k, v in numbersUpper.items()} chordSymbols = { 'plus': _B[346], diff --git a/music21/braille/translate.py b/music21/braille/translate.py index b5b33a046c..5505e837d4 100644 --- a/music21/braille/translate.py +++ b/music21/braille/translate.py @@ -463,22 +463,37 @@ def process_unmatched_part_staff_as_single_part(): return '\n'.join(allBrailleLines) -def metadataToString(music21Metadata, returnBrailleUnicode=False): +def metadataToString(music21Metadata: metadata.Metadata, returnBrailleUnicode=False) -> str: ''' + Convert a Metadata format to a format for BRF. + >>> from music21.braille import translate >>> corelli = corpus.parse('monteverdi/madrigal.3.1.rntxt') >>> mdObject = corelli.getElementsByClass(metadata.Metadata).first() >>> mdObject.__class__ + + The default is very close to ascii. + >>> print(translate.metadataToString(mdObject)) Alternative Title: 3.1 Composer: Claudio Monteverdi Title: La Giovinetta Pianta - >>> print(translate.metadataToString(mdObject, returnBrailleUnicode=True)) + And in Braille Unicode. + + >>> unicodeVersion = translate.metadataToString(mdObject, returnBrailleUnicode=True) + >>> print(unicodeVersion) ⠠⠁⠇⠞⠑⠗⠝⠁⠞⠊⠧⠑⠀⠠⠞⠊⠞⠇⠑⠒⠀⠼⠉⠲⠁ ⠠⠉⠕⠍⠏⠕⠎⠑⠗⠒⠀⠠⠉⠇⠁⠥⠙⠊⠕⠀⠠⠍⠕⠝⠞⠑⠧⠑⠗⠙⠊ ⠠⠞⠊⠞⠇⠑⠒⠀⠠⠇⠁⠀⠠⠛⠊⠕⠧⠊⠝⠑⠞⠞⠁⠀⠠⠏⠊⠁⠝⠞⠁ + + Note the difference between the first and then translating back to ASCII Braille: + + >>> print(braille.basic.brailleUnicodeToBrailleAscii(unicodeVersion)) + ,ALTERNATIVE ,TITLE3 #C4A + ,COMPOSER3 ,CLAUDIO ,MONTEVERDI + ,TITLE3 ,LA ,GIOVINETTA ,PIANTA ''' allBrailleLines = [] for uniqueName, value in music21Metadata.all(returnPrimitives=True, returnSorted=False): diff --git a/music21/common/stringTools.py b/music21/common/stringTools.py index de67e1cb96..9e340450f1 100644 --- a/music21/common/stringTools.py +++ b/music21/common/stringTools.py @@ -300,8 +300,13 @@ def stripAccents(inputString: str) -> str: True >>> common.stripAccents(s) 'tres vite' + + Also handles the German Eszett + + >>> common.stripAccents('Muß') + 'Muss' ''' - nfkd_form = unicodedata.normalize('NFKD', inputString) + nfkd_form = unicodedata.normalize('NFKD', inputString).replace('ß', 'ss') return ''.join([c for c in nfkd_form if not unicodedata.combining(c)]) diff --git a/music21/converter/__init__.py b/music21/converter/__init__.py index a5aaf9d9b6..28b726e30f 100644 --- a/music21/converter/__init__.py +++ b/music21/converter/__init__.py @@ -63,6 +63,10 @@ from music21.metadata import bundles +if t.TYPE_CHECKING: + from music21 import base + + __all__ = [ 'subConverters', 'ArchiveManagerException', 'PickleFilterException', 'ConverterException', 'ConverterFileException', @@ -1316,6 +1320,41 @@ def parse(value: bundles.MetadataEntry | bytes | str | pathlib.Path, # all else, including MidiBytes return parseData(value, number=number, format=format, **keywords) +def toData(obj: base.Music21Object, fmt: str, **keywords) -> str | bytes: + ''' + Convert `obj` to the given format `fmt` and return the information retrieved. + + Currently, this is somewhat inefficient: it calls Subconverter.toData which + calls `write()` on the object and reads back the value of the file. + + >>> tiny = converter.parse('tinyNotation: 4/4 C4 D E F G1') + >>> data = converter.toData(tiny, 'braille.ascii') + >>> type(data) + + >>> print(data) + #D4 + #A _?:$] ( pathlib.Path: # noinspection PyShadowingNames diff --git a/music21/converter/subConverters.py b/music21/converter/subConverters.py index 68477e66ab..d851555a92 100644 --- a/music21/converter/subConverters.py +++ b/music21/converter/subConverters.py @@ -329,6 +329,31 @@ def writeDataStream(self, fp.close() return pathlib.Path('') + def toData( + self, + obj, + fmt: str | None, + subformats: Iterable[str] = (), + **keywords, + ) -> str | bytes: + ''' + Write the object out in the given format and then read it back in + and return the object (str or bytes) returned. + ''' + fp = self.write(obj, fmt=fmt, subformats=subformats, **keywords) + if self.readBinary is False: + readFlags = 'r' + else: + readFlags = 'rb' + with open(fp, + mode=readFlags, + encoding=self.stringEncoding if self.codecWrite else None + ) as f: + out = f.read() + fp.unlink(missing_ok=True) + return out + + class ConverterIPython(SubConverter): ''' Meta-subconverter for displaying image data in a Notebook @@ -547,7 +572,14 @@ class ConverterBraille(SubConverter): registerOutputExtensions = ('txt',) codecWrite = True - def show(self, obj, fmt, app=None, subformats=(), **keywords): # pragma: no cover + def show( + self, + obj, + fmt, + app=None, + subformats=(), + **keywords + ): # pragma: no cover if not common.runningUnderIPython(): super().show(obj, fmt, app=None, subformats=subformats, **keywords) else: @@ -555,10 +587,17 @@ def show(self, obj, fmt, app=None, subformats=(), **keywords): # pragma: no cov dataStr = braille.translate.objectToBraille(obj) print(dataStr) - def write(self, obj, fmt, fp=None, subformats=(), **keywords): # pragma: no cover + def write( + self, + obj, + fmt, + fp=None, + subformats=(), + **keywords + ): # pragma: no cover from music21 import braille dataStr = braille.translate.objectToBraille(obj, **keywords) - if subformats is not None and 'ascii' in subformats: + if 'ascii' in subformats: dataStr = braille.basic.brailleUnicodeToBrailleAscii(dataStr) fp = self.writeDataStream(fp, dataStr) return fp @@ -1770,8 +1809,6 @@ def testMultiPageXMlShow1(self): if __name__ == '__main__': import music21 - # import sys - # sys.argv.append('SimpleTextShow') music21.mainTest(Test) # run command below to test commands that open musescore, etc. # music21.mainTest(TestExternal)