A fast, portable and lightweight library for engraving Music Encoding Initiative (MEI) music scores into SVG
This document covers advanced functionality including experimental editor features, layout manipulation, locale management, runtime measurement, and other specialized capabilities.
Experimental editing capabilities for modifying loaded MEI documents.
Warning: These functions are experimental and should not be relied upon for production use. The API may change in future versions.
class toolkit:
def edit(self, editor_action: dict) -> bool:
"""
Edit the MEI data (experimental).
This is experimental code not to rely on. The API and
behavior may change in future versions.
Args:
editor_action: Dictionary with editor action specifications
Returns:
True if edit action was successfully applied, False otherwise
"""
def editInfo(self) -> dict:
"""
Get editor status (experimental).
This is experimental code not to rely on. Returns information
about the current editor state.
Returns:
Dictionary with editor status information
"""Redo layout calculations with updated options or after modifications.
class toolkit:
def redoLayout(self, options: dict | None = None) -> None:
"""
Redo the layout of the loaded data.
Call this after changing rendering options (e.g., page size,
zoom level) to recalculate the layout without reloading data.
Args:
options: Dictionary with action options:
- resetCache: bool, true to reset cache (default: True)
"""
def redoPagePitchPosLayout(self) -> None:
"""
Redo pitch position layout for the current page.
Only recalculates vertical positions of notes. For full
recalculation, use redoLayout() instead.
"""Control locale settings for the toolkit instance.
class toolkit:
def setLocale(self) -> None:
"""
Set the global locale for the toolkit instance.
"""
def resetLocale(self) -> None:
"""
Reset the global locale to the previous value.
"""Measure and log execution time for performance analysis.
class toolkit:
def initClock(self) -> None:
"""
Initialize the runtime clock.
Start timing for performance measurement.
"""
def resetClock(self) -> None:
"""
Reset the runtime clock.
Reset timing measurements to zero.
"""
def getRuntimeInSeconds(self) -> float:
"""
Get the elapsed runtime in seconds.
Returns:
Runtime in seconds as float
"""
def logRuntime(self) -> None:
"""
Log runtime information.
Write runtime information to the log.
"""import verovio
tk = verovio.toolkit()
# Initialize timing
tk.initClock()
# Load and render
tk.loadFile("large_score.mei")
page_count = tk.getPageCount()
for page in range(1, page_count + 1):
svg = tk.renderToSVG(pageNo=page)
# Get elapsed time
elapsed = tk.getRuntimeInSeconds()
print(f"Rendered {page_count} pages in {elapsed:.2f} seconds")
print(f"Average: {elapsed/page_count:.3f} seconds per page")
# Log to Verovio's internal log
tk.logRuntime()import verovio
def benchmark_rendering(filename, options_list):
"""Benchmark rendering with different option sets."""
tk = verovio.toolkit()
tk.loadFile(filename)
results = []
for idx, options in enumerate(options_list):
# Reset and start timing
tk.resetClock()
tk.initClock()
# Apply options and redo layout
tk.setOptions(options)
tk.redoLayout()
# Render all pages
page_count = tk.getPageCount()
for page in range(1, page_count + 1):
svg = tk.renderToSVG(pageNo=page)
# Record time
elapsed = tk.getRuntimeInSeconds()
results.append({
'options': options,
'time': elapsed,
'pages': page_count
})
print(f"Config {idx+1}: {elapsed:.2f}s for {page_count} pages")
return results
# Test different scales
options_to_test = [
{'scale': 40},
{'scale': 60},
{'scale': 80},
{'scale': 100}
]
results = benchmark_rendering("score.mei", options_to_test)import verovio
tk = verovio.toolkit()
tk.loadFile("score.mei")
# Initial render
initial_options = {
'pageHeight': 2970,
'pageWidth': 2100,
'scale': 40
}
tk.setOptions(initial_options)
svg1 = tk.renderToSVG()
# Change options and redo layout
new_options = {
'pageHeight': 3960,
'pageWidth': 2800,
'scale': 60
}
tk.setOptions(new_options)
tk.redoLayout()
# Render with new layout
svg2 = tk.renderToSVG()
print(f"Pages after resize: {tk.getPageCount()}")import verovio
tk = verovio.toolkit()
tk.loadFile("score.mei")
# Full layout
tk.redoLayout()
svg = tk.renderToSVG(pageNo=1)
# Make a small pitch adjustment (if needed)
# Then do quick pitch position recalculation
tk.redoPagePitchPosLayout()
# Render updated page
svg_updated = tk.renderToSVG(pageNo=1)import verovio
class ResponsiveScoreViewer:
def __init__(self, filename):
self.tk = verovio.toolkit()
self.tk.loadFile(filename)
self.current_page = 1
def resize_viewport(self, width, height):
"""Adjust score layout for new viewport size."""
self.tk.resetClock()
self.tk.initClock()
# Update page dimensions
options = {
'pageWidth': width,
'pageHeight': height,
'adjustPageHeight': True
}
self.tk.setOptions(options)
# Redo layout
self.tk.redoLayout()
elapsed = self.tk.getRuntimeInSeconds()
print(f"Layout recalculated in {elapsed:.3f}s")
return self.tk.getPageCount()
def set_zoom(self, scale):
"""Change zoom level."""
self.tk.setScale(scale)
self.tk.redoLayout()
def render_current_page(self):
"""Render the current page."""
return self.tk.renderToSVG(pageNo=self.current_page)
# Usage
viewer = ResponsiveScoreViewer("score.mei")
# Simulate window resize
new_page_count = viewer.resize_viewport(2400, 3200)
print(f"Document now has {new_page_count} pages")
# Change zoom
viewer.set_zoom(60)
# Render
svg = viewer.render_current_page()import verovio
def find_optimal_scale(filename, target_pages):
"""Find scale that produces target number of pages."""
tk = verovio.toolkit()
tk.loadFile(filename)
# Binary search for optimal scale
min_scale = 10
max_scale = 150
tolerance = 2 # pages
while max_scale - min_scale > 5:
test_scale = (min_scale + max_scale) // 2
tk.setScale(test_scale)
tk.redoLayout()
pages = tk.getPageCount()
print(f"Scale {test_scale}: {pages} pages")
if abs(pages - target_pages) <= tolerance:
print(f"Found optimal scale: {test_scale} ({pages} pages)")
return test_scale
if pages > target_pages:
# Need larger scale to fit more on each page
min_scale = test_scale
else:
# Need smaller scale
max_scale = test_scale
final_scale = (min_scale + max_scale) // 2
tk.setScale(final_scale)
tk.redoLayout()
print(f"Best scale: {final_scale} ({tk.getPageCount()} pages)")
return final_scale
# Find scale for ~10 pages
optimal = find_optimal_scale("score.mei", target_pages=10)import verovio
tk = verovio.toolkit()
# Set locale for proper text rendering
tk.setLocale()
# Load and process
tk.loadFile("score_with_text.mei")
svg = tk.renderToSVG()
# Reset locale when done
tk.resetLocale()import verovio
import statistics
class PerformanceProfiler:
def __init__(self):
self.tk = verovio.toolkit()
self.measurements = []
def profile_operation(self, operation_name, operation_func):
"""Profile a single operation."""
self.tk.resetClock()
self.tk.initClock()
result = operation_func()
elapsed = self.tk.getRuntimeInSeconds()
self.measurements.append({
'operation': operation_name,
'time': elapsed
})
print(f"{operation_name}: {elapsed:.4f}s")
return result
def profile_file(self, filename):
"""Profile all major operations on a file."""
print(f"\nProfiling: {filename}")
# Load
self.profile_operation(
"Load",
lambda: self.tk.loadFile(filename)
)
# Get page count
page_count = self.profile_operation(
"Get page count",
lambda: self.tk.getPageCount()
)
# Render SVG
self.profile_operation(
"Render to SVG",
lambda: self.tk.renderToSVG()
)
# Render MIDI
self.profile_operation(
"Render to MIDI",
lambda: self.tk.renderToMIDI()
)
# Get MEI
self.profile_operation(
"Export MEI",
lambda: self.tk.getMEI()
)
# Generate timemap
self.profile_operation(
"Generate timemap",
lambda: self.tk.renderToTimemap()
)
def print_summary(self):
"""Print performance summary."""
print("\n=== Performance Summary ===")
total_time = sum(m['time'] for m in self.measurements)
print(f"Total time: {total_time:.4f}s")
times = [m['time'] for m in self.measurements]
print(f"Mean operation time: {statistics.mean(times):.4f}s")
print(f"Median operation time: {statistics.median(times):.4f}s")
print("\nSlowest operations:")
sorted_ops = sorted(self.measurements, key=lambda x: x['time'], reverse=True)
for op in sorted_ops[:3]:
print(f" {op['operation']}: {op['time']:.4f}s")
# Usage
profiler = PerformanceProfiler()
profiler.profile_file("score.mei")
profiler.print_summary()import verovio
tk = verovio.toolkit()
tk.loadFile("score.mei")
# Initial layout with cache
tk.redoLayout()
# Change options
tk.setScale(60)
# Redo with cache reset (full recalculation)
tk.redoLayout(options={'resetCache': True})
# Redo without cache reset (may use cached data)
tk.setScale(80)
tk.redoLayout(options={'resetCache': False})import verovio
def timed_render_pipeline(filename):
"""Time each step of the rendering pipeline."""
tk = verovio.toolkit()
# Time loading
tk.initClock()
tk.loadFile(filename)
load_time = tk.getRuntimeInSeconds()
print(f"Load time: {load_time:.4f}s")
# Time layout
tk.resetClock()
tk.initClock()
tk.redoLayout()
layout_time = tk.getRuntimeInSeconds()
print(f"Layout time: {layout_time:.4f}s")
# Time first page render
tk.resetClock()
tk.initClock()
svg = tk.renderToSVG(pageNo=1)
render_time = tk.getRuntimeInSeconds()
print(f"First page render: {render_time:.4f}s")
# Time MIDI generation
tk.resetClock()
tk.initClock()
midi = tk.renderToMIDI()
midi_time = tk.getRuntimeInSeconds()
print(f"MIDI generation: {midi_time:.4f}s")
total = load_time + layout_time + render_time + midi_time
print(f"\nTotal pipeline time: {total:.4f}s")
return {
'load': load_time,
'layout': layout_time,
'render': render_time,
'midi': midi_time,
'total': total
}
# Profile a file
timings = timed_render_pipeline("score.mei")Install with Tessl CLI
npx tessl i tessl/pypi-verovio