The active wokspace¶
There is always an active Workspace. When maelzel.core is imported a new Workspace is created, which can be retrieved via getWorkspace
[25]:
from maelzel.core import *
from IPython.display import display
w = getWorkspace()
w
[25]:
Workspace(scorestruct=ScoreStruct(3/4, tempo=96), config={'play.instr': '.piano'}, dynamicCurve=DynamicCurve(shape=expon(0.3), mindb=-60.0, maxdb=0.0))
Alternatively the active workspace can be accessed via the active class variable, as Workspace.active
[26]:
w2 = Workspace.active
assert w is w2
The active workspace contains the working environment. The most important attributes encapsulated here are the score structure (.scorestruct, an instance of ScoreStruct) and the configuration (.config, an instance of CoreConfig)
Each new Workspace contains a basic ScoreStruct (4/4, quarter=60) and a copy of the root config
[27]:
w.scorestruct
[27]:
ScoreStruct
| Meas. Index | Timesig | Tempo (quarter note) | Label | Rehearsal | Barline | Beats |
|---|---|---|---|---|---|---|
| 0 | 3/4 | 96 | 1+1+1 | |||
| 1 | (3/4) | 1+1+1 | ||||
| ... |
[28]:
w.config
19 35
[28]:
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 applied if the notes don't have an individual 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 for the converting dynamics to amplitudes. Normally an exponential curve, given as 'expon(exp)', where exp is the exponent used. 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 (chords within a voice) when evaluating an enharmonic variant |
| fixStringNotenames | False | type: bool | True: pitches given as notenames are fixed at the spelling given. False: they are respelled for better readability within the context. Pitches given as midi notes or frequencies are always respelled |
| htmlTheme | light | {dark, light} | Theme used when displaying html inside jupyter |
| jupyterReprShow | True | type: bool | Render notation within the html 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. This needs to be set only when using a specific lilypond installation (lilypond is auto-installed if not found, see lilyponddist) |
| 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 | Amplitude of a Note/Chord when an amplitude is needed and the object has an undefined amplitude, only used if play.useDynamics if False |
| play.defaultDynamic | f | {f, ff, fff, ffff, mf, mp, p, pp, ppp, pppp} | Dynamic of a Note/Chord when a dynamic is needed, only used if play.useDynamics is True. Any event with an amplitude will use 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 | 2 | between 1 - 128 | Default number of channels (channels can be set explicitely when calling startPlayEngine |
| 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 | True: any note/chord with a set dynamic will use dynamics to modify its playback amplitude 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} | True: allow nested tuplets when quantizing. If None, this flag is set by the complexity preset (quant.complexity). Some backends (musescore) can't parse nested tuplets from musicxml atm |
| quant.nestedTupletsMusicxml | False | type: bool | False: no nested tuplets are used for musicxml. Some backends (MuseScore) don't render 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 output files when rendering offline. If not given the default can be queried via `recordPath` |
| 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 | When showing an object which has a parent but is shown detached from it, shouldthe absolute offset be used? |
| 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 | True: add clef changes to a quantized part 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 | Pitches which deviate less than this 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. Flags don´t need a value. Example: "fontsize=12; italic; box=rectangle" |
| 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 | Simplifies 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 | True: when rendering notation, if an object has an amplitude and does not have an 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 which are part of a glissando |
| show.glissLineThickness | 2 | {1, 2, 3, 4} | Line thikness when rendering glissandi. The value is abstract and 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 actual result depends on the backend and the format used. |
| show.jupyterMaxImageWidth | 1000 | type: int | Max. width in pixels for images displayed 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 | Text size used for labelsA list of key=value pairs, separated by ;. Keys: fontsize, box (rectangle, square, circle), placement (above, below), italic, bold. Flags don´t need a value. Example: "fontsize=12; italic; box=rectangle" |
| show.lilypondGlissMinLength | 5 | type: int | Min. length of a glissando in points. Increase this value if gliss. linesare not shown or are too short if a gliss. collides with dots or accidentals |
| show.lilypondPngStaffsizeScale | 1.5 | type: float | Factor applied to the staffsize when rendering to png via lilypond. Useful if rendered images appear too small in a jupyter notebook |
| 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. Flags don´t need a value. Example: "fontsize=12; italic; box=rectangle" |
| 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 | 200 | {100, 200, 300, 600, 1200} | 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 1:1 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 a reference to convert between staff size and scaling factor. This allows to use staff size as a general way to indicate the scale of a score, independent of the backend |
| 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. Flags don´t need a value. Example: "fontsize=12; italic; box=rectangle" |
| show.respellPitches | True | type: bool | True: find a suitable enharmonic representation for pitches within their context. False: the canonical form of eachpitch is used, independent of 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} | Kind of 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. Even thougha voice is a sequence of non-simultaneous events (notes, chords, etc.), these canbe exploded over multiple staves |
| 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 |
| .enharmonic.150centMicroPenalty | 20 | type: int | |
| .enharmonic.debug | False | type: bool | True: print debug information while calculating automatic enharmonic spelling |
| .quant.complexityWeight | None | between 0 - 10 | Weight applied to the complexity of the rhythm during quantization. A higher value results in simpler rhythms. None sets this value from the complexity preset (quant.complexity) |
| .quant.debug | False | type: bool | Output extra debug info during quantization, showing how different divisions are evaluated by the quantizer |
| .quant.debugShowNumRows | 50 | type: int | When quantization debugging is turned on this setting limits the number of different quantization possibilities shown |
| .quant.divisionWeight | None | type: NoneType | Weight applied to the penalty of the beat divisionHigher values result in simpler subdivisions. None to use the value set by the complexity preset (quant.complexity) |
| .quant.gridErrorExp | None | type: NoneType | Exponent applied to the grid error. The error is a value between 0-1 showing the grid accuracy for a given quantization (0=perfect timing). A value between 0-1 makes grid errors weight more. None to use the value set by the complexity preset (quant.complexity) |
| .quant.mergeTupletsDifferentDur | False | type: bool | |
| .rec.compressionBitrate | 224 | type: int | default bitrate to use when encoding to ogg or mp3 |
| .show.centsTextPlusSign | True | type: bool | Show a plus sign for possitive cents deviations |
| .show.dynamicsResetAfterEmptyMeasure | True | type: bool | |
| .show.dynamicsResetAfterRest | 1 | type: int | |
| .show.dynamicsResetTime | 32 | between 0 - 999999999 | When removing redundant dynamics, reset after this number of quarters |
| .soundfilePlotWidth | 24 | type: int |
Modifying the active Workspace vs creating a new Workspace¶
If you are not planning on working on multiple scores simultaneously, the most convenient way to setup your own score structure or to customize the configuration is to modify the active workspace
[7]:
# Reset the scorestruct to the default
w.setScoreStruct()
chain = Chain(r"""
4C:0.5
4D:2
4E:1
""")
chain.show()
chain.rec(nchnls=1)
[7]:
"/home/em/.local/share/maelzel/recordings/rec-2025-09-26T19:25:01.585.wav", 1 channels, 3.52 secs, 44100 Hz)Soundfile: '/home/em/.local/share/maelzel/recordings/rec-2025-09-26T19:25:01.585.wav', duration:
3.52, sr: 44100, numchannels: 1)[9]:
w.setScoreStruct((3, 4), tempo=96)
chain.show()
chain.rec(nchnls=1)
[9]:
"/home/em/.local/share/maelzel/recordings/rec-2025-09-26T19:25:37.221.wav", 1 channels, 2.21 secs, 44100 Hz)Soundfile: '/home/em/.local/share/maelzel/recordings/rec-2025-09-26T19:25:37.221.wav', duration:
2.21, sr: 44100, numchannels: 1)Or one can create an independent Workspace with its own scorestruct:
[11]:
w = Workspace(scorestruct=ScoreStruct((5, 8), tempo=112), active=True)
chain
[11]:
Chain([4C:1/2♩, 4D:2♩, 4E:1♩], dur=3.5)Deactivating the active Workspace activates the previous workspace (there should always be an active Workspace)
[12]:
w.deactivate()
chain
[12]:
Chain([4C:1/2♩, 4D:2♩, 4E:1♩], dur=3.5)As a side note, if you just want to test something under a different scorestruct, you can use a ScoreStruct as a context manager. This clones the active Workspace with the given ScoreStruct and activates this newly created Workspace, to deactivate it when to context is exited.
[14]:
with ScoreStruct(r'''
7/16, 116
11/16, 48
'''):
chain.show()
display(chain.rec(nchnls=1))
"/home/em/.local/share/maelzel/recordings/rec-2025-09-26T19:27:21.767.wav", 1 channels, 3.11 secs, 44100 Hz)Soundfile: '/home/em/.local/share/maelzel/recordings/rec-2025-09-26T19:27:21.767.wav', duration:
3.11, sr: 44100, numchannels: 1)The same is valid for the configuration. You can modify the active Workspace by changing its config, either via setConfig or by directly assigning to .config:
[15]:
chain = Chain(r'''
C4+15:1
4Eb-19:0.5
3B+:1.5
''')
chain
[15]:
Chain([4C+15:1♩, 4E♭-19:1/2♩, 3B+:3/2♩], dur=3)[19]:
config = CoreConfig(active=True)
config['show.cents'] = False
chain
[19]:
Chain([4C+15:1♩, 4E♭-19:1/2♩, 3B+:3/2♩], dur=3)A config can also be used as a context manager:
[23]:
with CoreConfig({'show.flagStyle': 'normal'}):
chain.show()
Reset the config to the root config
[24]:
CoreConfig.root().activate()
chain
[24]:
Chain([4C+15:1♩, 4E♭-19:1/2♩, 3B+:3/2♩], dur=3)