Skip to content

Commit

Permalink
Merge pull request #1451 from cuthbertLab/toData-braille
Browse files Browse the repository at this point in the history
Adds converter.toData -- a drop in for getting the raw data from a format.

Improve documentation for Braille
  • Loading branch information
mscuthbert authored Oct 7, 2022
2 parents e0b2ae2 + a21937a commit 6cfdcf4
Show file tree
Hide file tree
Showing 7 changed files with 145 additions and 24 deletions.
5 changes: 3 additions & 2 deletions music21/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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']
Expand Down
50 changes: 37 additions & 13 deletions music21/braille/basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -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?<L$ ?W[<L? IJ\]<L[ WW]$ [W?<L?
"[W?[ \]R<L Q]]@C ]G%F]<L<K
Normally this would open up in a new window. But to store the data, use
`converter.toData(..., 'braille')`. Here we show this in Unicode braille
(the default for format braille)
>>> 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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 <https://en.wikipedia.org/wiki/Braille_ASCII#Braille_ASCII_values>`_.
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')
' '
Expand Down Expand Up @@ -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)
Expand All @@ -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)

Expand Down
2 changes: 1 addition & 1 deletion music21/braille/lookup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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],
Expand Down
19 changes: 17 additions & 2 deletions music21/braille/translate.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__
<class 'music21.metadata.Metadata'>
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):
Expand Down
7 changes: 6 additions & 1 deletion music21/common/stringTools.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)])


Expand Down
39 changes: 39 additions & 0 deletions music21/converter/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@
from music21.metadata import bundles


if t.TYPE_CHECKING:
from music21 import base


__all__ = [
'subConverters', 'ArchiveManagerException', 'PickleFilterException',
'ConverterException', 'ConverterFileException',
Expand Down Expand Up @@ -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)
<class 'str'>
>>> print(data)
#D4
#A _?:$] (<K
'''
if fmt.startswith('.'):
fmt = fmt[1:]
regularizedConverterFormat, unused_ext = common.findFormat(fmt)
if regularizedConverterFormat is None:
raise ConverterException(f'cannot support output in this format yet: {fmt}')

formatSubs = fmt.split('.')
fmt = formatSubs[0]
subformats = formatSubs[1:]

scClass = common.findSubConverterForFormat(regularizedConverterFormat)
if scClass is None: # pragma: no cover
raise ConverterException(f'cannot support output in this format yet: {fmt}')
formatWriter = scClass()
return formatWriter.toData(
obj,
fmt=regularizedConverterFormat,
subformats=subformats,
**keywords)


def freeze(streamObj, fmt=None, fp=None, fastButUnsafe=False, zipType='zlib') -> pathlib.Path:
# noinspection PyShadowingNames
Expand Down
47 changes: 42 additions & 5 deletions music21/converter/subConverters.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -547,18 +572,32 @@ 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:
from music21 import braille
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
Expand Down Expand Up @@ -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)

0 comments on commit 6cfdcf4

Please sign in to comment.