Configuration¶
The Active Config¶
Within maelzel.core there is, at any moment, an active config object (an instance of CoreConfig
). This config encompasses settings related to notation, playback, quantization and many other aspects of maelzel.core. It is a subclass of dict with a fixed set of possible keys
[1]:
from maelzel.core import *
from pitchtools import *
# Get the active config
conf = getConfig()
conf
[1]:
CoreConfig: maelzel:core
Key | Value | Type | Descr |
---|---|---|---|
A4 | 442 | between 10 - 10000 | Freq. of the Kammerton A4. Normal values are 440, 442, 443 or 432 for old tuning, but any 'fantasy' value can be used |
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 |
chordAdjustGain | True | type: bool | Adjust the gain of a chord according to the number of notes, to prevent clipping |
reprShowFreq | False | type: bool | Show frequency when printing a Note in the console |
semitoneDivisions | 4 | {1, 2, 4} | The number of divisions per semitone (2=quarter-tones, 4=eighth-tones) |
musescorepath |
| type: str | The command to use when calling MuseScore. For macOS users: it must be an absolute path pointing to the actual binary inside the .app bundle |
reprShowFractionsAsFloat | True | type: bool | All time offsets and durations are kept as rational numbers to avoid rounding errors. If this option is True, these fractions are printed as floats in order to make them more readable. |
jupyterHtmlRepr | True | type: bool | If True, output html inside jupyter as part of the _repr_html_ hook. Under certain circumstances (for example, when generating documentation from a notebook) this html might result in style conflict. Setting in False will just output plain text |
fixStringNotenames | False | type: bool | If True, pitches given as string notenames are fixed at the spelling given at creation. Otherwise pitches might be respelled to match their context for better readability. Pitches given as midi notes or frequencies are always respelled |
openImagesInExternalApp | False | type: bool | Force opening images with an external tool, even when inside a Jupyter notebook |
enharmonic.horizontalWeight | 1 | type: int | The weight of the horizontal dimension (note sequences) when evaluating an enharmonic variant |
enharmonic.verticalWeight | 0.01 | type: float | The weight of the vertical dimension (chords within a voice) when evaluating an enharmonic variant |
enharmonic.debug | False | type: bool | If True, print debug information while calculating automatic enharmonic spelling |
enharmonic.threeQuarterMicrotonePenalty | 20 | type: int | None |
show.arpeggiateChord | auto | {auto, False, True} | Arpeggiate notes of a chord when showing. In auto mode, only arpeggiate when needed |
show.lastBreakpointDur | 0.125 | between 0.015625 - 1 | Dur of a note representing the end of a line/gliss, which has no totalDuration per se |
show.centsDeviationAsTextAnnotation | True | type: bool | show cents deviation as text when rendering notation |
show.centsAnnotationFontSize | 8 | type: int | Font size used for cents annotations |
show.centSep | , | type: str | Separator used when displaying multiple cents deviation (in a chord) |
show.scaleFactor | 1.0 | type: float | Affects the size of the generated image when using png format |
show.staffSize | 12.0 | type: float | The size of a staff, in points |
show.backend | lilypond | {lilypond, music21} | method/backend used when rendering notation |
show.format | png | {pdf, png, repr} | Used when no explicit format is passed to .show |
show.cacheImages | True | type: bool | If True, cache rendered images. Set it to False for debugging. call `resetImageCache()` to reset manually |
show.arpeggioDuration | 0.5 | type: float | Duration used for individual notes when rendering a chord as arpeggio |
show.labelFontSize | 10.0 | type: float | Font size to use for labels |
show.pageOrientation | portrait | {landscape, portrait} | Page orientation when rendering to pdf |
show.pageSize | a4 | {a2, a3, a4} | The page size when rendering to pdf |
show.pageMarginMillimeters | 4 | between 0 - 1000 | The page margin in mm |
show.glissEndStemless | False | type: bool | When the end pitch of a gliss. is shown as gracenote, make this stemless |
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.lilypondPngStaffsizeScale | 1.5 | type: float | A factor applied to the staffsize when rendering to png via lilypond. Useful if rendered images appear too small in a jupyter notebook |
show.lilypondGlissandoMinimumLength | 5 | type: int | The minimum length of a glissando in points. Increase this value if glissando linesare not shown or are too short (this might be the case within the context of dottednotes or accidentals) |
show.pngResolution | 200 | {100, 200, 300, 600, 1200} | DPI used when rendering to png |
show.measureAnnotationStyle | box=square; fontsize=12 | type: str | None |
show.respellPitches | True | type: bool | If True, try to find a suitable enharmonic representation of pitches whichhave not been fixed already by the user. Otherwise the canonical form of eachpitch is used, independent of the context |
show.horizontalSpacing | medium | {default, large, medium, small, xlarge} | Hint for the renderer to adjust horizontal spacing. The actual result depends on the backend and the format used. |
show.fillDynamicFromAmplitude | False | type: bool | If 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.jupyterMaxImageWidth | 1000 | type: int | A max. width in pixels for images displayed in a jupyter notebook |
show.hideRedundantDynamics | True | type: bool | Hide redundant dynamics within a voice |
show.asoluteOffsetForDetachedObjects | False | type: bool | When showing an object which has a parent but is shown detached from it, shouldthe absolute offset be used? |
show.voiceMaxStaves | 1 | between 1 - 4 | The maximum number of staves per voice when showing a Voice as notation. A voiceis a sequence of non-simultaneous events (notes, chords, etc.) but these canbe exploded over multiple staves (for example, a chord might expand across awide range and would need multiple extra lines in any clef |
show.clipNoteheadShape | square | {, cluster, cross, diamond, harmonic, normal, rectangle, rhombus, slash, square, triangle, xcircle} | Notehead shape to use for clips |
play.gain | 1.0 | between 0 - 1 | Default gain used when playing/recording |
play.engineName | maelzel.core | type: str | Name of the play engine used |
play.instr | piano | type: str | default: sin | Default instrument used for playback. A list of available instruments can be queried via `availableInstrs`. New instrument presets can be defined via `defPreset` |
play.fade | 0.02 | type: float | default fade time |
play.fadeShape | cos | {cos, linear, scurve} | Curve-shape used for fading in/out |
play.pitchInterpolation | linear | {cos, linear} | Curve shape for interpolating between pitches |
play.numChannels | 2 | between 1 - 128 | Default number of channels (channels can be set explicitely when calling startPlayEngine |
play.unschedFadeout | 0.05 | type: float | fade out when stopping a note |
play.backend | default | {alsa, auhal, default, jack, pa_cb, portaudio, pulse} | backend used for playback |
play.defaultAmplitude | 1.0 | between 0 - 1 | The amplitude of a Note/Chord when an amplitude is needed and the object has an undefined amplitude. This is only used if play.useDynamics if False |
play.defaultDynamic | f | {f, ff, fff, ffff, mf, mp, p, pp, ppp, pppp} | THe dynamic of a Note/Chord when a dynamic is needed. This is only used if play.useDynamics is True. Any event with an amplitude will use that amplitude instead |
play.generalMidiSoundfont |
| type: str | Path to a soundfont (sf2 file) with a general midi mapping |
play.soundfontAmpDiv | 16384 | type: int | A divisor used to scale the amplitude of soundfonts to a range 0-1 |
play.soundfontInterpolation | linear | {cubic, linear} | Interpolation used when reading sample data from a soundfont. |
play.schedLatency | 0.05 | type: float | Added latency when scheduling events to ensure time precission |
play.verbose | False | type: bool | If True, outputs extra debugging information regarding playback |
play.useDynamics | True | type: bool | If True, any note/chord with a set dynamic will use that to modify its playback amplitude if no explicit amplitude is set |
play.waitAfterStart | 0.5 | type: float | How much to wait for the sound engine to be operational after starting it |
play.gracenoteDuration | 1/14 | type: (int, float, str) | Duration assigned to a gracenote for playback (in quarternotes) |
rec.blocking | True | type: bool | Should recording be blocking or should be done async? |
rec.sr | 44100 | {44100, 48000, 88200, 96000, 144000, 176400, 192000, 352800, 384000} | Sample rate used when rendering offline |
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 | The default 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.quiet | True | type: bool | Supress debug output when calling csound as a subprocess |
rec.compressionBitrate | 224 | type: int | default bitrate to use when encoding to ogg or mp3 |
htmlTheme | light | {dark, light} | Theme used when displaying html inside jupyter |
quant.minBeatFractionAcrossBeats | 0.5 | type: float | when merging durations across beats, a merged totalDuration cannot be smaller than this totalDuration. This is to prevent joining durations across beats which might result in high rhythmic complexity |
quant.nestedTuplets | None | {False, None, True} | Are nested tuples allowed when quantizing? Not all display backends support nested tuples (musescore, used to render musicxml has no support for nested tuples). If None, this flag is determined based on the complexity preset (quant.complexity) |
quant.breakSyncopationsLevel | weak | {all, none, strong, weak} | Level at which to break syncopations, one of "all" (break all syncopations), "weak (break only syncopations over secondary beats)", "strong" (break syncopations at strong beats) or "none" (do not break any syncopations) |
quant.complexity | high | {high, highest, low, lowest, medium} | Controls the allowed complexity in the notation. The higher the complexity, the more accurate the quantization, at the cost of a more complex notation. |
quant.divisionErrorWeight | None | type: NoneType | A weight (between 0 and 1) applied to the penalty of complex quantization of the beat. The higher this value is, the simpler the subdivision chosen. If set to None, this value is derived from the complexity preset (quant.complexity) |
quant.gridErrorWeight | None | type: NoneType | A weight (between 0 and 1) applied to the deviation of a quantization to the actual attack times and durations during quantization. The higher this value, the more accurate the quantization (possibly resulting in more complex subdivisions of the beat). If None, the value is derived from the complexity preset (quant.complexity) |
quant.rhythmComplexityWeight | None | type: NoneType | A weight (between 0 and 1) applied to the penalty calculated from the complexity of the rhythm during quantization. A higher value results in more complex rhythms being considered for quantization. If None, the value is derived from the complexity (quant.complexity) |
quant.gridErrorExp | None | type: NoneType | An exponent applied to the grid error. The grid error is a value between 0-1 which indicates how accurate the grid representation is for a given quantization (a value of 0 indicates perfect timing). An exponent betwenn 0 < exp <= 1 will make grid errors weight more dramatically as they diverge from the most accurate solution. If None, the value is derived from the complexity setting (quant.complexity) |
quant.debug | False | type: bool | Turns on debugging for the quantization process. This will show how different divisions of the beat are being evaluated by the quantizer in terms of what is contributing more to the ranking. With this information it is possible to adjust the weights (quant.rhythmCompleityWeight, quant.divisionErrorWeight, etc) |
quant.debugShowNumRows | 50 | type: int | When quantization debugging is turned on this setting limits the number of different quantization possibilities shown |
dynamicCurveShape | expon(0.3) | type: str | The shape used to create the default dynamics curve. The most convenient shape is some variation of an exponential, given as expon(exp), where exp is the exponential used. exp < 1 will result in more resolution for soft dynamics |
dynamicCurveMindb | -60 | between -160 - 0 | The amplitude (in dB) corresponding to the softest dynamic |
dynamicCurveMaxdb | 0 | between -160 - 0 | The amplitude (in dB) corresponding to the loudest dynamic |
dynamicCurveDynamics | ppp pp p mp mf f ff fff | type: str | Possible dynamic steps. A string with all dynamic steps, sorted from softest to loudest |
The config is a subclass of dict where only a well defined set of keys are allowed and values are validated.
All operations on a built-in dict can be performed.
Creating a new Config¶
To create a new configuration you can simply call CoreConfig()
to get a copy of the default config.
As an example, here we create a new config with a custom reference frequency. This modifies the reference frequency for any operation converting frequency to midinote or note name. After evaluating the following cell one can confirm that a frequency of 442 Hz is now 12 cents lower than A4
[3]:
conf = CoreConfig({'A4': 445}, active=True)
# Diff show the differences with the default config
conf.diff()
[3]:
{'A4': 445, 'play.instr': 'piano'}
[4]:
f2n(442)
[4]:
'4A-12'
To restore the original frequency, just modify the value of the active config:
[5]:
getConfig()['A4'] = 442
f2n(442)
[5]:
'4A'
Or activate the previous config
[6]:
conf.activate()
f2n(442)
[6]:
'4A'
Validation¶
A CoreConfig
is always valid (it is aware about valid values for its keys). For example, it is possible to customize the number of playback channels used by the sound engine, or the latency used for scheduling events.
[7]:
# Set the default number of channels for playback. Any playback engine created will use this setting
conf['play.numChannels'] = 4
conf['play.schedLatency'] = 0.1
A wrong type or range will throw an error and the previous value will not be overwritten so that the configuration remains in a usable state.
[8]:
conf['play.numChannels'] = 'four'
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
Cell In[8], line 1
----> 1 conf['play.numChannels'] = 'four'
File ~/.virtualenvs/em10/lib/python3.10/site-packages/configdict/configdict.py:682, in CheckedDict.__setitem__(self, key, value)
680 errormsg = self.checkValue(key, value)
681 if errormsg:
--> 682 raise ValueError(errormsg)
683 if self._precallback:
684 newvalue = self._precallback(self, key, oldvalue, value)
ValueError: Expected int for key play.numChannels, got str
Making customizations persistent across sessions¶
Customizations can be made persistent by calling save
on the dictionary (independently of it being active). After that, any config created in a future session will use this dict as its prototype
[5]:
conf.save()
To revert a config to its factory defaults, use reset
. Call save
to make the reset persistent.
[8]:
conf.reset()
conf['play.numChannels']
[8]:
2
Customizing playback / recording¶
By default any Note
/Chord
is played back using the default preset. It is possible to customize this default preset:
[11]:
defPresetSoundfont("accordion", "/home/em/Lib/snd/sf2/Accordion.sf2")
[11]:
(routing=True, properties={'sfpath': '/home/em/Lib/snd/sf2/Accordion.sf2'}, numouts=2, numsignals=2)
init: iSfTable_ sfloadonce "/home/em/Lib/snd/sf2/Accordion.sf2"
ipresetidx sfPresetIndex "/home/em/Lib/snd/sf2/Accordion.sf2", 0, 0
inote0_ = round(p(idataidx_ + 1))
ivel_ = p(idataidx_ + 2) * 127
aout1, aout2 sfplay ivel_, inote0_, kamp/16384, mtof:k(kpitch), ipresetidx, 1
turnoffWhenSilent aout1
[12]:
conf['play.instr'] = 'accordion'
We create a simple chromatic scale to have something to play with
[13]:
scale = Chain([Note(m, dur=0.25) for m in range(60, 72)])
scale.show()
scale.rec(extratime=0.1)
[13]:
"/home/em/.local/share/maelzel/recordings/rec-2023-03-28T12:19:04.213.wav"
, 2
channels, 3.10
secs, 44100
Hz)[16]:
conf['rec.sr'] = 48000 # set default recording samplerate
scale.rec(nchnls=1)
[16]:
"/home/em/.local/share/maelzel/recordings/rec-2023-03-28T12:19:31.684.wav"
, 1
channels, 3.02
secs, 48000
Hz)The active Workspace¶
At any moment there is one (and only one) active workspace. Whenever a config is made active, it is assigned to the active Workspace
A workspace contains the following attributes:
.config
(CoreConfig
): a config.scorestruct
(ScoreStruct
): a score structure.dynamicCurve
: a dynamics curve, mapping dynamics to amplitudesinternal attributes which determine the current state
[17]:
w = getWorkspace()
w
[17]:
Workspace(scorestruct=ScoreStruct(tempo=60, timesig=(4, 4)), config={'play.instr': 'accordion', 'play.numChannels': 4, 'play.schedLatency': 0.1, 'rec.sr': 48000, 'quant.minBeatFractionAcrossBeats': 0.3333333333333333}, dynamicCurve=DynamicCurve(shape=expon(0.3), mindb=-60.0, maxdb=0.0))
[18]:
if w.isActive():
print("This is the active workspace")
if w.config is getConfig():
print("Its config is active")
This is the active workspace
Its config is active
Ad-hoc config¶
It is possible to create a config to perform only one specific set of actions. This is accomplished by using a CoreConfig
as context manager
[19]:
scale = Chain([Note(m, dur=0.25) for m in range(60, 72)])
with CoreConfig({'play.instr': 'saw'}):
scale.rec(gain=0.1, nchnls=1, extratime=0.1).show()
# Outside the context the instr uses the previous default
scale.rec(nchnls=1, extratime=0.1)
"/home/em/.local/share/maelzel/recordings/rec-2023-03-28T12:20:04.658.wav"
, 1
channels, 3.10
secs, 44100
Hz)[19]:
"/home/em/.local/share/maelzel/recordings/rec-2023-03-28T12:20:05.306.wav"
, 1
channels, 3.10
secs, 48000
Hz)[ ]: