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.
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?)
Sure, if you consider it worthy
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
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()