Evolving Form
Example directory (scamp_extensions): examples/Composition & form/Larger-scale form/evolving_form
Download: evolving_form.zip
Requires the scamp_extensions package (pip install scamp_extensions).
A piece for cello, pianoteq (MIDI), and SuperCollider (OSC), shaped by envelope-driven formal parameters. See definitions.py and formal_parameters.py.
Topics: Time & clocks › Setting & changing tempo, Envelopes › As compositional parameters, Composition & form › Larger-scale form
1 / 8
evolving_form.py: The main script for the piece, drawing upon definitions.py and formal_parameters.py.
from scamp import *
from scamp_extensions.pitch import Scale
from definitions import *
from formal_parameters import *
from random import random, choice, uniform, seed
seed(0)
s = Session()
cello = s.new_part("cello")
pianoteq = s.new_midi_part("pianoteq", midi_output_device="IAC")
supercollider_instrument = s.new_osc_part("superInst", 57120, "127.0.0.1")
s.set_tempo_targets([150, 60],
[Moment.after_time(TOTAL_LENGTH * 0.618), Moment.after_time(TOTAL_LENGTH)])
s.start_transcribing()
interval = 1
cello_pitch = 48
def pianoteq_upbeat():
start_pitch = 72 + cello_pitch % 12
end_pitch = 72 + (cello_pitch + interval) % 12
if end_pitch < start_pitch:
end_pitch += 12
for i, p in enumerate([start_pitch + uniform(-k, k) for k in range(7)]):
pianoteq.play_note(p, 0.3 + 0.1 * i, 0.5/7)
set_rate_target(pianoteq_gesture_rit_factor.value_at(s.time), Moment.after_beats(15))
p = end_pitch
volume = 1.0
pianoteq.play_note(p, volume, 0.375)
for _ in range(int(pianoteq_gesture_length.value_at(s.time))):
scale = Scale.from_pitches([cello_pitch + x for x in (0, 2, 3, 5, 6, 8, 9, 11, 12)])
degree = round(scale.pitch_to_degree(p))
pianoteq.play_note(scale.degree_to_pitch(degree - 4), volume, 0.125)
pianoteq.play_note(scale.degree_to_pitch(degree - 3), volume, 0.375)
volume *= 0.8
p = scale.degree_to_pitch(degree - 3)
def pianoteq_filler():
p = 60
while random() < pianoteq_filler_probability.value_at(s.time):
scale = Scale.from_pitches([cello_pitch + x for x in (0, 2, 3, 5, 6, 8, 9, 11, 12)])
degree = round(scale.pitch_to_degree(p))
if p == 60:
wait(1/5)
for i in range(4):
pianoteq.play_chord([scale.degree_to_pitch(degree + 2*i),
scale.degree_to_pitch(degree + 2*i + 3)],
0.6 + 0.1*i, 1/5, "staccatissimo")
if p != 60 and random() < pianoteq_filler_probability.value_at(s.time):
pianoteq.play_chord([scale.degree_to_pitch(degree + 4),
scale.degree_to_pitch(degree + 9),
scale.degree_to_pitch(degree + 11)],
1.0, 1/5, "staccatissimo")
else:
wait(1/5)
p = wrap_in_range(p + 6, 62, 92)
def sc_gesture():
start_pan = uniform(-1, 1)
end_pan = uniform(0, 1) if start_pan < 0 else uniform(-1, 0)
register_ref = sc_register.value_at(s.time)
supercollider_instrument.play_note(
register_ref + cello_pitch % 12,
[0, 0.5], sc_gesture_length.value_at(s.time),
{"param_crackliness": [0, crackliness.value_at(s.time)],
"param_pan": start_pan}
)
supercollider_instrument.play_note(
[register_ref + cello_pitch % 12, register_ref + cello_pitch % 12 + 12],
[[0.5, 0], [1], [-4]], sc_gesture_length.value_at(s.time) * 3/2,
{"param_crackliness": [crackliness.value_at(s.time), 0],
"param_spread": [[0, 0, 0.5], [0.5, 0.5]],
"param_pan": end_pan}
)
piano_tech_clock = None
sc_gesture_clock = None
while True:
if random() < cello_continue_probability.value_at(s.time):
if len(bar_lines) > 0 and s.beat - bar_lines[-1] > 3 and \
(piano_tech_clock is None or not piano_tech_clock.alive):
piano_tech_clock = s.fork(pianoteq_filler)
do_bar_line(s.beat)
cello.play_note(cello_pitch, forte_piano, choice([1.0, 1.5]))
else:
# end condition; break and go to final note
if cello_pitch % 12 == 0 and s.time >= TOTAL_LENGTH:
break
if s.time > 20 and (sc_gesture_clock is None or not sc_gesture_clock.alive):
sc_gesture_clock = s.fork(sc_gesture)
cello.play_note(cello_pitch, diminuendo, choice([2.0, 2.5, 3.0]))
if piano_tech_clock is not None:
piano_tech_clock.kill()
wait(choice([0.5, 1.0]))
if s.time > 40:
piano_tech_clock = s.fork(pianoteq_upbeat)
if random() < cello_staccato_probability.value_at(s.time):
cello_pitch = wrap_in_range(cello_pitch + interval, 36, 60)
interval += 1
wait(0.25)
cello.play_note(cello_pitch, 0.9, 0.25, "staccato")
else:
wait(0.5)
do_bar_line(s.beat)
cello_pitch = wrap_in_range(cello_pitch + interval, 36, 60)
interval += 1
cello.play_note(36, diminuendo, [4.0])
do_bar_line(s.beat)
s.stop_transcribing().to_score(bar_line_locations=bar_lines, max_divisor=14).show()
definitions.py: Shared dynamics envelopes and bar-line helpers for evolving_form.py.
from scamp import *
# playback_settings.adjustments.set("staccato", "length * 0.3")
forte_piano = Envelope(
[0.8, 0.4, 1.0], [0.2, 0.8], curve_shapes=[0, 3]
)
diminuendo = Envelope.from_levels([0.8, 0.3])
def wrap_in_range(value, low, high):
return (value - low) % (high - low) + low
bar_lines = []
def do_bar_line(beat):
beats_since_last_bar_line = beat - bar_lines[-1] if len(bar_lines) > 0 else beat
if beats_since_last_bar_line % 1 == 0 and beats_since_last_bar_line < 5:
bar_lengths = [beats_since_last_bar_line]
else:
num_bars_to_add = 2
while beats_since_last_bar_line / num_bars_to_add > 4:
num_bars_to_add += 1
last_bar_length = round(beats_since_last_bar_line / num_bars_to_add)
remaining_bars_length = beats_since_last_bar_line - last_bar_length
if remaining_bars_length <= 5:
bar_lengths = [remaining_bars_length, last_bar_length]
else:
first_bar_length = remaining_bars_length % 3 + 3 if remaining_bars_length % 3 < 2 else remaining_bars_length % 3
bar_lengths = [first_bar_length] + [3] * int((remaining_bars_length - first_bar_length) / 3) + [last_bar_length]
for bar_length in bar_lengths:
bar_lines.append(bar_lines[-1] + bar_length if len(bar_lines) > 0 else bar_length)
formal_parameters.py: Formal-parameter envelopes for evolving_form.py; run directly to plot them.
from scamp import *
TOTAL_LENGTH = 180
# golden ratio durations
gr, one_minus_gr = TOTAL_LENGTH * 0.618, TOTAL_LENGTH * (1 - 0.618)
cello_continue_probability = Envelope([0.4, 1.0, 0], [gr, one_minus_gr])
cello_staccato_probability = Envelope.from_levels([0, 1], length=TOTAL_LENGTH)
pianoteq_gesture_length = Envelope.from_levels([0, 0, 10, 0], length=TOTAL_LENGTH)
pianoteq_gesture_rit_factor = Envelope([0.25, 0.8, 0.1], [gr, one_minus_gr])
pianoteq_filler_probability = Envelope([0.3, 0.97, 0.3], [gr, one_minus_gr])
crackliness = Envelope([0.6, 1.0, 0], [gr, one_minus_gr])
sc_gesture_length = Envelope([5, 2, 5], [gr, one_minus_gr])
sc_register = Envelope([72, 72, 60, 60, 48, 48, 36, 36 ],
[TOTAL_LENGTH * 0.7, 0, TOTAL_LENGTH * 0.1, 0, TOTAL_LENGTH * 0.1, 0, TOTAL_LENGTH * 0.1])
if __name__ == "__main__":
cello_continue_probability.show_plot("Cello continue probability")
cello_staccato_probability.show_plot("Cello staccato probability")
pianoteq_gesture_length.show_plot("Pianoteq gesture length")
pianoteq_gesture_rit_factor.show_plot("Pianoteq rit factor")
pianoteq_filler_probability.show_plot("Pianoteq filler probability")
crackliness.show_plot("SC instrument crackliness")
sc_gesture_length.show_plot("SC instrument gesture length")
sc_register.show_plot("SC instrument register")
Crackler.scd
MarcUtilities.setOutputLimiter(0.5, globalMul: 0.3)
(
ScampUtils.instrumentFromSynthDef(
SynthDef(\superInst, { |freq=440, volume=0, gate=1, crackliness=0, spread=0, pan=0|
var synth = Mix.fill(5, { |k|
Pan2.ar(
SinOsc.ar(
freq * spread.linlin(0, 1, 1.0, 0.8).pow(k-2) * LFNoise1.ar(crackliness.linlin(0, 1, 30, 100), crackliness.linlin(0, 1, 0, 0.2), 1),
crackliness.linexp(0, 1, 1, 300)
) * EnvGen.kr(Env.perc(
attackTime: crackliness.linexp(0, 1, 0.02, 0.01),
releaseTime:crackliness.linlin(0, 1, 0.2, 0.01)),
gate: Impulse.kr(40)),
k.linlin(0, 5, -1, 1)
)
});
var panned_out = volume * synth * EnvGen.ar(Env.adsr, gate: gate, doneAction: 2) * Pan2.ar(DC.ar(1), pan);
Out.ar(0, panned_out);
});
)
)