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)))
../_images/notebooks_maelzel-core-workspace_11_0.png
OfflineRenderer(outfile="/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


KeyValueTypeDescr
A4442between 10 - 10000Freq. of A4. Normal values are between 440-443, but any value can be used
chordAdjustGainTruetype: boolLimit the gain of a chord according to the number of notes, to prevent clipping. Only used if notes don't have an explicit amplitude
dynamicCurveDynamicsppp pp p mp mf f ff ffftype: strPossible dynamic steps. A str with all dynamic steps, sorted from soft to loud
dynamicCurveMaxdb0between -160 - 0Amplitude in dB corresponding to the loudest dynamic
dynamicCurveMindb-60between -160 - 0Amplitude in dB corresponding to the softest dynamic
dynamicCurveShapeexpon(0.3)type: strShape used to convert dynamics to amplitudes. Normally an exponential curve, given as 'expon(exp)'. exp < 1 results in more resolution for soft dynamics
enharmonic.horizontalWeight1type: intWeight of the horizontal dimension (note sequences) when evaluating an enharmonic variant
enharmonic.verticalWeight0.5type: floatWeight of the vertical dimension (notes within a chord) for enharmonic spelling
fixStringNotenamesFalsetype: boolFix pitches given as notenames at the spelling given. False: respell for better readability within the context. Pitches as midi or frequency are always respelled
htmlThemelight{dark, light}Theme used when displaying html inside jupyter
jupyterReprShowTruetype: boolUse html as repr within jupyter. If False, .show needs to be called explicitely to render notation
lilypondpathtype: strPath 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)
musescorepathtype: strCommand to use when calling MuseScore. For macOS users: it must be an absolute path pointing to the actual binary inside the .app bundle
openImagesInExternalAppFalsetype: boolForce opening images with an external tool, even when inside a Jupyter notebook
play.backenddefault{alsa, auhal, default, jack, pa_cb, portaudio, pulse}backend used for playback
play.defaultAmplitude1.0between 0 - 1Default amplitude for a Note/Chord, only used if play.useDynamics is False
play.defaultDynamicf{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.engineNamemaelzel.coretype: strName of the play engine used
play.fade0.02type: floatDefault fade time
play.fadeShapecos{cos, linear, scurve}Curve-shape used for fading in/out
play.gain1.0between 0 - 1Default gain used when playing/recording
play.generalMidiSoundfonttype: strPath to a soundfont (sf2 file) with a general midi mapping
play.graceDuration1/14type: (int, float, str)Duration assigned to a gracenote for playback (in quarternotes)
play.instr.pianotype: str | default: sinDefault instrument used for playback. A list of available instruments can be queried via `presetManager.definedPresets()`.
play.numChannels4between 1 - 128 | default: 2Default number of playback channels
play.pitchInterpollinear{cos, linear}Curve shape for interpolating between pitches
play.schedLatency0.05type: floatLatency when scheduling events to ensure time precission
play.soundfontAmpDiv16384type: intA divisor used to scale the amplitude of soundfonts to a range 0-1
play.soundfontFindPeakAOTFalsetype: boolTrue: find the peak of a soundfont to adjust its normalization at the moment an soundfont preset is defined
play.soundfontInterpollinear{cubic, linear}Interpolation used when reading sample data from a soundfont.
play.unschedFadeout0.05type: floatFade out when stopping a note
play.useDynamicsTruetype: boolAny note/chord with a set dynamic will use dynamics for playback if no explicit amplitude is set
play.verboseFalsetype: boolTrue: outputs extra debugging information regarding playback
quant.beatWeightTempoThresh52type: int
quant.breakBeatsweak{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.complexityhigh{high, highest, low, lowest, medium}Complexity used for notation.
quant.gridWeightNonebetween 0 - 10Weight 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.nestedTupletsNone{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.nestedTupletsMusicxmlFalsetype: boolFalse: 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.subdivTempoThresh96type: int
quant.syncopExcludeSymDurs(5, 7, 15)type: tupleDurations 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.syncopMaxAsymmetry3between 1 - 99Max. 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.syncopMinFraction1/6type: (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.syncopPartMinFraction1/10type: strMin. 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.blockingTruetype: boolShould recording be blocking or should be done async?
rec.extratime0.0between 0.0 - infDefault extratime added when recording
rec.ksmps64{1, 16, 32, 64, 128, 256}Samples per cycle when rendering offline (passed as ksmps to csound)
rec.numChannels2between 1 - 128Number of channels when rendering to disk
rec.pathtype: strPath used to save soundfiles when rendering offline. Otherwise the value returned by `recordPath` is used
rec.sr44100{44100, 48000, 88200, 96000, 192000}Sample rate used when rendering offline
rec.verboseFalsetype: boolShow debug output when calling csound as a subprocess
reprDurationAsFractionTruetype: boolShow durations as fractions instead of floats
reprShowFreqFalsetype: boolShow frequency when printing a Note in the console
reprUnicodeAccidentalsTrue{False, full, simple, True}Use unicode accidentals for representation of notes
reprUnicodeFractionsFalsetype: boolShow fractions (for durations/offsets) as unicode glyphs. Not all fonts have support for this
semitoneDivisions4{1, 2, 4}Number of divisions/semitone used for notation (2=quarter-tones, 4=eighth-tones)
show.absOffsetWhenDetachedFalsetype: boolUse the abs. offset of an object, even when shown detached from its parent
show.arpeggiateChordauto{auto, False, True}Display chords as an arpeggio. In auto mode, only arpeggiate when needed
show.arpeggioDuration0.5type: floatDuration used for individual notes when rendering a chord as arpeggio
show.autoClefChangesTruetype: boolAdd clef changes if needed. Otherwise, one clef is determined for each part and is not changed along the part.
show.backendlilypond{lilypond, musicxml}Method used when rendering notation
show.cacheImagesTruetype: boolTrue: cache rendered images. Set it to False for debugging. call `resetImageCache()` to reset manually
show.centSep,type: strSeparator used when displaying multiple cents deviation (in a chord)
show.centsTruetype: boolShow cents deviation as text when rendering notation
show.centsTextSnap2between 0 - 50Notes within this number of cents from a quantized pitchdon´t need a text annotation (see `show.cents`)
show.centsTextStylefontsize=6; placement=belowtype: strStyle 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.clefChangesWindow1type: intWhen adding automatic clef changes, use this window size (number of elements per evaluation)
show.clefSimplify0.0between 0 - 10000Simplifie automatic clef changes. Use higher values to limit clef changes
show.clipNoteheadShapesquare{cluster, cross, normal, rectangle, rhombus, slash, square, triangle}Notehead shape to use for clips
show.dynamicFromAmplitudeFalsetype: boolIf an object has an amplitude but no explicit dynamic, add a dynamic according to the amplitude
show.flagStylestraight{flat, normal, straight}Flag style, at the moment only valid in lilypond
show.formatpng{pdf, png, repr}Used when no explicit format is passed to .show
show.glissHideTiedNotesTruetype: boolHide tied notes when part of a gliss.
show.glissLineThickness2{1, 2, 3, 4}Line thikness for glissandi. The value is abstract, it isup to the renderer to interpret it
show.glissLineTypesolid{solid, wavy}Default line type for glissandi
show.glissStemlessFalsetype: boolWhen the end pitch of a gliss. is shown as gracenote, make this stemless
show.hideRedundantDynamicsTruetype: boolHide redundant dynamics within a voice
show.horizontalSpacemedium{default, large, medium, small, xlarge}Hint to adjust horizontal spacing, the result depends on format and backend
show.jupyterMaxImageWidth1000type: intMax. width in pixels for images in a jupyter notebook
show.keepClefBias2.0type: floatThe higher this value, the more likely it is to keep the previous clef during automatic clef changes
show.labelStylefontsize=9; placement=abovetype: strStyle 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.lilypondGlissMinLength5type: intMin. length of a glissando in points. Increase this value if gliss. linesare hidden or too short
show.lilypondPngStaffsizeScale1.5type: floatFactor applied to the staffsize when rendering to png via lilypond. Useful if images are too small within jupyter
show.measureLabelStylebox=rectangle; fontsize=12type: strStyle 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.musicxmlFontScaling1.0type: floatScaling factor applied to font sizes when rendering to musicxml
show.pageMarginMillim4between 0 - 1000Page margin in mm
show.pageOrientationportrait{landscape, portrait}Page orientation when rendering to pdf
show.pageSizea4{a2, a3, a4}Page size when rendering to pdf
show.pngResolution300{100, 200, 300, 600, 1200} | default: 200DPI used when rendering to png
show.proportionalDuration1/24type: strWhen 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.referenceStaffsize12.0type: floatStaff size used as reference. This allows to use staff size as a generic indicator for score scale across backends
show.rehearsalMarkStylebox=rectangle; fontsize=13; boldtype: strStyle 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.respellPitchesTruetype: boolFind best enharmonic spelling within the context.
show.scaleFactor0.75type: floatAffects the size of the generated image when using png format
show.scaleFactorMusicxml0.8type: floatApply a scaling factor to images rendered via musicxml
show.spacingnormal{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.staffSize10.0type: floatSize of a staff, in points
show.voiceMaxStaves2between 1 - 4Max. number of staves per voice when showing a Voice as notation
show.warnIfEmptyTruetype: boolTrue: warn if an object did not produce any scoring parts
soundfilePlotHeight3type: intHeight used for plotting soundfiles. This is used, for example, to set the figsize in matplotlib plots used inline within Jupyter.
splitAcceptableDeviation4type: intWhen 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 path

  • presetsPath(): presets created via defPreset are 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()
../_images/notebooks_maelzel-core-workspace_18_0.png

Testing dynamics

Luciano Berio, “O King”

image0

[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()
../_images/notebooks_maelzel-core-workspace_23_0.png

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]:
OfflineRenderer(outfile="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()
../_images/notebooks_maelzel-core-workspace_27_0.png
[25]:
voice.rec(instr='.piano', sustain=20, gain=2, nchnls=1, fade=(0, 2)).show()
OfflineRenderer(outfile="/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)

[ ]: