Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add converter.toData, improve braille docs #1451

Merged
merged 2 commits into from
Oct 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)