diff --git a/PRIMAT/LICENSE b/PRIMAT/LICENSE new file mode 100644 index 0000000..7562485 --- /dev/null +++ b/PRIMAT/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 cemfi + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/PRIMAT/Max4BachNet.maxpat b/PRIMAT/Max4BachNet.maxpat new file mode 100644 index 0000000..e52485d --- /dev/null +++ b/PRIMAT/Max4BachNet.maxpat @@ -0,0 +1,3305 @@ +{ + "patcher" : { + "fileversion" : 1, + "appversion" : { + "major" : 8, + "minor" : 1, + "revision" : 3, + "architecture" : "x64", + "modernui" : 1 + } +, + "classnamespace" : "box", + "rect" : [ 53.0, 81.0, 1470.0, 905.0 ], + "bglocked" : 0, + "openinpresentation" : 0, + "default_fontsize" : 12.0, + "default_fontface" : 0, + "default_fontname" : "Arial", + "gridonopen" : 1, + "gridsize" : [ 15.0, 15.0 ], + "gridsnaponopen" : 1, + "objectsnaponopen" : 1, + "statusbarvisible" : 2, + "toolbarvisible" : 1, + "lefttoolbarpinned" : 0, + "toptoolbarpinned" : 0, + "righttoolbarpinned" : 0, + "bottomtoolbarpinned" : 0, + "toolbars_unpinned_last_save" : 0, + "tallnewobj" : 0, + "boxanimatetime" : 200, + "enablehscroll" : 1, + "enablevscroll" : 1, + "devicewidth" : 0.0, + "description" : "", + "digest" : "", + "tags" : "", + "style" : "", + "subpatcher_template" : "", + "boxes" : [ { + "box" : { + "id" : "obj-121", + "maxclass" : "newobj", + "numinlets" : 0, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 401.5, 485.0, 45.0, 22.0 ], + "text" : "r count" + } + + } +, { + "box" : { + "id" : "obj-96", + "maxclass" : "newobj", + "numinlets" : 2, + "numoutlets" : 2, + "outlettype" : [ "", "" ], + "patching_rect" : [ 293.94868214925134, 509.5, 172.0, 22.0 ], + "text" : "if $f2 < 9 then $i1 else out2 $i1" + } + + } +, { + "box" : { + "fontface" : 1, + "fontname" : "Arial", + "fontsize" : 16.0, + "id" : "obj-120", + "maxclass" : "comment", + "numinlets" : 1, + "numoutlets" : 0, + "patching_rect" : [ 264.360468745231628, 26.807250261306763, 534.0, 24.0 ], + "presentation" : 1, + "presentation_rect" : [ 252.360468745231628, 38.807250261306763, 53.0, 24.0 ], + "text" : "V. 1.0" + } + + } +, { + "box" : { + "fontface" : 1, + "fontname" : "Arial", + "fontsize" : 30.0, + "id" : "obj-115", + "maxclass" : "comment", + "numinlets" : 1, + "numoutlets" : 0, + "patching_rect" : [ 904.42079496383667, 1199.082468748092651, 910.0, 40.0 ], + "presentation" : 1, + "presentation_rect" : [ 18.360468745231628, 25.807250261306763, 242.0, 40.0 ], + "text" : "\"BachNet\"-Live" + } + + } +, { + "box" : { + "fontname" : "Arial", + "fontsize" : 13.0, + "id" : "obj-7", + "linecount" : 4, + "maxclass" : "comment", + "numinlets" : 1, + "numoutlets" : 0, + "patching_rect" : [ 904.42079496383667, 1150.807250261306763, 383.0, 79.0 ], + "presentation" : 1, + "presentation_linecount" : 4, + "presentation_rect" : [ 18.360468745231628, 72.807250261306763, 350.631854772567749, 79.0 ], + "text" : "Interface programmed in Max by Sascha Etezazi (2020) using MaxScore.\nNeural Network \"BachNet\" programmed in PyTorch by Leemhuis et al. (2019)\n" + } + + } +, { + "box" : { + "id" : "obj-125", + "maxclass" : "button", + "numinlets" : 1, + "numoutlets" : 1, + "outlettype" : [ "bang" ], + "parameter_enable" : 0, + "patching_rect" : [ 591.743069887161255, 1107.339357137680054, 24.0, 24.0 ] + } + + } +, { + "box" : { + "id" : "obj-119", + "maxclass" : "newobj", + "numinlets" : 1, + "numoutlets" : 2, + "outlettype" : [ "", "int" ], + "patching_rect" : [ 635.504536628723145, 1199.082468748092651, 137.0, 22.0 ], + "text" : "conformpath native boot" + } + + } +, { + "box" : { + "id" : "obj-112", + "maxclass" : "newobj", + "numinlets" : 1, + "numoutlets" : 1, + "outlettype" : [ "bang" ], + "patching_rect" : [ 711.504536628723145, 1098.807250261306763, 58.0, 22.0 ], + "text" : "loadbang" + } + + } +, { + "box" : { + "fontname" : "Arial", + "fontsize" : 11.595186999999999, + "id" : "obj-113", + "maxclass" : "message", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 711.504536628723145, 1145.140583157539368, 65.0, 21.0 ], + "text" : "execute.sh" + } + + } +, { + "box" : { + "fontname" : "Arial", + "fontsize" : 11.595186999999999, + "id" : "obj-116", + "maxclass" : "newobj", + "numinlets" : 1, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 635.504536628723145, 1145.140583157539368, 59.0, 21.0 ], + "text" : "tosymbol" + } + + } +, { + "box" : { + "fontname" : "Arial", + "fontsize" : 11.595186999999999, + "id" : "obj-117", + "maxclass" : "newobj", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 635.504536628723145, 1170.140583157539368, 167.0, 21.0 ], + "text" : "sprintf symout %s%s" + } + + } +, { + "box" : { + "id" : "obj-118", + "maxclass" : "newobj", + "numinlets" : 0, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 635.610607504844666, 1098.807250261306763, 58.0, 22.0 ], + "text" : "r thispath" + } + + } +, { + "box" : { + "id" : "obj-91", + "maxclass" : "newobj", + "numinlets" : 1, + "numoutlets" : 1, + "outlettype" : [ "bang" ], + "patching_rect" : [ 243.0, 745.256880044937134, 58.0, 22.0 ], + "text" : "loadbang" + } + + } +, { + "box" : { + "fontname" : "Arial", + "fontsize" : 11.595186999999999, + "id" : "obj-102", + "maxclass" : "message", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 243.0, 775.076452851295471, 93.0, 21.0 ], + "text" : "XML/melody.xml" + } + + } +, { + "box" : { + "fontname" : "Arial", + "fontsize" : 11.595186999999999, + "id" : "obj-106", + "maxclass" : "newobj", + "numinlets" : 1, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 167.0, 829.076452851295471, 133.0, 21.0 ], + "text" : "prepend saveMusicXML" + } + + } +, { + "box" : { + "fontname" : "Arial", + "fontsize" : 11.595186999999999, + "id" : "obj-108", + "maxclass" : "newobj", + "numinlets" : 1, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 167.0, 775.076452851295471, 59.0, 21.0 ], + "text" : "tosymbol" + } + + } +, { + "box" : { + "fontname" : "Arial", + "fontsize" : 11.595186999999999, + "id" : "obj-109", + "maxclass" : "newobj", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 167.0, 800.076452851295471, 167.0, 21.0 ], + "text" : "sprintf symout %s%s" + } + + } +, { + "box" : { + "id" : "obj-110", + "maxclass" : "newobj", + "numinlets" : 0, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 167.106070876121521, 745.256880044937134, 58.0, 22.0 ], + "text" : "r thispath" + } + + } +, { + "box" : { + "id" : "obj-69", + "maxclass" : "newobj", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "bang" ], + "patching_rect" : [ 1373.0, 28.0, 47.0, 22.0 ], + "text" : "delay 5" + } + + } +, { + "box" : { + "id" : "obj-38", + "maxclass" : "newobj", + "numinlets" : 1, + "numoutlets" : 1, + "outlettype" : [ "bang" ], + "patching_rect" : [ 1432.60474967956543, 5.0, 58.0, 22.0 ], + "text" : "loadbang" + } + + } +, { + "box" : { + "id" : "obj-103", + "maxclass" : "newobj", + "numinlets" : 1, + "numoutlets" : 1, + "outlettype" : [ "bang" ], + "patching_rect" : [ 1735.503126382827759, 190.666667103767395, 58.0, 22.0 ], + "text" : "loadbang" + } + + } +, { + "box" : { + "fontname" : "Arial", + "fontsize" : 11.595186999999999, + "id" : "obj-58", + "maxclass" : "message", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 1735.503126382827759, 237.0, 123.496873617172241, 21.0 ], + "text" : "XML/harmony.xml" + } + + } +, { + "box" : { + "fontname" : "Arial", + "fontsize" : 11.595186999999999, + "id" : "obj-80", + "maxclass" : "newobj", + "numinlets" : 1, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 1659.503126382827759, 291.0, 98.0, 21.0 ], + "text" : "prepend filename" + } + + } +, { + "box" : { + "fontname" : "Arial", + "fontsize" : 11.595186999999999, + "id" : "obj-87", + "maxclass" : "newobj", + "numinlets" : 1, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 1659.503126382827759, 237.0, 59.0, 21.0 ], + "text" : "tosymbol" + } + + } +, { + "box" : { + "fontname" : "Arial", + "fontsize" : 11.595186999999999, + "id" : "obj-94", + "maxclass" : "newobj", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 1659.503126382827759, 262.0, 167.0, 21.0 ], + "text" : "sprintf symout %s%s" + } + + } +, { + "box" : { + "id" : "obj-55", + "maxclass" : "newobj", + "numinlets" : 0, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 1659.60919725894928, 190.666667103767395, 58.0, 22.0 ], + "text" : "r thispath" + } + + } +, { + "box" : { + "id" : "obj-104", + "maxclass" : "newobj", + "numinlets" : 1, + "numoutlets" : 0, + "patching_rect" : [ 1440.60474967956543, 119.0, 60.0, 22.0 ], + "text" : "s thispath" + } + + } +, { + "box" : { + "id" : "obj-101", + "maxclass" : "newobj", + "numinlets" : 0, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 63.0, 706.390243291854858, 74.0, 22.0 ], + "text" : "r recalculate" + } + + } +, { + "box" : { + "id" : "obj-99", + "maxclass" : "newobj", + "numinlets" : 1, + "numoutlets" : 0, + "patching_rect" : [ 1432.60474967956543, 328.478167653083801, 76.0, 22.0 ], + "text" : "s recalculate" + } + + } +, { + "box" : { + "id" : "obj-89", + "linecount" : 3, + "maxclass" : "message", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 1551.503126263618469, 59.814522624015808, 347.0, 49.0 ], + "text" : "\"Macintosh HD:/Users/Sascha/Documents/BachNetinteraktiv/BachNetLive/\"" + } + + } +, { + "box" : { + "id" : "obj-86", + "maxclass" : "message", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 1404.134706616401672, 59.814522624015808, 32.0, 22.0 ], + "text" : "path" + } + + } +, { + "box" : { + "id" : "obj-79", + "maxclass" : "newobj", + "numinlets" : 1, + "numoutlets" : 2, + "outlettype" : [ "", "" ], + "patching_rect" : [ 1456.222424626350403, 59.814522624015808, 67.0, 22.0 ], + "save" : [ "#N", "thispatcher", ";", "#Q", "end", ";" ], + "text" : "thispatcher" + } + + } +, { + "box" : { + "fontname" : "Arial", + "fontsize" : 13.0, + "id" : "obj-28", + "maxclass" : "comment", + "numinlets" : 1, + "numoutlets" : 0, + "patching_rect" : [ 306.5, 706.390243291854858, 149.0, 21.0 ], + "text" : "CHANGE THIS PATH" + } + + } +, { + "box" : { + "id" : "obj-63", + "maxclass" : "message", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ -1.149254441261292, 573.846157073974609, 118.0, 22.0 ], + "text" : "setTitle BachNetLive" + } + + } +, { + "box" : { + "id" : "obj-39", + "maxclass" : "button", + "numinlets" : 1, + "numoutlets" : 1, + "outlettype" : [ "bang" ], + "parameter_enable" : 0, + "patching_rect" : [ 15.384616851806641, 443.803032040596008, 24.0, 24.0 ] + } + + } +, { + "box" : { + "id" : "obj-35", + "maxclass" : "message", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 11.5, 485.0, 114.0, 22.0 ], + "text" : "setComposer \" Du \"" + } + + } +, { + "box" : { + "id" : "obj-15", + "maxclass" : "message", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 1432.60474967956543, 276.782699465751648, 95.0, 22.0 ], + "presentation" : 1, + "presentation_rect" : [ 331.781377792358398, 643.363637566566467, 95.0, 22.0 ], + "text" : "RECALCULATE" + } + + } +, { + "box" : { + "id" : "obj-100", + "maxclass" : "message", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 1488.452378869056702, 654.599494576454163, 31.0, 22.0 ], + "text" : "stop" + } + + } +, { + "box" : { + "bubble" : 1, + "bubbleside" : 3, + "fontname" : "Arial", + "fontsize" : 13.0, + "id" : "obj-97", + "linecount" : 2, + "maxclass" : "comment", + "numinlets" : 1, + "numoutlets" : 0, + "patching_rect" : [ -1.149254441261292, 180.601279258728027, 134.0, 40.0 ], + "presentation" : 1, + "presentation_linecount" : 2, + "presentation_rect" : [ 5.149523019790649, 321.520725846290588, 118.848486185073853, 40.0 ], + "text" : "1.) set key signature" + } + + } +, { + "box" : { + "id" : "obj-95", + "maxclass" : "newobj", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "int" ], + "patching_rect" : [ 1343.408255577087402, 811.595358490943909, 32.0, 22.0 ], + "text" : "+ 50" + } + + } +, { + "box" : { + "bubble" : 1, + "fontname" : "Arial", + "fontsize" : 13.0, + "id" : "obj-93", + "linecount" : 2, + "maxclass" : "comment", + "numinlets" : 1, + "numoutlets" : 0, + "patching_rect" : [ 145.5, 232.000020980834961, 148.999992609024048, 40.0 ], + "presentation" : 1, + "presentation_linecount" : 2, + "presentation_rect" : [ 75.289057970046997, 217.153849363327026, 157.571421384811401, 40.0 ], + "text" : "RESET LIVE BACH GENERATOR" + } + + } +, { + "box" : { + "id" : "obj-92", + "maxclass" : "message", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 1700.275863170623779, 858.576959609985352, 78.0, 22.0 ], + "presentation" : 1, + "presentation_rect" : [ 337.251075029373169, 586.363637566566467, 84.060605525970459, 22.0 ], + "text" : "PLAY AGAIN" + } + + } +, { + "box" : { + "id" : "obj-90", + "maxclass" : "button", + "numinlets" : 1, + "numoutlets" : 1, + "outlettype" : [ "bang" ], + "parameter_enable" : 0, + "patching_rect" : [ 42.0, 1027.0, 24.0, 24.0 ] + } + + } +, { + "box" : { + "id" : "obj-31", + "maxclass" : "newobj", + "numinlets" : 0, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 92.0, 1039.0, 45.0, 22.0 ], + "text" : "r count" + } + + } +, { + "box" : { + "id" : "obj-70", + "maxclass" : "newobj", + "numinlets" : 2, + "numoutlets" : 2, + "outlettype" : [ "", "" ], + "patching_rect" : [ 42.0, 1079.098744034767151, 174.0, 22.0 ], + "text" : "if $f2 == 0 then out2 $1 else $1" + } + + } +, { + "box" : { + "id" : "obj-85", + "maxclass" : "newobj", + "numinlets" : 0, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 1654.275863170623779, 670.576959609985352, 45.0, 22.0 ], + "text" : "r count" + } + + } +, { + "box" : { + "id" : "obj-83", + "maxclass" : "newobj", + "numinlets" : 1, + "numoutlets" : 0, + "patching_rect" : [ 48.5, 90.0, 47.0, 22.0 ], + "text" : "s count" + } + + } +, { + "box" : { + "id" : "obj-82", + "maxclass" : "newobj", + "numinlets" : 2, + "numoutlets" : 2, + "outlettype" : [ "", "" ], + "patching_rect" : [ 1604.275863170623779, 710.675703644752502, 174.0, 22.0 ], + "text" : "if $f2 == 0 then out2 $1 else $1" + } + + } +, { + "box" : { + "id" : "obj-81", + "maxclass" : "newobj", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "bang" ], + "patching_rect" : [ 1589.912282943725586, 622.870567917823792, 61.0, 22.0 ], + "text" : "delay 100" + } + + } +, { + "box" : { + "id" : "obj-76", + "maxclass" : "message", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 1626.275863170623779, 872.316442012786865, 32.0, 22.0 ], + "text" : "start" + } + + } +, { + "box" : { + "fontname" : "Arial", + "fontsize" : 11.595186999999999, + "id" : "obj-46", + "maxclass" : "message", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 1949.609176158905029, 696.076959609985352, 31.0, 21.0 ], + "text" : "next" + } + + } +, { + "box" : { + "fontname" : "Arial", + "fontsize" : 11.595186999999999, + "id" : "obj-75", + "maxclass" : "newobj", + "numinlets" : 4, + "numoutlets" : 3, + "outlettype" : [ "float", "float", "float" ], + "patching_rect" : [ 1986.609115170623681, 788.576959609985352, 101.0, 21.0 ], + "text" : "makenote 60 90 1" + } + + } +, { + "box" : { + "fontname" : "Arial", + "fontsize" : 11.595186999999999, + "id" : "obj-77", + "maxclass" : "newobj", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "bang" ], + "patching_rect" : [ 1896.503126263618469, 839.576959609985352, 39.0, 21.0 ], + "text" : "del" + } + + } +, { + "box" : { + "fontname" : "Arial", + "fontsize" : 11.595186999999999, + "id" : "obj-78", + "maxclass" : "newobj", + "numinlets" : 1, + "numoutlets" : 2, + "outlettype" : [ "bang", "int" ], + "patching_rect" : [ 1941.609176170623869, 755.576959609985352, 39.0, 21.0 ], + "text" : "t b i" + } + + } +, { + "box" : { + "id" : "obj-49", + "maxclass" : "newobj", + "numinlets" : 8, + "numoutlets" : 8, + "outlettype" : [ "int", "int", "int", "int", "int", "int", "int", "int" ], + "patching_rect" : [ 1941.609176158905029, 726.576959609985352, 144.5, 22.0 ], + "save" : [ "#N", "detonate", "u605031918", ";", "#X", "setparam", 0, "Time", 0, 0, 999999, 0, 1000, 200, 0, ";", "#X", "setparam", 1, "Pitch", 0, 0, 127, 60, 12, 4, 0, ";", "#X", "setparam", 2, "Vel", 0, 0, 127, 64, 12, 4, 0, ";", "#X", "setparam", 3, "Dur", 0, 1, 99999, 200, 800, 200, 0, ";", "#X", "setparam", 4, "Chan", 0, 1, 16, 1, 8, 1, 0, ";", "#X", "setparam", 5, "Track", 0, 1, 32, 1, 8, 1, 0, ";", "#X", "setparam", 6, "X1", 0, 0, 999, 0, 80, 20, 0, ";", "#X", "setparam", 7, "X2", 0, 0, 999, 0, 80, 20, 0, ";", "#X", "restore", ";", "#X", "stop", ";" ], + "text" : "detonate" + } + + } +, { + "box" : { + "id" : "obj-54", + "maxclass" : "newobj", + "numinlets" : 2, + "numoutlets" : 5, + "outlettype" : [ "", "", "", "", "" ], + "patching_rect" : [ 1825.275863170623779, 654.599494576454163, 205.0, 22.0 ], + "text" : "maxscore.makenote @outputmode 2" + } + + } +, { + "box" : { + "id" : "obj-67", + "maxclass" : "newobj", + "numinlets" : 3, + "numoutlets" : 0, + "patching_rect" : [ 1922.275863170623779, 882.576959609985352, 165.333251999999987, 22.0 ], + "text" : "noteout" + } + + } +, { + "box" : { + "id" : "obj-72", + "maxclass" : "message", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 1970.275863170623779, 612.576959609985352, 50.0, 22.0 ], + "text" : "start" + } + + } +, { + "box" : { + "id" : "obj-45", + "maxclass" : "button", + "numinlets" : 1, + "numoutlets" : 1, + "outlettype" : [ "bang" ], + "parameter_enable" : 0, + "patching_rect" : [ 20.076915740966797, 640.461538314819336, 24.0, 24.0 ] + } + + } +, { + "box" : { + "id" : "obj-62", + "maxclass" : "newobj", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "bang" ], + "patching_rect" : [ 1482.452378869056702, 424.341673493385315, 67.0, 22.0 ], + "text" : "delay 1900" + } + + } +, { + "box" : { + "id" : "obj-44", + "linecount" : 2, + "maxclass" : "message", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 195.0, 1125.107917428016663, 359.0, 35.0 ], + "text" : "/Users/Sascha/Documents/BachNetinteraktiv/BachNetLive/execute.sh" + } + + } +, { + "box" : { + "id" : "obj-24", + "maxclass" : "newobj", + "numinlets" : 1, + "numoutlets" : 2, + "outlettype" : [ "", "bang" ], + "patching_rect" : [ 195.0, 1190.725184798240662, 33.0, 22.0 ], + "text" : "shell" + } + + } +, { + "box" : { + "id" : "obj-18", + "maxclass" : "newobj", + "numinlets" : 1, + "numoutlets" : 1, + "outlettype" : [ "bang" ], + "patching_rect" : [ 1527.275863170623779, 328.478167653083801, 58.0, 22.0 ], + "text" : "loadbang" + } + + } +, { + "box" : { + "id" : "obj-17", + "maxclass" : "newobj", + "numinlets" : 1, + "numoutlets" : 1, + "outlettype" : [ "bang" ], + "patching_rect" : [ 269.571389079093933, 72.814522624015808, 58.0, 22.0 ], + "text" : "loadbang" + } + + } +, { + "box" : { + "id" : "obj-74", + "maxclass" : "newobj", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "bang" ], + "patching_rect" : [ 1488.452378869056702, 505.870567917823792, 61.0, 22.0 ], + "text" : "delay 100" + } + + } +, { + "box" : { + "id" : "obj-73", + "maxclass" : "button", + "numinlets" : 1, + "numoutlets" : 1, + "outlettype" : [ "bang" ], + "parameter_enable" : 0, + "patching_rect" : [ 1582.525919497013092, 515.215861976146698, 24.0, 24.0 ] + } + + } +, { + "box" : { + "id" : "obj-68", + "maxclass" : "button", + "numinlets" : 1, + "numoutlets" : 1, + "outlettype" : [ "bang" ], + "parameter_enable" : 0, + "patching_rect" : [ 1533.626837849617004, 462.445646047592163, 24.0, 24.0 ] + } + + } +, { + "box" : { + "id" : "obj-66", + "maxclass" : "newobj", + "numinlets" : 0, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 1423.409500598907471, 372.991397261619568, 76.0, 22.0 ], + "text" : "r bangimport" + } + + } +, { + "box" : { + "id" : "obj-65", + "maxclass" : "newobj", + "numinlets" : 1, + "numoutlets" : 0, + "patching_rect" : [ 42.0, 982.265904307365417, 98.0, 22.0 ], + "text" : "send bangimport" + } + + } +, { + "box" : { + "id" : "obj-64", + "maxclass" : "newobj", + "numinlets" : 1, + "numoutlets" : 2, + "outlettype" : [ "bang", "bang" ], + "patching_rect" : [ 65.0, 242.134012460708618, 32.0, 22.0 ], + "text" : "t b b" + } + + } +, { + "box" : { + "id" : "obj-61", + "maxclass" : "newobj", + "numinlets" : 2, + "numoutlets" : 4, + "outlettype" : [ "", "", "", "" ], + "patching_rect" : [ 1589.912282943725586, 788.076959609985352, 369.0, 22.0 ], + "text" : "MaxScore @grab grabscore" + } + + } +, { + "box" : { + "id" : "obj-50", + "maxclass" : "comment", + "numinlets" : 1, + "numoutlets" : 0, + "patching_rect" : [ 1773.503126263618469, 579.167868733406067, 169.0, 20.0 ], + "text" : "Skipped MusicXML attributes" + } + + } +, { + "box" : { + "id" : "obj-2", + "maxclass" : "message", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 1750.503126263618469, 472.167868733406067, 169.0, 22.0 ], + "text" : "script npm install --save xml-js" + } + + } +, { + "box" : { + "id" : "obj-51", + "maxclass" : "newobj", + "numinlets" : 2, + "numoutlets" : 4, + "outlettype" : [ "dictionary", "", "", "" ], + "patching_rect" : [ 1715.503126263618469, 578.167868733406067, 50.5, 22.0 ], + "saved_object_attributes" : { + "embed" : 0, + "parameter_enable" : 0, + "parameter_mappable" : 0 + } +, + "text" : "dict" + } + + } +, { + "box" : { + "id" : "obj-53", + "linecount" : 3, + "maxclass" : "message", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 1647.503126263618469, 350.162325143814087, 421.0, 49.0 ], + "text" : "filename \"Macintosh HD:/Users/Sascha/Documents/BachNetinteraktiv/BachNetLive/XML/harmony.xml\"" + } + + } +, { + "box" : { + "id" : "obj-12", + "maxclass" : "newobj", + "numinlets" : 3, + "numoutlets" : 3, + "outlettype" : [ "", "", "" ], + "patching_rect" : [ 1672.503126263618469, 545.167868733406067, 105.0, 22.0 ], + "text" : "route jmsl skipped" + } + + } +, { + "box" : { + "id" : "obj-56", + "maxclass" : "newobj", + "numinlets" : 1, + "numoutlets" : 2, + "outlettype" : [ "", "" ], + "patching_rect" : [ 1672.503126263618469, 512.167868733406067, 263.0, 22.0 ], + "saved_object_attributes" : { + "autostart" : 1, + "defer" : 0, + "node_bin_path" : "", + "npm_bin_path" : "", + "watch" : 0 + } +, + "text" : "node.script n4m_musicxml2jmsl.js @autostart 1" + } + + } +, { + "box" : { + "id" : "obj-16", + "linecount" : 3, + "maxclass" : "message", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 42.0, 854.309640526771545, 432.0, 49.0 ], + "text" : "saveMusicXML \"Macintosh HD:/Users/Sascha/Documents/BachNetinteraktiv/BachNetLive/XML/melody.xml\"" + } + + } +, { + "box" : { + "id" : "obj-144", + "maxclass" : "button", + "numinlets" : 1, + "numoutlets" : 1, + "outlettype" : [ "bang" ], + "parameter_enable" : 0, + "patching_rect" : [ 281.251747012138367, 943.297318696975708, 24.0, 24.0 ] + } + + } +, { + "box" : { + "id" : "obj-145", + "maxclass" : "message", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 281.251747012138367, 901.379737496376038, 47.0, 22.0 ], + "presentation" : 1, + "presentation_rect" : [ 212.643817663192749, 330.47594940662384, 47.0, 22.0 ], + "text" : "Sharps" + } + + } +, { + "box" : { + "id" : "obj-143", + "maxclass" : "button", + "numinlets" : 1, + "numoutlets" : 1, + "outlettype" : [ "bang" ], + "parameter_enable" : 0, + "patching_rect" : [ 374.303703665733337, 943.297318696975708, 24.0, 24.0 ] + } + + } +, { + "box" : { + "id" : "obj-141", + "maxclass" : "message", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 363.5, 901.379737496376038, 35.0, 22.0 ], + "presentation" : 1, + "presentation_rect" : [ 283.069437265396118, 330.47594940662384, 50.714286088943481, 22.0 ], + "text" : "Flats" + } + + } +, { + "box" : { + "id" : "obj-139", + "maxclass" : "newobj", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "int" ], + "patching_rect" : [ 281.251747012138367, 986.576959609985352, 29.5, 22.0 ], + "text" : "i 0" + } + + } +, { + "box" : { + "id" : "obj-138", + "maxclass" : "newobj", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "int" ], + "patching_rect" : [ 374.303703665733337, 986.576959609985352, 29.5, 22.0 ], + "text" : "i 0" + } + + } +, { + "box" : { + "id" : "obj-137", + "maxclass" : "number", + "numinlets" : 1, + "numoutlets" : 2, + "outlettype" : [ "", "bang" ], + "parameter_enable" : 0, + "patching_rect" : [ 319.5, 932.618270754814148, 50.0, 22.0 ], + "presentation" : 1, + "presentation_rect" : [ 140.789052844047546, 330.47594940662384, 50.0, 22.0 ] + } + + } +, { + "box" : { + "id" : "obj-135", + "maxclass" : "message", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 276.5, 1056.350238442420959, 209.0, 22.0 ], + "text" : "setKeySignature 0 0 $1 SHARP_KEY" + } + + } +, { + "box" : { + "id" : "obj-114", + "maxclass" : "button", + "numinlets" : 1, + "numoutlets" : 1, + "outlettype" : [ "bang" ], + "parameter_enable" : 0, + "patching_rect" : [ 262.0, 110.0, 24.0, 24.0 ] + } + + } +, { + "box" : { + "id" : "obj-111", + "maxclass" : "message", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 276.5, 1017.408154189586639, 196.0, 22.0 ], + "text" : "setKeySignature 0 0 $1 FLAT_KEY" + } + + } +, { + "box" : { + "id" : "obj-107", + "maxclass" : "message", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 205.25, 72.814522624015808, 29.5, 22.0 ], + "text" : "0." + } + + } +, { + "box" : { + "id" : "obj-105", + "maxclass" : "message", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 226.0, 158.341462135314941, 29.5, 22.0 ], + "text" : "-1." + } + + } +, { + "box" : { + "id" : "obj-98", + "maxclass" : "button", + "numinlets" : 1, + "numoutlets" : 1, + "outlettype" : [ "bang" ], + "parameter_enable" : 0, + "patching_rect" : [ 110.0, 241.134012460708618, 24.0, 24.0 ], + "presentation" : 1, + "presentation_rect" : [ 18.360468745231628, 209.439563274383545, 55.428572177886963, 55.428572177886963 ] + } + + } +, { + "box" : { + "format" : 6, + "id" : "obj-88", + "maxclass" : "flonum", + "numinlets" : 1, + "numoutlets" : 2, + "outlettype" : [ "", "bang" ], + "parameter_enable" : 0, + "patching_rect" : [ 32.0, 45.0, 50.0, 22.0 ] + } + + } +, { + "box" : { + "id" : "obj-84", + "maxclass" : "newobj", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "float" ], + "patching_rect" : [ 443.25, 192.134012460708618, 29.5, 22.0 ], + "text" : "f 0." + } + + } +, { + "box" : { + "id" : "obj-71", + "maxclass" : "newobj", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "float" ], + "patching_rect" : [ 195.253998041152954, 119.0, 29.5, 22.0 ], + "text" : "f 0." + } + + } +, { + "box" : { + "id" : "obj-52", + "maxclass" : "button", + "numinlets" : 1, + "numoutlets" : 1, + "outlettype" : [ "bang" ], + "parameter_enable" : 0, + "patching_rect" : [ 145.5, 5.428571939468384, 24.0, 24.0 ] + } + + } +, { + "box" : { + "id" : "obj-48", + "maxclass" : "newobj", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "float" ], + "patching_rect" : [ 388.5, 158.341462135314941, 29.5, 22.0 ], + "text" : "+ 0." + } + + } +, { + "box" : { + "format" : 6, + "id" : "obj-47", + "maxclass" : "flonum", + "numinlets" : 1, + "numoutlets" : 2, + "outlettype" : [ "", "bang" ], + "parameter_enable" : 0, + "patching_rect" : [ 288.5, 180.601279258728027, 50.0, 22.0 ] + } + + } +, { + "box" : { + "id" : "obj-41", + "maxclass" : "newobj", + "numinlets" : 2, + "numoutlets" : 2, + "outlettype" : [ "int", "int" ], + "patching_rect" : [ 360.0, 300.526314854621887, 55.0, 22.0 ], + "text" : "stripnote" + } + + } +, { + "box" : { + "id" : "obj-43", + "maxclass" : "newobj", + "numinlets" : 1, + "numoutlets" : 3, + "outlettype" : [ "int", "int", "int" ], + "patching_rect" : [ 374.0, 232.000020980834961, 41.0, 22.0 ], + "text" : "notein" + } + + } +, { + "box" : { + "id" : "obj-9", + "maxclass" : "newobj", + "numinlets" : 0, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 211.248252987861633, 706.390243291854858, 84.0, 22.0 ], + "text" : "r maxscore-2" + } + + } +, { + "box" : { + "id" : "obj-8", + "maxclass" : "newobj", + "numinlets" : 0, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 167.0, 567.0, 73.0, 22.0 ], + "text" : "r maxscore" + } + + } +, { + "box" : { + "fontname" : "Arial", + "fontsize" : 13.0, + "id" : "obj-42", + "maxclass" : "newobj", + "numinlets" : 1, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 165.0, 378.230768203735352, 75.0, 23.0 ], + "text" : "loadmess 2" + } + + } +, { + "box" : { + "id" : "obj-37", + "maxclass" : "live.tab", + "multiline" : 0, + "num_lines_patching" : 1, + "num_lines_presentation" : 1, + "numinlets" : 1, + "numoutlets" : 3, + "outlettype" : [ "", "", "float" ], + "parameter_enable" : 1, + "patching_rect" : [ 179.503998041152954, 431.076923370361328, 137.0, 34.0 ], + "pictures" : [ "whole.svg", "half.svg", "quarter.svg", "eighth.svg", "16th.svg" ], + "presentation" : 1, + "presentation_rect" : [ 140.789052844047546, 404.285709619522095, 265.571431636810303, 79.714286804199219 ], + "saved_attribute_attributes" : { + "valueof" : { + "parameter_shortname" : "live.tab[1]", + "parameter_enum" : [ "4", "2", "1", "0.5", "0.25" ], + "parameter_type" : 2, + "parameter_unitstyle" : 0, + "parameter_longname" : "live.tab[10]", + "parameter_initial_enable" : 1, + "parameter_mmax" : 4, + "parameter_initial" : [ 1 ] + } + + } +, + "spacing_x" : 6.26, + "usepicture" : 1, + "usesvgviewbox" : 1, + "varname" : "live.tab[2]" + } + + } +, { + "box" : { + "id" : "obj-27", + "maxclass" : "kslider", + "numinlets" : 2, + "numoutlets" : 2, + "offset" : 48, + "outlettype" : [ "int", "int" ], + "parameter_enable" : 0, + "patching_rect" : [ 340.0, 429.303032040596008, 168.0, 53.0 ], + "presentation" : 1, + "presentation_rect" : [ 18.360468745231628, 586.363637566566467, 266.0, 79.0 ], + "range" : 24 + } + + } +, { + "box" : { + "id" : "obj-23", + "linecount" : 2, + "maxclass" : "comment", + "numinlets" : 1, + "numoutlets" : 0, + "patching_rect" : [ 110.0, 523.5, 158.0, 33.0 ], + "text" : "addnote: duration, pitch, amplitude, hold time" + } + + } +, { + "box" : { + "bubble" : 1, + "bubbleside" : 2, + "fontname" : "Arial", + "fontsize" : 13.0, + "id" : "obj-32", + "linecount" : 2, + "maxclass" : "comment", + "numinlets" : 1, + "numoutlets" : 0, + "patching_rect" : [ 219.0, 335.0, 267.583333333333371, 55.0 ], + "presentation" : 1, + "presentation_linecount" : 2, + "presentation_rect" : [ 18.360468745231628, 529.727275133132935, 266.068181951840756, 55.0 ], + "text" : "3.) Play with MIDI Keyboard or set Pitch here" + } + + } +, { + "box" : { + "id" : "obj-5", + "maxclass" : "newobj", + "numinlets" : 1, + "numoutlets" : 0, + "patching_rect" : [ 293.94868214925134, 677.0, 80.0, 22.0 ], + "text" : "s transcriber" + } + + } +, { + "box" : { + "id" : "obj-33", + "maxclass" : "newobj", + "numinlets" : 1, + "numoutlets" : 0, + "patching_rect" : [ 179.503998041152954, 650.0, 61.0, 22.0 ], + "text" : "print info" + } + + } +, { + "box" : { + "id" : "obj-26", + "maxclass" : "newobj", + "numinlets" : 0, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 244.5, 567.0, 61.0, 22.0 ], + "text" : "r canvas" + } + + } +, { + "box" : { + "bubble" : 1, + "fontname" : "Arial", + "fontsize" : 13.0, + "id" : "obj-3", + "maxclass" : "comment", + "numinlets" : 1, + "numoutlets" : 0, + "patching_rect" : [ 196.5, 290.0, 142.0, 25.0 ], + "text" : "Create new score" + } + + } +, { + "box" : { + "bubble" : 1, + "fontname" : "Arial", + "fontsize" : 13.0, + "id" : "obj-59", + "maxclass" : "comment", + "numinlets" : 1, + "numoutlets" : 0, + "patching_rect" : [ 547.769235610961914, 230.500020980834961, 174.0, 25.0 ], + "text" : "Change size of canvas" + } + + } +, { + "box" : { + "fontname" : "Arial", + "fontsize" : 13.0, + "id" : "obj-34", + "maxclass" : "message", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 27.0, 333.736265301704407, 136.0, 23.0 ], + "text" : "setScoreSize 800 200" + } + + } +, { + "box" : { + "id" : "obj-6", + "maxclass" : "newobj", + "numinlets" : 1, + "numoutlets" : 0, + "patching_rect" : [ 393.863216638565063, 677.0, 102.0, 22.0 ], + "text" : "print transcriber" + } + + } +, { + "box" : { + "id" : "obj-36", + "maxclass" : "newobj", + "numinlets" : 1, + "numoutlets" : 0, + "patching_rect" : [ 114.071389079093933, 682.390243291854858, 156.0, 22.0 ], + "text" : "s jmsl_instrument_output" + } + + } +, { + "box" : { + "fontname" : "Arial", + "fontsize" : 13.0, + "id" : "obj-14", + "maxclass" : "message", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 23.5, 367.0, 90.0, 23.0 ], + "text" : "openWindow" + } + + } +, { + "box" : { + "fontname" : "Arial", + "fontsize" : 13.0, + "id" : "obj-19", + "maxclass" : "newobj", + "numinlets" : 3, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 270.0, 533.5, 211.0, 23.0 ], + "text" : "join 3 @triggers 1" + } + + } +, { + "box" : { + "fontname" : "Arial", + "fontsize" : 13.0, + "id" : "obj-20", + "maxclass" : "message", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 334.5, 567.0, 134.0, 23.0 ], + "text" : "addNote $1 $2 $3 0.8" + } + + } +, { + "box" : { + "fontname" : "Arial", + "fontsize" : 13.0, + "id" : "obj-21", + "maxclass" : "message", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 20.325174450874329, 306.043954968452454, 135.0, 23.0 ], + "text" : "newScore 1 1000 200" + } + + } +, { + "box" : { + "bubble" : 1, + "bubbleside" : 3, + "fontname" : "Arial", + "fontsize" : 13.0, + "id" : "obj-22", + "maxclass" : "comment", + "numinlets" : 1, + "numoutlets" : 0, + "patching_rect" : [ 44.153842926025391, 431.076923370361328, 134.0, 25.0 ], + "presentation" : 1, + "presentation_rect" : [ 4.789052844047546, 435.571424722671509, 134.0, 25.0 ], + "text" : "2.) set duration " + } + + } +, { + "box" : { + "id" : "obj-25", + "maxclass" : "newobj", + "numinlets" : 1, + "numoutlets" : 0, + "patching_rect" : [ 257.461517333984375, 650.0, 175.0, 22.0 ], + "text" : "print jmsl_instrument_output" + } + + } +, { + "box" : { + "id" : "obj-29", + "maxclass" : "newobj", + "numinlets" : 2, + "numoutlets" : 4, + "outlettype" : [ "", "", "", "" ], + "patching_rect" : [ 114.071389079093933, 609.538461685180664, 369.0, 22.0 ], + "text" : "MaxScore @grab grabscore" + } + + } +, { + "box" : { + "id" : "obj-30", + "maxclass" : "newobj", + "numinlets" : 2, + "numoutlets" : 4, + "outlettype" : [ "", "", "", "" ], + "patching_rect" : [ 63.0, 650.0, 110.0, 22.0 ], + "text" : "maxscore.canvas", + "varname" : "JMSLMaxScore-11" + } + + } +, { + "box" : { + "fontname" : "Arial", + "fontsize" : 13.0, + "id" : "obj-40", + "linecount" : 3, + "maxclass" : "comment", + "numinlets" : 1, + "numoutlets" : 0, + "patching_rect" : [ 904.42079496383667, 1098.807250261306763, 406.0, 50.0 ], + "presentation" : 1, + "presentation_linecount" : 3, + "presentation_rect" : [ 18.360468745231628, 139.807250261306763, 406.0, 50.0 ], + "text" : "MaxScore was programmed in Java Music Specification Language by Nick Didkovsky. Max programming by Georg Hajdu.\n© 2007-20 Nick Didkovsky, Georg Hajdu." + } + + } +, { + "box" : { + "id" : "obj-4", + "maxclass" : "newobj", + "numinlets" : 2, + "numoutlets" : 2, + "outlettype" : [ "int", "int" ], + "patching_rect" : [ 1331.908255577087402, 776.16678512096405, 55.0, 22.0 ], + "text" : "stripnote" + } + + } +, { + "box" : { + "id" : "obj-13", + "maxclass" : "newobj", + "numinlets" : 3, + "numoutlets" : 0, + "patching_rect" : [ 1334.908255577087402, 962.530960738658905, 49.0, 22.0 ], + "text" : "noteout" + } + + } +, { + "box" : { + "id" : "obj-11", + "maxclass" : "newobj", + "numinlets" : 3, + "numoutlets" : 2, + "outlettype" : [ "float", "float" ], + "patching_rect" : [ 1320.408255577087402, 854.309640526771545, 78.0, 22.0 ], + "text" : "makenote 60" + } + + } +, { + "box" : { + "id" : "obj-1", + "maxclass" : "newobj", + "numinlets" : 1, + "numoutlets" : 3, + "outlettype" : [ "int", "int", "int" ], + "patching_rect" : [ 1338.908255577087402, 720.738213181495667, 41.0, 22.0 ], + "text" : "notein" + } + + } +, { + "box" : { + "bgmode" : 0, + "border" : 1, + "clickthrough" : 0, + "enablehscroll" : 0, + "enablevscroll" : 0, + "id" : "obj-60", + "lockeddragscroll" : 1, + "maxclass" : "bpatcher", + "name" : "maxscore.bcanvas.maxpat", + "numinlets" : 2, + "numoutlets" : 4, + "offset" : [ 0.0, 0.0 ], + "outlettype" : [ "", "", "", "" ], + "patching_rect" : [ 515.0, 40.0, 1000.0, 200.0 ], + "presentation" : 1, + "presentation_rect" : [ 515.0, 40.0, 1000.0, 200.0 ], + "prototypename" : "bcanvas", + "varname" : "bcanvas", + "viewvisibility" : 1 + } + + } +, { + "box" : { + "bgmode" : 0, + "border" : 1, + "clickthrough" : 0, + "enablehscroll" : 0, + "enablevscroll" : 0, + "id" : "obj-57", + "lockeddragscroll" : 1, + "maxclass" : "bpatcher", + "name" : "maxscore.bcanvas.maxpat", + "numinlets" : 2, + "numoutlets" : 4, + "offset" : [ 0.0, 0.0 ], + "outlettype" : [ "", "", "", "" ], + "patching_rect" : [ 515.0, 271.0, 800.0, 800.0 ], + "presentation" : 1, + "presentation_rect" : [ 515.0, 271.0, 800.0, 800.0 ], + "prototypename" : "bcanvas", + "varname" : "bcanvas[1]", + "viewvisibility" : 1 + } + + } + ], + "lines" : [ { + "patchline" : { + "destination" : [ "obj-4", 1 ], + "midpoints" : [ 1359.408255577087402, 762.0, 1377.408255577087402, 762.0 ], + "source" : [ "obj-1", 1 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-4", 0 ], + "midpoints" : [ 1348.408255577087402, 762.0, 1341.408255577087402, 762.0 ], + "source" : [ "obj-1", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-61", 0 ], + "midpoints" : [ 1497.952378869056702, 774.0, 1599.412282943725586, 774.0 ], + "source" : [ "obj-100", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-16", 0 ], + "midpoints" : [ 72.5, 840.0, 51.5, 840.0 ], + "source" : [ "obj-101", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-109", 1 ], + "midpoints" : [ 252.5, 798.0, 240.0, 798.0, 240.0, 771.0, 336.0, 771.0, 336.0, 795.0, 324.5, 795.0 ], + "source" : [ "obj-102", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-58", 0 ], + "midpoints" : [ 1745.003126382827759, 213.0, 1745.003126382827759, 213.0 ], + "source" : [ "obj-103", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-88", 0 ], + "midpoints" : [ 235.5, 183.0, 144.0, 183.0, 144.0, 39.0, 93.0, 39.0, 93.0, 30.0, 41.5, 30.0 ], + "source" : [ "obj-105", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-16", 1 ], + "midpoints" : [ 176.5, 852.0, 162.0, 852.0, 162.0, 825.0, 312.0, 825.0, 312.0, 840.0, 464.5, 840.0 ], + "source" : [ "obj-106", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-71", 1 ], + "midpoints" : [ 214.75, 96.0, 215.253998041152954, 96.0 ], + "order" : 1, + "source" : [ "obj-107", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-84", 1 ], + "midpoints" : [ 214.75, 105.0, 249.0, 105.0, 249.0, 144.0, 463.25, 144.0 ], + "order" : 0, + "source" : [ "obj-107", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-109", 0 ], + "midpoints" : [ 176.5, 798.0, 176.5, 798.0 ], + "source" : [ "obj-108", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-106", 0 ], + "midpoints" : [ 176.5, 822.0, 176.5, 822.0 ], + "source" : [ "obj-109", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-13", 1 ], + "midpoints" : [ 1388.908255577087402, 948.0, 1359.408255577087402, 948.0 ], + "source" : [ "obj-11", 1 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-13", 0 ], + "midpoints" : [ 1329.908255577087402, 948.0, 1344.408255577087402, 948.0 ], + "source" : [ "obj-11", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-108", 0 ], + "midpoints" : [ 176.606070876121521, 768.0, 176.5, 768.0 ], + "source" : [ "obj-110", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-29", 0 ], + "midpoints" : [ 286.0, 1041.0, 150.0, 1041.0, 150.0, 900.0, 6.0, 900.0, 6.0, 606.0, 123.571389079093933, 606.0 ], + "source" : [ "obj-111", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-113", 0 ], + "midpoints" : [ 721.004536628723145, 1122.0, 721.004536628723145, 1122.0 ], + "source" : [ "obj-112", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-117", 1 ], + "midpoints" : [ 721.004536628723145, 1167.0, 793.004536628723145, 1167.0 ], + "source" : [ "obj-113", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-48", 0 ], + "midpoints" : [ 271.5, 144.0, 398.0, 144.0 ], + "source" : [ "obj-114", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-117", 0 ], + "midpoints" : [ 645.004536628723145, 1167.0, 645.004536628723145, 1167.0 ], + "source" : [ "obj-116", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-119", 0 ], + "midpoints" : [ 645.004536628723145, 1194.0, 645.004536628723145, 1194.0 ], + "source" : [ "obj-117", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-116", 0 ], + "midpoints" : [ 645.110607504844666, 1122.0, 645.004536628723145, 1122.0 ], + "source" : [ "obj-118", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-44", 1 ], + "source" : [ "obj-119", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-51", 0 ], + "midpoints" : [ 1725.003126263618469, 570.0, 1725.003126263618469, 570.0 ], + "source" : [ "obj-12", 1 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-61", 1 ], + "midpoints" : [ 1682.003126263618469, 657.0, 1812.0, 657.0, 1812.0, 774.0, 1938.0, 774.0, 1938.0, 783.0, 1949.412282943725586, 783.0 ], + "source" : [ "obj-12", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-96", 1 ], + "source" : [ "obj-121", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-117", 0 ], + "source" : [ "obj-125", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-29", 0 ], + "midpoints" : [ 286.0, 1080.0, 228.0, 1080.0, 228.0, 900.0, 6.0, 900.0, 6.0, 606.0, 123.571389079093933, 606.0 ], + "source" : [ "obj-135", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-138", 1 ], + "midpoints" : [ 329.0, 981.0, 394.303703665733337, 981.0 ], + "order" : 0, + "source" : [ "obj-137", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-139", 1 ], + "midpoints" : [ 329.0, 981.0, 301.251747012138367, 981.0 ], + "order" : 1, + "source" : [ "obj-137", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-111", 0 ], + "midpoints" : [ 383.803703665733337, 1011.0, 286.0, 1011.0 ], + "source" : [ "obj-138", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-135", 0 ], + "midpoints" : [ 290.751747012138367, 1011.0, 273.0, 1011.0, 273.0, 1047.0, 286.0, 1047.0 ], + "source" : [ "obj-139", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-29", 0 ], + "midpoints" : [ 33.0, 429.0, 0.0, 429.0, 0.0, 558.0, 123.571389079093933, 558.0 ], + "source" : [ "obj-14", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-143", 0 ], + "midpoints" : [ 373.0, 939.0, 383.803703665733337, 939.0 ], + "source" : [ "obj-141", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-138", 0 ], + "midpoints" : [ 383.803703665733337, 969.0, 383.803703665733337, 969.0 ], + "source" : [ "obj-143", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-139", 0 ], + "midpoints" : [ 290.751747012138367, 969.0, 290.751747012138367, 969.0 ], + "source" : [ "obj-144", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-144", 0 ], + "midpoints" : [ 290.751747012138367, 924.0, 290.751747012138367, 924.0 ], + "source" : [ "obj-145", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-53", 0 ], + "midpoints" : [ 1442.10474967956543, 315.0, 1644.0, 315.0, 1644.0, 336.0, 1657.003126263618469, 336.0 ], + "order" : 0, + "source" : [ "obj-15", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-99", 0 ], + "midpoints" : [ 1442.10474967956543, 300.0, 1442.10474967956543, 300.0 ], + "order" : 1, + "source" : [ "obj-15", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-29", 0 ], + "midpoints" : [ 51.5, 891.0, 6.0, 891.0, 6.0, 606.0, 123.571389079093933, 606.0 ], + "order" : 0, + "source" : [ "obj-16", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-65", 0 ], + "midpoints" : [ 51.5, 891.0, 51.5, 891.0 ], + "order" : 2, + "source" : [ "obj-16", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-90", 0 ], + "midpoints" : [ 51.5, 969.0, 27.0, 969.0, 27.0, 1014.0, 51.5, 1014.0 ], + "order" : 1, + "source" : [ "obj-16", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-98", 0 ], + "midpoints" : [ 279.071389079093933, 96.0, 246.0, 96.0, 246.0, 105.0, 144.0, 105.0, 144.0, 234.0, 119.5, 234.0 ], + "source" : [ "obj-17", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-53", 0 ], + "midpoints" : [ 1536.775863170623779, 360.0, 1632.0, 360.0, 1632.0, 345.0, 1657.003126263618469, 345.0 ], + "source" : [ "obj-18", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-20", 0 ], + "midpoints" : [ 279.5, 525.0, 279.0, 525.0, 279.0, 534.0, 344.0, 534.0 ], + "source" : [ "obj-19", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-56", 0 ], + "midpoints" : [ 1760.003126263618469, 495.0, 1682.003126263618469, 495.0 ], + "source" : [ "obj-2", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-29", 0 ], + "midpoints" : [ 344.0, 594.0, 123.571389079093933, 594.0 ], + "source" : [ "obj-20", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-29", 0 ], + "midpoints" : [ 29.825174450874329, 330.0, 0.0, 330.0, 0.0, 558.0, 123.571389079093933, 558.0 ], + "order" : 0, + "source" : [ "obj-21", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-39", 0 ], + "midpoints" : [ 29.825174450874329, 330.0, 6.0, 330.0, 6.0, 429.0, 24.884616851806641, 429.0 ], + "order" : 1, + "source" : [ "obj-21", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-30", 0 ], + "midpoints" : [ 254.0, 591.0, 117.0, 591.0, 117.0, 606.0, 72.5, 606.0 ], + "source" : [ "obj-26", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-19", 2 ], + "midpoints" : [ 498.5, 495.0, 471.5, 495.0 ], + "source" : [ "obj-27", 1 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-45", 0 ], + "midpoints" : [ 349.5, 483.0, 135.0, 483.0, 135.0, 519.0, 96.0, 519.0, 96.0, 570.0, 117.0, 570.0, 117.0, 606.0, 29.576915740966797, 606.0 ], + "order" : 2, + "source" : [ "obj-27", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-52", 0 ], + "midpoints" : [ 349.5, 483.0, 327.0, 483.0, 327.0, 411.0, 150.0, 411.0, 150.0, 357.0, 174.0, 357.0, 174.0, 54.0, 132.0, 54.0, 132.0, 0.0, 155.0, 0.0 ], + "order" : 1, + "source" : [ "obj-27", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-96", 0 ], + "order" : 0, + "source" : [ "obj-27", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-25", 0 ], + "midpoints" : [ 356.904722412427304, 633.0, 267.0, 633.0, 267.0, 645.0, 266.961517333984375, 645.0 ], + "order" : 0, + "source" : [ "obj-29", 2 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-30", 1 ], + "midpoints" : [ 240.238055745760619, 633.0, 165.0, 633.0, 165.0, 645.0, 163.5, 645.0 ], + "order" : 2, + "source" : [ "obj-29", 1 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-30", 0 ], + "midpoints" : [ 123.571389079093933, 633.0, 72.5, 633.0 ], + "order" : 0, + "source" : [ "obj-29", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-33", 0 ], + "midpoints" : [ 240.238055745760619, 633.0, 192.0, 633.0, 192.0, 645.0, 189.003998041152954, 645.0 ], + "order" : 1, + "source" : [ "obj-29", 1 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-36", 0 ], + "midpoints" : [ 356.904722412427304, 633.0, 174.0, 633.0, 174.0, 678.0, 123.571389079093933, 678.0 ], + "order" : 1, + "source" : [ "obj-29", 2 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-5", 0 ], + "midpoints" : [ 473.571389079093933, 663.0, 432.0, 663.0, 432.0, 672.0, 303.44868214925134, 672.0 ], + "order" : 1, + "source" : [ "obj-29", 3 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-6", 0 ], + "midpoints" : [ 473.571389079093933, 663.0, 432.0, 663.0, 432.0, 672.0, 403.363216638565063, 672.0 ], + "order" : 0, + "source" : [ "obj-29", 3 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-60", 1 ], + "midpoints" : [ 240.238055745760619, 633.0, 501.0, 633.0, 501.0, 483.0, 510.0, 483.0, 510.0, 231.0, 501.0, 231.0, 501.0, 27.0, 1505.5, 27.0 ], + "order" : 0, + "source" : [ "obj-29", 1 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-60", 0 ], + "midpoints" : [ 123.571389079093933, 633.0, 111.0, 633.0, 111.0, 597.0, 117.0, 597.0, 117.0, 558.0, 96.0, 558.0, 96.0, 510.0, 231.0, 510.0, 231.0, 477.0, 327.0, 477.0, 327.0, 402.0, 501.0, 402.0, 501.0, 36.0, 524.5, 36.0 ], + "order" : 1, + "source" : [ "obj-29", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-29", 0 ], + "midpoints" : [ 72.5, 675.0, 60.0, 675.0, 60.0, 606.0, 123.571389079093933, 606.0 ], + "source" : [ "obj-30", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-70", 1 ], + "midpoints" : [ 101.5, 1062.0, 206.5, 1062.0 ], + "source" : [ "obj-31", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-29", 0 ], + "midpoints" : [ 36.5, 357.0, 0.0, 357.0, 0.0, 558.0, 123.571389079093933, 558.0 ], + "source" : [ "obj-34", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-29", 0 ], + "midpoints" : [ 21.0, 558.0, 123.571389079093933, 558.0 ], + "source" : [ "obj-35", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-19", 0 ], + "midpoints" : [ 248.003998041152954, 486.0, 279.5, 486.0 ], + "order" : 1, + "source" : [ "obj-37", 1 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-47", 0 ], + "midpoints" : [ 248.003998041152954, 477.0, 327.0, 477.0, 327.0, 411.0, 150.0, 411.0, 150.0, 363.0, 183.0, 363.0, 183.0, 192.0, 273.0, 192.0, 273.0, 174.0, 298.0, 174.0 ], + "order" : 0, + "source" : [ "obj-37", 1 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-69", 0 ], + "midpoints" : [ 1442.10474967956543, 30.0, 1422.0, 30.0, 1422.0, 15.0, 1382.5, 15.0 ], + "source" : [ "obj-38", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-35", 0 ], + "midpoints" : [ 24.884616851806641, 480.0, 21.0, 480.0 ], + "order" : 0, + "source" : [ "obj-39", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-63", 0 ], + "midpoints" : [ 24.884616851806641, 468.0, 8.350745558738708, 468.0 ], + "order" : 1, + "source" : [ "obj-39", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-11", 0 ], + "midpoints" : [ 1341.408255577087402, 801.0, 1329.908255577087402, 801.0 ], + "source" : [ "obj-4", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-95", 0 ], + "midpoints" : [ 1377.408255577087402, 801.0, 1353.0, 801.0, 1353.0, 807.0, 1352.908255577087402, 807.0 ], + "source" : [ "obj-4", 1 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-27", 1 ], + "midpoints" : [ 405.5, 326.278194487094879, 498.5, 326.278194487094879 ], + "source" : [ "obj-41", 1 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-27", 0 ], + "midpoints" : [ 369.5, 326.278194487094879, 498.0, 326.278194487094879, 498.0, 414.0, 349.5, 414.0 ], + "source" : [ "obj-41", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-37", 0 ], + "midpoints" : [ 174.5, 417.0, 189.003998041152954, 417.0 ], + "source" : [ "obj-42", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-41", 1 ], + "midpoints" : [ 394.5, 255.0, 426.0, 255.0, 426.0, 249.0, 498.0, 249.0, 498.0, 297.0, 417.0, 297.0, 417.0, 288.0, 405.5, 288.0 ], + "source" : [ "obj-43", 1 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-41", 0 ], + "source" : [ "obj-43", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-24", 0 ], + "midpoints" : [ 204.5, 1197.0, 435.0, 1197.0, 435.0, 1188.0, 204.5, 1188.0 ], + "source" : [ "obj-44", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-16", 0 ], + "midpoints" : [ 29.576915740966797, 840.0, 51.5, 840.0 ], + "source" : [ "obj-45", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-49", 0 ], + "midpoints" : [ 1959.109176158905029, 720.0, 1951.109176158905029, 720.0 ], + "source" : [ "obj-46", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-114", 0 ], + "midpoints" : [ 298.0, 204.0, 267.0, 204.0, 267.0, 144.0, 249.0, 144.0, 249.0, 105.0, 271.5, 105.0 ], + "order" : 1, + "source" : [ "obj-47", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-48", 1 ], + "midpoints" : [ 298.0, 213.0, 375.0, 213.0, 375.0, 144.0, 408.5, 144.0 ], + "order" : 0, + "source" : [ "obj-47", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-84", 1 ], + "midpoints" : [ 398.0, 192.0, 429.0, 192.0, 429.0, 177.0, 463.25, 177.0 ], + "source" : [ "obj-48", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-75", 3 ], + "midpoints" : [ 2040.75203330176214, 774.0, 2078.109115170623681, 774.0 ], + "source" : [ "obj-49", 5 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-75", 2 ], + "midpoints" : [ 2004.89489044461925, 774.0, 2050.775781837290197, 774.0 ], + "source" : [ "obj-49", 3 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-75", 1 ], + "midpoints" : [ 1986.966319016047919, 759.0, 2023.442448503956939, 759.0 ], + "source" : [ "obj-49", 2 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-75", 0 ], + "midpoints" : [ 1969.03774758747636, 750.0, 1992.0, 750.0, 1992.0, 783.0, 1996.109115170623681, 783.0 ], + "source" : [ "obj-49", 1 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-78", 0 ], + "midpoints" : [ 1951.109176158905029, 750.0, 1951.109176170623869, 750.0 ], + "source" : [ "obj-49", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-71", 0 ], + "midpoints" : [ 155.0, 105.0, 204.753998041152954, 105.0 ], + "order" : 1, + "source" : [ "obj-52", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-84", 0 ], + "midpoints" : [ 155.0, 57.0, 452.75, 57.0 ], + "order" : 0, + "source" : [ "obj-52", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-56", 0 ], + "midpoints" : [ 1657.003126263618469, 498.0, 1682.003126263618469, 498.0 ], + "source" : [ "obj-53", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-49", 0 ], + "midpoints" : [ 1927.775863170623779, 720.0, 1951.109176158905029, 720.0 ], + "source" : [ "obj-54", 2 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-67", 0 ], + "midpoints" : [ 1834.775863170623779, 774.0, 1938.0, 774.0, 1938.0, 783.0, 1968.0, 783.0, 1968.0, 867.0, 1931.775863170623779, 867.0 ], + "source" : [ "obj-54", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-87", 0 ], + "midpoints" : [ 1669.10919725894928, 213.0, 1669.003126382827759, 213.0 ], + "source" : [ "obj-55", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-12", 0 ], + "midpoints" : [ 1682.003126263618469, 537.0, 1682.003126263618469, 537.0 ], + "source" : [ "obj-56", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-61", 0 ], + "midpoints" : [ 524.5, 1083.0, 1554.0, 1083.0, 1554.0, 783.0, 1599.412282943725586, 783.0 ], + "source" : [ "obj-57", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-94", 1 ], + "midpoints" : [ 1745.003126382827759, 258.0, 1817.003126382827759, 258.0 ], + "source" : [ "obj-58", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-29", 0 ], + "midpoints" : [ 524.5, 258.0, 498.0, 258.0, 498.0, 414.0, 327.0, 414.0, 327.0, 486.0, 135.0, 486.0, 135.0, 519.0, 96.0, 519.0, 96.0, 570.0, 123.571389079093933, 570.0 ], + "source" : [ "obj-60", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-54", 0 ], + "midpoints" : [ 1832.745616277058843, 822.0, 1968.0, 822.0, 1968.0, 777.0, 1926.0, 777.0, 1926.0, 687.0, 1812.0, 687.0, 1812.0, 648.0, 1834.775863170623779, 648.0 ], + "source" : [ "obj-61", 2 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-57", 1 ], + "midpoints" : [ 1716.078949610392328, 822.0, 1398.0, 822.0, 1398.0, 267.0, 1305.5, 267.0 ], + "source" : [ "obj-61", 1 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-57", 0 ], + "midpoints" : [ 1599.412282943725586, 813.0, 1398.0, 813.0, 1398.0, 258.0, 524.5, 258.0 ], + "source" : [ "obj-61", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-68", 0 ], + "midpoints" : [ 1491.952378869056702, 456.0, 1543.126837849617004, 456.0 ], + "source" : [ "obj-62", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-29", 0 ], + "midpoints" : [ 8.350745558738708, 606.0, 123.571389079093933, 606.0 ], + "source" : [ "obj-63", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-105", 0 ], + "midpoints" : [ 87.5, 267.0, 213.0, 267.0, 213.0, 153.0, 235.5, 153.0 ], + "order" : 0, + "source" : [ "obj-64", 1 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-107", 0 ], + "midpoints" : [ 87.5, 267.0, 180.0, 267.0, 180.0, 66.0, 214.75, 66.0 ], + "order" : 1, + "source" : [ "obj-64", 1 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-16", 0 ], + "midpoints" : [ 74.5, 291.0, 0.0, 291.0, 0.0, 558.0, 117.0, 558.0, 117.0, 606.0, 48.0, 606.0, 48.0, 849.0, 51.5, 849.0 ], + "source" : [ "obj-64", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-21", 0 ], + "midpoints" : [ 87.5, 291.0, 29.825174450874329, 291.0 ], + "order" : 3, + "source" : [ "obj-64", 1 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-52", 0 ], + "midpoints" : [ 87.5, 267.0, 144.0, 267.0, 144.0, 39.0, 132.0, 39.0, 132.0, 0.0, 155.0, 0.0 ], + "order" : 2, + "source" : [ "obj-64", 1 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-100", 0 ], + "midpoints" : [ 1432.909500598907471, 639.0, 1497.952378869056702, 639.0 ], + "order" : 0, + "source" : [ "obj-66", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-62", 0 ], + "midpoints" : [ 1432.909500598907471, 411.0, 1491.952378869056702, 411.0 ], + "order" : 1, + "source" : [ "obj-66", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-53", 0 ], + "midpoints" : [ 1543.126837849617004, 489.0, 1632.0, 489.0, 1632.0, 345.0, 1657.003126263618469, 345.0 ], + "order" : 0, + "source" : [ "obj-68", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-74", 0 ], + "midpoints" : [ 1543.126837849617004, 489.0, 1497.952378869056702, 489.0 ], + "order" : 1, + "source" : [ "obj-68", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-86", 0 ], + "midpoints" : [ 1382.5, 51.0, 1413.634706616401672, 51.0 ], + "source" : [ "obj-69", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-44", 0 ], + "midpoints" : [ 51.5, 1122.0, 204.5, 1122.0 ], + "source" : [ "obj-70", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-48", 0 ], + "midpoints" : [ 204.753998041152954, 144.0, 398.0, 144.0 ], + "source" : [ "obj-71", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-49", 0 ], + "midpoints" : [ 1979.775863170623779, 636.0, 2040.0, 636.0, 2040.0, 711.0, 1980.0, 711.0, 1980.0, 717.0, 1951.109176158905029, 717.0 ], + "source" : [ "obj-72", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-53", 0 ], + "midpoints" : [ 1592.025919497013092, 540.0, 1632.0, 540.0, 1632.0, 345.0, 1657.003126263618469, 345.0 ], + "order" : 0, + "source" : [ "obj-73", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-81", 0 ], + "midpoints" : [ 1592.025919497013092, 609.0, 1599.412282943725586, 609.0 ], + "order" : 1, + "source" : [ "obj-73", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-73", 0 ], + "midpoints" : [ 1497.952378869056702, 537.0, 1569.0, 537.0, 1569.0, 510.0, 1592.025919497013092, 510.0 ], + "source" : [ "obj-74", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-67", 2 ], + "midpoints" : [ 2078.109115170623681, 810.0, 2078.109115170623681, 810.0 ], + "source" : [ "obj-75", 2 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-67", 1 ], + "midpoints" : [ 2037.109115170623681, 867.0, 2004.94248917062373, 867.0 ], + "source" : [ "obj-75", 1 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-67", 0 ], + "midpoints" : [ 1996.109115170623681, 867.0, 1931.775863170623779, 867.0 ], + "source" : [ "obj-75", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-61", 0 ], + "midpoints" : [ 1635.775863170623779, 897.0, 1575.0, 897.0, 1575.0, 783.0, 1599.412282943725586, 783.0 ], + "source" : [ "obj-76", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-46", 0 ], + "midpoints" : [ 1906.003126263618469, 861.0, 1968.0, 861.0, 1968.0, 777.0, 1926.0, 777.0, 1926.0, 690.0, 1959.109176158905029, 690.0 ], + "source" : [ "obj-77", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-77", 1 ], + "midpoints" : [ 1971.109176170623869, 825.0, 1926.003126263618469, 825.0 ], + "source" : [ "obj-78", 1 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-77", 0 ], + "midpoints" : [ 1951.109176170623869, 783.0, 1968.0, 783.0, 1968.0, 825.0, 1906.003126263618469, 825.0 ], + "source" : [ "obj-78", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-104", 0 ], + "midpoints" : [ 1513.722424626350403, 105.0, 1450.10474967956543, 105.0 ], + "order" : 1, + "source" : [ "obj-79", 1 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-89", 1 ], + "midpoints" : [ 1513.722424626350403, 84.0, 1536.0, 84.0, 1536.0, 45.0, 1889.003126263618469, 45.0 ], + "order" : 0, + "source" : [ "obj-79", 1 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-29", 0 ], + "midpoints" : [ 176.5, 591.0, 123.571389079093933, 591.0 ], + "source" : [ "obj-8", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-53", 1 ], + "midpoints" : [ 1669.003126382827759, 336.0, 2059.003126263618469, 336.0 ], + "source" : [ "obj-80", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-82", 0 ], + "midpoints" : [ 1599.412282943725586, 696.0, 1613.775863170623779, 696.0 ], + "source" : [ "obj-81", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-76", 0 ], + "midpoints" : [ 1613.775863170623779, 774.0, 1575.0, 774.0, 1575.0, 858.0, 1635.775863170623779, 858.0 ], + "source" : [ "obj-82", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-88", 0 ], + "midpoints" : [ 452.75, 216.0, 144.0, 216.0, 144.0, 39.0, 93.0, 39.0, 93.0, 30.0, 41.5, 30.0 ], + "source" : [ "obj-84", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-82", 1 ], + "midpoints" : [ 1663.775863170623779, 693.0, 1768.775863170623779, 693.0 ], + "source" : [ "obj-85", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-79", 0 ], + "midpoints" : [ 1413.634706616401672, 84.0, 1452.0, 84.0, 1452.0, 54.0, 1465.722424626350403, 54.0 ], + "source" : [ "obj-86", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-94", 0 ], + "midpoints" : [ 1669.003126382827759, 261.0, 1669.003126382827759, 261.0 ], + "source" : [ "obj-87", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-71", 1 ], + "midpoints" : [ 41.5, 69.0, 192.0, 69.0, 192.0, 105.0, 215.253998041152954, 105.0 ], + "order" : 0, + "source" : [ "obj-88", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-83", 0 ], + "midpoints" : [ 41.5, 81.0, 58.0, 81.0 ], + "order" : 1, + "source" : [ "obj-88", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-29", 1 ], + "midpoints" : [ 220.748252987861633, 729.0, 495.0, 729.0, 495.0, 603.0, 473.571389079093933, 603.0 ], + "source" : [ "obj-9", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-70", 0 ], + "midpoints" : [ 51.5, 1053.0, 51.5, 1053.0 ], + "source" : [ "obj-90", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-102", 0 ], + "midpoints" : [ 252.5, 768.0, 252.5, 768.0 ], + "source" : [ "obj-91", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-76", 0 ], + "midpoints" : [ 1709.775863170623779, 882.0, 1668.0, 882.0, 1668.0, 858.0, 1635.775863170623779, 858.0 ], + "source" : [ "obj-92", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-80", 0 ], + "midpoints" : [ 1669.003126382827759, 285.0, 1669.003126382827759, 285.0 ], + "source" : [ "obj-94", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-11", 1 ], + "midpoints" : [ 1352.908255577087402, 849.0, 1359.408255577087402, 849.0 ], + "source" : [ "obj-95", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-19", 1 ], + "source" : [ "obj-96", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-64", 0 ], + "midpoints" : [ 119.5, 267.0, 60.0, 267.0, 60.0, 237.0, 74.5, 237.0 ], + "source" : [ "obj-98", 0 ] + } + + } + ], + "parameters" : { + "obj-30::obj-31" : [ "live.tab[2]", "live.tab", 0 ], + "obj-37" : [ "live.tab[10]", "live.tab[1]", 0 ], + "obj-30::obj-38" : [ "live.tab[3]", "live.tab", 0 ], + "parameterbanks" : { + + } + + } +, + "dependency_cache" : [ { + "name" : "maxscore.bcanvas.maxpat", + "bootpath" : "~/Documents/Max 8/Packages/MaxScore/patchers/abstractions", + "patcherrelativepath" : "../../Max 8/Packages/MaxScore/patchers/abstractions", + "type" : "JSON", + "implicit" : 1 + } +, { + "name" : "pane.js", + "bootpath" : "~/Documents/Max 8/Packages/MaxScore/javascript", + "patcherrelativepath" : "../../Max 8/Packages/MaxScore/javascript", + "type" : "TEXT", + "implicit" : 1 + } +, { + "name" : "picster-select.js", + "bootpath" : "~/Documents/Max 8/Packages/MaxScore/javascript", + "patcherrelativepath" : "../../Max 8/Packages/MaxScore/javascript", + "type" : "TEXT", + "implicit" : 1 + } +, { + "name" : "render2canvas.js", + "bootpath" : "~/Documents/Max 8/Packages/MaxScore/javascript", + "patcherrelativepath" : "../../Max 8/Packages/MaxScore/javascript", + "type" : "TEXT", + "implicit" : 1 + } +, { + "name" : "maxscore.proportionalNotation.js", + "bootpath" : "~/Documents/Max 8/Packages/MaxScore/javascript", + "patcherrelativepath" : "../../Max 8/Packages/MaxScore/javascript", + "type" : "TEXT", + "implicit" : 1 + } +, { + "name" : "MaxScoreKeyMap.txt", + "bootpath" : "~/Documents/Max 8/Packages/MaxScore/patchers/maps", + "patcherrelativepath" : "../../Max 8/Packages/MaxScore/patchers/maps", + "type" : "TEXT", + "implicit" : 1 + } +, { + "name" : "mouseEvents.js", + "bootpath" : "~/Documents/Max 8/Packages/MaxScore/javascript", + "patcherrelativepath" : "../../Max 8/Packages/MaxScore/javascript", + "type" : "TEXT", + "implicit" : 1 + } +, { + "name" : "boxSize.js", + "bootpath" : "~/Documents/Max 8/Packages/MaxScore/javascript", + "patcherrelativepath" : "../../Max 8/Packages/MaxScore/javascript", + "type" : "TEXT", + "implicit" : 1 + } +, { + "name" : "jit.pane.js", + "bootpath" : "~/Documents/Max 8/Packages/MaxScore/javascript", + "patcherrelativepath" : "../../Max 8/Packages/MaxScore/javascript", + "type" : "TEXT", + "implicit" : 1 + } +, { + "name" : "socket.pane.js", + "bootpath" : "~/Documents/Max 8/Packages/MaxScore/javascript", + "patcherrelativepath" : "../../Max 8/Packages/MaxScore/javascript", + "type" : "TEXT", + "implicit" : 1 + } +, { + "name" : "n4m.max-fs.js", + "bootpath" : "C74:/packages/Node For Max/examples/filesystem", + "type" : "TEXT", + "implicit" : 1 + } +, { + "name" : "swissarmyknife.js", + "bootpath" : "~/Documents/Max 8/Packages/MaxScore/javascript", + "patcherrelativepath" : "../../Max 8/Packages/MaxScore/javascript", + "type" : "TEXT", + "implicit" : 1 + } +, { + "name" : "maxscore.canvas.maxpat", + "bootpath" : "~/Documents/Max 8/Packages/MaxScore/patchers/abstractions", + "patcherrelativepath" : "../../Max 8/Packages/MaxScore/patchers/abstractions", + "type" : "JSON", + "implicit" : 1 + } +, { + "name" : "M4L.First.svg", + "bootpath" : "~/Documents/Max 8/Packages/MaxScore/media/Images/svg", + "patcherrelativepath" : "../../Max 8/Packages/MaxScore/media/Images/svg", + "type" : "svg", + "implicit" : 1 + } +, { + "name" : "M4L.Rewind.svg", + "bootpath" : "~/Documents/Max 8/Packages/MaxScore/media/Images/svg", + "patcherrelativepath" : "../../Max 8/Packages/MaxScore/media/Images/svg", + "type" : "svg", + "implicit" : 1 + } +, { + "name" : "M4L.Dec.svg", + "bootpath" : "~/Documents/Max 8/Packages/MaxScore/media/Images/svg", + "patcherrelativepath" : "../../Max 8/Packages/MaxScore/media/Images/svg", + "type" : "svg", + "implicit" : 1 + } +, { + "name" : "M4L.Inc.svg", + "bootpath" : "~/Documents/Max 8/Packages/MaxScore/media/Images/svg", + "patcherrelativepath" : "../../Max 8/Packages/MaxScore/media/Images/svg", + "type" : "svg", + "implicit" : 1 + } +, { + "name" : "M4L.FastForward.svg", + "bootpath" : "~/Documents/Max 8/Packages/MaxScore/media/Images/svg", + "patcherrelativepath" : "../../Max 8/Packages/MaxScore/media/Images/svg", + "type" : "svg", + "implicit" : 1 + } +, { + "name" : "M4L.Last.svg", + "bootpath" : "~/Documents/Max 8/Packages/MaxScore/media/Images/svg", + "patcherrelativepath" : "../../Max 8/Packages/MaxScore/media/Images/svg", + "type" : "svg", + "implicit" : 1 + } +, { + "name" : "pause_wob.svg", + "bootpath" : "~/Documents/Max 8/Packages/MaxScore/media/Images/svg", + "patcherrelativepath" : "../../Max 8/Packages/MaxScore/media/Images/svg", + "type" : "svg", + "implicit" : 1 + } +, { + "name" : "fwdarrow_wob.svg", + "bootpath" : "~/Documents/Max 8/Packages/MaxScore/media/Images/svg", + "patcherrelativepath" : "../../Max 8/Packages/MaxScore/media/Images/svg", + "type" : "svg", + "implicit" : 1 + } +, { + "name" : "continue_wob.svg", + "bootpath" : "~/Documents/Max 8/Packages/MaxScore/media/Images/svg", + "patcherrelativepath" : "../../Max 8/Packages/MaxScore/media/Images/svg", + "type" : "svg", + "implicit" : 1 + } +, { + "name" : "MaxScore.maxpat", + "bootpath" : "~/Documents/Max 8/Packages/MaxScore/patchers/abstractions", + "patcherrelativepath" : "../../Max 8/Packages/MaxScore/patchers/abstractions", + "type" : "JSON", + "implicit" : 1 + } +, { + "name" : "whole.svg", + "bootpath" : "~/Documents/Max 8/Packages/MaxScore/media/Images/svg", + "patcherrelativepath" : "../../Max 8/Packages/MaxScore/media/Images/svg", + "type" : "svg", + "implicit" : 1 + } +, { + "name" : "half.svg", + "bootpath" : "~/Documents/Max 8/Packages/MaxScore/media/Images/svg", + "patcherrelativepath" : "../../Max 8/Packages/MaxScore/media/Images/svg", + "type" : "svg", + "implicit" : 1 + } +, { + "name" : "quarter.svg", + "bootpath" : "~/Documents/Max 8/Packages/MaxScore/media/Images/svg", + "patcherrelativepath" : "../../Max 8/Packages/MaxScore/media/Images/svg", + "type" : "svg", + "implicit" : 1 + } +, { + "name" : "eighth.svg", + "bootpath" : "~/Documents/Max 8/Packages/MaxScore/media/Images/svg", + "patcherrelativepath" : "../../Max 8/Packages/MaxScore/media/Images/svg", + "type" : "svg", + "implicit" : 1 + } +, { + "name" : "16th.svg", + "bootpath" : "~/Documents/Max 8/Packages/MaxScore/media/Images/svg", + "patcherrelativepath" : "../../Max 8/Packages/MaxScore/media/Images/svg", + "type" : "svg", + "implicit" : 1 + } +, { + "name" : "n4m_musicxml2jmsl.js", + "bootpath" : "~/Documents/Max 8/Packages/MaxScore/patchers/node.js/musicxml2jmsl", + "patcherrelativepath" : "../../Max 8/Packages/MaxScore/patchers/node.js/musicxml2jmsl", + "type" : "TEXT", + "implicit" : 1 + } +, { + "name" : "maxscore.makenote.maxpat", + "bootpath" : "~/Documents/Max 8/Packages/MaxScore/patchers/abstractions", + "patcherrelativepath" : "../../Max 8/Packages/MaxScore/patchers/abstractions", + "type" : "JSON", + "implicit" : 1 + } +, { + "name" : "first-int.maxpat", + "bootpath" : "~/Documents/Max 8/Packages/MaxScore/patchers/extensions", + "patcherrelativepath" : "../../Max 8/Packages/MaxScore/patchers/extensions", + "type" : "JSON", + "implicit" : 1 + } +, { + "name" : "maxscore.sax.maxpat", + "bootpath" : "~/Documents/Max 8/Packages/MaxScore/patchers/extensions", + "patcherrelativepath" : "../../Max 8/Packages/MaxScore/patchers/extensions", + "type" : "JSON", + "implicit" : 1 + } +, { + "name" : "selectInterval.maxpat", + "bootpath" : "~/Documents/Max 8/Packages/MaxScore/patchers/extensions", + "patcherrelativepath" : "../../Max 8/Packages/MaxScore/patchers/extensions", + "type" : "JSON", + "implicit" : 1 + } +, { + "name" : "my-LtoColl.maxpat", + "bootpath" : "~/Documents/Max 8/Packages/MaxScore/patchers/extensions", + "patcherrelativepath" : "../../Max 8/Packages/MaxScore/patchers/extensions", + "type" : "JSON", + "implicit" : 1 + } +, { + "name" : "shell.mxo", + "type" : "iLaX" + } +, { + "name" : "sadam.rapidXML.mxo", + "type" : "iLaX" + } + ], + "autosave" : 0, + "styles" : [ { + "name" : "black on white", + "number" : { + "fontsize" : [ 12.0 ], + "fontname" : [ "Arial" ], + "textcolor_inverse" : [ 0.239216, 0.254902, 0.278431, 1.0 ] + } +, + "umenu" : { + "textcolor_inverse" : [ 0.239216, 0.254902, 0.278431, 1.0 ], + "bgfillcolor" : { + "type" : "color", + "color1" : [ 0.862745, 0.870588, 0.878431, 1.0 ], + "color2" : [ 0.290196, 0.309804, 0.301961, 1.0 ], + "color" : [ 1.0, 1.0, 1.0, 1.0 ], + "angle" : 270.0, + "proportion" : 0.39, + "autogradient" : 0 + } + + } +, + "parentstyle" : "", + "multi" : 0 + } +, { + "name" : "caption text", + "default" : { + "fontsize" : [ 11.0 ], + "fontface" : [ 2 ] + } +, + "parentstyle" : "", + "multi" : 0 + } +, { + "name" : "section dividers", + "default" : { + "fontsize" : [ 15.0 ], + "fontname" : [ "Arial" ], + "fontface" : [ 3 ] + } +, + "parentstyle" : "", + "multi" : 0 + } +, { + "name" : "section info reg", + "default" : { + "fontsize" : [ 12.0 ], + "fontname" : [ "Arial" ] + } +, + "parentstyle" : "", + "multi" : 0 + } +, { + "name" : "titles", + "default" : { + "fontsize" : [ 20.0 ], + "fontname" : [ "Arial" ], + "fontface" : [ 1 ] + } +, + "parentstyle" : "", + "multi" : 0 + } + ] + } + +} diff --git a/PRIMAT/README.md b/PRIMAT/README.md new file mode 100644 index 0000000..6ad59f2 --- /dev/null +++ b/PRIMAT/README.md @@ -0,0 +1,12 @@ +# BachNetInteraktiv v. 1.0 +Sascha Etezazi, 2020 + +Max 8 patch for realtime visualisation and auralisation of "BachNet" (Leemhuis et al., 2019). + +To run open "LiveBachGenerator.maxpatch" (in presentation mode) with Max. Input either via MIDI Keyboard or mouse. Each calculation takes around 1.7s. + +Requires: +- MaxScore (http://www.computermusicnotation.com/downloadmaxscore/) +- Java +- Python3 (EXPECTED BY execute.sh AT /usr/local/bin/python3) +- PyTorch \ No newline at end of file diff --git a/PRIMAT/beam_search.xml b/PRIMAT/beam_search.xml new file mode 100644 index 0000000..807998e --- /dev/null +++ b/PRIMAT/beam_search.xml @@ -0,0 +1,450 @@ + + + + + JMSLMaxScore-53 (None) + + JMSLMaxScore-53 (None) + + Melody: None +Arrangement: BachNet (2020) + + 2020-09-10 + music21 v.6.1.0 + + + + + 7 + 40 + + + + + + + + + + + + + + + + + + + + + + 10080 + + 0 + + + + G + 2 + + + + + + quarter + 90 + + + + + + + C + 4 + + 10080 + quarter + + + + D + 4 + + 10080 + quarter + + + + E + 4 + + 10080 + quarter + + + + F + 4 + + 10080 + quarter + + + + + + + G + 4 + + 10080 + quarter + + + + F + 4 + + 10080 + quarter + + + + E + 4 + + 10080 + quarter + + + + D + 4 + + 10080 + quarter + + + light-heavy + + + + + + + + + 10080 + + 0 + + + + G + 2 + + + + + + quarter + 90 + + + + + + + G + 3 + + 10080 + quarter + + + + G + 3 + + 10080 + quarter + + + + C + 4 + + 10080 + quarter + + + + D + 4 + + 10080 + quarter + + + + + + + C + 4 + + 10080 + quarter + + + + D + 4 + + 10080 + quarter + + + + D + 4 + + 2520 + 16th + begin + begin + + + + E + 4 + + 2520 + 16th + end + end + + + + F + 4 + + 15120 + quarter + + + + light-heavy + + + + + + + + + 10080 + + 0 + + + + F + 4 + + + + + + quarter + 90 + + + + + + + E + 3 + + 10080 + quarter + + + + G + 3 + + 10080 + quarter + + + + G + 3 + + 10080 + quarter + + + + A + 3 + + 10080 + quarter + + + + + + + G + 3 + + 10080 + quarter + + + + A + 3 + + 2520 + 16th + begin + begin + + + + G + 3 + + 2520 + 16th + continue + end + + + + A + 3 + + 5040 + eighth + end + + + + B + 3 + + 20160 + half + + + light-heavy + + + + + + + + + 10080 + + 0 + + + + F + 4 + + + + + + quarter + 90 + + + + + + + C + 3 + + 10080 + quarter + + + + B + 2 + + 10080 + quarter + + + + C + 3 + + 10080 + quarter + + + + D + 3 + + 10080 + quarter + + + + + + + E + 3 + + 10080 + quarter + + + + F + 3 + + 10080 + quarter + + + + G + 3 + + 20160 + half + + + light-heavy + + + + diff --git a/PRIMAT/checkpoints/README.md b/PRIMAT/checkpoints/README.md new file mode 100644 index 0000000..d0aa5ba --- /dev/null +++ b/PRIMAT/checkpoints/README.md @@ -0,0 +1 @@ +# Insert your PyTorch Checkpoints here and refer to them in the inference.py \ No newline at end of file diff --git a/PRIMAT/data.py b/PRIMAT/data.py new file mode 100644 index 0000000..f2042bd --- /dev/null +++ b/PRIMAT/data.py @@ -0,0 +1,404 @@ +from copy import deepcopy + +import math +import os +import random +import shutil +from glob import glob + +import torch +from music21 import converter +from music21.analysis.discrete import Ambitus +from music21.corpus import chorales +from music21.expressions import Fermata +# from music21.key import KeySignature, Key +from music21.key import Key, KeySignature +from music21.meter import TimeSignature +from music21.note import Note, Rest +from torch.utils.data import Dataset, RandomSampler, BatchSampler, SequentialSampler, DataLoader +from tqdm import tqdm + +indices_parts = { + 'is_continued': 0, + 'is_rest': 1 +} + +indices_extra = { + 'has_fermata': 0, + 'has_time_signature_3/4': 1, + 'has_time_signature_4/4': 2, + 'has_time_signature_3/2': 3, + 'time_pos': 4, + 'pitch_offset': 5, + 'has_sharps_0': 6, + 'has_sharps_1': 7, + 'has_sharps_2': 8, + 'has_sharps_3': 9, + 'has_sharps_4': 10, + 'has_sharps_5': 11, + 'has_sharps_6': 12, + 'has_sharps_7': 13, + 'has_sharps_8': 14, + 'has_sharps_9': 15, + 'has_sharps_10': 16, + 'has_sharps_11': 17 +} + +trans = 0 + +min_pitches = { + 'bass': 36 - trans, + 'tenor': 48 - trans, + 'alto': 53 - trans, + 'soprano': 57 - trans +} + +max_pitches = { + 'bass': 64 + trans, + 'tenor': 69 + trans, + 'alto': 74 + trans, + 'soprano': 81 + trans +} + +pitch_sizes_parts = {} +for part_name in min_pitches.keys(): + pitch_sizes_parts[part_name] = max_pitches[part_name] - min_pitches[part_name] + 1 + +ambitus = Ambitus() + + +class ChoralesDataset(Dataset): + def __init__(self, root_dir, context_radius=32): + self.root_dir = root_dir + self.context_radius = context_radius + + # Make empty intros for each part + self.data = { + 'soprano': [torch.zeros((context_radius, pitch_sizes_parts['soprano'] + len(indices_parts)))], + 'tenor': [torch.zeros((context_radius, pitch_sizes_parts['tenor'] + len(indices_parts)))], + 'alto': [torch.zeros((context_radius, pitch_sizes_parts['alto'] + len(indices_parts)))], + 'bass': [torch.zeros((context_radius, pitch_sizes_parts['bass'] + len(indices_parts)))], + 'extra': [torch.zeros((context_radius, len(indices_extra)))] + } + + # Concat all pieces into large tensors for each part + for file_path in sorted(glob(os.path.join(self.root_dir, '*.pt'))): + data = torch.load(file_path)['data'] + for part_name, part_data in data.items(): + self.data[part_name].append(torch.cat([part_data, torch.zeros((context_radius, part_data.shape[1]))], dim=0)) + + for part_name, part_data in self.data.items(): + self.data[part_name] = torch.cat(part_data, dim=0) + + def __len__(self): + return self.data['soprano'].shape[0] - 2 * self.context_radius + + def __getitem__(self, idx): + # Return windowed parts from dataset for training and correct "pitch classes" as targets + return { + 'soprano': self.data['soprano'][idx:idx + 2 * self.context_radius + 1], + 'alto': self.data['alto'][idx:idx + self.context_radius], + 'tenor': self.data['tenor'][idx:idx + self.context_radius], + 'bass': self.data['bass'][idx:idx + self.context_radius], + + 'bass_with_context': self.data['bass'][idx:idx + 2 * self.context_radius + 1], + 'extra': self.data['extra'][idx:idx + 2 * self.context_radius + 1] + }, { + 'alto': torch.argmax(self.data['alto'][idx + self.context_radius]), + 'tenor': torch.argmax(self.data['tenor'][idx + self.context_radius]), + 'bass': torch.argmax(self.data['bass'][idx + self.context_radius]) + } + + +def generate_data_inference(time_grid, soprano_path): + stream = converter.parse(soprano_path) + + length = math.ceil(stream.highestTime / time_grid) + data = { + 'extra': torch.zeros((length, len(indices_extra))), + 'soprano': torch.zeros((length, pitch_sizes_parts['soprano'] + len(indices_parts))) + } + keys = list(stream.flat.getElementsByClass(Key)) + if len(keys) > 0: + num_sharps = keys[0].sharps + num_sharps = (num_sharps + 12) % 12 + else: + key_sigs = list(stream.flat.getElementsByClass(KeySignature)) + if len(key_sigs) > 0: + num_sharps = key_sigs[0].sharps + num_sharps = (num_sharps + 12) % 12 + else: + num_sharps = 0 + + data['extra'][:, num_sharps + indices_extra['has_sharps_0']] = 1 + + # Iterate through all musical elements in current voice stream + for element in stream.flat: + offset = int(element.offset / time_grid) + + if type(element) == Note: + # Skip grace notes + if element.duration.quarterLength == 0: + continue + + pitch = element.pitch.midi - min_pitches['soprano'] + len(indices_parts) + duration = int(element.duration.quarterLength / time_grid) + + # Store pitch and ties + data['soprano'][offset, pitch] = 1 + data['soprano'][offset + 1:offset + duration, indices_parts['is_continued']] = 1 + + # Fermata + if any([type(e) == Fermata for e in element.expressions]): + data['extra'][offset, indices_extra['has_fermata']] = 1 + + if type(element) == Rest: + duration = int(element.duration.quarterLength / time_grid) + data['soprano'][offset, indices_parts['is_rest']] = 1 + data['soprano'][offset + 1:offset + duration, indices_parts['is_continued']] = 1 + + if type(element) == TimeSignature: + if element.ratioString == '3/4': + data['extra'][offset:, indices_extra['has_time_signature_3/4']] = 1 + data['extra'][offset:, indices_extra['has_time_signature_4/4']] = 0 + data['extra'][offset:, indices_extra['has_time_signature_3/2']] = 0 + elif element.ratioString == '4/4': + data['extra'][offset:, indices_extra['has_time_signature_3/4']] = 0 + data['extra'][offset:, indices_extra['has_time_signature_4/4']] = 1 + data['extra'][offset:, indices_extra['has_time_signature_3/2']] = 0 + elif element.ratioString == '3/2': + data['extra'][offset:, indices_extra['has_time_signature_3/4']] = 0 + data['extra'][offset:, indices_extra['has_time_signature_4/4']] = 0 + data['extra'][offset:, indices_extra['has_time_signature_3/2']] = 1 + + measure_offsets = [o / time_grid for o in stream.measureOffsetMap().keys()] + cur_offset = stream.flat.notesAndRests[0].beat + data['extra'][0, indices_extra['time_pos']] = cur_offset + for offset in range(1, length): + if offset in measure_offsets: + cur_offset = 1 + else: + cur_offset += time_grid + data['extra'][offset, indices_extra['time_pos']] = cur_offset + + return { + 'data': data, + 'metadata': stream.metadata, + } + + +def _generate_data_training(time_grid, root_dir, overwrite, split): + target_dir = os.path.join(root_dir, f'time_grid={time_grid} split={split}') + + if os.path.exists(target_dir) and not overwrite: + return target_dir + + if overwrite and os.path.exists(target_dir): + shutil.rmtree(root_dir) + + train_dir = os.path.join(target_dir, 'train') + test_dir = os.path.join(target_dir, 'test') + musicxml_dir = os.path.join(root_dir, 'musicxml') + + os.makedirs(train_dir, exist_ok=True) + os.makedirs(test_dir, exist_ok=True) + os.makedirs(musicxml_dir, exist_ok=True) + + chorale_numbers = [] + + for chorale in tqdm(chorales.Iterator(returnType='stream'), unit='chorales', desc='Generating dataset'): + # Skip chorales with more or less than 4 parts + if len(chorale.parts) != 4: + continue + + # Skip if parts do not contain correct choral voices + try: + streams = { + 'soprano': chorale['Soprano'], + 'alto': chorale['Alto'], + 'tenor': chorale['Tenor'], + 'bass': chorale['Bass'] + } + except KeyError: + continue + + keys = list(chorale.flat.getElementsByClass(Key)) + if len(keys) > 0: + num_sharps = keys[0].sharps + num_sharps = (num_sharps + 12) % 12 + else: + key_sigs = list(chorale.flat.getElementsByClass(KeySignature)) + if len(key_sigs) > 0: + num_sharps = key_sigs[0].sharps + num_sharps = (num_sharps + 12) % 12 + else: + num_sharps = 0 + + # Save soprano in own file for inference + chorale['Soprano'].write('musicxml', os.path.join(musicxml_dir, f'{str(chorale.metadata.number).zfill(3)}_soprano.musicxml')) + chorale.write('musicxml', os.path.join(musicxml_dir, f'{str(chorale.metadata.number).zfill(3)}_full.musicxml')) + + # # Get minimum and maximum transpositions + transpositions_down = -float('inf') + transpositions_up = float('inf') + for part_name, part in streams.items(): + min_pitch, max_pitch = ambitus.getPitchSpan(part) + transpositions_down = max(transpositions_down, min_pitches[part_name] - min_pitch.midi) + transpositions_up = min(transpositions_up, max_pitches[part_name] - max_pitch.midi) + # transpositions_down = -trans + # transpositions_up = trans + + length = math.ceil(streams['soprano'].highestTime / time_grid) + for t in range(transpositions_down, transpositions_up + 1): + data = {'extra': torch.zeros((length, len(indices_extra)))} + # Note transposition offset + data['extra'][:, indices_extra['pitch_offset']] = t + cur_sharps = (num_sharps + (t * 7)) % 12 + data['extra'][:, cur_sharps + indices_extra['has_sharps_0']] = 1 + + for part_name, part in streams.items(): + part = part.flat.transpose(t) + # Init empty tensor for current voice + data[part_name] = torch.zeros((length, pitch_sizes_parts[part_name] + len(indices_parts))) + + # Iterate through all musical elements in current voice stream + for element in part: + offset = int(element.offset / time_grid) + + if type(element) == Note: + # Skip grace notes + if element.duration.quarterLength == 0: + continue + + pitch = element.pitch.midi - min_pitches[part_name] + len(indices_parts) + duration = int(element.duration.quarterLength / time_grid) + + # Store pitch and ties + data[part_name][offset, pitch] = 1 + data[part_name][offset + 1:offset + duration, indices_parts['is_continued']] = 1 + + # Fermata (only used in soprano) + if part_name == 'soprano' and any([type(e) == Fermata for e in element.expressions]): + data['extra'][offset, indices_extra['has_fermata']] = 1 + + if type(element) == Rest: + duration = int(element.duration.quarterLength / time_grid) + data[part_name][offset, indices_parts['is_rest']] = 1 + data[part_name][offset + 1:offset + duration, indices_parts['is_continued']] = 1 + + if part_name == 'soprano': + if type(element) == TimeSignature: + if element.ratioString == '3/4': + data['extra'][offset:, indices_extra['has_time_signature_3/4']] = 1 + data['extra'][offset:, indices_extra['has_time_signature_4/4']] = 0 + data['extra'][offset:, indices_extra['has_time_signature_3/2']] = 0 + elif element.ratioString == '4/4': + data['extra'][offset:, indices_extra['has_time_signature_3/4']] = 0 + data['extra'][offset:, indices_extra['has_time_signature_4/4']] = 1 + data['extra'][offset:, indices_extra['has_time_signature_3/2']] = 0 + elif element.ratioString == '3/2': + data['extra'][offset:, indices_extra['has_time_signature_3/4']] = 0 + data['extra'][offset:, indices_extra['has_time_signature_4/4']] = 0 + data['extra'][offset:, indices_extra['has_time_signature_3/2']] = 1 + + measure_offsets = [o / time_grid for o in streams['soprano'].measureOffsetMap().keys()] + cur_offset = streams['soprano'].flat.notesAndRests[0].beat + data['extra'][0, indices_extra['time_pos']] = cur_offset + for offset in range(1, length): + if offset in measure_offsets: + cur_offset = 1 + else: + cur_offset += time_grid + data['extra'][offset, indices_extra['time_pos']] = cur_offset + + signum = '+' if t >= 0 else '-' + + target_file_path = os.path.join(target_dir, f'{str(chorale.metadata.number).zfill(3)}{signum}{int(math.fabs(t))}.pt') + torch.save({ + 'data': data, + 'title': chorale.metadata.title + }, target_file_path) + + chorale_numbers.append(str(chorale.metadata.number).zfill(3)) + + # Move files to train / test directories + random.shuffle(chorale_numbers) # Shuffle in place + split_idx = int(len(chorale_numbers) * split) + + for cn in chorale_numbers[split_idx:]: # Train + file_paths = glob(os.path.join(target_dir, f'*{cn}*.pt')) + for file_path in file_paths: + shutil.move(file_path, train_dir) + + for cn in chorale_numbers[:split_idx]: # Test + file_paths = glob(os.path.join(target_dir, f'*{cn}+0.pt')) + for file_path in file_paths: + shutil.move(file_path, test_dir) + + remove_paths = glob(os.path.join(target_dir, '*.pt')) + for remove_path in remove_paths: + os.remove(remove_path) + + return target_dir + + +def _make_data_loaders(root_dir, batch_size, num_workers, context_radius): + # Training data loader: random sampling + train_dataset = ChoralesDataset( + root_dir=os.path.join(root_dir, 'train'), + context_radius=context_radius + ) + train_sampler = RandomSampler(train_dataset) + train_batch_sampler = BatchSampler( + train_sampler, batch_size, drop_last=False + ) + train_data_loader = DataLoader( + train_dataset, + batch_sampler=train_batch_sampler, + num_workers=num_workers + ) + + # Testing data loader: sequential sampling + test_dataset = ChoralesDataset( + root_dir=os.path.join(root_dir, 'test'), + context_radius=context_radius + ) + test_sampler = SequentialSampler(test_dataset) + test_batch_sampler = BatchSampler( + test_sampler, batch_size, drop_last=False + ) + test_data_loader = DataLoader( + test_dataset, + batch_sampler=test_batch_sampler, + num_workers=num_workers + ) + + return { + 'train': train_data_loader, + 'test': test_data_loader + } + + +def get_data_loaders(time_grid=0.25, root_dir=None, overwrite=False, split=0.05, batch_size=1, num_workers=1, context_radius=32): + if root_dir is None: + root_dir = os.path.join('.', 'data') + + data_dir = _generate_data_training( + time_grid=time_grid, + root_dir=root_dir, + overwrite=overwrite, + split=split + ) + + data_loaders = _make_data_loaders( + data_dir, + batch_size=batch_size, + num_workers=num_workers, + context_radius=context_radius + ) + + return data_loaders + + +if __name__ == '__main__': + get_data_loaders(overwrite=True) diff --git a/PRIMAT/eval.py b/PRIMAT/eval.py new file mode 100644 index 0000000..8b25c04 --- /dev/null +++ b/PRIMAT/eval.py @@ -0,0 +1,54 @@ +import re +from glob import glob + +import matplotlib.pyplot as plt + +loss_per_epoch_bass = {} +loss_per_epoch_middle = {} +expr = re.compile(r'bass=(.+) middle=(.+) e=(\d+)') + +file_paths = glob('./all_test_pieces_beam_search/**/*.midi') + +for fp in file_paths: + bass, middle, epoch = expr.findall(fp)[0] + if epoch not in loss_per_epoch_bass: + loss_per_epoch_bass[epoch] = 0 + loss_per_epoch_middle[epoch] = 0 + loss_per_epoch_bass[epoch] += float(bass) + loss_per_epoch_middle[epoch] += float(middle) + +# Bass +sorted_loss_bass = [(int(k), loss_per_epoch_bass[k]) for k in sorted(loss_per_epoch_bass, key=loss_per_epoch_bass.get, reverse=True)] +print('Bass part:') +for e, l in sorted_loss_bass: + print(e, l) + +sorted_loss_bass_epochs = sorted(loss_per_epoch_bass) + +epochs_bass = [int(e) for e in sorted_loss_bass_epochs] +losses_bass = [loss_per_epoch_bass[e] for e in sorted_loss_bass_epochs] +plt.plot(epochs_bass, losses_bass, label='bass') + +print() + +# Middle +sorted_loss_middle = [(int(k), loss_per_epoch_middle[k]) for k in sorted(loss_per_epoch_middle, key=loss_per_epoch_middle.get, reverse=True)] +print('Middle parts:') +for e, l in sorted_loss_middle: + print(e, l) + +sorted_loss_middle_epochs = sorted(loss_per_epoch_middle) + +epochs_middle = [int(e) for e in sorted_loss_middle_epochs] +losses_middle = [loss_per_epoch_middle[e] for e in sorted_loss_middle_epochs] +plt.plot(epochs_middle, losses_middle, label='middle') + + +# Both +losses_all = [] +for i in range(len(losses_bass)): + losses_all.append(losses_bass[i] + losses_middle[i]) +plt.plot(epochs_bass, losses_all, label='all') +plt.legend(loc='lower right') + +plt.show() diff --git a/PRIMAT/execute.sh b/PRIMAT/execute.sh new file mode 100644 index 0000000..b02ff74 --- /dev/null +++ b/PRIMAT/execute.sh @@ -0,0 +1,3 @@ +#!/bin/bash +BASEDIR=$(dirname "$0") +/usr/local/bin/python3 -u $BASEDIR"/inference.py" diff --git a/PRIMAT/from __future__ import print_function.py b/PRIMAT/from __future__ import print_function.py new file mode 100644 index 0000000..b2411ae --- /dev/null +++ b/PRIMAT/from __future__ import print_function.py @@ -0,0 +1,4 @@ +from __future__ import print_function +import torch +x = torch.rand(5, 3) +print(x) \ No newline at end of file diff --git a/PRIMAT/generate_midi.py b/PRIMAT/generate_midi.py new file mode 100644 index 0000000..987e6b3 --- /dev/null +++ b/PRIMAT/generate_midi.py @@ -0,0 +1,297 @@ +import torch +from torch.nn.functional import one_hot +from tqdm import tqdm + +import utils +from data import pitch_sizes_parts, generate_data_inference, indices_parts +from model import BachNetInferenceContinuo, BachNetInferenceMiddleParts + + +# from music21 import environment +# environment.set('musicxmlPath', 'C:\\Program Files\\MuseScore 3\\bin\\MuseScore3.exe') + + +def predict_middle_parts(bass, soprano_path, checkpoint_path, num_candidates=1): + bass_for_playback = bass + + checkpoint = torch.load(checkpoint_path, map_location='cpu') + config = checkpoint['config'] + state = checkpoint['state_middle_parts'] + + model = BachNetInferenceMiddleParts( + hidden_size=config.hidden_size, + context_radius=config.context_radius, + num_candidates=num_candidates + ) + model.load_state_dict(state) + model.set_part_weights( + # loss_bass=checkpoint['loss_bass'], + loss_alto=checkpoint['loss_alto'], + loss_tenor=checkpoint['loss_tenor'] + ) + model.eval() + + sample = generate_data_inference( + time_grid=config.time_grid, + soprano_path=soprano_path + ) + + inputs = sample['data'] + metadata = sample['metadata'] + + soprano = inputs['soprano'].clone() + extra = inputs['extra'].clone() + + length = inputs['soprano'].shape[0] + + # Zero padding for input data + for part in ['soprano', 'extra']: + inputs[part] = torch.cat([ + torch.zeros((config.context_radius, inputs[part].shape[1])), + inputs[part], + torch.zeros((config.context_radius, inputs[part].shape[1])) + ], dim=0) + bass = torch.cat([ + torch.zeros((config.context_radius, bass.shape[1])), + bass.float(), + torch.zeros((config.context_radius, bass.shape[1])) + ], dim=0) + predictions = model({ + 'soprano': inputs['soprano'][:2 * config.context_radius + 1], + 'alto': torch.zeros((config.context_radius, pitch_sizes_parts['alto'] + len(indices_parts))), + 'tenor': torch.zeros((config.context_radius, pitch_sizes_parts['tenor'] + len(indices_parts))), + 'bass_with_context': bass[:2 * config.context_radius + 1], + 'extra': inputs['extra'][:2 * config.context_radius + 1] + }) + + probabilities = [torch.max(predictions[:, 0]).item()] + + acc_probabilities = predictions[:, 0] + history_pitches = [predictions[:, 1:]] + + for step in range(1, length): + candidates = [] + padding_size = max(0, config.context_radius - len(history_pitches)) + history_size = min(config.context_radius, len(history_pitches)) + cur_history = torch.stack(history_pitches[-history_size:], dim=0).long() + + for candidate_idx in range(num_candidates): + predictions = model({ + 'soprano': inputs['soprano'][step:step + 2 * config.context_radius + 1], + 'bass_with_context': bass[step:step + 2 * config.context_radius + 1], + 'alto': torch.cat([ + torch.zeros((padding_size, pitch_sizes_parts['alto'] + len(indices_parts))), + one_hot(cur_history[:, candidate_idx, 0], pitch_sizes_parts['alto'] + len(indices_parts)).float() + ], dim=0), + 'tenor': torch.cat([ + torch.zeros((padding_size, pitch_sizes_parts['tenor'] + len(indices_parts))), + one_hot(cur_history[:, candidate_idx, 1], pitch_sizes_parts['tenor'] + len(indices_parts)).float() + ], dim=0), + 'extra': inputs['extra'][step:step + 2 * config.context_radius + 1] + }) + candidates.append(predictions) + + candidates = torch.stack(candidates, dim=0) + # 0: Index for chord in history_pitches[-1] + # 1: Candidate idx + # 2: Probability Pitches + + # Add log probabilities of candidates to current probabilities + candidates[:, :, 0] = (acc_probabilities + candidates[:, :, 0].t()).t() + + candidate_indices = torch.argsort(candidates.view(-1, 4)[:, 0], dim=0, descending=True) + best_indices = torch.stack([candidate_indices // num_candidates, candidate_indices % num_candidates], dim=1)[:num_candidates] + # [[last_chord_idx, new_chord_idx]] + + history_pitches.append(torch.empty_like(history_pitches[-1])) + for i in range(num_candidates): + for k in range(len(history_pitches) - 2, -1, -1): + history_pitches[k][i] = history_pitches[k][best_indices[i, 0]] + history_pitches[-1][i] = candidates[best_indices[i, 0], best_indices[i, 1], 1:] + acc_probabilities[i] = candidates[best_indices[i, 0], best_indices[i, 1], 0] + + probabilities.append(torch.max(acc_probabilities, dim=0)[0].item()) + + winner = torch.stack(history_pitches, dim=1)[torch.argmax(acc_probabilities)].long().t() + + score = utils.tensors_to_stream({ + 'soprano': soprano, + 'extra': extra, + 'bass': bass_for_playback, + 'alto': one_hot(winner[0], pitch_sizes_parts['alto'] + len(indices_parts)), + 'tenor': one_hot(winner[1], pitch_sizes_parts['tenor'] + len(indices_parts)), + }, config, metadata) + + # for e in zip(winner.t(), probabilities): + # print(e) + + return score, probabilities[-1] + + # score.write('musicxml', f'beam_{num_candidates}.musicxml') + # score.show('musicxml') + + +def predict_bass(soprano_path, checkpoint_path, num_candidates=1): + checkpoint = torch.load(checkpoint_path, map_location='cpu') + config = checkpoint['config'] + state = checkpoint['state_continuo'] + + model = BachNetInferenceContinuo( + hidden_size=config.hidden_size, + context_radius=config.context_radius, + num_candidates=num_candidates, + ) + model.load_state_dict(state) + model.eval() + + sample = generate_data_inference( + time_grid=config.time_grid, + soprano_path=soprano_path + ) + + inputs = sample['data'] + length = inputs['soprano'].shape[0] + + # Zero padding for input data + for part in ['soprano', 'extra']: + inputs[part] = torch.cat([ + torch.zeros((config.context_radius, inputs[part].shape[1])), + inputs[part], + torch.zeros((config.context_radius, inputs[part].shape[1])) + ], dim=0) + + predictions = model({ + 'soprano': inputs['soprano'][:2 * config.context_radius + 1], + 'bass': torch.zeros((config.context_radius, pitch_sizes_parts['bass'] + len(indices_parts))), + 'extra': inputs['extra'][:2 * config.context_radius + 1] + }) + probabilities = [torch.max(predictions[:, 0]).item()] + acc_probabilities = predictions[:, 0] + history_pitches = [predictions[:, 1:]] + + for step in range(1, length): + candidates = [] + history_size = min(config.context_radius, len(history_pitches)) + padding_size = config.context_radius - history_size + + cur_history = torch.stack(history_pitches[-history_size:], dim=0).long() + + for candidate_idx in range(num_candidates): + predictions = model({ + 'soprano': inputs['soprano'][step:step + 2 * config.context_radius + 1], + 'bass': torch.cat([ + torch.zeros((padding_size, pitch_sizes_parts['bass'] + len(indices_parts))), + one_hot(cur_history[:, candidate_idx, 0], pitch_sizes_parts['bass'] + len(indices_parts)).float() + ], dim=0), + 'extra': inputs['extra'][step:step + 2 * config.context_radius + 1] + }) + candidates.append(predictions) + + candidates = torch.stack(candidates, dim=0) + # 0: Index for chord in history_pitches[-1] + # 1: Candidate idx + # 2: Probability Pitches + + # Add log probabilities of candidates to current probabilities + candidates[:, :, 0] = (acc_probabilities + candidates[:, :, 0].t()).t() + + candidate_indices = torch.argsort(candidates.view(-1, 4)[:, 0], dim=0, descending=True) + best_indices = torch.stack([candidate_indices // num_candidates, candidate_indices % num_candidates], dim=1)[:num_candidates] + # [[last_chord_idx, new_chord_idx]] + + history_pitches.append(torch.empty_like(history_pitches[-1])) + for i in range(num_candidates): + for k in range(len(history_pitches) - 2, -1, -1): + history_pitches[k][i] = history_pitches[k][best_indices[i, 0]] + history_pitches[-1][i] = candidates[best_indices[i, 0], best_indices[i, 1], 1:] + acc_probabilities[i] = candidates[best_indices[i, 0], best_indices[i, 1], 0] + + probabilities.append(torch.max(acc_probabilities, dim=0)[0].item()) + + winner = torch.stack(history_pitches, dim=1)[torch.argmax(acc_probabilities)].long().t() + + # for e in zip(winner.t(), probabilities): + # print(e) + bass_predicted = one_hot(winner[0], pitch_sizes_parts['bass'] + len(indices_parts)) + + return bass_predicted, probabilities[-1] + + +if __name__ == '__main__': + import os + from glob import glob + import re + + # checkpoint_paths = sorted(glob('./checkpoints/2019-06-17_17-10-47 batch_size=8192 hidden_size=650 context_radius=32 time_grid=0.25 lr=0.0005 lr_gamma=0.98 lr_step_size=20 split=0.05/*.pt')) + checkpoint_paths = ['./checkpoints/2019-06-17_17-10-47 batch_size=8192 hidden_size=650 context_radius=32 time_grid=0.25 lr=0.0005 lr_gamma=0.98 lr_step_size=20 split=0.05/2019-06-17_17-10-47 batch_size=8192 hidden_size=650 context_radius=32 time_grid=0.25 lr=0.0005 lr_gamma=0.98 lr_step_size=20 split=0.05 0580.pt'] + expr = re.compile(r'hidden_size=(\d+)') + + table = [] + + bass_bs = { + '001': 1, + '003': 6, + '030': 2, + '035': 1, + '037': 1, + '071': 1, + '110': 1, + '112': 1, + '131': 2, + '166': 4, + '203': 1, + '213': 1, + '215': 2, + '271': 2, + '335': 2, + '349': 1, + '367': 2 + } + + middle_bs = { + '001': 1, + '003': 1, + '030': 1, + '035': 1, + '037': 2, + '071': 1, + '110': 2, + '112': 1, + '131': 24, + '166': 21, + '203': 20, + '213': 1, + '215': 1, + '271': 6, + '335': 1, + '349': 6, + '367': 8 + } + + for cp in checkpoint_paths: + min_losses = {} + config = os.path.split(os.path.dirname(cp))[-1] + hidden_size = expr.findall(config)[0] + dirname = os.path.join('.', 'all_test_pieces_beam_search_final') + os.makedirs(dirname, exist_ok=True) + epoch = cp[-7:-3] + + for n in tqdm(['001', '003', '030', '035', '037', '071', '110', '112', '131', '166', '203', '213', '215', '271', '335', '349', '367']): + os.makedirs(os.path.join(dirname, n), exist_ok=True) + soprano_path = f'./data/musicxml/{n}_soprano.musicxml' + + bass, bass_loss = predict_bass( + soprano_path=soprano_path, + checkpoint_path=cp, + num_candidates=bass_bs[n] + ) + + score, middle_loss = predict_middle_parts( + bass, + soprano_path=soprano_path, + checkpoint_path=cp, + num_candidates=middle_bs[n] + ) + + target_path_musicxml = os.path.join(dirname, n, f'no={n} middle={middle_loss}@{middle_bs[n]} bass={bass_loss}@{bass_bs[n]}.musicxml') + score.write('musicxml', target_path_musicxml) diff --git a/PRIMAT/inference.py b/PRIMAT/inference.py new file mode 100644 index 0000000..21ce2c0 --- /dev/null +++ b/PRIMAT/inference.py @@ -0,0 +1,247 @@ +import torch +from torch.nn.functional import one_hot +from tqdm import tqdm +import pathlib + +import utils +from data import pitch_sizes_parts, generate_data_inference, indices_parts +from model import BachNetInferenceContinuo, BachNetInferenceMiddleParts + +thispath=pathlib.Path(__file__).parent.absolute() + +# from music21 import environment +# environment.set('musicxmlPath', 'C:\\Program Files\\MuseScore 3\\bin\\MuseScore3.exe') + + +def predict_middle_parts(bass, soprano_path, checkpoint_path, num_candidates=1): + bass_for_playback = bass + + checkpoint = torch.load(checkpoint_path, map_location='cpu') + config = checkpoint['config'] + state = checkpoint['state_middle_parts'] + + model = BachNetInferenceMiddleParts( + hidden_size=config.hidden_size, + context_radius=config.context_radius, + num_candidates=num_candidates + ) + model.load_state_dict(state) + model.set_part_weights( + # loss_bass=checkpoint['loss_bass'], + loss_alto=checkpoint['loss_alto'], + loss_tenor=checkpoint['loss_tenor'] + ) + model.eval() + + sample = generate_data_inference( + time_grid=config.time_grid, + soprano_path=soprano_path + ) + + inputs = sample['data'] + metadata = sample['metadata'] + + soprano = inputs['soprano'].clone() + extra = inputs['extra'].clone() + + length = inputs['soprano'].shape[0] + + # Zero padding for input data + for part in ['soprano', 'extra']: + inputs[part] = torch.cat([ + torch.zeros((config.context_radius, inputs[part].shape[1])), + inputs[part], + torch.zeros((config.context_radius, inputs[part].shape[1])) + ], dim=0) + bass = torch.cat([ + torch.zeros((config.context_radius, bass.shape[1])), + bass, + torch.zeros((config.context_radius, bass.shape[1])) + ], dim=0) + predictions = model({ + 'soprano': inputs['soprano'][:2 * config.context_radius + 1], + 'alto': torch.zeros((config.context_radius, pitch_sizes_parts['alto'] + len(indices_parts))), + 'tenor': torch.zeros((config.context_radius, pitch_sizes_parts['tenor'] + len(indices_parts))), + 'bass_with_context': bass[:2 * config.context_radius + 1], + 'extra': inputs['extra'][:2 * config.context_radius + 1] + }) + + probabilities = [torch.max(predictions[:, 0]).item()] + + acc_probabilities = predictions[:, 0] + history_pitches = [predictions[:, 1:]] + + for step in tqdm(range(1, length), unit='time steps'): + candidates = [] + padding_size = max(0, config.context_radius - len(history_pitches)) + history_size = min(config.context_radius, len(history_pitches)) + cur_history = torch.stack(history_pitches[-history_size:], dim=0).long() + + for candidate_idx in range(num_candidates): + predictions = model({ + 'soprano': inputs['soprano'][step:step + 2 * config.context_radius + 1], + 'bass_with_context': bass[step:step + 2 * config.context_radius + 1], + 'alto': torch.cat([ + torch.zeros((padding_size, pitch_sizes_parts['alto'] + len(indices_parts))), + one_hot(cur_history[:, candidate_idx, 0], pitch_sizes_parts['alto'] + len(indices_parts)).float() + ], dim=0), + 'tenor': torch.cat([ + torch.zeros((padding_size, pitch_sizes_parts['tenor'] + len(indices_parts))), + one_hot(cur_history[:, candidate_idx, 1], pitch_sizes_parts['tenor'] + len(indices_parts)).float() + ], dim=0), + 'extra': inputs['extra'][step:step + 2 * config.context_radius + 1] + }) + candidates.append(predictions) + + candidates = torch.stack(candidates, dim=0) + # 0: Index for chord in history_pitches[-1] + # 1: Candidate idx + # 2: Probability Pitches + + # Add log probabilities of candidates to current probabilities + candidates[:, :, 0] = (acc_probabilities + candidates[:, :, 0].t()).t() + + candidate_indices = torch.argsort(candidates.view(-1, 4)[:, 0], dim=0, descending=True) + best_indices = torch.stack([candidate_indices // num_candidates, candidate_indices % num_candidates], dim=1)[:num_candidates] + # [[last_chord_idx, new_chord_idx]] + + history_pitches.append(torch.empty_like(history_pitches[-1])) + for i in range(num_candidates): + for k in range(len(history_pitches) - 2, -1, -1): + history_pitches[k][i] = history_pitches[k][best_indices[i, 0]] + history_pitches[-1][i] = candidates[best_indices[i, 0], best_indices[i, 1], 1:] + acc_probabilities[i] = candidates[best_indices[i, 0], best_indices[i, 1], 0] + + probabilities.append(torch.max(acc_probabilities, dim=0)[0].item()) + + winner = torch.stack(history_pitches, dim=1)[torch.argmax(acc_probabilities)].long().t() + + score = utils.tensors_to_stream({ + 'soprano': soprano, + 'extra': extra, + 'bass': bass_for_playback, + 'alto': one_hot(winner[0], pitch_sizes_parts['alto'] + len(indices_parts)), + 'tenor': one_hot(winner[1], pitch_sizes_parts['tenor'] + len(indices_parts)), + }, config, metadata) + + # for e in zip(winner.t(), probabilities): + # print(e) + + return score + + # score.write('musicxml', f'beam_{num_candidates}.musicxml') + # score.show('musicxml') + + +def predict_bass(soprano_path, checkpoint_path, num_candidates=1): + checkpoint = torch.load(checkpoint_path, map_location='cpu') + config = checkpoint['config'] + state = checkpoint['state_continuo'] + + model = BachNetInferenceContinuo( + hidden_size=config.hidden_size, + context_radius=config.context_radius, + num_candidates=num_candidates, + ) + model.load_state_dict(state) + model.eval() + + sample = generate_data_inference( + time_grid=config.time_grid, + soprano_path=soprano_path + ) + + inputs = sample['data'] + length = inputs['soprano'].shape[0] + + # Zero padding for input data + for part in ['soprano', 'extra']: + inputs[part] = torch.cat([ + torch.zeros((config.context_radius, inputs[part].shape[1])), + inputs[part], + torch.zeros((config.context_radius, inputs[part].shape[1])) + ], dim=0) + + predictions = model({ + 'soprano': inputs['soprano'][:2 * config.context_radius + 1], + 'bass': torch.zeros((config.context_radius, pitch_sizes_parts['bass'] + len(indices_parts))), + 'extra': inputs['extra'][:2 * config.context_radius + 1] + }) + probabilities = [torch.max(predictions[:, 0]).item()] + acc_probabilities = predictions[:, 0] + history_pitches = [predictions[:, 1:]] + + for step in tqdm(range(1, length), unit='time steps'): + candidates = [] + history_size = min(config.context_radius, len(history_pitches)) + padding_size = config.context_radius - history_size + + cur_history = torch.stack(history_pitches[-history_size:], dim=0).long() + + for candidate_idx in range(num_candidates): + predictions = model({ + 'soprano': inputs['soprano'][step:step + 2 * config.context_radius + 1], + 'bass': torch.cat([ + torch.zeros((padding_size, pitch_sizes_parts['bass'] + len(indices_parts))), + one_hot(cur_history[:, candidate_idx, 0], pitch_sizes_parts['bass'] + len(indices_parts)).float() + ], dim=0), + 'extra': inputs['extra'][step:step + 2 * config.context_radius + 1] + }) + candidates.append(predictions) + + candidates = torch.stack(candidates, dim=0) + # 0: Index for chord in history_pitches[-1] + # 1: Candidate idx + # 2: Probability Pitches + + # Add log probabilities of candidates to current probabilities + candidates[:, :, 0] = (acc_probabilities + candidates[:, :, 0].t()).t() + + candidate_indices = torch.argsort(candidates.view(-1, 4)[:, 0], dim=0, descending=True) + best_indices = torch.stack([candidate_indices // num_candidates, candidate_indices % num_candidates], dim=1)[:num_candidates] + # [[last_chord_idx, new_chord_idx]] + + history_pitches.append(torch.empty_like(history_pitches[-1])) + for i in range(num_candidates): + for k in range(len(history_pitches) - 2, -1, -1): + history_pitches[k][i] = history_pitches[k][best_indices[i, 0]] + history_pitches[-1][i] = candidates[best_indices[i, 0], best_indices[i, 1], 1:] + acc_probabilities[i] = candidates[best_indices[i, 0], best_indices[i, 1], 0] + + probabilities.append(torch.max(acc_probabilities, dim=0)[0].item()) + + winner = torch.stack(history_pitches, dim=1)[torch.argmax(acc_probabilities)].long().t() + + # for e in zip(winner.t(), probabilities): + # print(e) + bass_predicted = one_hot(winner[0], pitch_sizes_parts['bass'] + len(indices_parts)) + + return bass_predicted + + +if __name__ == '__main__': + torch.set_grad_enabled(False) + + checkpoint_path = f'{thispath}/checkpoints/2019-06-14_21-09-06 batch_size=8192 hidden_size=650 context_radius=32 time_grid=0.25 lr=0.0005 lr_gamma=0.98 lr_step_size=10 split=0.05 0870.pt' + + # checkpoint_path = f'{thispath}/checkpoints/2019-06-13_23-54-23 batch_size=8192 hidden_size=650 context_radius=32 time_grid=0.25 lr=0.0005 lr_gamma=0.98 lr_step_size=10 split=0.05 1000.pt' + + soprano_path = f'{thispath}/XML/melody.XML' + + bass = predict_bass( + soprano_path=soprano_path, + # checkpoint_path='./checkpoints/2019-06-09_19-31-32 batch_size=8192 hidden_size=800 context_radius=32 time_grid=0.25 lr=0.001 lr_gamma=0.98 lr_step_size=10 split=0.05 0020.pt', + checkpoint_path=checkpoint_path, + num_candidates=3 + ).clone().detach().float() + + score = predict_middle_parts( + bass, + soprano_path=soprano_path, + # checkpoint_path='./checkpoints/2019-06-09_19-31-32 batch_size=8192 hidden_size=800 context_radius=32 time_grid=0.25 lr=0.001 lr_gamma=0.98 lr_step_size=10 split=0.05 0020.pt', + checkpoint_path=checkpoint_path, + num_candidates=3 + ) + + score.write('musicxml', f'{thispath}/XML/harmony.xml') + #score.show('musicxml') diff --git a/PRIMAT/model.py b/PRIMAT/model.py new file mode 100644 index 0000000..0ac1915 --- /dev/null +++ b/PRIMAT/model.py @@ -0,0 +1,166 @@ +import math + +import torch.nn +from torch.nn.functional import one_hot + +from data import indices_extra, pitch_sizes_parts, indices_parts + + +class BachNetTrainingContinuo(torch.nn.Module): + def __init__(self, hidden_size, context_radius, dropout=0.5): + super(BachNetTrainingContinuo, self).__init__() + + self.bass = torch.nn.Sequential( + torch.nn.Linear( + (2 * context_radius + 1) * (pitch_sizes_parts['soprano'] + len(indices_parts)) + + (2 * context_radius + 1) * len(indices_extra) + + context_radius * (pitch_sizes_parts['bass'] + len(indices_parts)), + + hidden_size + ), + torch.nn.SELU(), + torch.nn.Dropout(dropout), + torch.nn.Linear(hidden_size, hidden_size), + torch.nn.SELU(), + torch.nn.Dropout(dropout), + torch.nn.Linear(hidden_size, pitch_sizes_parts['bass'] + len(indices_parts)), + ) + + def forward(self, inputs): + batch_size = inputs['soprano'].shape[0] + inputs_bass = torch.cat([inputs[k].view(batch_size, -1) for k in ['soprano', 'bass', 'extra']], dim=1) + + outputs_bass = self.bass(inputs_bass) + + return { + 'bass': outputs_bass + } + + +class BachNetTrainingMiddleParts(torch.nn.Module): + def __init__(self, hidden_size, context_radius, dropout=0.5): + super(BachNetTrainingMiddleParts, self).__init__() + + self.alto = torch.nn.Sequential( + torch.nn.Linear( + (2 * context_radius + 1) * (pitch_sizes_parts['soprano'] + len(indices_parts)) + + (2 * context_radius + 1) * (pitch_sizes_parts['bass'] + len(indices_parts)) + + (2 * context_radius + 1) * len(indices_extra) + + context_radius * (pitch_sizes_parts['alto'] + len(indices_parts)) + + context_radius * (pitch_sizes_parts['tenor'] + len(indices_parts)), + + hidden_size + ), + torch.nn.SELU(), + torch.nn.Dropout(dropout), + torch.nn.Linear(hidden_size, hidden_size), + torch.nn.SELU(), + torch.nn.Dropout(dropout), + torch.nn.Linear(hidden_size, pitch_sizes_parts['alto'] + len(indices_parts)), + ) + + self.tenor = torch.nn.Sequential( + torch.nn.Linear( + (2 * context_radius + 1) * (pitch_sizes_parts['soprano'] + len(indices_parts)) + + (2 * context_radius + 1) * (pitch_sizes_parts['bass'] + len(indices_parts)) + + (2 * context_radius + 1) * len(indices_extra) + + context_radius * (pitch_sizes_parts['alto'] + len(indices_parts)) + + context_radius * (pitch_sizes_parts['tenor'] + len(indices_parts)) + + (pitch_sizes_parts['alto'] + len(indices_parts)), + + hidden_size + ), + torch.nn.SELU(), + torch.nn.Dropout(dropout), + torch.nn.Linear(hidden_size, hidden_size), + torch.nn.SELU(), + torch.nn.Dropout(dropout), + torch.nn.Linear(hidden_size, pitch_sizes_parts['tenor'] + len(indices_parts)), + ) + + def forward(self, inputs): + batch_size = inputs['soprano'].shape[0] + inputs_alto = torch.cat( + [inputs[k].view(batch_size, -1) for k in ['soprano', 'alto', 'tenor', 'bass_with_context', 'extra']], dim=1) + + outputs_alto = self.alto(inputs_alto) + prediction_alto = one_hot(torch.max(outputs_alto, dim=1)[1], pitch_sizes_parts['alto'] + len(indices_parts)).float() + + inputs_tenor = torch.cat([inputs_alto, prediction_alto], dim=1) + outputs_tenor = self.tenor(inputs_tenor) + + return { + 'alto': outputs_alto, + 'tenor': outputs_tenor + } + + +class BachNetInferenceContinuo(BachNetTrainingContinuo): + def __init__(self, num_candidates, *args, **kwargs): + super(BachNetInferenceContinuo, self).__init__(*args, **kwargs) + self.num_candidates = num_candidates + + def forward(self, inputs): + num_parts = 3 + results = torch.zeros((self.num_candidates, 1 + num_parts)) # [[Candidate index], [[ProbAcc, PitchB, PitchA, PitchT]] + + inputs_bass = torch.cat([ + inputs[k].view(1, -1) for k in ['soprano', 'bass', 'extra'] + ], dim=1).squeeze() + + outputs_bass = self.bass(inputs_bass) + + log_probabilities, pitches = torch.sort(torch.log(torch.softmax(outputs_bass, dim=0)), dim=0, descending=True) + results[:, 0] = log_probabilities[:self.num_candidates] + results[:, 1] = pitches[:self.num_candidates] + + return results + + +class BachNetInferenceMiddleParts(BachNetTrainingMiddleParts): + def __init__(self, num_candidates, *args, **kwargs): + super(BachNetInferenceMiddleParts, self).__init__(*args, **kwargs) + self.num_candidates = num_candidates + self.weight_alto = 1 + self.weight_tenor = 1 + + def set_part_weights(self, loss_alto, loss_tenor): + maximum = max(1 / loss_alto, 1 / loss_tenor) + self.weight_alto = 1 / loss_alto / maximum + self.weight_tenor = 1 / loss_tenor / maximum + + def forward(self, inputs): + num_parts = 3 + results = torch.zeros((self.num_candidates, 1 + num_parts)) # [[Candidate index], [[ProbAcc, PitchB, PitchA, PitchT]] + + # Alto ################################################################# + inputs_alto = torch.cat([inputs[k].view(1, -1) for k in ['soprano', 'alto', 'tenor', 'bass_with_context', 'extra']], + dim=1).squeeze() # !!! SQUEEZED !!! + outputs_alto = self.alto(inputs_alto) + + log_probabilities, pitches = torch.sort(torch.log(torch.softmax(outputs_alto, dim=0)), dim=0, descending=True) + log_probabilities += math.log(self.weight_alto) + results[:, 0] = log_probabilities[:self.num_candidates] + results[:, 1] = pitches[:self.num_candidates] + + # Tenor ################################################################# + inputs_tenor = torch.cat([ + inputs_alto.repeat(self.num_candidates, 1), + one_hot(results[:, 1].long(), pitch_sizes_parts['alto'] + len(indices_parts)).float() + ], dim=1) + outputs_tenor = self.tenor(inputs_tenor) + + log_probabilities = torch.log(torch.softmax(outputs_tenor, dim=1)) + log_probabilities += math.log(self.weight_tenor) + log_probabilities = log_probabilities.t() + results[:, 0] + log_probabilities, pitches_indices = torch.sort(log_probabilities.t().contiguous().view(1, -1).squeeze(), dim=0, descending=True) + + pitches_tenor = pitches_indices % (pitch_sizes_parts['tenor'] + len(indices_parts)) + history_indices = pitches_indices // (pitch_sizes_parts['tenor'] + len(indices_parts)) + pitches_alto = results[:, 1][history_indices] + + results[:, 0] = log_probabilities[:self.num_candidates] + results[:, 1] = pitches_alto[:self.num_candidates] + results[:, 2] = pitches_tenor[:self.num_candidates] + + return results diff --git a/PRIMAT/test.sh b/PRIMAT/test.sh new file mode 100644 index 0000000..82e8f03 --- /dev/null +++ b/PRIMAT/test.sh @@ -0,0 +1,3 @@ +#!/bin/bash +BASEDIR=$(dirname "$0") +echo "$BASEDIR" \ No newline at end of file diff --git a/PRIMAT/training.py b/PRIMAT/training.py new file mode 100644 index 0000000..8d876b0 --- /dev/null +++ b/PRIMAT/training.py @@ -0,0 +1,172 @@ +import datetime +import logging +import os +import pprint +from statistics import mean + +import torch +import torch.nn +from torch.utils.tensorboard import SummaryWriter +from tqdm import trange + +import data +import utils +from model import BachNetTrainingContinuo, BachNetTrainingMiddleParts + + +def main(config): + logging.debug('Initializing...') + + # Prepare logging + date = datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S") + log_dir = os.path.join('.', 'runs', f'{date} {str(config)}') + writer = SummaryWriter(log_dir=log_dir) + + logging.debug(f'Configuration:\n{pprint.pformat(config)}') + + device = torch.device("cuda:0" if config.use_cuda and torch.cuda.is_available() else "cpu") + logging.debug(f'Using device: {device}') + + logging.debug('Loading datasets...') + data_loaders = data.get_data_loaders( + batch_size=config.batch_size, + num_workers=config.num_workers, + time_grid=config.time_grid, + context_radius=config.context_radius, + split=config.split, + ) + + logging.debug('Creating model...') + model_continuo = BachNetTrainingContinuo( + hidden_size=config.hidden_size, + context_radius=config.context_radius, + ).to(device) + model_middle_parts = BachNetTrainingMiddleParts( + hidden_size=config.hidden_size, + context_radius=config.context_radius + ).to(device) + params_continuo = [p for p in model_continuo.parameters() if p.requires_grad] + params_middleparts = [p for p in model_middle_parts.parameters() if p.requires_grad] + optimizer_continuo = torch.optim.Adam(params_continuo, lr=config.lr) + optimizer_middleparts = torch.optim.Adam(params_middleparts, lr=config.lr) + + lr_scheduler_continuo = torch.optim.lr_scheduler.StepLR( + optimizer_continuo, + step_size=config.lr_step_size, + gamma=config.lr_gamma, + ) + lr_scheduler_middleparts = torch.optim.lr_scheduler.StepLR( + optimizer_middleparts, + step_size=config.lr_step_size, + gamma=config.lr_gamma, + ) + criterion = torch.nn.CrossEntropyLoss().to(device) + + logging.debug('Training and testing...') + # for epoch in range(config.num_epochs): + for epoch in trange(config.num_epochs, unit='epoch'): + + for phase in ['train', 'test']: + model_continuo.train() if phase == 'train' else model_continuo.eval() + model_middle_parts.train() if phase == 'train' else model_middle_parts.eval() + loss_lists = { + 'all': [], + 'bass': [], + 'alto': [], + 'tenor': [] + } + + with torch.set_grad_enabled(phase == 'train'): + + for batch_idx, batch in enumerate(data_loaders[phase]): + inputs, targets = batch + # Transfer to device + inputs_for_continuo = {k: inputs[k].to(device) for k in ['soprano', 'bass', 'extra']} + inputs_for_middle_parts = {k: inputs[k].to(device) for k in ['soprano', 'alto', 'tenor', 'bass_with_context', 'extra']} + targets_continuo = {k: targets[k].to(device) for k in ['bass']} + targets_middleparts = {k: targets[k].to(device) for k in ['alto', 'tenor']} + + predictions_continuo = model_continuo(inputs_for_continuo) + losses_continuo = {k: criterion(predictions_continuo[k], targets_continuo[k]) for k in targets_continuo.keys()} + + predictions_middleparts = model_middle_parts(inputs_for_middle_parts) + losses_middleparts = {k: criterion(predictions_middleparts[k], targets_middleparts[k]) for k in targets_middleparts.keys()} + + loss = sum([sum(losses_middleparts.values()), sum(losses_continuo.values())]) + + loss_lists['all'].append(loss.item()) + for k in losses_continuo.keys(): + loss_lists[k].append(losses_continuo[k].item()) + for k in losses_middleparts.keys(): + loss_lists[k].append(losses_middleparts[k].item()) + + if phase == 'train': + optimizer_continuo.zero_grad() + optimizer_middleparts.zero_grad() + sum(losses_continuo.values()).backward() + sum(losses_middleparts.values()).backward() + # loss.backward() + optimizer_continuo.step() + optimizer_middleparts.step() + + # Log current loss + if batch_idx % config.log_interval == 0: + step = int((float(epoch) + (batch_idx / len(data_loaders[phase]))) * 1000) + writer.add_scalars('loss', {phase: loss.item()}, step) + writer.add_scalars('loss_per_parts', {f'{phase}_{k}': v for k, v in losses_continuo.items()}, step) + writer.add_scalars('loss_per_parts', {f'{phase}_{k}': v for k, v in losses_middleparts.items()}, step) + + # Log mean loss per epoch + mean_loss_per_epoch = mean(loss_lists['all']) + writer.add_scalars('loss', {phase + '_mean': mean_loss_per_epoch}, (epoch + 1) * 1000) + writer.file_writer.flush() + + lr_scheduler_continuo.step() + lr_scheduler_middleparts.step() + + if config.checkpoint_interval is not None and (epoch + 1) % config.checkpoint_interval == 0: + subfolder = f'{date} {str(config)}' + os.makedirs(os.path.join(config.checkpoint_root_dir, subfolder), exist_ok=True) + checkpoint_path = os.path.join(config.checkpoint_root_dir, subfolder, f'{date} {str(config)} {str(epoch + 1).zfill(4)}.pt') + torch.save({ + 'config': config, + 'state_continuo': model_continuo.state_dict(), + 'state_middle_parts': model_middle_parts.state_dict(), + 'epoch': epoch, + 'loss_bass': mean(loss_lists['bass']), + 'loss_alto': mean(loss_lists['alto']), + 'loss_tenor': mean(loss_lists['tenor']) + + }, checkpoint_path) + + writer.close() + + +if __name__ == '__main__': + logging.basicConfig(level=logging.ERROR) + + configs = [] + params = [ + (32, 650, 0.0005) + ] + for radius, hidden_size, lr in params: + config = utils.Config({ + 'num_epochs': 3000, + 'batch_size': 8192, + 'num_workers': 1, + 'hidden_size': hidden_size, + 'context_radius': radius, + 'time_grid': 0.25, + 'lr': lr, + 'lr_gamma': 0.99, + 'lr_step_size': 30, + 'checkpoint_interval': 10, + 'split': 0.05, + + }) + configs.append(config) + + from tqdm import tqdm + + for config in tqdm(configs): + main(config) diff --git a/PRIMAT/utils.py b/PRIMAT/utils.py new file mode 100644 index 0000000..8ea98dc --- /dev/null +++ b/PRIMAT/utils.py @@ -0,0 +1,161 @@ +import logging +import os +import random +from copy import deepcopy +from datetime import datetime + +import numpy as np +import torch +from music21 import clef +from music21.expressions import Fermata +from music21.key import KeySignature +from music21.metadata import Metadata +from music21.meter import TimeSignature +from music21.note import Rest, Note +from music21.pitch import Pitch +from music21.stream import Score, Part, Measure +from music21.tempo import MetronomeMark +from music21.tie import Tie + +from data import indices_extra, indices_parts, min_pitches + + +class Config(object): + num_epochs = 100 + batch_size = 128 + hidden_size = 75 + use_cuda = True + num_workers = 1 + lr = 0.001 + lr_step_size = 10 + lr_gamma = 0.95 + time_grid = 0.25 + context_radius = 32 + checkpoint_root_dir = os.path.join('.', 'checkpoints') + checkpoint_interval = None + log_interval = 1 + split = 0.05 + seed = 1234 + + def __init__(self, config=None): + seed_torch(self.seed) + if config is not None: + self.explicit = config + self.__dict__.update(config) + else: + self.explicit = {} + + def __repr__(self): + blacklist = ['checkpoint_root_dir', 'checkpoint_interval', 'use_cuda', 'num_epochs', 'num_workers', 'log_interval'] + config_string = ' '.join([f'{k}={v}' for k, v in self.explicit.items() if k not in blacklist]).strip() + return config_string + + +def seed_torch(seed): + random.seed(seed) + os.environ['PYTHONHASHSEED'] = str(seed) + np.random.seed(seed) + torch.manual_seed(seed) + torch.cuda.manual_seed(seed) + torch.backends.cudnn.deterministic = True + + +def generate_txt_output(data, path): + with open(path, 'w') as fp: + for pitches in data.t().flip(dims=[0]): + line = '' + for step in pitches: + char = '*' if step == 1 else ' ' + line += char + fp.write(line) + fp.write('\n') + + +def tensors_to_stream(outputs, config, metadata=None): + cur_measure_number = 0 + parts = {} + for part_name in outputs.keys(): + if part_name == 'extra': + continue + part = Part(id=part_name) + parts[part_name] = part + + last_time_signature = None + cur_time_signature = '4/4' + for step in range(outputs['soprano'].shape[0]): + extra = outputs['extra'][step] + if extra[indices_extra['has_time_signature_3/4']].item() == 1: + cur_time_signature = '3/4' + elif extra[indices_extra['has_time_signature_4/4']].item() == 1: + cur_time_signature = '4/4' + elif extra[indices_extra['has_time_signature_3/2']].item() == 1: + cur_time_signature = '3/2' + cur_time_pos = extra[indices_extra['time_pos']].item() + has_fermata = extra[indices_extra['has_fermata']].item() == 1 + + if cur_time_pos == 1.0 or cur_measure_number == 0: + for part_name, part in parts.items(): + part.append(Measure(number=cur_measure_number)) + if cur_measure_number == 0: + if part_name in ['soprano', 'alto']: + part[-1].append(clef.TrebleClef()) + else: + part[-1].append(clef.BassClef()) + key = int(torch.argmax(outputs['extra'][0, indices_extra['has_sharps_0']:indices_extra['has_sharps_11'] + 1], dim=0).item()) + if key >= 6: + key -= 12 + part[-1].append(KeySignature(key)) + part[-1].append(MetronomeMark(number=90)) + cur_measure_number += 1 + + if last_time_signature is None or cur_time_signature != last_time_signature: + for part in parts.values(): + part[-1].append(TimeSignature(cur_time_signature)) + last_time_signature = cur_time_signature + + for part_name, part in parts.items(): + idx = torch.argmax(outputs[part_name][step]).item() + if idx == indices_parts['is_continued']: + try: + last_element = part[-1].flat.notesAndRests[-1] + cur_element = deepcopy(last_element) + if last_element.tie is not None and last_element.tie.type == 'stop': + last_element.tie = Tie('continue') + else: + last_element.tie = Tie('start') + cur_element.tie = Tie('stop') + except IndexError: + logging.debug('Warning: "is_continued" on first beat. Replaced by rest.') + cur_element = Rest(quarterLength=config.time_grid) + part[-1].append(cur_element) + elif idx == indices_parts['is_rest']: + part[-1].append(Rest(quarterLength=config.time_grid)) + else: + pitch = Pitch() + part[-1].append(Note(pitch, quarterLength=config.time_grid)) + # Set pitch value AFTER appending to measure in order to avoid unnecessary accidentals + pitch.midi = idx + min_pitches[part_name] - len(indices_parts) + + if has_fermata: + for part in parts.values(): + fermata = Fermata() + fermata.type = 'upright' + part[-1][-1].expressions.append(fermata) + + score = Score() + if metadata is not None: + score.append(Metadata()) + score.metadata.title = f"{metadata.title} ({metadata.number})" + score.metadata.composer = f"Melody: {metadata.composer}\nArrangement: BachNet ({datetime.now().year})" + for part in parts.values(): + part[-1].rightBarline = 'light-heavy' + + score.append(parts['soprano']) + if 'alto' in parts: + score.append(parts['alto']) + score.append(parts['tenor']) + score.append(parts['bass']) + + score.stripTies(inPlace=True, retainContainers=True) + + return score