Appendix C: Selected Code Listings

Appendix C: Selected Code Listings#

This appendix is reserved for code listings that are too long for the main body. The main paper should use short excerpts when a small example is enough. Longer listings belong here, where they can support reproducibility without breaking the flow of the argument.

One useful example is the second quartet chord builder, which contains much of the recent experimental work on left-hand and right-hand spacing:

Full chord-construction function for Quartet No. 2.#
def _build_piano_chord(
    seed_pitch: int,
    low: int,
    high: int,
    pitch_classes: tuple[int, ...],
    minimum_tones: int,
    maximum_tones: int,
    max_span: int,
    minimum_total_span: int,
    preferred_steps: tuple[int, ...],
    minimum_separation: int,
    rng: random.Random,
) -> tuple[int, ...]:
    chord_low = max(low, seed_pitch - max_span)
    chord_high = min(high, seed_pitch + max_span)
    candidates = [
        pitch
        for pitch in range(chord_low, chord_high + 1)
        if pitch % 12 in pitch_classes and pitch != seed_pitch
    ]
    if not candidates:
        return (seed_pitch,)

    def interval_quality(pitch: int) -> tuple[int, int]:
        interval = abs(pitch - seed_pitch)
        nearest_preferred = min(abs(interval - step) for step in preferred_steps)
        return (nearest_preferred, interval)

    candidates.sort(key=lambda pitch: (*interval_quality(pitch), pitch))
    candidate_pool = [seed_pitch, *candidates[: min(len(candidates), 12)]]
    available_size = min(maximum_tones, len(candidate_pool))
    if available_size <= 1:
        return (seed_pitch,)

    target_size = rng.randint(minimum_tones, available_size)
    valid_chords: list[tuple[tuple[int, ...], tuple[int, int, int]]] = []

    for combo in combinations(candidate_pool[1:], target_size - 1):
        chord = tuple(sorted((seed_pitch, *combo)))
        if any((upper - lower) < minimum_separation for lower, upper in zip(chord, chord[1:])):
            continue
        total_span = chord[-1] - chord[0]
        if total_span > max_span:
            continue
        span_penalty = max(0, minimum_total_span - total_span)
        adjacent_penalty = sum(
            min(abs((upper - lower) - step) for step in preferred_steps)
            for lower, upper in zip(chord, chord[1:])
        )
        seed_penalty = sum(
            min(abs(abs(pitch - seed_pitch) - step) for step in preferred_steps)
            for pitch in chord
            if pitch != seed_pitch
        )
        valid_chords.append((chord, (span_penalty, adjacent_penalty + seed_penalty, -total_span)))

    if valid_chords:
        valid_chords.sort(key=lambda item: item[1])
        best_score = valid_chords[0][1]
        top_chords = [chord for chord, score in valid_chords if score == best_score][:4]
        return rng.choice(top_chords)

    return (seed_pitch,)

Another useful example is the first quartet score assembly layer:

Final score assembly for Quartet No. 1.#
    if not piece.generation_note_lines:
        return None

    lines = " ".join(
        rf'\line {{ "{_quote_markup_text(line)}" }}'
        for line in piece.generation_note_lines
    )
    return abjad.Markup(rf'\markup \column {{ {lines} }}')


def build_lilypond_file(piece: Piece) -> abjad.LilyPondFile:
    voice_lookup = {voice.staff_id: voice for voice in piece.voices}

    violin_staff = _voice_to_staff(voice_lookup["violin"], piece)
    viola_staff = _voice_to_staff(voice_lookup["viola"], piece)
    cello_staff = _voice_to_staff(voice_lookup["cello"], piece)
    piano_rh_staff = _voice_to_staff(voice_lookup["piano_rh"], piece)
    piano_lh_staff = _voice_to_staff(voice_lookup["piano_lh"], piece)

    piano_staff = abjad.StaffGroup(
        [piano_rh_staff, piano_lh_staff],
        lilypond_type="PianoStaff",
        name="PianoStaff",
    )
    strings_group = abjad.StaffGroup(
        [violin_staff, viola_staff, cello_staff],
        lilypond_type="StaffGroup",
        name="Strings",
    )

    score = abjad.Score([strings_group, piano_staff], name="Score")
    _apply_dynamics(
        violin_staff=violin_staff,
        viola_staff=viola_staff,
        cello_staff=cello_staff,
        piano_rh_staff=piano_rh_staff,
        piano_lh_staff=piano_lh_staff,
        piece=piece,
    )
    _apply_ending(score, piece)

    instrument_names = [
        (violin_staff, "Violin", "Vln."),
        (viola_staff, "Viola", "Vla."),
        (cello_staff, "Cello", "Vc."),
        (piano_staff, "Piano", "Pno."),
    ]
    for component, full_name, short_name in instrument_names:
        abjad.setting(component).instrumentName = rf'\markup "{full_name}"'
        abjad.setting(component).shortInstrumentName = rf'\markup "{short_name}"'

    tempo = abjad.MetronomeMark(
        reference_duration=abjad.Duration(1, 4),
        units_per_minute=piece.tempo_bpm,
    )
    abjad.attach(tempo, abjad.select.leaf(violin_staff, 0))

    header_block = abjad.Block("header")
    header_block.items.append(rf'title = "{piece.title}"')
    header_block.items.append(rf'composer = "{piece.composer}"')
    header_block.items.append(r"tagline = ##f")

    layout_block = abjad.Block("layout")
    layout_block.items.append(r"indent = 2.0\cm")