{
"cells": [
{
"cell_type": "markdown",
"id": "6f294ce6-2c68-4a4b-a65a-18e932552683",
"metadata": {},
"source": [
"# An introduction to durations\n",
"\n",
"In this demonstration we reconstruct the first part of the Kyrie in Ockeghem's Missa Prolationum. "
]
},
{
"cell_type": "markdown",
"id": "74b208a1-2779-4f26-bc8d-7fb15fba76ac",
"metadata": {},
"source": [
"## Ockeghem, Missa Prolationum\n",
"\n",
"
"
]
},
{
"cell_type": "markdown",
"id": "8a2f2de3-aaad-4686-907f-f50734ed0394",
"metadata": {},
"source": [
"The Kyrie is structured as a double canon, where the imitation replies with an augmented version by interpreting the durations using a different prolatio. In some cases there are minor exceptions. To account for these we use properties to set the augmented duration of the imitation (see the notes with 'setdur'). \n",
"\n",
"> **Properties**: Any note/chord can have a user-defined set of properties. These properties are implemented as a dict mapping string keys to any value and are normally used to attach data to a note in place in order to perform some action later"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "b0588d45-c031-493c-89cd-c78fc6e5dec2",
"metadata": {},
"outputs": [],
"source": [
"from maelzel.core import *"
]
},
{
"cell_type": "markdown",
"id": "f8a1da4d-4d93-47a2-81dc-2a85f6d38829",
"metadata": {},
"source": [
"### Cantus\n",
"\n",
"The first voice of the canon, the cantus. "
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "75725fa6-3c0b-4dc6-85f9-4f4404ae861f",
"metadata": {},
"outputs": [],
"source": [
"cantus = Chain([\n",
" Note(\"4F\", 4),\n",
" Note(\"4C\", 4),\n",
" Note(\"4F\", 4),\n",
" Note(\"4A\", 4),\n",
" Rest(2),\n",
" # Each Note/Chord can have user-defined properties. These can be used to attach any data to them\n",
" Note(\"4E\", 2, properties={'setdur': 4}),\n",
" Note(\"4F\", 4),\n",
" Note(\"4E\", 2),\n",
" Note(\"4C\", 2),\n",
" Note(\"4C\", 2),\n",
" Rest(2),\n",
" Note(\"4F\", 4, properties={'setdur': 4}),\n",
" Note(\"4G\", 3),\n",
" Note(\"4E\", 1),\n",
" Note(\"4E\", 2),\n",
" Note(\"4F\", 3),\n",
" Note(\"4A\", 1.5),\n",
" Note(\"4B\", 0.5),\n",
" Note(\"5C\", 1),\n",
" Note(\"4B\", 1),\n",
" Note(\"4B\", 2),\n",
" Note(\"4A\", 0.5),\n",
" Note(\"4B\", 0.5),\n",
" Note(\"5C\", 2),\n",
" Rest(2),\n",
" Note(\"4A\", 2),\n",
" Note(\"4F\", 2),\n",
" Note(\"4G\", 2),\n",
" Note(\"4F\", 2),\n",
" Note(\"4E\", 2),\n",
" Note(\"4C\", 4, properties={'setdur': 4}),\n",
" Note(\"5C\", 2),\n",
" Note(\"4Bb\", 1),\n",
" Note(\"5D\", 2),\n",
" Note(\"5C\", 0.5),\n",
" Note(\"4Bb\", 0.5),\n",
" Note(\"4A\", 6),\n",
" Note(\"4F\", 2),\n",
" Note(\"4F\", 3),\n",
" Note(\"4E\", 0.5),\n",
" Note(\"4D\", 0.5),\n",
" Note(\"4C\", 2)\n",
"])\n"
]
},
{
"cell_type": "markdown",
"id": "f9780ee8-ecce-41a4-97be-48a0fd0c020b",
"metadata": {},
"source": [
"### Contra"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "626136f9-1a57-4989-95cd-a0ddca0bcaac",
"metadata": {},
"outputs": [],
"source": [
"\n",
"contra = Chain([\n",
" Note(\"4F\", 6),\n",
" Note(\"4A\", 3),\n",
" Note(\"4F\", 3, properties={'setdur': 6}),\n",
" Note(\"5C\", 6),\n",
" Note(\"4A\", 3),\n",
" Note(\"4F\", 3, properties={'setdur': 6}),\n",
" Note(\"4C\", 4),\n",
" Note(\"5C\", 2),\n",
" Note(\"5C\", 2),\n",
" Note(\"4A\", 2),\n",
" Note(\"4A\", 2),\n",
" Note(\"4G\", 4),\n",
" Note(\"4A\", 2),\n",
" Note(\"4F\", 2),\n",
" Note(\"5C\", 4),\n",
" Note(\"5D\", 2),\n",
" Note(\"5D\", 2),\n",
" Note(\"5C\", 4),\n",
" Note(\"4F\", 2),\n",
" Note(\"4A\", 2),\n",
" Note(\"4G\", 1.5),\n",
" Note(\"4F\", 0.5),\n",
" Note(\"4D\", 2),\n",
" Note(\"4A\", 3),\n",
" Note(\"4B\", 1),\n",
" Note(\"5C\", 2),\n",
" Note(\"4A\", 2),\n",
" Rest(1),\n",
" Note(\"4Bb\", 2),\n",
" Note(\"4A\", 1),\n",
" Note(\"5C\", 2),\n",
" Note(\"4F\", 4),\n",
" Note(\"4F\", 2),\n",
" Note(\"3Bb\", 4),\n",
" Note(\"4F\", 2),\n",
" Note(\"4F\", 1),\n",
" Note(\"4A\", 2),\n",
" Note(\"4B\", 1),\n",
" Note(\"5C\", 2) \n",
"])\n",
"\n",
"contra = contra.transpose(-12)"
]
},
{
"cell_type": "markdown",
"id": "876a699d-0737-4dcb-a085-36604503f856",
"metadata": {},
"source": [
"Here we calculate the duration of the imitation, using the previously set properties to account for the exceptions to the rule"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "ba32f51d-8a44-4b1d-8993-1e5c87d913d2",
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"Score(2 voices)
\n",
" "
],
"text/plain": [
"Score(2 voices)"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"def cantusProlatio(n):\n",
" if dur := n.getProperty('setdur'):\n",
" return dur\n",
" elif n.dur == 4:\n",
" return n.dur * 1.5\n",
" return n.dur\n",
" \n",
"cantus2 = Chain([n.clone(dur=cantusProlatio(n)) for n in cantus])\n",
"\n",
"Score([cantus, cantus2])"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "19e8bd5f-a775-42c5-9e59-f1a271f99493",
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"
Score(2 voices)
\n",
" "
],
"text/plain": [
"Score(2 voices)"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"def contraProlatio(n):\n",
" if dur := n.getProperty('setdur'):\n",
" return dur\n",
" elif n.dur == 6:\n",
" return n.dur * F(3, 2)\n",
" return n.dur\n",
" \n",
"contra2 = Chain(n.clone(dur=contraProlatio(n))\n",
" for n in contra)\n",
"\n",
"Score([contra, contra2])\n",
" "
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "98fab5d6-3bf4-4133-8872-21db0a19aede",
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"
Score(4 voices)
\n",
" "
],
"text/plain": [
"Score(4 voices)"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"struct = ScoreStruct(timesig=(2, 2), tempo=120)\n",
"# setScoreStruct(struct)\n",
"\n",
"origscore = Score([Voice(cantus, name='cantus'),\n",
" Voice(cantus2, name='cantus2'),\n",
" Voice(contra, name='contra'),\n",
" Voice(contra2, name='contra2')\n",
" ], scorestruct=struct)\n",
"origscore"
]
},
{
"cell_type": "markdown",
"id": "7eed104f-45e3-40ef-a747-220f80e8c6df",
"metadata": {},
"source": [
"For playback we will use a very simple singing voice based on the formants of a male tenor. To match\n",
"the range it might be a good idea to transpose the score down."
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "72696794-805b-404f-8975-6aaf098907a4",
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"
Score(4 voices)
\n",
" "
],
"text/plain": [
"Score(4 voices)"
]
},
"execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"s = origscore.transpose(-5)\n",
"s"
]
},
{
"cell_type": "markdown",
"id": "722ee4ea-438d-4fb7-a277-5279c4089c2b",
"metadata": {},
"source": [
"## Playback \n",
"For playback we can define a simple singing preset with formant values for a male voice. Here we will not get into the details: for now it is enough to notice that we define values for 5 vowels (a, e, i, o, u) which are placed on the 2D plane and we can select a vowel by setting its corresponding coordinate"
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "fd196ae3-4bc0-449c-81ba-be78cf0f147d",
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"Preset: sing\n",
"
\n",
" (routing=True, numouts=2, numsignals=1)\n",
"\n",
"init:
gi__formantFreqs__[] fillarray \\\n",
" 668, 1191, 2428, 3321, 4600, \\ ; A\n",
" 327, 2157, 2754, 3630, 4600, \\ ; E \n",
" 208, 2152, 3128, 3425, 4200, \\ ; I\n",
" 335, 628, 2689, 3515, 4200, \\ ; O\n",
" 254, 796, 2515, 3274, 4160 ; U\n",
"\n",
" gi__formantDbs__[] fillarray \\\n",
" 28, 28, 22, 20, 20, \\\n",
" 15, 25, 24, 20, 23, \\\n",
" 10, 20, 27, 26, 20, \\\n",
" 15, 18, 5, 7, 12, \\\n",
" 12, 10, 6, 5, 12\n",
"\n",
" gi__formantBws__[] fillarray \\\n",
" 80, 90, 120, 130, 140, \\\n",
" 60, 100, 120, 150, 200, \\\n",
" 60, 90, 100, 120, 120, \\\n",
" 40, 80, 100, 120, 120, \\\n",
" 50, 60, 170, 180, 200\n",
"\n",
" gi__formantAmps__[] maparray gi__formantDbs__, "ampdb"\n",
" reshapearray gi__formantFreqs__, 5, 5\n",
" reshapearray gi__formantAmps__, 5, 5\n",
" reshapearray gi__formantBws__, 5, 5\n",
" \n",
" |kx=0.0, ky=0.0, kvibamount=1.0|\n",
" kx *= random:i(0.95, 1.05)\n",
" ky *= randomi:k(0.96, 1.04, 1)\n",
" kvibfreq = linsegr:k(0, 0.1, 0, 0.4, 4.7, 0.2, 1) * randomi:k(0.9, 1.1, 3)\n",
" kvibsemi = linsegr:k(0, 0.2, 0, 0.8, 0.4, 0.2, 0) * randomi:k(0.85, 1.15, 8)\n",
" kvib = oscil:k(kvibsemi/2, kvibfreq) - kvibsemi/2\n",
" kpitch = lag:k(kpitch, 0.2) + kvib*kvibamount\n",
" asource = butterlp:a(vco2:a(kamp, mtof(kpitch)), 4000)\n",
" ; x y weight\n",
" kcoords[] fillarray 0, 0, 1, \\ ; A\n",
" 0.5, 0.5, 0.3, \\ ; E\n",
" 1, 0, 1, \\ ; I\n",
" 0, 1, 1, \\ ; O\n",
" 1, 1, 1 ; U\n",
"\n",
" kweights[] presetinterp kx, ky, kcoords, 0.2\n",
" kformantFreqs[] weightedsum gi__formantFreqs__, kweights\n",
" kformantBws[] weightedsum gi__formantBws__, kweights\n",
" kformantAmps[] weightedsum gi__formantAmps__, kweights\n",
" kformantFreqs poly 5, "lag", kformantFreqs, 0.2\n",
" kformantAmps poly 5, "lag", kformantAmps, 0.2\n",
" aformants[] poly 5, "resonx", asource, kformantFreqs, kformantBws, 2, 1\n",
" aformants *= kformantAmps\n",
" aout1 = sumarray(aformants) * 0.1\n",
" a1, a2 monitor\n",
" a1post, a2post reverbsc a1, a2, 0.75, 12000, sr, 0.5, 1\n",
" outch 1, a1post - a1, 2, a2post - a2\n",
"\"/home/em/.local/share/maelzel/recordings/rec-2023-03-27T20:37:57.358.wav\", 2 channels, 58.00 secs, 44100 Hz)