{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Voice leading in C major triads and their permutations\n",
"\n",
"*Understanding efficient leading voices between triad chords is only possible if you account for chord permutation. Victor E. Bazterra*\n",
"\n",
"## Introduction\n",
"\n",
"This notebook is a demonstration of Orbichord, a project to explore topologically non-trivial space of music chords that are global-quotient orbifolds, see references:\n",
"\n",
"* Project page: https://orbichord.github.io\n",
"* Project Github: https://github.com/orbichord/orbichord\n",
"* Tymoczko, Dmitri. \"The geometry of musical chords.\" Science 313.5783 (2006): 72-74.\n",
"* Callender, Clifton, Ian Quinn, and Dmitri Tymoczko. \"Generalized voice-leading spaces.\" Science 320.5874 (2008): 346-348.\n",
"* Dmitri Tymoczko, A Geometry of Music: Harmony and Counterpoint in the Extended Common Practice, Oxford University Press, 2011.\n",
"\n",
"Orbichord comes from combining the words orbifold and chord. It is a collection of python modules build on top of [music21 project](https://web.mit.edu/music21).\n",
"\n",
"## Importing modules\n",
"\n",
"Import music and graphite modules"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"data": {
"application/javascript": [
"\n",
"(function(root) {\n",
" function now() {\n",
" return new Date();\n",
" }\n",
"\n",
" var force = true;\n",
"\n",
" if (typeof root._bokeh_onload_callbacks === \"undefined\" || force === true) {\n",
" root._bokeh_onload_callbacks = [];\n",
" root._bokeh_is_loading = undefined;\n",
" }\n",
"\n",
" var JS_MIME_TYPE = 'application/javascript';\n",
" var HTML_MIME_TYPE = 'text/html';\n",
" var EXEC_MIME_TYPE = 'application/vnd.bokehjs_exec.v0+json';\n",
" var CLASS_NAME = 'output_bokeh rendered_html';\n",
"\n",
" /**\n",
" * Render data to the DOM node\n",
" */\n",
" function render(props, node) {\n",
" var script = document.createElement(\"script\");\n",
" node.appendChild(script);\n",
" }\n",
"\n",
" /**\n",
" * Handle when an output is cleared or removed\n",
" */\n",
" function handleClearOutput(event, handle) {\n",
" var cell = handle.cell;\n",
"\n",
" var id = cell.output_area._bokeh_element_id;\n",
" var server_id = cell.output_area._bokeh_server_id;\n",
" // Clean up Bokeh references\n",
" if (id != null && id in Bokeh.index) {\n",
" Bokeh.index[id].model.document.clear();\n",
" delete Bokeh.index[id];\n",
" }\n",
"\n",
" if (server_id !== undefined) {\n",
" // Clean up Bokeh references\n",
" var cmd = \"from bokeh.io.state import curstate; print(curstate().uuid_to_server['\" + server_id + \"'].get_sessions()[0].document.roots[0]._id)\";\n",
" cell.notebook.kernel.execute(cmd, {\n",
" iopub: {\n",
" output: function(msg) {\n",
" var id = msg.content.text.trim();\n",
" if (id in Bokeh.index) {\n",
" Bokeh.index[id].model.document.clear();\n",
" delete Bokeh.index[id];\n",
" }\n",
" }\n",
" }\n",
" });\n",
" // Destroy server and session\n",
" var cmd = \"import bokeh.io.notebook as ion; ion.destroy_server('\" + server_id + \"')\";\n",
" cell.notebook.kernel.execute(cmd);\n",
" }\n",
" }\n",
"\n",
" /**\n",
" * Handle when a new output is added\n",
" */\n",
" function handleAddOutput(event, handle) {\n",
" var output_area = handle.output_area;\n",
" var output = handle.output;\n",
"\n",
" // limit handleAddOutput to display_data with EXEC_MIME_TYPE content only\n",
" if ((output.output_type != \"display_data\") || (!output.data.hasOwnProperty(EXEC_MIME_TYPE))) {\n",
" return\n",
" }\n",
"\n",
" var toinsert = output_area.element.find(\".\" + CLASS_NAME.split(' ')[0]);\n",
"\n",
" if (output.metadata[EXEC_MIME_TYPE][\"id\"] !== undefined) {\n",
" toinsert[toinsert.length - 1].firstChild.textContent = output.data[JS_MIME_TYPE];\n",
" // store reference to embed id on output_area\n",
" output_area._bokeh_element_id = output.metadata[EXEC_MIME_TYPE][\"id\"];\n",
" }\n",
" if (output.metadata[EXEC_MIME_TYPE][\"server_id\"] !== undefined) {\n",
" var bk_div = document.createElement(\"div\");\n",
" bk_div.innerHTML = output.data[HTML_MIME_TYPE];\n",
" var script_attrs = bk_div.children[0].attributes;\n",
" for (var i = 0; i < script_attrs.length; i++) {\n",
" toinsert[toinsert.length - 1].firstChild.setAttribute(script_attrs[i].name, script_attrs[i].value);\n",
" }\n",
" // store reference to server id on output_area\n",
" output_area._bokeh_server_id = output.metadata[EXEC_MIME_TYPE][\"server_id\"];\n",
" }\n",
" }\n",
"\n",
" function register_renderer(events, OutputArea) {\n",
"\n",
" function append_mime(data, metadata, element) {\n",
" // create a DOM node to render to\n",
" var toinsert = this.create_output_subarea(\n",
" metadata,\n",
" CLASS_NAME,\n",
" EXEC_MIME_TYPE\n",
" );\n",
" this.keyboard_manager.register_events(toinsert);\n",
" // Render to node\n",
" var props = {data: data, metadata: metadata[EXEC_MIME_TYPE]};\n",
" render(props, toinsert[toinsert.length - 1]);\n",
" element.append(toinsert);\n",
" return toinsert\n",
" }\n",
"\n",
" /* Handle when an output is cleared or removed */\n",
" events.on('clear_output.CodeCell', handleClearOutput);\n",
" events.on('delete.Cell', handleClearOutput);\n",
"\n",
" /* Handle when a new output is added */\n",
" events.on('output_added.OutputArea', handleAddOutput);\n",
"\n",
" /**\n",
" * Register the mime type and append_mime function with output_area\n",
" */\n",
" OutputArea.prototype.register_mime_type(EXEC_MIME_TYPE, append_mime, {\n",
" /* Is output safe? */\n",
" safe: true,\n",
" /* Index of renderer in `output_area.display_order` */\n",
" index: 0\n",
" });\n",
" }\n",
"\n",
" // register the mime type if in Jupyter Notebook environment and previously unregistered\n",
" if (root.Jupyter !== undefined) {\n",
" var events = require('base/js/events');\n",
" var OutputArea = require('notebook/js/outputarea').OutputArea;\n",
"\n",
" if (OutputArea.prototype.mime_types().indexOf(EXEC_MIME_TYPE) == -1) {\n",
" register_renderer(events, OutputArea);\n",
" }\n",
" }\n",
"\n",
" \n",
" if (typeof (root._bokeh_timeout) === \"undefined\" || force === true) {\n",
" root._bokeh_timeout = Date.now() + 5000;\n",
" root._bokeh_failed_load = false;\n",
" }\n",
"\n",
" var NB_LOAD_WARNING = {'data': {'text/html':\n",
" \"
\\n\"+\n",
" \"
\\n\"+\n",
" \"BokehJS does not appear to have successfully loaded. If loading BokehJS from CDN, this \\n\"+\n",
" \"may be due to a slow or bad network connection. Possible fixes:\\n\"+\n",
" \"
\\n\"+\n",
" \"
\\n\"+\n",
" \"
re-rerun `output_notebook()` to attempt to load from CDN again, or
\"}};\n",
"\n",
" function display_loaded() {\n",
" var el = document.getElementById(null);\n",
" if (el != null) {\n",
" el.textContent = \"BokehJS is loading...\";\n",
" }\n",
" if (root.Bokeh !== undefined) {\n",
" if (el != null) {\n",
" el.textContent = \"BokehJS \" + root.Bokeh.version + \" successfully loaded.\";\n",
" }\n",
" } else if (Date.now() < root._bokeh_timeout) {\n",
" setTimeout(display_loaded, 100)\n",
" }\n",
" }\n",
"\n",
"\n",
" function run_callbacks() {\n",
" try {\n",
" root._bokeh_onload_callbacks.forEach(function(callback) {\n",
" if (callback != null)\n",
" callback();\n",
" });\n",
" } finally {\n",
" delete root._bokeh_onload_callbacks\n",
" }\n",
" console.debug(\"Bokeh: all callbacks have finished\");\n",
" }\n",
"\n",
" function load_libs(css_urls, js_urls, callback) {\n",
" if (css_urls == null) css_urls = [];\n",
" if (js_urls == null) js_urls = [];\n",
"\n",
" root._bokeh_onload_callbacks.push(callback);\n",
" if (root._bokeh_is_loading > 0) {\n",
" console.debug(\"Bokeh: BokehJS is being loaded, scheduling callback at\", now());\n",
" return null;\n",
" }\n",
" if (js_urls == null || js_urls.length === 0) {\n",
" run_callbacks();\n",
" return null;\n",
" }\n",
" console.debug(\"Bokeh: BokehJS not loaded, scheduling load and callback at\", now());\n",
" root._bokeh_is_loading = css_urls.length + js_urls.length;\n",
"\n",
" function on_load() {\n",
" root._bokeh_is_loading--;\n",
" if (root._bokeh_is_loading === 0) {\n",
" console.debug(\"Bokeh: all BokehJS libraries/stylesheets loaded\");\n",
" run_callbacks()\n",
" }\n",
" }\n",
"\n",
" function on_error() {\n",
" console.error(\"failed to load \" + url);\n",
" }\n",
"\n",
" for (var i = 0; i < css_urls.length; i++) {\n",
" var url = css_urls[i];\n",
" const element = document.createElement(\"link\");\n",
" element.onload = on_load;\n",
" element.onerror = on_error;\n",
" element.rel = \"stylesheet\";\n",
" element.type = \"text/css\";\n",
" element.href = url;\n",
" console.debug(\"Bokeh: injecting link tag for BokehJS stylesheet: \", url);\n",
" document.body.appendChild(element);\n",
" }\n",
"\n",
" for (var i = 0; i < js_urls.length; i++) {\n",
" var url = js_urls[i];\n",
" var element = document.createElement('script');\n",
" element.onload = on_load;\n",
" element.onerror = on_error;\n",
" element.async = false;\n",
" element.src = url;\n",
" console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n",
" document.head.appendChild(element);\n",
" }\n",
" };\n",
"\n",
" function inject_raw_css(css) {\n",
" const element = document.createElement(\"style\");\n",
" element.appendChild(document.createTextNode(css));\n",
" document.body.appendChild(element);\n",
" }\n",
"\n",
" \n",
" var js_urls = [];\n",
" var css_urls = [];\n",
" \n",
"\n",
" var inline_js = [\n",
" function(Bokeh) {\n",
" /* BEGIN bokeh.min.js */\n",
" /*!\n",
" * Copyright (c) 2012 - 2019, Anaconda, Inc., and Bokeh Contributors\n",
" * All rights reserved.\n",
" * \n",
" * Redistribution and use in source and binary forms, with or without modification,\n",
" * are permitted provided that the following conditions are met:\n",
" * \n",
" * Redistributions of source code must retain the above copyright notice,\n",
" * this list of conditions and the following disclaimer.\n",
" * \n",
" * Redistributions in binary form must reproduce the above copyright notice,\n",
" * this list of conditions and the following disclaimer in the documentation\n",
" * and/or other materials provided with the distribution.\n",
" * \n",
" * Neither the name of Anaconda nor the names of any contributors\n",
" * may be used to endorse or promote products derived from this software\n",
" * without specific prior written permission.\n",
" * \n",
" * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n",
" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n",
" * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n",
" * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE\n",
" * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n",
" * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n",
" * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n",
" * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n",
" * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n",
" * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF\n",
" * THE POSSIBILITY OF SUCH DAMAGE.\n",
" */\n",
" (function(root, factory) {\n",
" root[\"Bokeh\"] = factory();\n",
" })(this, function() {\n",
" var define;\n",
" var parent_require = typeof require === \"function\" && require\n",
" return (function(modules, entry, aliases, externals) {\n",
" if (aliases === undefined) aliases = {};\n",
" if (externals === undefined) externals = {};\n",
"\n",
" var cache = {};\n",
"\n",
" var normalize = function(name) {\n",
" if (typeof name === \"number\")\n",
" return name;\n",
"\n",
" if (name === \"bokehjs\")\n",
" return entry;\n",
"\n",
" var prefix = \"@bokehjs/\"\n",
" if (name.slice(0, prefix.length) === prefix)\n",
" name = name.slice(prefix.length)\n",
"\n",
" var alias = aliases[name]\n",
" if (alias != null)\n",
" return alias;\n",
"\n",
" var trailing = name.length > 0 && name[name.lenght-1] === \"/\";\n",
" var index = aliases[name + (trailing ? \"\" : \"/\") + \"index\"];\n",
" if (index != null)\n",
" return index;\n",
"\n",
" return name;\n",
" }\n",
"\n",
" var require = function(name) {\n",
" var mod = cache[name];\n",
" if (!mod) {\n",
" var id = normalize(name);\n",
"\n",
" mod = cache[id];\n",
" if (!mod) {\n",
" if (!modules[id]) {\n",
" if (parent_require && externals[id]) {\n",
" try {\n",
" mod = {exports: parent_require(id)};\n",
" cache[id] = cache[name] = mod;\n",
" return mod.exports;\n",
" } catch (e) {}\n",
" }\n",
"\n",
" var err = new Error(\"Cannot find module '\" + name + \"'\");\n",
" err.code = 'MODULE_NOT_FOUND';\n",
" throw err;\n",
" }\n",
"\n",
" mod = {exports: {}};\n",
" cache[id] = cache[name] = mod;\n",
" modules[id].call(mod.exports, require, mod, mod.exports);\n",
" } else\n",
" cache[name] = mod;\n",
" }\n",
"\n",
" return mod.exports;\n",
" }\n",
"\n",
" var main = require(entry);\n",
" main.require = require;\n",
"\n",
" main.register_plugin = function(plugin_modules, plugin_entry, plugin_aliases, plugin_externals) {\n",
" if (plugin_aliases === undefined) plugin_aliases = {};\n",
" if (plugin_externals === undefined) plugin_externals = {};\n",
"\n",
" for (var name in plugin_modules) {\n",
" modules[name] = plugin_modules[name];\n",
" }\n",
"\n",
" for (var name in plugin_aliases) {\n",
" aliases[name] = plugin_aliases[name];\n",
" }\n",
"\n",
" for (var name in plugin_externals) {\n",
" externals[name] = plugin_externals[name];\n",
" }\n",
"\n",
" var plugin = require(plugin_entry);\n",
"\n",
" for (var name in plugin) {\n",
" main[name] = plugin[name];\n",
" }\n",
"\n",
" return plugin;\n",
" }\n",
"\n",
" return main;\n",
" })\n",
" ([\n",
" function _(n,o,r){n(1),function(n){for(var o in n)r.hasOwnProperty(o)||(r[o]=n[o])}(n(102))},\n",
" function _(n,c,f){n(2),n(11),n(14),n(21),n(49),n(52),n(87),n(94),n(100)},\n",
" function _(e,n,a){e(3)()||Object.defineProperty(Object,\"assign\",{value:e(4),configurable:!0,enumerable:!1,writable:!0})},\n",
" function _(r,t,o){t.exports=function(){var r,t=Object.assign;return\"function\"==typeof t&&(t(r={foo:\"raz\"},{bar:\"dwa\"},{trzy:\"trzy\"}),r.foo+r.bar+r.trzy===\"razdwatrzy\")}},\n",
" function _(t,r,n){var o=t(5),c=t(10),a=Math.max;r.exports=function(t,r){var n,f,h,i=a(arguments.length,2);for(t=Object(c(t)),h=function(o){try{t[o]=r[o]}catch(t){n||(n=t)}},f=1;f= 0\");if(!isFinite(r))throw new RangeError(\"Count must be < ∞\");for(n=\"\";r;)r%2&&(n+=t),r>1&&(t+=t),r>>=1;return n}},\n",
" function _(t,i,n){var r=t(18),a=Math.abs,o=Math.floor;i.exports=function(t){return isNaN(t)?0:0!==(t=Number(t))&&isFinite(t)?r(t)*o(a(t)):t}},\n",
" function _(n,t,i){t.exports=n(19)()?Math.sign:n(20)},\n",
" function _(n,t,o){t.exports=function(){var n=Math.sign;return\"function\"==typeof n&&(1===n(10)&&-1===n(-20))}},\n",
" function _(n,r,t){r.exports=function(n){return n=Number(n),isNaN(n)||0===n?n:n>0?1:-1}},\n",
" function _(e,r,a){e(22)()||Object.defineProperty(Array,\"from\",{value:e(23),configurable:!0,enumerable:!1,writable:!0})},\n",
" function _(n,o,r){o.exports=function(){var n,o,r=Array.from;return\"function\"==typeof r&&(o=r(n=[\"raz\",\"dwa\"]),Boolean(o&&o!==n&&\"dwa\"===o[1]))}},\n",
" function _(e,l,r){var n=e(24).iterator,t=e(44),a=e(45),i=e(46),u=e(47),o=e(10),f=e(8),c=e(48),v=Array.isArray,h=Function.prototype.call,y={configurable:!0,enumerable:!0,writable:!0,value:null},s=Object.defineProperty;l.exports=function(e){var l,r,A,g,p,w,b,d,x,j,O=arguments[1],m=arguments[2];if(e=Object(o(e)),f(O)&&u(O),this&&this!==Array&&a(this))l=this;else{if(!O){if(t(e))return 1!==(p=e.length)?Array.apply(null,e):((g=new Array(1))[0]=e[0],g);if(v(e)){for(g=new Array(p=e.length),r=0;r
\"],_default:[0,\"\",\"\"]};function ve(e,t){var n;return n=void 0!==e.getElementsByTagName?e.getElementsByTagName(t||\"*\"):void 0!==e.querySelectorAll?e.querySelectorAll(t||\"*\"):[],void 0===t||t&&N(e,t)?b.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n-1)i&&i.push(o);else if(l=ie(o),a=ve(f.appendChild(o),\"script\"),l&&ye(a),n)for(c=0;o=a[c++];)he.test(o.type||\"\")&&n.push(o);return f}me=r.createDocumentFragment().appendChild(r.createElement(\"div\")),(xe=r.createElement(\"input\")).setAttribute(\"type\",\"radio\"),xe.setAttribute(\"checked\",\"checked\"),xe.setAttribute(\"name\",\"t\"),me.appendChild(xe),h.checkClone=me.cloneNode(!0).cloneNode(!0).lastChild.checked,me.innerHTML=\"\",h.noCloneChecked=!!me.cloneNode(!0).lastChild.defaultValue;var Te=/^key/,Ce=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,Ee=/^([^.]*)(?:\\.(.+)|)/;function ke(){return!0}function Se(){return!1}function Ne(e,t){return e===function(){try{return r.activeElement}catch(e){}}()==(\"focus\"===t)}function Ae(e,t,n,r,i,o){var a,s;if(\"object\"==typeof t){for(s in\"string\"!=typeof n&&(r=r||n,n=void 0),t)Ae(e,s,n,r,t[s],o);return e}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&(\"string\"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),!1===i)i=Se;else if(!i)return e;return 1===o&&(a=i,(i=function(e){return b().off(e),a.apply(this,arguments)}).guid=a.guid||(a.guid=b.guid++)),e.each(function(){b.event.add(this,t,i,r,n)})}function De(e,t,n){n?(Y.set(e,t,!1),b.event.add(e,t,{namespace:!1,handler:function(e){var r,i,a=Y.get(this,t);if(1&e.isTrigger&&this[t]){if(a.length)(b.event.special[t]||{}).delegateType&&e.stopPropagation();else if(a=o.call(arguments),Y.set(this,t,a),r=n(this,t),this[t](),a!==(i=Y.get(this,t))||r?Y.set(this,t,!1):i={},a!==i)return e.stopImmediatePropagation(),e.preventDefault(),i.value}else a.length&&(Y.set(this,t,{value:b.event.trigger(b.extend(a[0],b.Event.prototype),a.slice(1),this)}),e.stopImmediatePropagation())}})):void 0===Y.get(e,t)&&b.event.add(e,t,ke)}b.event={global:{},add:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=Y.get(e);if(v)for(n.handler&&(n=(o=n).handler,i=o.selector),i&&b.find.matchesSelector(re,i),n.guid||(n.guid=b.guid++),(u=v.events)||(u=v.events={}),(a=v.handle)||(a=v.handle=function(t){return void 0!==b&&b.event.triggered!==t.type?b.event.dispatch.apply(e,arguments):void 0}),l=(t=(t||\"\").match(P)||[\"\"]).length;l--;)d=g=(s=Ee.exec(t[l])||[])[1],h=(s[2]||\"\").split(\".\").sort(),d&&(f=b.event.special[d]||{},d=(i?f.delegateType:f.bindType)||d,f=b.event.special[d]||{},c=b.extend({type:d,origType:g,data:r,handler:n,guid:n.guid,selector:i,needsContext:i&&b.expr.match.needsContext.test(i),namespace:h.join(\".\")},o),(p=u[d])||((p=u[d]=[]).delegateCount=0,f.setup&&!1!==f.setup.call(e,r,h,a)||e.addEventListener&&e.addEventListener(d,a)),f.add&&(f.add.call(e,c),c.handler.guid||(c.handler.guid=n.guid)),i?p.splice(p.delegateCount++,0,c):p.push(c),b.event.global[d]=!0)},remove:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=Y.hasData(e)&&Y.get(e);if(v&&(u=v.events)){for(l=(t=(t||\"\").match(P)||[\"\"]).length;l--;)if(d=g=(s=Ee.exec(t[l])||[])[1],h=(s[2]||\"\").split(\".\").sort(),d){for(f=b.event.special[d]||{},p=u[d=(r?f.delegateType:f.bindType)||d]||[],s=s[2]&&new RegExp(\"(^|\\\\.)\"+h.join(\"\\\\.(?:.*\\\\.|)\")+\"(\\\\.|$)\"),a=o=p.length;o--;)c=p[o],!i&&g!==c.origType||n&&n.guid!==c.guid||s&&!s.test(c.namespace)||r&&r!==c.selector&&(\"**\"!==r||!c.selector)||(p.splice(o,1),c.selector&&p.delegateCount--,f.remove&&f.remove.call(e,c));a&&!p.length&&(f.teardown&&!1!==f.teardown.call(e,h,v.handle)||b.removeEvent(e,d,v.handle),delete u[d])}else for(d in u)b.event.remove(e,d+t[l],n,r,!0);b.isEmptyObject(u)&&Y.remove(e,\"handle events\")}},dispatch:function(e){var t,n,r,i,o,a,s=b.event.fix(e),u=new Array(arguments.length),l=(Y.get(this,\"events\")||{})[s.type]||[],c=b.event.special[s.type]||{};for(u[0]=s,t=1;t=1))for(;l!==this;l=l.parentNode||this)if(1===l.nodeType&&(\"click\"!==e.type||!0!==l.disabled)){for(o=[],a={},n=0;n-1:b.find(i,this,null,[l]).length),a[i]&&o.push(r);o.length&&s.push({elem:l,handlers:o})}return l=this,u\\x20\\t\\r\\n\\f]*)[^>]*)\\/>/gi,qe=/"
],
"text/plain": [
":Chord [source,target] (value)"
]
},
"execution_count": 5,
"metadata": {
"application/vnd.holoviews_exec.v0+json": {
"id": "1004"
}
},
"output_type": "execute_result"
}
],
"source": [
"edges, vertices = convertGraphToData(graph)\n",
"links = pd.DataFrame(edges)\n",
"nodes = hv.Dataset(pd.DataFrame(vertices), 'index')\n",
"chord = hv.Chord((links, nodes))\n",
"chord.opts(\n",
" opts.Chord(\n",
" cmap='Category20',\n",
" edge_cmap='Category20',\n",
" edge_color=dim('source').str(), \n",
" labels='name', node_color=dim('index').str()\n",
" )\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This rather complicated looking graph shows the efficient voice leading connection between chords and their permutations."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Graph decomposition into connected components\n",
"\n",
"Hidden in the previous graph is the fact that it is made of two connected components. Using networkx module, we can find out and plot those components. We will call these components the good and evil twins."
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.holoviews_exec.v0+json": "",
"text/html": [
"
\n",
"\n",
"\n",
"\n",
"\n",
"\n",
" \n",
"
\n",
""
],
"text/plain": [
":Chord [source,target] (value)"
]
},
"execution_count": 6,
"metadata": {
"application/vnd.holoviews_exec.v0+json": {
"id": "1186"
}
},
"output_type": "execute_result"
}
],
"source": [
"# Graph decomposition into its connected components\n",
"good_twin, evil_twin = (graph.subgraph(c) for c in connected_components(graph))\n",
"\n",
"# Preparing data for the good\n",
"good_edges, good_vertices = convertGraphToData(good_twin)\n",
"good_links = pd.DataFrame(good_edges)\n",
"good_nodes = hv.Dataset(pd.DataFrame(good_vertices), 'index')\n",
"good_chord = hv.Chord((good_links, good_nodes))\n",
"\n",
"# Plot both components side by side\n",
"good_chord.opts(\n",
" opts.Chord(\n",
" cmap='Category20',\n",
" edge_cmap='Category20',\n",
" edge_color=dim('source').str(), \n",
" labels='name', node_color=dim('index').str()\n",
" )\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.holoviews_exec.v0+json": "",
"text/html": [
"
\n",
"\n",
"\n",
"\n",
"\n",
"\n",
" \n",
"
\n",
""
],
"text/plain": [
":Chord [source,target] (value)"
]
},
"execution_count": 7,
"metadata": {
"application/vnd.holoviews_exec.v0+json": {
"id": "1368"
}
},
"output_type": "execute_result"
}
],
"source": [
"# Preparing data for the evil\n",
"evil_edges, evil_vertices = convertGraphToData(evil_twin)\n",
"evil_links = pd.DataFrame(evil_edges)\n",
"evil_nodes = hv.Dataset(pd.DataFrame(evil_vertices), 'index')\n",
"evil_chord = hv.Chord((evil_links, evil_nodes))\n",
"\n",
"evil_chord.opts(\n",
" opts.Chord(\n",
" cmap='Category20',\n",
" edge_cmap='Category20',\n",
" edge_color=dim('source').str(), \n",
" labels='name', node_color=dim('index').str()\n",
" )\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The good twin contains all the C major triad chords and its inversions (three cyclic permutations). The same for evil twin except each inversion has the last two pitches permutated."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Analyzing the good twin\n",
"\n",
"We can analyze all the C major neighbor chords from the good-twin graph."
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAAAdMAAABdCAIAAABxURvmAAASkUlEQVR4nO2dz2/b9P/H3/uWaau0IruICQbSGmdD4oCgTrUDh+1gF6QdUV0mhHaaUwlOuzj9E5pcKMc4u8Auk9MLh3FJeigSEqC4O4AAsSZdD6XrJc4WpAoVlM/hxd56f500iX++7fT1OFSOm7798stvv/p+v/z0632m1+sRBEEQJEb+j7cBCIIgpw6MvAiCIHGDkRdBECRuMPIiCILEDUZeBEGQuMHIiyAIEjcYeREEQeIGIy+CIEjcYORFEASJG4y8CIIgcRNm5G21Wo7jhNgggiDIRBJa5LVtO5fLaZoWVoMIgiCTSjiR17ZtVVU7nc7m5qZpmqG0iSAIMqmcCaVWWS6X297ehu1MJtNqtYK3iSAIMqmEMOYtFos07BJCdnd3cdiLIAgyhBDGvJIk7e7uEkIEQVhdXSWEWJZl23YI1iEIgkwiQce8tm3TsFuv1wuFAmQeMOGAIAhyEiFEXtgwTTOXyxFC4Ge1Wg3YMoIgyKQSNPI2m01CiKIoVE8miiIhpNFoBGwZQRBkUglHVbayskK3YRSM2QYEQZCTCCfysi9QQMxl1Q4IgiAISwiRVxAE9iPkGVw7EQRBEMpLw39drVbL5fKQLxweHv7777+qqtI933//PSHkzJkz7M5TxfHxcbfbnZmZOXv2LG9bRoCmRkGKTD06Ojo6OpqdneVtyGjSZerU1NR333035Dsj9LyO4wxX5m5vbxcKhVqtBh8fPny4vr5OCFleXtZ13bPJEwH4pFgsyrLM25YRoKlRkCJTK5WKZVn0/k0yqTN1eGgdMeYVRXH40DWXy4GGVxRFx3Hy+TzsNwwD5GWnFlmW0zLqR1OjIBWmQiBLvp0khaYOJ2ieVxTFTCZTr9cJIYVCAd6qUBTllIddBEGQIYwY846DqqqNRsNxnEqlAnvgHWIEQRBkICFE3oWFhbW1NVoT3TCMVMwIEARBeBGCqkxV1d3d3U6nQwiRZRkHvAiCIMMJqio7Pj5+9OjRf2299NLU1FRYy1I8f/78n3/+IYSkQp3D0m63CSGGYSRfAYOmRkGKTN3Z2SEpeWyVOlOHMyLyqqoKdRhO4osvvuh2u7C9urp648aN8e0bSLfbffDgwbfffnv9+vULFy4QQp4+ffrKK6/cunVrZmYmYOPxsL29/ejRo1u3biVfVISmRkGKTK1UKnt7e6mYp6bO1BFf6gXApdhtNptBWuv1eo1GI5PJEEIMw2D312q1paWlgI3HBmhKarUab0NGg6ZGQYpMNQwjYBCIjQkz1X+e1zRNKmbQdZ1qy4KQz+dBl8b+ZzNNU9O0jY0NXOoCQZDJwGfktW27UCjAtq7rEBwDVoY0TRPq7AiCwKY4CoUCPL6zLCtI+wiCIAnBZ+TN5/NUzFAsFgkhCwsLAauh0xc/XG9hwIEQBEEmBj+Rly55KQhCtVqF8Sms+h5k+bWTkhWQNCGELC8v+24cQRAkOYyomDNQVba1tQV6r2w2Ozc3R/f/9NNPs7Ozs7Oz09PT09PTXk3Z3NyEDVEUXQ+Fnz9/Tgh5+eWXvbbJhXa7/ejRo/n5eU+iosPDw263e3R0dHx8TAg5e/bs9PT0xYsXIz1rf6ZyAb0aBTs7O3t7e4qiePorLl71ZyoXwNThodWzquzhw4cQIufn50ulEt354MGDbrfb7XZBTnH16tWPP/7Yk8iMRt65ublUaEdOwquoaGtr6969e0+fPr158+bdu3ehhXv37j1+/Hhvb29+fv7OnTtvvfVWEkzlCHo1CrxKtTh69bSryqiSjIpmhlSD1HV9/JZpMXVFUbxalSg8iYrYKQUry2u329QhgiA0Gg3upvIFvRoFnqRafL162lVlkMmlFfBYbRlIceEnUKlUqARiJCPfThm/qRTBnpQkSXRbFEVacrPT6Qx/kxBxgV6NAvRqiHiumAPP1ujDrrW1NUKILMvValWSJEmSisViLpcrl8uQiyiVSisrK+x1OonFxcWNjQ3CrCTP4jiOP71wtVptNBqtVgtq+oiiKEnS8vJyAutY2rbNWrW4uEjzOYkiRS4l6NVoSItXk4vXgTT8Fcyk4P10QRDa7Tb8dmlpib5+Rv/1uV5IGwLNi9EGKYZhjN8OYFkWDMBp0qNWq9FDKIqShMkmO0BwvacH/9VIkubFvFzq1VT06pj4zjbE79UJyzZ4PhN4tgiREXoYGxANw2CztPBlWZbHbPykt4fL5bKnlHGPa07K940H9x74tt1uw85khrPkJ0/Rq+PgNZxx9OqERV7PqrLffvvtzz//pNqOzc3Ny5cvX7lyBT622+2ff/6ZShoODw9/+eUXuCRDjsJyfHy8t7e3v79/8eJFKFF2dHQ0PT1NDzEmVPrWf3TQfMD2pUuX3n77bU8tj8SfqOiPP/549uzZ0dERXJGpqalz5869+eabr7/+erjmsXgylaNLvZpKQa8OJ4iqDLSeJNmqMo4CuOGh1Y+qbH19/YMPPoDZ0PHx8cHBAZV6HBwc3L59+/PPP6d1xRYXF4mvVSogoUwIuXr1qo8qZT/++ONff/0F2x999BGrdIEFCmH7vffeA3FMiISof/r111/PnTuXEP0TR5d6NZWgV8cjdVKtFAngRnzJx1haEASaDYBaDWtra+xv2XkW4aQS45iTStFkc1KTp+jVMZmwKTwl+TkcP043DIN9qgYnSfOwiqLQQAxdkI3LccIrJ+XpxqP9oP9S0demiUdl9PhMavIUvTomkxp5+XaAqKpEFotFSZJUVQX5Sz6fbzQa2WwWfitJEmgeCCHlclkQBKr1ixlN01qt1t27d69du/bNN99ks9nZ2dl33nnn1VdfvX//fr1eT5pexyWng0RNokidS0l6vGpZlmEYrVZL0zRVVfP5PNT/S6BXq9VqoVAAO1VV1TStUCgEqdkSKQntAP6CervdlmVZluWB8i9IL8BY2LIsf4cIDqrKwjW1h6qyCEzli29tAwrghhBVtgFot9uKokDOlz0By7JkWV5aWiKElMtl3+0HBFVloZuaouRpLz1e5cuEJU9ZEp7D8VOrjGV/f39/f58uxUa5dOnS3Nycj4plYcFRrPP8+fOdnZ0rV654Uq5w0T95MpWv/smfV7non4KYGrP+aX9///DwcHy5CMcO4MlUCpfban9/v91uHx4eDvlO0BUwgW63+/jxY9g+ODhYX1//6quvxjc0CviKdTzBUf/kiRS5NF1w1D95Il0dgO9tNTpsRjHYjqhZT/BN9IwP3xmcJ9Li0nSBHSAKku9V/yHSsixd13Vd719yOAmRt8c10TM+fOUvXkmFS9MFdoAoSL5XfYZI1npBEFzBNyGRFwAJ1MWLF0VRFEXxjTfeuHbt2v3793nb9R9sF3H1WrowXXJuPAD0T8oLoExSQm45F2Dq0tJSYk1NYwdI+D3VS4NX/YRIKtelsAVu4K228Cz0D0cFzPikaAaXLlJx9Xtp6wDo1bAYoW0YSL1ed6mRDcOAFYgJIaZprq2ttVotr80Gp9Vq0eNCfQnY/vrrr+mjzG63e/v2bXhQcOHChWKxCFn2XC43zrPEKABd+u7uLiFE1/VisSiKouM4uVxud3dXURQoeczFNkKIbdvwyowPYvYqNdXr1edoKmGeBRFCbt68eefOnZmZmW63+9lnnz19+rT/CVuKvDpyuYNwYUt4e/VqzKb6HJy6GmGli5D8Def/gkfYKYZXXP8Y4yeZU3iYwaTCq6fHVK+FqjmaGmflADaNkHBTe/7GvIQQTdM2NjZkWRZFcWVlRdM02A/D4WazOc4iFKEDVf1h+8svv/z7779h+/bt26+99hr92pMnTyzLgu133333ww8/JIQktvg/d4rFIs0v/f7771tbW8+ePaN+e/LkydbWFkgXL1++fOPGDdbVMXuVmur16nM0lUy0V1dXV2MbnjuOQzMJxLtX4zSVEL9j3mazKQiC6+3hRqPBljHjS/ITPakj+UodSoquPno1CpLvVf+PwmD9CEEQdF2Hx8ck3nnQSNKigEkLyVfqsKTl6qNXoyD5XvWZbQAcx6HL9uVyuTFXuowZsJDWK0r4woJJRhTFTqcD241Gg3Ug+9BV13XTNOM3byDJv/ro1ShIgVd5hXwkdaRospki0KtRkHyvYuRFPJCWyWa6QK9GQcK9GijbgJxOkj/ZTCPo1ShIrFcx8iIIgsSNn9WAEARBkCBg5EUQBImbEZXRJwPbti3LarVakiQtLi6qqlosFjVNi1kDV6/XXS84LiwsQOKJlxqv3yQXtByHP65fvz4/P3/+/Hl25+LiYoin7OkUNE0zTZO+qsSrY/Qft1qtiqLoKh2gadonn3zyww8/uP48XAey1Ot127ZpjfOAhOvtgRd6uCtarRYVOSQht8sy+WNeWCQ1m82urKwsLCzAx9XVVS41fQgh1Wq1VCrB0RuNRq1Wy+VykiRxXLqVNQmwbbtUKpVKpSDN2rZ9cHBw/vx5V/uWZWWzWbp2dSj0n0K9Xnedgm3brVaLBgJeHcN13LW1NU3TlpeXXWEFrJ2Zmek/uygcCJTLZfYF3CBE5G1PrhBFcXFxsdVqlUol9gtBikCFBkddRQzouu4qHwyrJhN+CxTCAlbs0cEkjurCfpN63hcc7McwDFgCtb99uL1DfINo4CnAe5X99vT4dYz+41I7Xe9/stbG4MAeU/01lIVrI/K2D1dAT2b/pN/b8TPJY17TNCuVSj6fZycjoigGnEGHDpjU6XRo4SG+QKm91dXVgO2YpnnSGcG8L7rRJZyC60JTe3h1jIHHhf39ZfaGeI9E40DTNOF/AK19E7C1eLztwxWWZQXv3gGZ5MgLKR5XKWFCiKqqVGKdEFRVFQRhd3e3P+cAqbeTPkYBeEwURdf6sp7sqVarI5dPja40FJyCJEk0orH28OoYJx1XFEVXkB3HeyRsB5bL5WKxmMlkNjc3hwSycTpA/N7ud4Vt22y5XhZN0wa6LtZ7je+QOzro1Kl/mbher2dZ1sD9MTBwXkz3wxwNJmKGYciyDNvwEo4sy/A1RVEiMgnmbuxv/dmj6/pJk+V2u720tBRudmX4KbD28OoYzWYTjsuW92N/yx6X9V4vFgfWajW4iP2Tdx8dIDpvj+OKdrutKIogCIqiyLIMA3n4EzgF8iLbEP+9RpnYyEufV/A2xM3IyNt7YTztT3AzZDIZ9mMoyTj20LIs0zGI6wte7YF3NPvbhzsBtsMNcMNPgbWHV8cY/7gu7/VicaArVgqCwP6H8NQBIvX2OK6AxC7YBqVrXcGaMHnemO81yiRnG9JOPp+HHBaIjVRVhY/5fJ4QQsdQYVEsFm3b1nU9uD0w2exvH6ZvzWZTVdVsNhuWemnkKQy0JwnYtq3+f2zbPsna6BwIRQfhOoqiqOt6p9OpVquur43ZAWLw9nBXlEolRVHAtlwu5zJmYJ4h5nuNTHCel2r3Bmas2BXbEgLIXLLZbP+v4Fxoj4kuPSqKIls0r16vD/TSSHtqtVp/Xo8iSVKxWJRluVQqnZSJ843rFPrt4dUx6PMlVs8kSdLq6moul9vc3IRtSZKGe49E4EDTNDudzpkXVCoV8iI/O5DhHSBOb/e7An6y0l1PwufY7rWJjbyiKMJkZGCOvFwuc9TPDmR7e5vEvwzfIGjpaNC9e/1zx3Hq9fpInQbcDwHXzjoJtvq1yx5eHUOSJMiEsLES3p6gjzTh6o/jPRKqA8vlsitlnMlktre3fbiCi7cj7UsRMbGRl7zQRfVfD5hbJUTCBcAwTdf1JJSWB7kPeMnHn9N563CGjPGDwyqW+u3h1THAqv7jsnvG9B4Jz4H1el0URdf7XSsrK2TosPckuHibdUW/yCxps1tgkiOvpmm6rlcqFXb66ThOoVDgruZjqdfrhUJBluWTFI7xvG/DHsVxnHw+3+l0Bs62httTLpeXl5eHH8s0zc3NTVmWx4wyQei3h1fHGHhc27ZZPe843iOhOnBgeIVmq9XqwGs9pAPE722XKyBPTRNltm1vbGyM31p877aF/swuaUCBZFmWDcPQdV1RlCieVI5DuVymCtlMJgOru8MGaxJVumQyGVZk1v8xuN6FNQlUOPSRMe0enuxpNpvsc/mB7cPaqYZhDNRXBT8F1pkue1h4dYxyucweV5blRqMBixn2WxupAxuNBr3WmUyGnr5hGFQikslkPv300zE7wPvvvx+dt8d3hUtVBqknOEFXd43zXnNxWurzwpva/bMqJFwcx4FF+Xgb8h8j7eHVMVzHdRwHPibKe15JlLfhWEl4cDKQ0xJ5EQRBksMk53kRBEGSCUZeBEGQuMHIiyAIEjcYeREEQeIGIy+CIEjcYORFEASJG4y8CIIgcYORF0EQJG4w8iIIgsTN/wBrksklGCHjEwAAAABJRU5ErkJggg==\n",
"text/plain": [
""
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# Set a step to bring all pitchs to a treble clef\n",
"interval3 = Interval(3*12)\n",
"interval4 = Interval(4*12)\n",
"# Set the central node\n",
"node = 'C (CEG)'\n",
"\n",
"# Collect its neigbors\n",
"neighbors = [node]\n",
"for neighbor, edge in good_twin.adj[node].items():\n",
" neighbors.append(neighbor)\n",
"\n",
"# Sort chords based on their named pitch \n",
"neighbors.sort(key = lambda name: name[0])\n",
"size = len(neighbors)\n",
"# Cyclic permutations to start for C major cord\n",
"neighbors = [neighbors[(i+2)%size] for i in range(size)]\n",
"\n",
"# Define a stream of chords\n",
"stream = Stream()\n",
"counter = 0\n",
"for neighbor in neighbors:\n",
" if counter in (2, 4, 6):\n",
" chord = node_to_chord[neighbor].transpose(interval3)\n",
" else:\n",
" chord = node_to_chord[neighbor].transpose(interval4)\n",
" chord.addLyric(chordSymbolFigure(chord))\n",
" chord.duration.type = 'whole'\n",
" stream.append(chord)\n",
" counter += 1\n",
"# Render the resulting chord progression\n",
"renderWithLily(stream)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The neighbor chords are some inversion of major triads from the C major scale. Transposing them to the right octave, we can verify they are only one scale step away from each other. However, the sound different than the usual no inverted progression. "
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
" \n",
" "
],
"text/plain": [
""
]
},
"execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# Play the chord progression\n",
"playAudio(stream)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Analyzing the bad twin\n",
"\n",
"We can analyze all the C major neighbor chords from the bad-twin graph."
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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\n",
"text/plain": [
""
]
},
"execution_count": 10,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# Set a step to bring all pitchs to a treble clef\n",
"interval3 = Interval(3*12)\n",
"interval4 = Interval(4*12)\n",
"# Set the central node\n",
"node = 'C (CGE)'\n",
"\n",
"# Collect its neigbors\n",
"neighbors = [node]\n",
"for neighbor, edge in evil_twin.adj[node].items():\n",
" neighbors.append(neighbor)\n",
"\n",
"# Sort chords based on their named pitch \n",
"neighbors.sort(key = lambda name: name[0])\n",
"size = len(neighbors)\n",
"# Cyclic permutations to start for C major cord\n",
"neighbors = [neighbors[(i+2)%size] for i in range(size)]\n",
"\n",
"# Define a stream of chords\n",
"stream = Stream()\n",
"counter = 0\n",
"for neighbor in neighbors:\n",
" if counter in (2, 4, 6):\n",
" chord = node_to_chord[neighbor].transpose(interval3)\n",
" else:\n",
" chord = node_to_chord[neighbor].transpose(interval4)\n",
" chord.addLyric(chordSymbolFigure(chord))\n",
" chord.duration.type = 'whole'\n",
" stream.append(chord)\n",
" counter += 1\n",
"# Render the resulting chord progression\n",
"renderWithLily(stream)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The neighbor chords are permutations of all inversion of major triads from the C major scale. Transposing them to the right octave, we can verify they are only one scale step away from each other.\n",
"\n",
"The main difference with good and evil chords is the fact evil chords tend to spread beyond one octave, making the much harder to play. This could answer the old question of why talk about [triad three inversions and say nothing about its six possible permutations](https://music.stackexchange.com/questions/56404/why-do-we-have-inversions-but-not-permutations/56415)."
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
" \n",
" "
],
"text/plain": [
""
]
},
"execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# Play the chord progression\n",
"playAudio(stream)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Music modes, voice leading and chord inversions\n",
"\n",
"Exploring the neighbors of the G major chord is equivalent to derive the chord progression in G Mixolydian mode."
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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\n",
"text/plain": [
""
]
},
"execution_count": 12,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# Set a step to bring all pitchs to a treble clef\n",
"interval4 = Interval(4*12)\n",
"# Set the central node\n",
"node = 'G (GBD)'\n",
"\n",
"# Collect its neigbors\n",
"neighbors = [node]\n",
"for neighbor, edge in good_twin.adj[node].items():\n",
" neighbors.append(neighbor)\n",
"\n",
"# Sort chords based on their named pitch \n",
"neighbors.sort(key = lambda name: name[0])\n",
"size = len(neighbors)\n",
"# Cyclic permutations to start for C major cord\n",
"neighbors = [neighbors[(i-1)%size] for i in range(size)]\n",
"\n",
"# Define a stream of chords\n",
"stream = Stream()\n",
"for neighbor in neighbors:\n",
" chord = node_to_chord[neighbor].transpose(interval4)\n",
" chord.addLyric(chordSymbolFigure(chord))\n",
" chord.duration.type = 'whole'\n",
" stream.append(chord)\n",
"# Render the resulting chord progression\n",
"renderWithLily(stream)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The resulting chord progression is a cyclic permutation of chords relative to the neighbors of C major chord. However, these chords are not present in the same inversions as C major neighbors. Therefore, efficient voice leading in different music mode implies more than a different starting point to the chord progression, it also means that those chords to be present in different inversions relative to Ionian mode. "
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
" \n",
" "
],
"text/plain": [
""
]
},
"execution_count": 13,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# Play the chord progression\n",
"playAudio(stream)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Ascending chord progression\n",
"\n",
"Chord progression from C major triad generated by our definition of effective voice leading has the problem that does not sound as ascending progression like the original inversion free. In this section, we will try to create ascending chord progression using the neighbors of C major."
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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\n",
"text/plain": [
""
]
},
"execution_count": 14,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# Set a step to bring all pitchs to a treble clef\n",
"interval3 = Interval(3*12)\n",
"interval4 = Interval(4*12)\n",
"# Set the central node\n",
"node = 'C (CEG)'\n",
"\n",
"# Collect its neigbors\n",
"neighbors = [node]\n",
"for neighbor, edge in good_twin.adj[node].items():\n",
" neighbors.append(neighbor)\n",
"\n",
"# Transpose octaves to get chords as close as possible\n",
"counter = 0\n",
"chords = []\n",
"for neighbor in neighbors:\n",
" if counter > 3:\n",
" chord = node_to_chord[neighbor].transpose(interval3)\n",
" else:\n",
" chord = node_to_chord[neighbor].transpose(interval4)\n",
" counter += 1\n",
" chords.append(chord)\n",
"\n",
"# Define a comparison function between chords \n",
"def chordComp(chordA, chordB):\n",
" A = chordA.pitches\n",
" B = chordB.pitches\n",
" if A[0] < B[0]:\n",
" return -1\n",
" elif A[0] > B[0]:\n",
" return +1\n",
" if A[1] < B[1]:\n",
" return -1\n",
" elif A[1] > B[1]:\n",
" return +1\n",
" if A[2] < B[2]:\n",
" return -1\n",
" elif A[2] > B[2]:\n",
" return +1\n",
" return 0\n",
" \n",
"# Sort chords based on their named pitch\n",
"from functools import cmp_to_key \n",
"chords.sort(key = cmp_to_key(chordComp))\n",
"\n",
"# Define a stream of chords\n",
"stream = Stream()\n",
"for chord in chords:\n",
" chord.addLyric(chordSymbolFigure(chord))\n",
" chord.duration.type = 'whole'\n",
" stream.append(chord)\n",
"# Render the resulting chord progression\n",
"renderWithLily(stream)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The resulting chord progression starts from the Bdim chord and then round-robin on each pitch and transposing them one scale step up."
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
" \n",
" "
],
"text/plain": [
""
]
},
"execution_count": 15,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# Play the chord progression\n",
"playAudio(stream)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Conclusion\n",
"\n",
"Major scale triads can be connected by efficient voice leading as define in this notebook. In this sense, triads as very close to each other. The only caveat is that this tight cluster of chords is made of some inversions of the typical triads. These different inversions allow keeping all the chords of the progression just one scale step away from each other in $L_{\\infty}$ norm. \n",
"\n",
"We can quickly generate this chord progression for any significant scale. Start with *vii* chord with its bass note below the bass note of the starting *I* chord for a given scale. Then round robin on each pitch of the initial triad and transpose them one scale up at the time. The final chord of this sequence is the *ii* in where all the pitches are transposed by one scale step up from *I*."
]
}
],
"metadata": {
"kernelspec": {
"display_name": "orbichord",
"language": "python",
"name": "orbichord"
},
"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.8.1"
}
},
"nbformat": 4,
"nbformat_minor": 4
}