Source code for maelzel.scoring.renderer
"""
This module declares the basic classes for all renderers.
"""
from __future__ import annotations
from abc import ABC, abstractmethod
from maelzel.scorestruct import ScoreStruct
from maelzel.scoring.renderoptions import RenderOptions
from maelzel.scoring import quant
from maelzel.scoring.config import config
from maelzel import _util
from maelzel import _imgtools
import emlib.img
import emlib.misc
import emlib.envir
[docs]
class Renderer(ABC):
"""
Renders a quantizedscore to a given format
This is an abstract base class for different backend renderers (lilypond, musicxml, etc)
"""
def __init__(self, score: quant.QuantizedScore, options: RenderOptions):
if not score:
raise ValueError("The quantized score is empty")
if score[0].struct is None:
raise ValueError("The score has no associated ScoreStruct, cannot render")
self.quantizedScore: quant.QuantizedScore = score
"""The quantized score used for rendering"""
self.struct: ScoreStruct = score[0].struct
"""The ScoreStruct used for rendering"""
self.options: RenderOptions = options
"""The render options used"""
def __hash__(self) -> int:
return hash((hash(self.quantizedScore), hash(self.struct), hash(self.options)))
[docs]
@abstractmethod
def render(self, options: RenderOptions | None = None) -> str:
"""
Render the quantized score
.. note::
The result is internally cached so calling this method multiple times
only performs the rendering once as long as the options used do not
change
Args:
options: if given, these options override the own options
Returns:
the rendered score, as text. This will be lilypond text if the
renderer is a LilypondRenderer, or musicxml text in the case
of a MusicxmlRenderer
"""
raise NotImplementedError("Please Implement this method")
[docs]
@abstractmethod
def write(self, outfile: str, fmt='', removeTemporaryFiles=False) -> None:
"""
Write the rendered score to a file
Args:
outfile: the path to the written file
fmt: if given, this will be used as format for the output, independent
of the extension used in outfile. The possible values depend on the
formats supported by this Renderer (see :meth:`Renderer.writeFormats`)
removeTemporaryFiles: if True, removes any temporary files generated during
the rendering/writing process
"""
raise NotImplementedError("Please Implement this method")
[docs]
def musicxml(self) -> str | None:
"""
Returns the rendered score as musicxml if supported
Returns:
either the musicxml as str, or None if not supported by
this renderer
"""
return None
[docs]
def show(self, fmt='png', external=False, scalefactor=1.0) -> None:
"""
Display the rendered score
Args:
fmt: one of 'png', 'pdf'
external: if True, for the use of an external app to open the rendered result.
Otherwise, if running inside jupyter this command will try to display
the result inline
scalefactor: a factor to scale the generated image when shown inline
"""
if fmt == 'pdf':
external = True
if fmt == 'png':
png = _util.mktemp(suffix='.png')
self.write(png)
_util.pngShow(png, forceExternal=external, inlineScale=scalefactor)
elif fmt == 'pdf':
outfile = _util.mktemp(suffix=f'.{fmt}')
self.write(outfile)
emlib.misc.open_with_app(outfile)
else:
raise ValueError(f"fmt should be 'png' or 'pdf', got '{fmt}'")
def _repr_html_(self) -> str:
scale = config['pngScale']
pngfile = _util.mktemp(suffix=".png", prefix="render-")
self.write(pngfile)
img64, w, h = _imgtools.readImageAsBase64(pngfile)
html = _util.htmlImage64(img64, w, width=f'{int(w*scale)}px')
parts = "1 part" if len(self.quantizedScore) == 1 else f"{len(self.quantizedScore)} parts"
return f'<b>{type(self).__name__}</b> ({parts})<br>'+html