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

score page 1
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)
envelope plot
envelope plot
envelope plot
envelope plot
envelope plot
envelope plot
envelope plot
envelope plot
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);
     });
)
)