{ "cells": [ { "cell_type": "markdown", "id": "d843e8d3-be48-4183-8aa4-6e2b82f50829", "metadata": { "tags": [] }, "source": [ "# Messiaen: La liturgie de Cristal\n", "\n", "![](assets/messiaen-liturgie.jpg)" ] }, { "cell_type": "markdown", "id": "4729fee6-008f-4fff-8f15-e04787aca70c", "metadata": { "tags": [] }, "source": [ "## Piano and Cello ostinato\n", "\n", "This is the first movement in Messiaen's *Quatour pour la fin dur temps*. It is structured around a main voice in the clarinet, supported by **two isorhythmic patterns in the cello and piano**. The violin voice is composed with more freely repeating patterns in *oisseau* style. Here we will explore the two more strict isorhythmic ostinati. " ] }, { "cell_type": "code", "execution_count": 1, "id": "284f1bb7-f851-4d7c-9cba-bee95ae89095", "metadata": {}, "outputs": [], "source": [ "from maelzel.core import *\n", "from itertools import cycle\n", "from pitchtools import *" ] }, { "cell_type": "markdown", "id": "5219bb9a-c24d-4377-88bd-917f06fad3b5", "metadata": {}, "source": [ "### Configure the environment" ] }, { "cell_type": "code", "execution_count": 2, "id": "5da55415-079e-4463-af2c-ab596c4e8d2a", "metadata": {}, "outputs": [], "source": [ "cfg = CoreConfig(active=True)\n", "cfg['play.verbose'] = False\n", "cfg['play.pitchInterpolation'] = 'cos'\n", "cfg['htmlTheme'] = 'light' # match the jupyter theme\n", "cfg['fixStringNotenames'] = True\n", "cfg['jupyterHtmlRepr'] = False" ] }, { "cell_type": "markdown", "id": "dce938ee-cbc2-469b-985b-88fc942639dc", "metadata": {}, "source": [ "### Score Structure\n", "\n", "The score does not have any tempo changes, just a slow 3/4. Messiaen writes q = ca. 54, but it is often played somewhat slower" ] }, { "cell_type": "code", "execution_count": 3, "id": "2819a0a3-c2fa-4ee6-83ae-bb7bc188b635", "metadata": {}, "outputs": [], "source": [ "scorestruct = ScoreStruct(timesig=(3, 4), tempo=50)\n", "setScoreStruct(scorestruct)" ] }, { "cell_type": "markdown", "id": "96fc8d66-e0cc-4270-8f5e-9d459a7e515a", "metadata": { "tags": [] }, "source": [ "## Talea / Color\n", "\n", "The two *ostinati* (piano and cello) are defined by a seq. of pitches/chords and a sequence of durations, of different size. When combining both, these sequences fall out of phase, resulting in multiple rhythmical variations of the pitch structure. At the third piano cycle Messiaen skipped one color in the right hand, resuling in the hands themselves falling out of phase. For the sake of simplicity we leave this aspect out of the current analysis \n", "\n", "The cello durations are defined based on two cells, **A** and **B**. The *talea* (duration sequence) consists of **A** + **B** + **B**reversed" ] }, { "cell_type": "code", "execution_count": 4, "id": "461d6584-3a9b-4a2b-9cf9-ab69d1a5638c", "metadata": {}, "outputs": [], "source": [ "lowerChords = [\n", " \"F3 G3 Bb3 C4\",\n", " \"F3 G3 Bb3 C4\",\n", " \"F3 Ab3 Bb3 Db4\",\n", " \"F3 Ab3 Bb3 Db4\",\n", " \n", " \"F3 G3 Bb3 D4\",\n", " \"F3 G3 Bb3 D4\",\n", " \"F3 A3 C4 D4\",\n", " \"F3 A3 C4 D4\",\n", " \n", " \"F3 Bb3 Db4\",\n", " \"F3 B3 D4\",\n", " \"F3 C4 Eb4\",\n", " \"F3 C#4 E4\",\n", " \n", " \"Ab3 Eb4 Gb4\",\n", " \"Ab3 Eb4 F4\",\n", " \"Gb3 Db4 Ab4\",\n", " \"Gb3 Db4 Bb4\",\n", " \n", " \"A3 C4 D4 F#4 Bb4\",\n", " \"Bb3 C#4 E4 G#4\",\n", " \"C4 D4 F4\",\n", " \"C#4 E4 F#4\",\n", " \n", " \"F4 G#4 Bb4\",\n", " \"F#4 A4 B4\",\n", " \"F4 Bb4\",\n", " \"E4 Ab4\",\n", " \n", " \"D4 G4\",\n", " \"C#4 F4\",\n", " \"B3 E4\",\n", " \"Ab3 C#4\",\n", " \"Gb3 3B\",\n", "]\n", "\n", "upperChords = [\n", " \"Eb4 B4 E5\",\n", " \"Eb4 A4 D5\",\n", " \"Eb4 A4 D5\",\n", " \"Eb4 G4 C5\",\n", " \"F#4 B4 C5\",\n", " \"E4 A4 C5\",\n", " \"G4 C#5 F#5\",\n", " \"G4 B4 E5\",\n", " \"Gb4 E5\",\n", " \"G4 E5 G5\",\n", " \"Ab4 G5\",\n", " \"A4 G5 B5\",\n", " \"Bb4 Eb5 Gb5 Cb6\",\n", " \"Bb4 Db5 F5 Bb5\",\n", " \"Eb5 Ab5 Cb6 Eb6\",\n", " \"D5 F5 Bb5 D6\",\n", " \"Db5 Gb5 Bb5 Db6\",\n", " \"C5 D5 G#5 C6\",\n", " \"A4 C#5 E5 A5\",\n", " \"Bb4 D5 F5\",\n", " \"D5 F#5 A5\",\n", " \"D#5 E#5 G#5\",\n", " \"D5 E5 G5\",\n", " \"C#5 D5\",\n", " \"B4 C#5 E5\",\n", " \"Bb4 B4 F5\",\n", " \"Ab4 Bb4\",\n", " \"F4 G4\",\n", " \"Eb4 F4\",\n", "]\n", "\n", "pianoDurs = [1, 1, 1, 0.5, \n", " 0.75, 0.5, 0.5, 0.5, \n", " 0.5, 0.75, 0.75, 0.75, \n", " 0.25, 0.5, 0.75, 1, \n", " 2]\n", "\n", "celloPitches = [\"C4\", \"E4\", \"D4\", \"F#4\", \"Bb3\"]\n", "celloA = [2, 1.5, 2] \n", "celloB = [2, 0.5, 0.5, 1.5, 0.5, 0.5]\n", "celloDurs = celloA + celloB + celloB[::-1]" ] }, { "cell_type": "code", "execution_count": 5, "id": "9515636f-981d-4530-a8d8-52de43c655c2", "metadata": {}, "outputs": [], "source": [ "def makeIsorhythm(cls, color, talea, maxdur):\n", " totaldur = 0\n", " for n, dur in zip(cycle(color), cycle(talea)):\n", " totaldur += dur\n", " if totaldur > maxdur:\n", " break\n", " yield cls(n, dur=dur)" ] }, { "cell_type": "markdown", "id": "b474d8b4-657d-41f4-8f83-77023f973bf7", "metadata": {}, "source": [ "For each voice we create an isorhythm. The duration of the glissandi in the cello seem to be related to readability, starting always in a quarter note if they are longer than an 1/8 note. " ] }, { "cell_type": "code", "execution_count": 6, "id": "50128b12-d4ed-468b-8822-71bf3e46e5ce", "metadata": {}, "outputs": [], "source": [ "# Total duration of the score. This sets how much music we are going to generate\n", "\n", "maxdur = 80\n", "\n", "# Piano\n", "leftchords = list(makeIsorhythm(Chord, lowerChords, pianoDurs, maxdur))\n", "rightchords = list(makeIsorhythm(Chord, upperChords, pianoDurs, maxdur))\n", "\n", "# Cello\n", "cellonotes0 = makeIsorhythm(Note, celloPitches, celloDurs, maxdur-4)\n", "cellonotes0 = Chain(cellonotes0).timeShift(5.5).setPlay(transpose=24)\n", "cellonotes0.stack()\n", "\n", "# Add cello glissandi between F# and Bb\n", "cellonotes = []\n", "for n in cellonotes0:\n", " if n.pitch == n2m(\"4F#\"):\n", " if n.dur <= 0.75:\n", " cellonotes.append(n.clone(gliss=True))\n", " else:\n", " glissdur = 1 if n.end % 1 == 0 else 1.5 if n.dur > 1.5 else 0.5\n", " n0 = n.clone(dur=n.dur-glissdur, tied=True)\n", " n1 = n.clone(dur=glissdur, offset=n0.end, gliss=True)\n", " cellonotes.extend([n0, n1])\n", " else:\n", " cellonotes.append(n)\n", " \n", "# Display the cello notes as artificial 4th harmonics\n", "for note in cellonotes:\n", " note.addSymbol(symbols.Harmonic('artificial', 5))\n" ] }, { "cell_type": "code", "execution_count": 8, "id": "1fa4d194-60b2-4092-9c94-5f9b79629815", "metadata": {}, "outputs": [ { "data": { "text/html": [ "Score(3 voices)
\n", " " ], "text/plain": [ "Score(3 voices)" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Create voices, add offset\n", "lefthand = Voice(leftchords, 'L', clef='bass').timeShift(2)\n", "righthand = Voice(rightchords, 'R', clef='treble').timeShift(2)\n", "cello = Voice(cellonotes, name='cello', clef='treble')\n", "\n", "lefthand.setConfig('quant.breakSyncopationsLevel', 'all')\n", "righthand.setConfig('quant.breakSyncopationsLevel', 'all')\n", "\n", "# Score, from high to low\n", "sco = Score([cello, righthand, lefthand])\n", "# cfg['show.respellPitches'] = False\n", "sco" ] }, { "cell_type": "code", "execution_count": 8, "id": "09fe3b63-6b6c-4bbf-8c30-f9d9f9136b44", "metadata": {}, "outputs": [], "source": [ "# Make the highest note of the piano somewhat louder than the rest\n", "for ch in righthand:\n", " ch[-1].amp = 0.4\n", " for n in ch[:-1]:\n", " n.amp = 0.15\n", " \n", "for ch in lefthand:\n", " ch.amp = 0.15" ] }, { "cell_type": "code", "execution_count": 9, "id": "014189d3-8077-4f92-aaae-cedb206f0eac", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
Preset: piano  \n",
       "    routing=True, properties={'sfpath': '/home/em/Lib/snd/sf2/Piano--grand-piano-YDP.sf2'}\n",
       "  init: iSfTable_ sfloadonce \"/home/em/Lib/snd/sf2/Piano--grand-piano-YDP.sf2\"\n",
       "\n",
       "  ipresetidx sfPresetIndex \"/home/em/Lib/snd/sf2/Piano--grand-piano-YDP.sf2\", 0, 0\n",
       "  inote0_ = round(p(idataidx_ + 1))\n",
       "  ivel_ = p(idataidx_ + 2) * 127\n",
       "  aout1, aout2 sfplay ivel_, inote0_, kamp/16384, mtof:k(kpitch), ipresetidx, 1\n",
       "\n",
       "  epilogue:\n",
       "    turnoffWhenSilent aout1
" ], "text/plain": [ "Preset: piano \n", " routing=True, properties={'sfpath': '/home/em/Lib/snd/sf2/Piano--grand-piano-YDP.sf2'}\n", " init: iSfTable_ sfloadonce \"/home/em/Lib/snd/sf2/Piano--grand-piano-YDP.sf2\"\n", "\n", " ipresetidx sfPresetIndex \"/home/em/Lib/snd/sf2/Piano--grand-piano-YDP.sf2\", 0, 0\n", " inote0_ = round(p(idataidx_ + 1))\n", " ivel_ = p(idataidx_ + 2) * 127\n", " aout1, aout2 sfplay ivel_, inote0_, kamp/16384, mtof:k(kpitch), ipresetidx, 1\n", "\n", " epilogue:\n", " turnoffWhenSilent aout1" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Define instrument presets \n", "\n", "# cello harmonics\n", "defPreset('flageolet-vibr', r'''\n", " |imaxvibfreq=5.6, itransp=24, ivibdepth=0.35|\n", " kt timeinsts\n", " kvibfreq = bpf:k(kt, 0, 0, 0.4, imaxvibfreq)\n", " kvibsteps = bpf:k(kt, 0, 0, 1.1, ivibdepth)\n", " kvibr0 = oscili:k(1, kvibfreq) * kvibsteps - kvibsteps * 0.8\n", " kvibr = lag:k(1 - changed(kpitch), 0.2) * kvibr0\n", " kpitch2 = kpitch + kvibr + itransp\n", " kpitch2 = lag:k(kpitch2, 0.8)\n", " kfreq = mtof:k(kpitch2)\n", " aout1 = oscili:a(kamp, a(kfreq))\n", " aout1 += oscili:a(kamp*0.08, kfreq*2)\n", " aout1 += oscili:a(kamp*0.01, kfreq*3)\n", " ''', \n", ")\n", "\n", "# Use a soundfont as instrument preset for the piano part\n", "# sf2 from http://freepats.zenvoid.org/Piano/YDP-GrandPiano/grand-piano-YDP-20160804.tar.bz2\n", "defPresetSoundfont('piano', '/home/em/Lib/snd/sf2/Piano--grand-piano-YDP.sf2')\n" ] }, { "cell_type": "code", "execution_count": 10, "id": "2b30c07c-0df3-48ad-9e41-cf55d44c1170", "metadata": {}, "outputs": [ { "data": { "text/html": [ "OfflineRenderer(outfile=\"/home/em/.local/share/maelzel/recordings/rec-2023-03-27T14:03:01.622.wav\", 2 channels, 98.80 secs, 44100 Hz)
\n", "
\n", " \n", " " ], "text/plain": [ "OfflineRenderer(sr=44100)" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Set playback options\n", "\n", "maingain = 0.8\n", "lefthand.setPlay(instr='piano', gain=maingain, position=0.25, fade=(0.01, 0.2), sustain=0.2)\n", "righthand.setPlay(instr='piano', gain=maingain, position=0.25, fade=(0.01, 0.2), sustain=0.2)\n", "cello.setPlay(instr='flageolet-vibr', gain=maingain*0.15, fade=(0.25, 0.1), position=0.75)\n", "sco.rec()\n" ] }, { "cell_type": "markdown", "id": "2ff2d3e6-924d-4ddb-b706-b4d9a7baf5a6", "metadata": { "tags": [] }, "source": [ "## Global Effects\n", "\n", "Create a reverb, scheduled at a higher priority" ] }, { "cell_type": "code", "execution_count": 12, "id": "7e236c55-249b-48fb-b913-c8a2844ce1b1", "metadata": {}, "outputs": [ { "data": { "text/html": [ "OfflineRenderer(outfile=\"/home/em/dev/python/maelzel/docs/tmp/messiaen.wav\", 2 channels, 101.40 secs, 44100 Hz)
\n", "
\n", " \n", " " ], "text/plain": [ "OfflineRenderer(sr=44100)" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "s = playSession()\n", "s.defInstr('reverb', r'''\n", " |kfeedback=0.6|\n", " amon1, amon2 monitor\n", " a1, a2 reverbsc amon1, amon2, kfeedback, 12000, sr, 0.3\n", " outch 1, a1-amon1, 2, a2-amon2\n", "''')\n", "\n", "endtime = sco.durSecs()\n", "with render(\"tmp/messiaen.wav\") as r:\n", " s.sched('reverb', dur=float(endtime)+3, priority=2, kfeedback=0.6)\n", " sco.play()\n", "r" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.7" } }, "nbformat": 4, "nbformat_minor": 5 }