Workspace¶
A workspace contains the current state: the active config, the active scorestrucutre, a playback engine, etc. Many actions, like note playback, notation rendering, etc., use the active workspace to determine tempo, score structure, default playback instrument, etc.
Contents¶
[1]:
from maelzel.core import *
from IPython.display import display
The active workspace¶
To customize a Workspace for a specific task there are three slightly different methods:
1. Modify the active Workspace¶
We modify the workspace by modifying its configuration
[2]:
w = getWorkspace()
w.config['play.numChannels'] = 4
w.config['show.pngResolution'] = 300
w.config['quant.complexity'] = 'high'
2. Create a new config¶
We can customize the workspace by setting a new configuration
[3]:
w = getWorkspace()
w.config = CoreConfig({'play.numChannels': 4, 'quant.complexity': 'high'})
This is the same as creating a new CoreConfig with active=True
[4]:
config = CoreConfig({'quant.complexity': 'low'}, active=True)
assert config is Workspace.active.config
3. Create a new Workspace with the needed customizations¶
[5]:
w = Workspace(scorestruct=ScoreStruct((3, 4), tempo=72),
updates={'play.numChannels': 4,
'show.pngResolution': 300,
'quant.complexity': 'high'}, active=True)
w
[5]:
Workspace(scorestruct=ScoreStruct(3/4, tempo=72), config={'show.pngResolution': 300, 'play.instr': '.piano', 'play.numChannels': 4}, dynamicCurve=DynamicCurve(shape=expon(0.3), mindb=-60.0, maxdb=0.0))
4. Temporary Workspace (as context manager)¶
[6]:
with Workspace(scorestruct=ScoreStruct((3, 4), tempo=72)):
scale = Chain(Note(m, dur=0.5) for m in range(60, 72))
scale.show()
display(scale.rec(instr='.piano', nchnls=1, sustain=0.2, fade=(0, 0.1)))
"/home/em/.local/share/maelzel/recordings/rec-2025-09-28T18:46:40.879.wav", 1 channels, 5.30 secs, 44100 Hz)Soundfile: '/home/em/.local/share/maelzel/recordings/rec-2025-09-28T18:46:40.879.wav', duration:
5.3, sr: 44100, numchannels: 1)Parts of a Workspace¶
The workspace bundles the different elements which determine playback, notation and general behaviour of maelzel.core
.config: holds the active configuration.scorestruct: the active score structure.dynamicCurve: determines the mapping between amplitude and musical dynamic. This is used for playback and transcription.a4: the reference frequency for A4.renderer: used internally when rendering offline. When an object (note, chord, voice, …) is played it uses this attribute to check how to route the generated playback events
The active Config¶
The .config attribute of the active Workspace holds the active configuration. This is an instance of CoreConfig, which is itself a subclass of dict and holds defaults and customizations regarding playback, notation, etc
[7]:
config = getWorkspace().config
config
19 35
[7]:
CoreConfig: maelzel:core
| Key | Value | Type | Descr |
|---|---|---|---|
| A4 | 442 | between 10 - 10000 | Freq. of A4. Normal values are between 440-443, but any value can be used |
| chordAdjustGain | True | type: bool | Limit the gain of a chord according to the number of notes, to prevent clipping. Only used if notes don't have an explicit amplitude |
| dynamicCurveDynamics | ppp pp p mp mf f ff fff | type: str | Possible dynamic steps. A str with all dynamic steps, sorted from soft to loud |
| dynamicCurveMaxdb | 0 | between -160 - 0 | Amplitude in dB corresponding to the loudest dynamic |
| dynamicCurveMindb | -60 | between -160 - 0 | Amplitude in dB corresponding to the softest dynamic |
| dynamicCurveShape | expon(0.3) | type: str | Shape used to convert dynamics to amplitudes. Normally an exponential curve, given as 'expon(exp)'. exp < 1 results in more resolution for soft dynamics |
| enharmonic.horizontalWeight | 1 | type: int | Weight of the horizontal dimension (note sequences) when evaluating an enharmonic variant |
| enharmonic.verticalWeight | 0.5 | type: float | Weight of the vertical dimension (notes within a chord) for enharmonic spelling |
| fixStringNotenames | False | type: bool | Fix pitches given as notenames at the spelling given. False: respell for better readability within the context. Pitches as midi or frequency are always respelled |
| htmlTheme | light | {dark, light} | Theme used when displaying html inside jupyter |
| jupyterReprShow | True | type: bool | Use html as repr within jupyter. If False, .show needs to be called explicitely to render notation |
| lilypondpath | | type: str | Path to the lilypond binary. If set, it must be an absolute, existing path. Only needed if using a specific lilypond installation (lilypond is auto-installed if not found) |
| musescorepath | | type: str | Command to use when calling MuseScore. For macOS users: it must be an absolute path pointing to the actual binary inside the .app bundle |
| openImagesInExternalApp | False | type: bool | Force opening images with an external tool, even when inside a Jupyter notebook |
| play.backend | default | {alsa, auhal, default, jack, pa_cb, portaudio, pulse} | backend used for playback |
| play.defaultAmplitude | 1.0 | between 0 - 1 | Default amplitude for a Note/Chord, only used if play.useDynamics is False |
| play.defaultDynamic | f | {f, ff, fff, ffff, mf, mp, p, pp, ppp, pppp} | Dynamic of a Note/Chord, only used if play.useDynamics is True. Any event with an amplitude uses that value instead |
| play.engineName | maelzel.core | type: str | Name of the play engine used |
| play.fade | 0.02 | type: float | Default fade time |
| play.fadeShape | cos | {cos, linear, scurve} | Curve-shape used for fading in/out |
| play.gain | 1.0 | between 0 - 1 | Default gain used when playing/recording |
| play.generalMidiSoundfont | | type: str | Path to a soundfont (sf2 file) with a general midi mapping |
| play.graceDuration | 1/14 | type: (int, float, str) | Duration assigned to a gracenote for playback (in quarternotes) |
| play.instr | .piano | type: str | default: sin | Default instrument used for playback. A list of available instruments can be queried via `presetManager.definedPresets()`. |
| play.numChannels | 4 | between 1 - 128 | default: 2 | Default number of playback channels |
| play.pitchInterpol | linear | {cos, linear} | Curve shape for interpolating between pitches |
| play.schedLatency | 0.05 | type: float | Latency when scheduling events to ensure time precission |
| play.soundfontAmpDiv | 16384 | type: int | A divisor used to scale the amplitude of soundfonts to a range 0-1 |
| play.soundfontFindPeakAOT | False | type: bool | True: find the peak of a soundfont to adjust its normalization at the moment an soundfont preset is defined |
| play.soundfontInterpol | linear | {cubic, linear} | Interpolation used when reading sample data from a soundfont. |
| play.unschedFadeout | 0.05 | type: float | Fade out when stopping a note |
| play.useDynamics | True | type: bool | Any note/chord with a set dynamic will use dynamics for playback if no explicit amplitude is set |
| play.verbose | False | type: bool | True: outputs extra debugging information regarding playback |
| quant.beatWeightTempoThresh | 52 | type: int | |
| quant.breakBeats | weak | {all, none, strong, weak} | Level at which to break syncopations. "all": break all syncopations; "weak": break syncopations over weak beats; "strong": only break syncopations at strong beats; "none": don´t break syncopations |
| quant.complexity | high | {high, highest, low, lowest, medium} | Complexity used for notation. |
| quant.gridWeight | None | between 0 - 10 | Weight applied to the time quantization error. Higher values result in more accurate quantization, at the cost of complexity. None sets this value from the complexity preset (quant.complexity) |
| quant.nestedTuplets | None | {False, None, True} | Allow nested tuplets when quantizing. None: follow the complexity preset (quant.complexity). The musescore backend cannot parse nested tuplets from musicxml atm |
| quant.nestedTupletsMusicxml | False | type: bool | False: no nested tuplets are used for musicxml. The musescore backend cannot parse nested tuplets properly from mxml. Nested tuplets are used for other formats if "quant.nestedTuplets" = True |
| quant.subdivTempoThresh | 96 | type: int | |
| quant.syncopExcludeSymDurs | (5, 7, 15) | type: tuple | Durations with a numerator in this list cannot be placed across a beat. A value of (7, 15), for example, excludes double and trippled dotted notes |
| quant.syncopMaxAsymmetry | 3 | between 1 - 99 | Max. asymmetry of a syncopation. For notes across beats, this sets the max. allowed asymmetry across the beat, as a ratio longest:shortest part across the beat. A note exactly across the beat has an asymmetry of 1 |
| quant.syncopMinFraction | 1/6 | type: (str, float, Rational) | Min. duration of a syncopation as a ratio of the beat. Any syncopation shorter is broken and its parts tied, to prevent complex syncopations |
| quant.syncopPartMinFraction | 1/10 | type: str | Min. duration of any part of a syncopation, as a fraction of the beat. A syncopation consistings of two parts, one left and one right to the beat boundary |
| rec.blocking | True | type: bool | Should recording be blocking or should be done async? |
| rec.extratime | 0.0 | between 0.0 - inf | Default extratime added when recording |
| rec.ksmps | 64 | {1, 16, 32, 64, 128, 256} | Samples per cycle when rendering offline (passed as ksmps to csound) |
| rec.numChannels | 2 | between 1 - 128 | Number of channels when rendering to disk |
| rec.path | | type: str | Path used to save soundfiles when rendering offline. Otherwise the value returned by `recordPath` is used |
| rec.sr | 44100 | {44100, 48000, 88200, 96000, 192000} | Sample rate used when rendering offline |
| rec.verbose | False | type: bool | Show debug output when calling csound as a subprocess |
| reprDurationAsFraction | True | type: bool | Show durations as fractions instead of floats |
| reprShowFreq | False | type: bool | Show frequency when printing a Note in the console |
| reprUnicodeAccidentals | True | {False, full, simple, True} | Use unicode accidentals for representation of notes |
| reprUnicodeFractions | False | type: bool | Show fractions (for durations/offsets) as unicode glyphs. Not all fonts have support for this |
| semitoneDivisions | 4 | {1, 2, 4} | Number of divisions/semitone used for notation (2=quarter-tones, 4=eighth-tones) |
| show.absOffsetWhenDetached | False | type: bool | Use the abs. offset of an object, even when shown detached from its parent |
| show.arpeggiateChord | auto | {auto, False, True} | Display chords as an arpeggio. In auto mode, only arpeggiate when needed |
| show.arpeggioDuration | 0.5 | type: float | Duration used for individual notes when rendering a chord as arpeggio |
| show.autoClefChanges | True | type: bool | Add clef changes if needed. Otherwise, one clef is determined for each part and is not changed along the part. |
| show.backend | lilypond | {lilypond, musicxml} | Method used when rendering notation |
| show.cacheImages | True | type: bool | True: cache rendered images. Set it to False for debugging. call `resetImageCache()` to reset manually |
| show.centSep | , | type: str | Separator used when displaying multiple cents deviation (in a chord) |
| show.cents | True | type: bool | Show cents deviation as text when rendering notation |
| show.centsTextSnap | 2 | between 0 - 50 | Notes within this number of cents from a quantized pitchdon´t need a text annotation (see `show.cents`) |
| show.centsTextStyle | fontsize=6; placement=below | type: str | Style used for cents annotations. A list of key=value pairs, separated by ;. Keys: fontsize, box (rectangle, square, circle), placement (above, below), italic, bold. Example: "fontsize=12; italic; box=square" |
| show.clefChangesWindow | 1 | type: int | When adding automatic clef changes, use this window size (number of elements per evaluation) |
| show.clefSimplify | 0.0 | between 0 - 10000 | Simplifie automatic clef changes. Use higher values to limit clef changes |
| show.clipNoteheadShape | square | {cluster, cross, normal, rectangle, rhombus, slash, square, triangle} | Notehead shape to use for clips |
| show.dynamicFromAmplitude | False | type: bool | If an object has an amplitude but no explicit dynamic, add a dynamic according to the amplitude |
| show.flagStyle | straight | {flat, normal, straight} | Flag style, at the moment only valid in lilypond |
| show.format | png | {pdf, png, repr} | Used when no explicit format is passed to .show |
| show.glissHideTiedNotes | True | type: bool | Hide tied notes when part of a gliss. |
| show.glissLineThickness | 2 | {1, 2, 3, 4} | Line thikness for glissandi. The value is abstract, it isup to the renderer to interpret it |
| show.glissLineType | solid | {solid, wavy} | Default line type for glissandi |
| show.glissStemless | False | type: bool | When the end pitch of a gliss. is shown as gracenote, make this stemless |
| show.hideRedundantDynamics | True | type: bool | Hide redundant dynamics within a voice |
| show.horizontalSpace | medium | {default, large, medium, small, xlarge} | Hint to adjust horizontal spacing, the result depends on format and backend |
| show.jupyterMaxImageWidth | 1000 | type: int | Max. width in pixels for images in a jupyter notebook |
| show.keepClefBias | 2.0 | type: float | The higher this value, the more likely it is to keep the previous clef during automatic clef changes |
| show.labelStyle | fontsize=9; placement=above | type: str | Style used for labelsA list of key=value pairs, separated by ;. Keys: fontsize, box (rectangle, square, circle), placement (above, below), italic, bold. Example: "fontsize=12; italic; box=square" |
| show.lilypondGlissMinLength | 5 | type: int | Min. length of a glissando in points. Increase this value if gliss. linesare hidden or too short |
| show.lilypondPngStaffsizeScale | 1.5 | type: float | Factor applied to the staffsize when rendering to png via lilypond. Useful if images are too small within jupyter |
| show.measureLabelStyle | box=rectangle; fontsize=12 | type: str | Style for measure annotations. A list of key=value pairs separated by ;. Keys: fontsize, box (rectangle, square, circle), placement (above, below), italic, bold. Example: "fontsize=12; italic; box=square" |
| show.musicxmlFontScaling | 1.0 | type: float | Scaling factor applied to font sizes when rendering to musicxml |
| show.pageMarginMillim | 4 | between 0 - 1000 | Page margin in mm |
| show.pageOrientation | portrait | {landscape, portrait} | Page orientation when rendering to pdf |
| show.pageSize | a4 | {a2, a3, a4} | Page size when rendering to pdf |
| show.pngResolution | 300 | {100, 200, 300, 600, 1200} | default: 200 | DPI used when rendering to png |
| show.proportionalDuration | 1/24 | type: str | When using proportional spacing, the lower this value, the longer the space taken by each note. This corresponds to the value as used by lilypond. See also: https://lilypond.org/doc/v2.23/Documentation/notation/proportional-notation |
| show.referenceStaffsize | 12.0 | type: float | Staff size used as reference. This allows to use staff size as a generic indicator for score scale across backends |
| show.rehearsalMarkStyle | box=rectangle; fontsize=13; bold | type: str | Style for rehearsal marks. A list of key=value pairs, separated by ";". Keys: fontsize, box (rectangle, square, circle), placement (above, below), italic, bold. Example: "fontsize=12; italic; box=rectangle" |
| show.respellPitches | True | type: bool | Find best enharmonic spelling within the context. |
| show.scaleFactor | 0.75 | type: float | Affects the size of the generated image when using png format |
| show.scaleFactorMusicxml | 0.8 | type: float | Apply a scaling factor to images rendered via musicxml |
| show.spacing | normal | {normal, strict, uniform} | Horizontal spacing used. "normal": traditional spacing; "uniform": proportional spacing with uniform stretching; "strict": proportional spacing with strict placement (clef changes and bar lines don´t add spacing andmight overlap) |
| show.staffSize | 10.0 | type: float | Size of a staff, in points |
| show.voiceMaxStaves | 2 | between 1 - 4 | Max. number of staves per voice when showing a Voice as notation |
| show.warnIfEmpty | True | type: bool | True: warn if an object did not produce any scoring parts |
| soundfilePlotHeight | 3 | type: int | Height used for plotting soundfiles. This is used, for example, to set the figsize in matplotlib plots used inline within Jupyter. |
| splitAcceptableDeviation | 4 | type: int | When splitting notes between staves, notes within this range of the split point will be grouped together if they all fit |
Environment¶
Some aspects of the environment can be queried through the Workspace
recordPath(): returns the path where recordings are placed whenever the user does not give an absolute pathpresetsPath(): presets created viadefPresetare saved in this path and loaded in future sessions.
[8]:
w.recordPath()
[8]:
'/home/em/.local/share/maelzel/recordings'
[9]:
w.presetsPath()
[9]:
'/home/em/.local/share/maelzel/core/presets'
Dynamics¶
Mapping dynamic expressions to amplitudes
The dynamic curve within the active Workspace is used to map dynamics to amplitude for playback, or to transcribe amplitudes as dynamics
[10]:
w.dynamicCurve.plot()
Testing dynamics¶
Luciano Berio, “O King”

[12]:
# Reset any active scorestruct to the default
events = [
"4F:4:ff",
"4A:2.5:pp",
"4F:0", # dur=0 indicates a grace note
"4A:1:pp",
"4B:3",
"5C#:3",
"4F:3",
"4A:2:ff",
"4F:0:pp",
"4A:1.5:pp",
"4Ab:1.5",
"4Bb:1",
"5D:.5",
"5C#:2",
"4B:1.5:ff",
"4F:2.5:pp"
]
voice = Chain(events)
Set the score structure to match the original. Either the .scorestruct attribute can be modified directly or the function setScoreStruct can be used
[13]:
w = getWorkspace()
w.scorestruct = ScoreStruct('''
4/4, 60
2/4
3/8
3/4
.
.
2/4
3/8
3/4,,A
2/4
.
3/4
4/4
3/4
4/4
3/4
2/4
4/4
.
2/4,,B
''')
voice.show()
Play with the default instr (piano, with pedal)
[19]:
# voice.play(instr='piano', sustain=8, gain=2)
r = voice.rec("tmp/oking.ogg", instr='.piano', sustain=20, nchnls=1, gain=2, fade=(0, 2))
r
[19]:
"tmp/oking.ogg", 1 channels, 49.00 secs, 44100 Hz)Soundfile: 'tmp/oking.ogg', duration:
49, sr: 44100, numchannels: 1)A dynamic curve with less contrast
[24]:
dyncurve = w.setDynamicCurve(shape='expon(0.25)', mindb=-40)
w.dynamicCurve.plot()
[25]:
voice.rec(instr='.piano', sustain=20, gain=2, nchnls=1, fade=(0, 2)).show()
"/home/em/.local/share/maelzel/recordings/rec-2025-09-28T18:52:42.209.wav", 1 channels, 49.00 secs, 44100 Hz)Soundfile: '/home/em/.local/share/maelzel/recordings/rec-2025-09-28T18:52:42.209.wav', duration:
49, sr: 44100, numchannels: 1)[ ]: