For whom the bell tolls

The following piece was generated with Scamp, using some extensions I implemented to allow outputing lyrics in the musicxml output. Some minor manual postprocessing was done in musescore to add dynamics and to extend the duration of the final measure.

1 Like

Wonderful! Is this a VST or a real performance? (Because if VST, I’m definitely curious what you used!)

But whatever the case, the main thing is that I feel you’ve made a real piece of art here. You really feel the way in which none of the four lines is an island unto itself.

I’m curious how you structured the code, in particular how the form takes shape.

(By the way, can I feature this on the SCAMP website as one of the pieces made in SCAMP?)

1 Like

Sure, if you consider it worthy :slight_smile:

For the singing: it’s a web application called “Cantamus” (Cantāmus) which takes in musicxml and outputs choir singing. Officially it’s branded as an application to help choir members rehearse pieces. I guess some hostile feelings towards anything AI that replaces real musicians necessitates such approach? I’ve been using it for one year now to hear my choral compositions before they can be performed by a real choir (in the majority of cases a real performance never happens and the AI version is all I have).

The code is nothing special really, each small cell is composed “by hand” and then unrolled a few times in code. It uses a small part of a third-party library called “expremigen” to convert between notenames and midi note numbers (I cannot efficiently reason in midi note numbers directly).

Note that the code pasted below will only work with a patched version of scamp that supports attaching lyrics to notes (and emitting them in the music-xml output). The music was inspired by the typical polyrhythms you hear when a few church bells cling each in their own tempo. Instead of bells clinging in some temporal relationship to each other, here we have small voice fragments singing in some temporal (and harmonic) relationship to each other. It sounds way more difficult than it is really :smiley:

import scamp

from scamp import *
from expremigen.musicalmappings import note2midi
import itertools

def performer(voice, note_lengths, phonemes):
    text_idx = 0
    for notes_voice, num in note_lengths:
        if notes_voice is not None and num > 0:
            for note, duration in itertools.islice(itertools.cycle(notes_voice), num):
                if text_idx < len(phonemes):
                    if note == "r":
                        scamp.wait(duration)
                    else:
                        voice.play_note(pitch=note, volume=0.5, length=duration,
                                        properties={"lyric": phonemes[text_idx]})
                        text_idx = text_idx + 1
                else:
                    break


session = Session(default_spelling_policy="D major", tempo=56)
v1 = session.new_midi_part("Sopranos")
v2 = session.new_midi_part("Altos")
v3 = session.new_midi_part("Tenors")
v4 = session.new_midi_part("Basses")

n = note2midi.Note2Midi()

phonemes = ["No", "man", "is", "an", "is-[ay]", "land,",
            "En-", "tire", "of", "it-", "self.",
            "Each", "is", "a", "piece", "of", "the", "con-", "ti-", "nent,",
            "A", "part", "of", "the", "main.",
            "If", "a", "clod", "be", "washed", "a-", "way", "by", "the", "sea,",
            "Eu-[y.uh]", "rope", "is", "the", "less.",
            "As", "well", "as", "if", "a", "pro-", "mon-", "to-", "ry[r.iy]", "were.",
            "As", "well", "as", "if", "a", "ma-[m.ae]", "nor", "of", "thine", "own",
            "Or", "of", "thine", "friend's", "were.",
            "Each", "man's", "death", "di-", "mi-", "ni-", "shes", "me,",
            "For", "I", "am", "in-", "volved", "in", "man-", "kind.",
            "There-", "fore", "send", "not", "to", "know",
            "For", "whom", "the", "bell", "tolls,",
            "It", "tolls", "for", "thee."]

session.start_transcribing()
session.fast_forward(True)
# passing comma-separated key value pairs

sopranos_1 = [
    (n.lookup("a4"), 1),
    (n.lookup("d5"), 1.5),
    (n.lookup("c5"), 1.0),
    ("r", 0.5)
]

sopranos_1b = [
    (n.lookup("a4"), 1),
    (n.lookup("d5"), 1.0),
    ("r", 1.0),
    (n.lookup("c5"), 1.0)
]

altos_1 = [
    ("r", 1.0),
    (n.lookup("f4"), 0.5),
    (n.lookup("d4"), 1),
    (n.lookup("e4"), 1.5)
]

tenors_1 = [
    ("r", 0.5),
    (n.lookup("a3"), 0.5),
    (n.lookup("b-3"), 1),
    (n.lookup("c4"), 2.0),
]

basses_1 = [
    (n.lookup("d3"), 0.5),
    (n.lookup("b-2"), 1.0),
    (n.lookup("a2"), 1.5),
    ("r", 1.0),
]

sopranos_2 = [
    (n.lookup("a4"), 1),
    (n.lookup("d5"), 1.5),
    (n.lookup("e5"), 1.0),
    ("r", 0.5),
    (n.lookup("a4"), 1),
    (n.lookup("e5"), 1.5),
    (n.lookup("d5"), 1.0),
    ("r", 0.5)
]

sopranos_2b = [
    (n.lookup("a4"), 1),
    (n.lookup("d5"), 0.5),
    ("r", 1.0),
    (n.lookup("e5"), 1.0),
    ("r", 0.5),
    (n.lookup("a4"), 1),
    (n.lookup("e5"), 1.5),
    (n.lookup("d5"), 1.5),
]

altos_2 = [
    ("r", 0.5),
    (n.lookup("g4"), 0.5),
    (n.lookup("a4"), 2),
    (n.lookup("g4"), 1.0),
    ("r", 0.5),
    (n.lookup("g4"), 0.5),
    (n.lookup("e4"), 2),
    (n.lookup("f4"), 1.0)
]

tenors_2 = [
    ("r", 1.0),
    (n.lookup("f3"), 0.5),
    (n.lookup("e3"), 1),
    (n.lookup("g3"), 1.5),
    ("r", 1.0),
    (n.lookup("g3"), 0.5),
    (n.lookup("f3"), 1),
    (n.lookup("a3"), 1.5),
]

basses_2 = [
    (n.lookup("d3"), 1),
    (n.lookup("b-2"), 0.5),
    (n.lookup("c3"), 1.5),
    ("r", 1.0),
    (n.lookup("d3"), 1.0),
    (n.lookup("a2"), 0.5),
    (n.lookup("d3"), 1.5),
    ("r", 1.0),
]

sopranos_3 = [
    (n.lookup("f5"), 1),
    ("r", 0.5),
    (n.lookup("b-4"), 1.0),
    ("r", 0.5),
    (n.lookup("a4"), 0.5),
    (n.lookup("d5"), 0.5),

    (n.lookup("f5"), 0.5),
    ("r", 0.5),
    (n.lookup("b-4"), 1.0),
    ("r", 0.5),
    (n.lookup("a4"), 1.0),
    (n.lookup("d5"), 0.5),
]

altos_3 = [
    ("r", 0.5),
    (n.lookup("a4"), 0.5),
    (n.lookup("f4"), 1.0),
    ("r", 0.5),
    (n.lookup("e4"), 1.0),
    (n.lookup("g4"), 0.5),

    ("r", 0.5),
    (n.lookup("a4"), 1.0),
    (n.lookup("f4"), 1.0),
    ("r", 0.5),
    (n.lookup("e4"), 0.5),
    (n.lookup("g4"), 0.5),
]

tenors_3 = [
    ("r", 0.5),
    (n.lookup("a3"), 0.5),
    (n.lookup("d4"), 1.5),
    (n.lookup("a3"), 0.5),
    ("r", 0.5),
    (n.lookup("g3"), 0.5),

    ("r", 0.5),
    (n.lookup("a3"), 0.5),
    (n.lookup("d4"), 1.0),
    (n.lookup("a3"), 0.5),
    ("r", 0.5),
    (n.lookup("g3"), 1.0),
]

basses_3 = [
    (n.lookup("d3"), 1),
    (n.lookup("f3"), 0.5),
    ("r", 0.5),
    (n.lookup("g3"), 1.0),
    ("r", 0.5),
    (n.lookup("a3"), 0.5),

    (n.lookup("d3"), 0.5),
    (n.lookup("f3"), 1.0),
    ("r", 0.5),
    (n.lookup("g3"), 1.0),
    ("r", 0.5),
    (n.lookup("a3"), 0.5),
]

sopranos_4 = [
    (n.lookup("e5"), 1),
    ("r", 0.5),
    (n.lookup("b4"), 1.0),
    ("r", 0.5),
    (n.lookup("g#4"), 0.5),
    (n.lookup("b4"), 0.5),
    (n.lookup("d5"), 1.0),
]

altos_4 = [
    ("r", 0.5),
    (n.lookup("a4"), 1.0),
    (n.lookup("g#4"), 0.5),
    (n.lookup("e4"), 1.0),
    ("r", 0.5),
    (n.lookup("e4"), 1.0),
    (n.lookup("b4"), 0.5),
]

tenors_4 = [
    ("r", 0.5),
    (n.lookup("f#3"), 1.0),
    (n.lookup("g#3"), 0.5),
    (n.lookup("d4"), 1.5),
    (n.lookup("b3"), 0.5),
    ("r", 0.5),
    (n.lookup("e3"), 0.5),
]

basses_4 = [
    (n.lookup("e3"), 1),
    (n.lookup("f#3"), 0.5),
    ("r", 0.5),
    (n.lookup("g#3"), 1.0),
    ("r", 0.5),
    (n.lookup("a3"), 0.5),
    (n.lookup("g#3"), 1.0),
]

sopranos_5 = [
    (n.lookup("e5"), 1),
    ("r", 0.5),
    (n.lookup("a4"), 1.0),
    ("r", 0.5),
    (n.lookup("c5"), 0.5),
    (n.lookup("b4"), 0.5),

    (n.lookup("e5"), 1),
    ("r", 0.5),
    (n.lookup("a4"), 0.5),
    ("r", 0.5),
    (n.lookup("c5"), 1.0),
    (n.lookup("b4"), 0.5),
]

altos_5 = [
    ("r", 0.5),
    (n.lookup("a4"), 0.5),
    (n.lookup("e4"), 1.0),
    ("r", 0.5),
    (n.lookup("e4"), 1.0),
    (n.lookup("b4"), 0.5),

    ("r", 0.5),
    (n.lookup("a4"), 0.5),
    (n.lookup("e4"), 0.5),
    ("r", 1.0),
    (n.lookup("e4"), 1.0),
    (n.lookup("b4"), 0.5),
]

tenors_5 = [
    ("r", 0.5),
    (n.lookup("a3"), 1.0),
    (n.lookup("c4"), 0.5),
    (n.lookup("e4"), 0.5),
    ("r", 1.0),
    (n.lookup("e3"), 0.5),

    ("r", 1.0),
    (n.lookup("a3"), 0.5),
    (n.lookup("c4"), 0.5),
    (n.lookup("b3"), 1.0),
    ("r", 0.5),
    (n.lookup("e3"), 0.5),
]

basses_5 = [
    (n.lookup("e3"), 1),
    (n.lookup("a3"), 0.5),
    ("r", 0.5),
    (n.lookup("g#3"), 1.0),
    ("r", 0.5),
    (n.lookup("g3"), 0.5),

    (n.lookup("f#3"), 1),
    (n.lookup("f3"), 0.5),
    ("r", 0.5),
    (n.lookup("e3"), 0.5),
    ("r", 0.5),
    (n.lookup("d3"), 1.0),
]


sopranos_6 = [
    (n.lookup("e5"), 1),
    ("r", 0.5),
    (n.lookup("c5"), 1.0),
    ("r", 0.5),
    (n.lookup("c5"), 0.5),
    (n.lookup("b4"), 0.5),

    (n.lookup("e5"), 0.5),
    ("r", 0.5),
    (n.lookup("a4"), 1.0),
    ("r", 1.0),
    (n.lookup("c5"), 0.5),
    (n.lookup("b4"), 0.5),
]

altos_6 = [
    ("r", 0.5),
    (n.lookup("c5"), 0.5),
    (n.lookup("a4"), 1.0),
    ("r", 0.5),
    (n.lookup("a4"), 1.0),
    (n.lookup("e4"), 0.5),
]

tenors_6 = [
    ("r", 0.5),
    (n.lookup("a3"), 0.5),
    (n.lookup("c4"), 1.5),
    (n.lookup("e3"), 0.5),
    ("r", 0.5),
    (n.lookup("e3"), 0.5),
]

basses_6 = [
    (n.lookup("e3"), 1),
    (n.lookup("f#3"), 0.5),
    ("r", 0.5),
    (n.lookup("g#3"), 1.0),
    ("r", 0.5),
    (n.lookup("a3"), 0.5),

    (n.lookup("b3"), 1),
    (n.lookup("c4"), 0.5),
    ("r", 0.5),
    (n.lookup("a3"), 1.0),
    ("r", 0.5),
    (n.lookup("f3"), 0.5),
]

sopranos_7 = [
    (n.lookup("d5"), 1),
    (n.lookup("a4"), 1.0),
    (n.lookup("d5"), 1.0),
    ("r", 1.0),
    (n.lookup("d5"), 1),
    (n.lookup("a4"), 1.0),
    (n.lookup("f4"), 1.0),
    ("r", 1.0)
]

altos_7 = [
    ("r", 1.0),
    (n.lookup("e4"), 0.5),
    (n.lookup("d4"), 1),
    (n.lookup("f4"), 1.5),
    ("r", 1.0),
    (n.lookup("e4"), 0.5),
    (n.lookup("d4"), 1),
    (n.lookup("d4"), 1.5)
]

tenors_7 = [
    ("r", 0.5),
    (n.lookup("a3"), 0.5),
    (n.lookup("f3"), 1),
    (n.lookup("d3"), 1.0),
    ("r", 1.0),

    ("r", 0.5),
    (n.lookup("a3"), 0.5),
    (n.lookup("d3"), 1),
    (n.lookup("d3"), 1.0),
    ("r", 1.0),
]

basses_7 = [
    (n.lookup("d3"), 1),
    (n.lookup("a2"), 0.5),
    (n.lookup("d3"), 1.5),
    ("r", 1.0),
]

print(f"no of phonemes = {len(phonemes)}")
session.fork(performer, args=[v1,
                              [(sopranos_1, 12),
                               (sopranos_1b, 4),
                               (sopranos_2,  8),
                               (sopranos_2b, 8),
                               (sopranos_3, 24),
                               (sopranos_4, 24),
                               (sopranos_5, 24),
                               (sopranos_6, 12),
                               (sopranos_7, 24),
                               ],
                              phonemes])
session.fork(performer, args=[v2,
                              [(altos_1, 16),
                               (altos_2, 16),
                               (altos_3, 24),
                               (altos_4, 24),
                               (altos_5, 24),
                               (altos_6, 12),
                               (altos_7, 24),
                               ],
                              phonemes])
session.fork(performer, args=[v3,
                              [(tenors_1, 16),
                               (tenors_2, 16),
                               (tenors_3, 24),
                               (tenors_4, 24),
                               (tenors_5, 24),
                               (tenors_6, 12),
                               (tenors_7, 24),
                               ],
                              phonemes])
session.fork(performer, args=[v4,
                              [(basses_1, 16),
                               (basses_2, 16),
                               (basses_3, 24),
                               (basses_4, 24),
                               (basses_5, 24),
                               (basses_6, 12),
                               (basses_7, 24),
                               ],
                              phonemes])

session.wait_for_children_to_finish()
performance = session.stop_transcribing()

performance.to_score(title="For whom the bell tolls", composer="Lyrics: John Donne").show_xml()