Playback¶
The play method¶
Each MObj
(Note
,
Chord
, Chain
, etc.)
can play itself by calling the play()
method.
Audio playback and offline rendering within maelzel is delegated to csound
(https://csound.com/) using csoundengine.
This makes it possible to interact between the core classes, like
Note
or Voice
and other “unrelated”
parts of maelzel, like the maelzel.snd.audiosample.Sample
class.
Each MObj
expresses its playback in
terms of a list of SynthEvent
.
synthesis events
A SynthEvent
is a score line with a number of fixed fields,
user-defined fields and a sequence of breakpoints to define pitch, amplitude
and any other parameter over time.
Within the play()
method a number of parameters
regarding playback can be determined. **Such parameters are common to all objects. **The most
relevant of these playback parameters are:
- instr
which instrument preset (see
PresetDef
for more information) is used for playback. If not given the default preset is used, as determined in the configuration (see play.instr)- delay
delay in seconds, added to the start of the object As opposed to the
offset
attribute of each object, which is defined in quarternote beats, the delay is always a time in seconds- gain
modifies the own amplitude for playback/recording (0-1)
- chan
the channel to output to. Channels start at 1
- pitchinterpol
‘linear’, ‘cos’, ‘freqlinear’, ‘freqcos’
- fade
fade duration in seconds, can be a tuple (fadein, fadeout)
- position
the panning position (0=left, 1=right)
- args
a
Preset
can define custom parameters, like a cutoff frequency for a filter or a modulation ratio for FM synthesis, etc.
Presets - Introduction¶
maelzel uses csound for audio synthesis in realtime and offline. Within an
instrument preset (a PresetDef
) the user is given
three variables: kfreq
(the current frequency of the event), kamp
(the current
amplitude of the event) and kpitch
(the current midinote of the event, corresponds
to kfreq). With this information the user needs to provide the audio-generating part
using csound code, by assigning the audio output to the variable aout1
for channel 1,
aout2 for channel 2, etc.
There are a number of built-in Presets, but it is very easy to define new Presets. A new
Preset is defined via defPreset()
or
defPresetSoundfont()
(a shortcut to define a preset by
just pointing to a soundfont).
Example¶
from maelzel.core import *
f0 = Note("1E")
notes = Chain([Note(f2m(f0.freq*i), dur=0.5) for i in range(20)])
defPreset("detuned", r'''
; kfreq and kamp are always available within the preset body
a0 = vco2(kamp/3, kfreq)
a1 = vco2(kamp/3, kfreq+2)
a2 = vco2(kamp/3, kfreq-3)
; aout1 is assigned the audio output (this is a mono-preset)
aout1 = sum(a0, a1, a2)
''')
notes.play(instr='detuned')
See Also
Playback attributes (setPlay)¶
Any playback attribute determined via the play()
method can be set beforehand for each object individually, using
setPlay()
. Then, when this object is played
any play settings fixed via .setPlay
is used as if it was passed
to play()
.
In the following example we fix the instr of 1 every 4 notes to ‘mypiano’. For the
rest of the notes this settings stays undetermined. When .play
is called on the
Chain
, 'saw'
(which is a built-in preset) is set as the
default instr. Any fixed playarg will be used for playback, otherwise a group default
is used (the instr set for Chain
) or, as a fallback, the default instr as
defined in the config (key: ‘play.instr’).
.play param → param set via .setPlay → .play param for the group → default value
Example¶
from maelzel.core import *
defPresetSoundfont('mypiano', '/path/to/mypiano.sf2')
notes = Chain([Note(m, dur=1) for m in range(48, 72)])
for i, note in enumerate(notes):
if i%4 == 0:
note.setPlay(instr='mypiano')
notes.play(instr='saw')
Preset parameters¶
A Preset can have parameters which modify its behaviour. For example, an FM-synth preset might allow to define modulation ratio or depth, or a subtractive-synth preset could define parameters for its filter. Any custom parameter must define a default value
from maelzel.core import *
defPreset('substractive', r'''
a0 vco2 kamp, kfreq, 10
aout1 moogladder a0, lag:k(kcutoff, 0.1), kq
''', params={'kcutoff': 3000, 'kQ': 0.9})
Chord("C4 E4 G4", dur=10).play(instr='substractive', args={'kcutoff': 1000})
Preset parameters can also be defined inline, following the syntax for csoundengine’s Instr
from maelzel.core import *
defPreset('substractive', r'''
|kcutoff=3000, kQ=0.9|
a0 vco2 kamp, kfreq, 10
aout1 moogladder a0, lag:k(kcutoff, 0.1), kq
''')
Chord("C4 E4 G4", dur=10).play(instr='substractive', kcutoff=1000)
Recording / Offline Rendering¶
By replacing a call to play()
with a call to
rec()
it is possible to render any
MObj
as a soundfile (offline rendering). To
render multiple objects to the same soundfile you can either group them in a container
(for example, place multiple notes inside a Chain
and
render that) or render them via render
from maelzel.core import *
voice1 = Voice([Note(m, dur=1)
for m in range(60, 72)])
voice2 = Voice([Note(m+0.5, dur=1.75)
for m in range(60, 72)])
voice1.setPlay(instr='saw')
voice2.setPlay(instr='tri')
render("out.wav", [voice1, voice2])
Notice that in this way, any parameter normally passed to .rec
needs to be fixed
via .setPlay
.
Using render
as context manager¶
A simpler method is to use the render()
as context
manager:
from maelzel.core import *
voice1 = Voice([Note(m, dur=1)
for m in range(60, 72)])
voice2 = Voice([Note(m+0.5, dur=1.75)
for m in range(60, 72)])
with render('out.wav'):
voice1.play(instr='saw')
voice2.play(instr='tri')
Within the context manager the same code used for playing in realtime can be used to render offline
(see also: OfflineRenderer
)