From 115f499d07f1f7a4a12495fb446eba4bf9cd0d38 Mon Sep 17 00:00:00 2001 From: mwinter Date: Sun, 7 Jan 2024 19:35:24 +0100 Subject: [PATCH] more codagit add .git add .! --- lilypond/includes/part_I.ly | 14 +- lilypond/includes/part_II.ly | 14 +- lilypond/includes/part_III.ly | 14 +- lilypond/includes/part_IV.ly | 14 +- lilypond/score_template.pdf | Bin 1301850 -> 1277698 bytes resources/string_quartet_3_rise.json | 15 +- resources/string_quartet_3_rise.json_bak | 13 +- .../4dd2a130/4dd2a130_code.scd | 992 ++++++++++++++++++ .../4dd2a130/4dd2a130_mus_model.json | 65 ++ .../4dd2a130/lilypond/part_I.ly | 4 + .../4dd2a130/lilypond/part_II.ly | 4 + .../4dd2a130/lilypond/part_III.ly | 4 + .../4dd2a130/lilypond/part_IV.ly | 4 + .../50620822/50620822_code.scd | 992 ++++++++++++++++++ .../50620822/50620822_mus_model.json | 141 +++ .../50c2b0ad/50c2b0ad_code.scd | 992 ++++++++++++++++++ .../50c2b0ad/50c2b0ad_mus_model.json | 96 ++ .../50c2b0ad/lilypond/part_I.ly | 6 + .../50c2b0ad/lilypond/part_II.ly | 6 + .../50c2b0ad/lilypond/part_III.ly | 6 + .../50c2b0ad/lilypond/part_IV.ly | 6 + .../536cac90/536cac90_code.scd | 992 ++++++++++++++++++ .../536cac90/536cac90_mus_model.json | 96 ++ .../536cac90/lilypond/part_I.ly | 8 + .../536cac90/lilypond/part_II.ly | 8 + .../536cac90/lilypond/part_III.ly | 8 + .../536cac90/lilypond/part_IV.ly | 8 + .../567a9375/567a9375_code.scd | 992 ++++++++++++++++++ .../567a9375/567a9375_mus_model.json | 65 ++ .../567a9375/lilypond/part_I.ly | 4 + .../567a9375/lilypond/part_II.ly | 4 + .../567a9375/lilypond/part_III.ly | 4 + .../567a9375/lilypond/part_IV.ly | 4 + .../5ef20586/5ef20586_code.scd | 992 ++++++++++++++++++ .../5ef20586/5ef20586_mus_model.json | 65 ++ .../5ef20586/lilypond/part_I.ly | 4 + .../5ef20586/lilypond/part_II.ly | 4 + .../5ef20586/lilypond/part_III.ly | 4 + .../5ef20586/lilypond/part_IV.ly | 4 + .../5f0075ab/5f0075ab_code.scd | 992 ++++++++++++++++++ .../5f0075ab/5f0075ab_mus_model.json | 65 ++ .../5f0075ab/lilypond/part_I.ly | 4 + .../5f0075ab/lilypond/part_II.ly | 4 + .../5f0075ab/lilypond/part_III.ly | 4 + .../5f0075ab/lilypond/part_IV.ly | 4 + .../61207e49/61207e49_code.scd | 992 ++++++++++++++++++ .../61207e49/61207e49_mus_model.json | 141 +++ .../61207e49/lilypond/part_I.ly | 12 + .../61207e49/lilypond/part_II.ly | 12 + .../61207e49/lilypond/part_III.ly | 12 + .../61207e49/lilypond/part_IV.ly | 12 + .../624f7439/624f7439_mus_model.json | 10 +- .../624f7439/lilypond/part_I.ly | 12 + .../624f7439/lilypond/part_II.ly | 12 + .../624f7439/lilypond/part_III.ly | 12 + .../624f7439/lilypond/part_IV.ly | 12 + .../65120e88/65120e88_code.scd | 992 ++++++++++++++++++ .../65120e88/65120e88_mus_model.json | 65 ++ .../65120e88/lilypond/part_I.ly | 4 + .../65120e88/lilypond/part_II.ly | 4 + .../65120e88/lilypond/part_III.ly | 4 + .../65120e88/lilypond/part_IV.ly | 4 + .../66b20499/66b20499_code.scd | 992 ++++++++++++++++++ .../66b20499/66b20499_mus_model.json | 96 ++ .../66b20499/lilypond/part_I.ly | 8 + .../66b20499/lilypond/part_II.ly | 8 + .../66b20499/lilypond/part_III.ly | 8 + .../66b20499/lilypond/part_IV.ly | 8 + .../6c46f950/6c46f950_code.scd | 992 ++++++++++++++++++ .../6c46f950/6c46f950_mus_model.json | 96 ++ .../736745da/736745da_code.scd | 992 ++++++++++++++++++ .../736745da/736745da_mus_model.json | 65 ++ .../736745da/lilypond/part_I.ly | 4 + .../736745da/lilypond/part_II.ly | 4 + .../736745da/lilypond/part_III.ly | 4 + .../736745da/lilypond/part_IV.ly | 4 + .../767e70f0/767e70f0_code.scd | 992 ++++++++++++++++++ .../767e70f0/767e70f0_mus_model.json | 65 ++ .../767e70f0/lilypond/part_I.ly | 4 + .../767e70f0/lilypond/part_II.ly | 4 + .../767e70f0/lilypond/part_III.ly | 4 + .../767e70f0/lilypond/part_IV.ly | 4 + .../tmp/tmp_mus_model.json | 240 +---- supercollider/material_tweak.scd | 35 +- 84 files changed, 14450 insertions(+), 232 deletions(-) create mode 100644 resources/string_quartet_3_rise/4dd2a130/4dd2a130_code.scd create mode 100644 resources/string_quartet_3_rise/4dd2a130/4dd2a130_mus_model.json create mode 100644 resources/string_quartet_3_rise/4dd2a130/lilypond/part_I.ly create mode 100644 resources/string_quartet_3_rise/4dd2a130/lilypond/part_II.ly create mode 100644 resources/string_quartet_3_rise/4dd2a130/lilypond/part_III.ly create mode 100644 resources/string_quartet_3_rise/4dd2a130/lilypond/part_IV.ly create mode 100644 resources/string_quartet_3_rise/50620822/50620822_code.scd create mode 100644 resources/string_quartet_3_rise/50620822/50620822_mus_model.json create mode 100644 resources/string_quartet_3_rise/50c2b0ad/50c2b0ad_code.scd create mode 100644 resources/string_quartet_3_rise/50c2b0ad/50c2b0ad_mus_model.json create mode 100644 resources/string_quartet_3_rise/50c2b0ad/lilypond/part_I.ly create mode 100644 resources/string_quartet_3_rise/50c2b0ad/lilypond/part_II.ly create mode 100644 resources/string_quartet_3_rise/50c2b0ad/lilypond/part_III.ly create mode 100644 resources/string_quartet_3_rise/50c2b0ad/lilypond/part_IV.ly create mode 100644 resources/string_quartet_3_rise/536cac90/536cac90_code.scd create mode 100644 resources/string_quartet_3_rise/536cac90/536cac90_mus_model.json create mode 100644 resources/string_quartet_3_rise/536cac90/lilypond/part_I.ly create mode 100644 resources/string_quartet_3_rise/536cac90/lilypond/part_II.ly create mode 100644 resources/string_quartet_3_rise/536cac90/lilypond/part_III.ly create mode 100644 resources/string_quartet_3_rise/536cac90/lilypond/part_IV.ly create mode 100644 resources/string_quartet_3_rise/567a9375/567a9375_code.scd create mode 100644 resources/string_quartet_3_rise/567a9375/567a9375_mus_model.json create mode 100644 resources/string_quartet_3_rise/567a9375/lilypond/part_I.ly create mode 100644 resources/string_quartet_3_rise/567a9375/lilypond/part_II.ly create mode 100644 resources/string_quartet_3_rise/567a9375/lilypond/part_III.ly create mode 100644 resources/string_quartet_3_rise/567a9375/lilypond/part_IV.ly create mode 100644 resources/string_quartet_3_rise/5ef20586/5ef20586_code.scd create mode 100644 resources/string_quartet_3_rise/5ef20586/5ef20586_mus_model.json create mode 100644 resources/string_quartet_3_rise/5ef20586/lilypond/part_I.ly create mode 100644 resources/string_quartet_3_rise/5ef20586/lilypond/part_II.ly create mode 100644 resources/string_quartet_3_rise/5ef20586/lilypond/part_III.ly create mode 100644 resources/string_quartet_3_rise/5ef20586/lilypond/part_IV.ly create mode 100644 resources/string_quartet_3_rise/5f0075ab/5f0075ab_code.scd create mode 100644 resources/string_quartet_3_rise/5f0075ab/5f0075ab_mus_model.json create mode 100644 resources/string_quartet_3_rise/5f0075ab/lilypond/part_I.ly create mode 100644 resources/string_quartet_3_rise/5f0075ab/lilypond/part_II.ly create mode 100644 resources/string_quartet_3_rise/5f0075ab/lilypond/part_III.ly create mode 100644 resources/string_quartet_3_rise/5f0075ab/lilypond/part_IV.ly create mode 100644 resources/string_quartet_3_rise/61207e49/61207e49_code.scd create mode 100644 resources/string_quartet_3_rise/61207e49/61207e49_mus_model.json create mode 100644 resources/string_quartet_3_rise/61207e49/lilypond/part_I.ly create mode 100644 resources/string_quartet_3_rise/61207e49/lilypond/part_II.ly create mode 100644 resources/string_quartet_3_rise/61207e49/lilypond/part_III.ly create mode 100644 resources/string_quartet_3_rise/61207e49/lilypond/part_IV.ly create mode 100644 resources/string_quartet_3_rise/624f7439/lilypond/part_I.ly create mode 100644 resources/string_quartet_3_rise/624f7439/lilypond/part_II.ly create mode 100644 resources/string_quartet_3_rise/624f7439/lilypond/part_III.ly create mode 100644 resources/string_quartet_3_rise/624f7439/lilypond/part_IV.ly create mode 100644 resources/string_quartet_3_rise/65120e88/65120e88_code.scd create mode 100644 resources/string_quartet_3_rise/65120e88/65120e88_mus_model.json create mode 100644 resources/string_quartet_3_rise/65120e88/lilypond/part_I.ly create mode 100644 resources/string_quartet_3_rise/65120e88/lilypond/part_II.ly create mode 100644 resources/string_quartet_3_rise/65120e88/lilypond/part_III.ly create mode 100644 resources/string_quartet_3_rise/65120e88/lilypond/part_IV.ly create mode 100644 resources/string_quartet_3_rise/66b20499/66b20499_code.scd create mode 100644 resources/string_quartet_3_rise/66b20499/66b20499_mus_model.json create mode 100644 resources/string_quartet_3_rise/66b20499/lilypond/part_I.ly create mode 100644 resources/string_quartet_3_rise/66b20499/lilypond/part_II.ly create mode 100644 resources/string_quartet_3_rise/66b20499/lilypond/part_III.ly create mode 100644 resources/string_quartet_3_rise/66b20499/lilypond/part_IV.ly create mode 100644 resources/string_quartet_3_rise/6c46f950/6c46f950_code.scd create mode 100644 resources/string_quartet_3_rise/6c46f950/6c46f950_mus_model.json create mode 100644 resources/string_quartet_3_rise/736745da/736745da_code.scd create mode 100644 resources/string_quartet_3_rise/736745da/736745da_mus_model.json create mode 100644 resources/string_quartet_3_rise/736745da/lilypond/part_I.ly create mode 100644 resources/string_quartet_3_rise/736745da/lilypond/part_II.ly create mode 100644 resources/string_quartet_3_rise/736745da/lilypond/part_III.ly create mode 100644 resources/string_quartet_3_rise/736745da/lilypond/part_IV.ly create mode 100644 resources/string_quartet_3_rise/767e70f0/767e70f0_code.scd create mode 100644 resources/string_quartet_3_rise/767e70f0/767e70f0_mus_model.json create mode 100644 resources/string_quartet_3_rise/767e70f0/lilypond/part_I.ly create mode 100644 resources/string_quartet_3_rise/767e70f0/lilypond/part_II.ly create mode 100644 resources/string_quartet_3_rise/767e70f0/lilypond/part_III.ly create mode 100644 resources/string_quartet_3_rise/767e70f0/lilypond/part_IV.ly diff --git a/lilypond/includes/part_I.ly b/lilypond/includes/part_I.ly index 69a6c32..f71fd0c 100644 --- a/lilypond/includes/part_I.ly +++ b/lilypond/includes/part_I.ly @@ -13,5 +13,15 @@ \include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_3_rise/47bdba0e/lilypond/part_I.ly" \include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_3_rise/5f3f1c84/lilypond/part_I.ly" \include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_3_rise/4f53a446/lilypond/part_I.ly" -\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_3_rise/731cf7ae/lilypond/part_I.ly" -\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_3_rise/4da80bfb/lilypond/part_I.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_3_rise/624f7439/lilypond/part_I.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_3_rise/61207e49/lilypond/part_I.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_3_rise/50c2b0ad/lilypond/part_I.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_3_rise/66b20499/lilypond/part_I.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_3_rise/65120e88/lilypond/part_I.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_3_rise/567a9375/lilypond/part_I.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_3_rise/736745da/lilypond/part_I.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_3_rise/5f0075ab/lilypond/part_I.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_3_rise/5ef20586/lilypond/part_I.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_3_rise/4dd2a130/lilypond/part_I.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_3_rise/767e70f0/lilypond/part_I.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_3_rise/536cac90/lilypond/part_I.ly" diff --git a/lilypond/includes/part_II.ly b/lilypond/includes/part_II.ly index 335b398..6a498df 100644 --- a/lilypond/includes/part_II.ly +++ b/lilypond/includes/part_II.ly @@ -13,5 +13,15 @@ \include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_3_rise/47bdba0e/lilypond/part_II.ly" \include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_3_rise/5f3f1c84/lilypond/part_II.ly" \include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_3_rise/4f53a446/lilypond/part_II.ly" -\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_3_rise/731cf7ae/lilypond/part_II.ly" -\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_3_rise/4da80bfb/lilypond/part_II.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_3_rise/624f7439/lilypond/part_II.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_3_rise/61207e49/lilypond/part_II.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_3_rise/50c2b0ad/lilypond/part_II.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_3_rise/66b20499/lilypond/part_II.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_3_rise/65120e88/lilypond/part_II.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_3_rise/567a9375/lilypond/part_II.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_3_rise/736745da/lilypond/part_II.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_3_rise/5f0075ab/lilypond/part_II.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_3_rise/5ef20586/lilypond/part_II.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_3_rise/4dd2a130/lilypond/part_II.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_3_rise/767e70f0/lilypond/part_II.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_3_rise/536cac90/lilypond/part_II.ly" diff --git a/lilypond/includes/part_III.ly b/lilypond/includes/part_III.ly index 87a3832..5a8556b 100644 --- a/lilypond/includes/part_III.ly +++ b/lilypond/includes/part_III.ly @@ -13,5 +13,15 @@ \include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_3_rise/47bdba0e/lilypond/part_III.ly" \include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_3_rise/5f3f1c84/lilypond/part_III.ly" \include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_3_rise/4f53a446/lilypond/part_III.ly" -\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_3_rise/731cf7ae/lilypond/part_III.ly" -\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_3_rise/4da80bfb/lilypond/part_III.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_3_rise/624f7439/lilypond/part_III.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_3_rise/61207e49/lilypond/part_III.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_3_rise/50c2b0ad/lilypond/part_III.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_3_rise/66b20499/lilypond/part_III.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_3_rise/65120e88/lilypond/part_III.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_3_rise/567a9375/lilypond/part_III.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_3_rise/736745da/lilypond/part_III.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_3_rise/5f0075ab/lilypond/part_III.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_3_rise/5ef20586/lilypond/part_III.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_3_rise/4dd2a130/lilypond/part_III.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_3_rise/767e70f0/lilypond/part_III.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_3_rise/536cac90/lilypond/part_III.ly" diff --git a/lilypond/includes/part_IV.ly b/lilypond/includes/part_IV.ly index 4fbe550..ec3fac8 100644 --- a/lilypond/includes/part_IV.ly +++ b/lilypond/includes/part_IV.ly @@ -13,5 +13,15 @@ \include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_3_rise/47bdba0e/lilypond/part_IV.ly" \include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_3_rise/5f3f1c84/lilypond/part_IV.ly" \include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_3_rise/4f53a446/lilypond/part_IV.ly" -\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_3_rise/731cf7ae/lilypond/part_IV.ly" -\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_3_rise/4da80bfb/lilypond/part_IV.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_3_rise/624f7439/lilypond/part_IV.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_3_rise/61207e49/lilypond/part_IV.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_3_rise/50c2b0ad/lilypond/part_IV.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_3_rise/66b20499/lilypond/part_IV.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_3_rise/65120e88/lilypond/part_IV.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_3_rise/567a9375/lilypond/part_IV.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_3_rise/736745da/lilypond/part_IV.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_3_rise/5f0075ab/lilypond/part_IV.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_3_rise/5ef20586/lilypond/part_IV.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_3_rise/4dd2a130/lilypond/part_IV.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_3_rise/767e70f0/lilypond/part_IV.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_3_rise/536cac90/lilypond/part_IV.ly" diff --git a/lilypond/score_template.pdf b/lilypond/score_template.pdf index 1fe58505671a61b3c5ce548ec689e8a22f132f42..36f751db6106ad00ed49ec7d626de260405a4392 100644 GIT binary patch delta 186869 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�YG*X@1MPHS|CB3wS+;H`pBFyg@U}lgR z+3U}Z(sY7RBa=%A30`sf!Gwd!@wJT l3r#OB}+Ehba`Kp!#&-Qx$+iX@c~e7WZU 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 diff --git a/resources/string_quartet_3_rise.json b/resources/string_quartet_3_rise.json index f66928b..aebc7d6 100644 --- a/resources/string_quartet_3_rise.json +++ b/resources/string_quartet_3_rise.json @@ -16,8 +16,17 @@ "47bdba0e", "5f3f1c84", "4f53a446", - "731cf7ae", - "4da80bfb", - "624f7439" + "624f7439", + "61207e49", + "50c2b0ad", + "66b20499", + "65120e88", + "567a9375", + "736745da", + "5f0075ab", + "5ef20586", + "4dd2a130", + "767e70f0", + "536cac90" ] } \ No newline at end of file diff --git a/resources/string_quartet_3_rise.json_bak b/resources/string_quartet_3_rise.json_bak index a1aae83..69a714a 100644 --- a/resources/string_quartet_3_rise.json_bak +++ b/resources/string_quartet_3_rise.json_bak @@ -16,7 +16,16 @@ "47bdba0e", "5f3f1c84", "4f53a446", - "731cf7ae", - "4da80bfb" + "624f7439", + "61207e49", + "50c2b0ad", + "66b20499", + "65120e88", + "567a9375", + "736745da", + "5f0075ab", + "5ef20586", + "4dd2a130", + "767e70f0" ] } \ No newline at end of file diff --git a/resources/string_quartet_3_rise/4dd2a130/4dd2a130_code.scd b/resources/string_quartet_3_rise/4dd2a130/4dd2a130_code.scd new file mode 100644 index 0000000..e1a9d92 --- /dev/null +++ b/resources/string_quartet_3_rise/4dd2a130/4dd2a130_code.scd @@ -0,0 +1,992 @@ +( +// helper funcs +var hsArrayToCents, pDist, hdSum, hsChordalDistance, hsArrayToFreq; + +// score funcs +var isInRange, spacingScore, rangeScore, intervalScore, inclusionScore; + +// subroutines +var genTuples, initVoices, genOrders, genSubMotif, updateVoices, genDurFunc, genStepFunc; + +// primary routines +var genMotif, genSecondarySeq; + +// audition funcs +var genPatterns, genMidiPatterns; + +// resource management funcs +var seedFunc, genUID, writeResources, stringifyToDepth, setSeeds, sanityCheck, +msgInterpret, loadLedgerFile, loadLedgerJSON, loadModelFile, loadModelJSON, +setGlobalVars, globalVarsToDict, saveLedger; + +// model vars +//(model and global vars mostly set by OSC funcs +var seq, lastXChanges, +curUID, refUID, orderSeed, durSeed, motifSeed, +entrancesProbVals, passagesProbVals, exitsProbVals, +ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, +orders, susWeights, orderSize, passagesSize, +motifEdited, orderEdited; + +// model aux vars +var entrancesDurFunc, passagesDurFunc, exitsDurFunc, stepFunc; + +// other global vars +var popSize, exPath, dir, primes, dims, tuples, +group, player, resourceDir, ledgerPath, ledger, currentlyPlayingUID, +nameSpaces; + +// install JSON quark (not used) +/* +if(Quarks.isInstalled("JSONlib").not, { + Quarks.install("https://github.com/musikinformatik/JSONlib.git"); + thisProcess.recompile; + //HelpBrowser.openHelpFor("Classes/JSONlib"); +}); +*/ + + +//------helper funcs + +hsArrayToCents = { + arg hsArray; + hsArray.collect({arg dist, p; dist * 1200 * log2(primes[p][0]/primes[p][1])}).sum +}; + +pDist = { + arg array1, array2, signed = false; + var pDistance; + pDistance = hsArrayToCents.value(array2) - hsArrayToCents.value(array1); + if(signed, {pDistance}, {abs(pDistance)}) +}; + +hdSum = { + arg hsArrays; + var size, distances, mean; + size = hsArrays.size; + distances = (size - 1).collect({arg i; + ((i + 1)..(size - 1)).collect({arg j; + abs(hsArrays[i] - hsArrays[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsChordalDistance = { + arg hsArrays1, hsArrays2; + var size, distances, mean; + size = hsArrays1.size; + distances = hsArrays1.size.collect({arg i; + hsArrays2.size.collect({arg j; + abs(hsArrays1[i] - hsArrays2[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsArrayToFreq = { + arg array; + array.collect({arg dim, d; pow(primes[d][0]/primes[d][1], dim)}).product +}; + +//------score funcs + +/* +isInRange = { + arg hsArray, min, max; + var cents; + cents = hsArrayToCents.value(hsArray); + (cents >= min) && (cents <= max) +}; +*/ + +spacingScore = { + arg hsArrays, min; + var centsArray; + centsArray = hsArrays.collect({arg hsArray; hsArrayToCents.value(hsArray)}).sort({arg a, b; a < b}); + centsArray.differentiate.drop(1).collect({arg pDistance; if(pDistance >= min, {1}, {0.01})}).sum; +}; + +rangeScore = { + arg hsArray1, hsArray2, min, max, low, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + if((pDistance >= min) && (pDistance <= max), {1}, {low}); +}; + +/* +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + stepFunc.value(pDistance) +}; +*/ + +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = true; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + //if(pDistance >= 0, {stepFunc.value(abs(pDistance))}, {0.01}); + stepFunc.value(pDistance) +}; + +inclusionScore = { + arg array, test, min = 0.01; + if(array.collect({arg v; v.hash}).includes(test.hash), {min}, {1}); +}; + + +//------subroutines + +genTuples = { + var tuples; + tuples = dims.collect({[-1, 0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum <= 1) && (tuple[0] == 0)}); + //tuples = dims.collect({[0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum == 1) && (tuple[0] == 0)}); + tuples = tuples ++ tuples.collect({arg tuple; [-3, -2, -1, 1, 2, 3].collect({arg octTrans; tuple.deepCopy.put(0, octTrans)})}).flatten; +}; + +initVoices = { + var init, voicesInit; + voicesInit = popSize.collect({dims.collect({0})}); + /* + voicesInit = [dims.collect({0})]; + (popSize - 1).do({ + arg rep, new; + rep = dims.rand; + new = voicesInit.last.deepCopy; + new[rep] = new[rep] + [-1, 1].choose(); + voicesInit = voicesInit.add(new); + }); + */ + voicesInit.deepCopy; +}; + +genDurFunc = {arg chordProb, minPad, maxPad, minDur, maxDur, envData, seed; + var env, pTable, durFunc; + env = Env.pairs([[0, 0]] ++ envData.clump(2) ++ [[1, 0]]).asSignal(256).asList.asArray; + pTable = env.asRandomTable; + //[chordProb, minPad, maxPad, minDur, maxDur, envData].postln; + durFunc = {arg allowChord, pad = false; + var res; + res = if(allowChord.not, { + pTable.tableRand * (maxDur - minDur) + minDur + }, { + if(1.0.rand < chordProb, {0}, {pTable.tableRand * (maxDur - minDur) + minDur}); + }).round(0.125); + if(pad, {res = res + rrand(minPad.asFloat, maxPad.asFloat).round(0.125)}); + if(res.asInteger == res, {res = res.asInteger}); + res + }; + seedFunc.value(durFunc, seed); +}; + +genStepFunc = {arg minStep, maxStep, envData, seed; + var envDataNorm, env, pTable, stepFunc; + //[minStep, maxStep, envData].postln; + envDataNorm = ([[0, 0]] ++ envData.clump(2) ++ [[1, 0]]).flop; + envDataNorm = [envDataNorm[0].normalize(minStep, maxStep), envDataNorm[1]].flop; + env = Env.pairs(envDataNorm); + stepFunc = {arg pDist; + env.at(pDist).clip(0.001, 1); + }; + seedFunc.value(stepFunc, seed); +}; + +genOrders = {arg minMotifLength = 1, maxMotifLength = 5, minProgLength = 0, maxProgLength = 5; + ((maxMotifLength.asInteger - minMotifLength.asInteger).rand + minMotifLength.asInteger).collect({ + var noProgIns, noSusIns, noSilentIns, prog, sus, silent, order; + noSusIns = [1, 2, 3].wchoose(susWeights.normalizeSum); + + //noProgIns = (popSize - noSusIns).rand + 1; + noProgIns = (popSize - noSusIns); + noSilentIns = popSize - noSusIns - noProgIns; + + /* + noSilentIns = (popSize - noSusIns).rand.clip(0, 1); + noProgIns = popSize - noSusIns - noSilentIns; + */ + + # prog, sus, silent = (0..(popSize-1)).scramble.clumps([noProgIns, noSusIns, noSilentIns]); + + prog = (prog.scramble ++ ((maxProgLength.asInteger - minProgLength.asInteger).rand + minProgLength.asInteger).collect({prog.choose}).scramble); + //prog = ((maxProgLength.asInteger - minProgLength.asInteger).rand + minProgLength.asInteger).collect({prog.choose}); + + if(silent == nil, {silent = []}); + [sus.scramble, prog, silent.scramble] + }); +}; + +updateVoices = {arg ins, sus; + var voices, candidates, nWeights, nProbs, sel; + + voices = lastXChanges.deepCopy.last; + + candidates = sus.collect({arg v; tuples.collect({arg t; voices[v] + t})}).flatten; + candidates = difference(candidates.asSet, voices.asSet).asList; + //candidates.select({arg item; (item ++ voices).asSet.size >= 4}); + nProbs = candidates.collect({arg candidate; + var stepScore, recentlySoundedScore, isInRangeScore, regScore, hdScore; + + //stepScore = intervalScore.value(voices[ins], candidate, 30, 400, 0.1); + stepScore = intervalScore.value(voices[ins], candidate, 100, 100); + recentlySoundedScore = inclusionScore.value(lastXChanges.flop[ins], candidate, 0.01); + //recentlySoundedScore = inclusionScore.value(lastXChanges.flatten.collect({arg item; item.drop(1)}), candidate.drop(1), 0.01); + + /* + if(rangeScore.value(candidate.collect({0}), voices[ins], ranges[ins][1] - 500, ranges[ins][1], 0, true) == 1, { + isInRangeScore = 1 - rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0] + 500, ranges[ins][1] - 500, 0, true); + isInRangeScore = isInRangeScore * rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true) + }, { + isInRangeScore = rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true); + }); + */ + + + isInRangeScore = rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true); + //old way - worked but actually by accident (I think) + //isInRangeScore = rangeScore.value(candidate, candidate.collect({0}), ranges[ins][0], ranges[ins][1], 0, true); + + + regScore = spacingScore.value(voices.deepCopy.put(ins, candidate), 300); + hdScore = pow(hdSum.value(voices.deepCopy.put(ins, candidate)), hdExp); + if(hdInvert == 0, {hdScore = 1/hdScore}); + //maybe what you want here is a vector to another root and then favoring movement towards it. + //distScore = pow(hsChordalDistance.value(voices, voices.put(ins, candidate)), 2); + + [stepScore, recentlySoundedScore, isInRangeScore, regScore, hdScore] + }); + + nWeights = passagesWeights; + + //this handles nWeights of 0; mainly for testing + nProbs = nProbs.flop.select({arg scores, s; nWeights[s] != 0}).flop; + nWeights = nWeights.select({arg weight; weight != 0}); + nProbs = nProbs.flop.collect({arg scores, s; + if(scores.sum == 0, {scores}, {scores.normalizeSum * nWeights[s]}).postln; + }); + nProbs = nProbs.flop.collect({arg scores, s; scores.product}).normalizeSum; + + nProbs.round(0.001).postln; + [candidates, nProbs.round(0.001)].flop.postln; + + sel = candidates.wchoose(nProbs); + + voices[ins] = sel; + lastXChanges = lastXChanges.add(voices).keep(-5); +}; + +genSubMotif = {arg order, orderIndex, lastState, repeatLast = false, startFromLast = false, isLastOrder = false; + var sus, prog, silent, flatOrder, res, isInChord, allowChord, pad, lastXChangesHold, voices, adder; + # sus, prog, silent = order; + flatOrder = silent ++ sus ++ prog; + lastXChangesHold = lastXChanges.deepCopy; + voices = lastState.deepCopy; + isInChord = popSize.collect({false}); + allowChord = false; + pad = false; + res = []; + "------generating motif".postln; + //need to figure out here if voices move between motifs + flatOrder.do({arg ins, i; + + if(prog.includes(ins) && repeatLast.not, {updateVoices.value(ins, sus)}); + adder = if(silent.includes(ins), {["Rest"]}, {lastXChanges.last.deepCopy[ins]}); + + if(voices[ins] != adder, { + var dur; + + if((sus ++ silent).includes(ins), { + allowChord = (ins != sus.last); + pad = (ins == sus.last); + }, { + if(i < (flatOrder.size - 1), { + allowChord = (isInChord[flatOrder[i + 1]] || (ins == flatOrder[i + 1])).not; + pad = false; + }, { + allowChord = false; + pad = true + }); + }); + if((orderIndex == 0) && sus.includes(ins), { + dur = entrancesDurFunc.value(allowChord, pad); + }, { + dur = passagesDurFunc.value(allowChord, pad); + }); + if(dur == 0, {isInChord[ins] = true}, {isInChord = popSize.collect({false})}); + + voices[ins] = adder; + res = res.add([voices.deepCopy.postln, dur]); + }); + }); + + // pad ending + /* + if(orderIndex == (orders.size - 1), { + (0..(popSize-1)).scramble.do({arg ins; + if(res.last.first[ins] != ["Rest"], { + var dur; + voices[ins] = ["Rest"]; + allowChord = (voices != popSize.collect({["Rest"]})); + pad = allowChord.not; + dur = exitsDurFunc.value(allowChord, pad); + res = res.add([voices.deepCopy.postln, dur]); + }); + }); + }); + */ + + //format and return + if(startFromLast, {lastXChanges = lastXChangesHold.deepCopy}); + res; +}; + + +//------primary routines + +genMotif = { + var repeats, fSeq, fDur, durAdd; + + repeats = 1; + fSeq = []; + + repeats.do({arg index; + var motif; + + motif = []; + + orders.do({arg order, o; + var lastState, subMotif; + //lastState = if(o == 0, {popSize.collect({["Rest"]})}, {motif.last.last.first}); + lastState = if(o == 0, {lastXChanges.last.deepCopy}, {motif.last.last.first}); + subMotif = genSubMotif.value(order, o, lastState, isLastOrder: o == (orders.size - 1)); + motif = motif.add(subMotif); + + }); + + sanityCheck.value(motif, index); + + fSeq = fSeq.add(motif); + }); + + //round last duration to measure + /* + fDur = fSeq.flatten.flatten.slice(nil, 1).sum; + durAdd = fDur.round(4) - fDur; + if(durAdd < 0, {durAdd = 4 - durAdd}); + fSeq[0][orders.size - 1][fSeq[0][orders.size - 1].size - 1][1] = fSeq[0][orders.size - 1][fSeq[0][orders.size - 1].size - 1][1] + durAdd; + */ + + fSeq +}; + +genSecondarySeq = {arg seq; + var curdles, fSeq; + curdles = []; + while({curdles.sum < seq.size}, {curdles = curdles ++ [3.rand + 1]}); + + fSeq = seq.clumps(curdles).collect({arg clump, m; + var repeats, paddedSeq; + + //add rest + paddedSeq = clump.add([[[popSize.collect({["Rest"]}), 0.5.rand]]]); + + //implement repeats + repeats = [0.rand + 1, 1].wchoose([1, 0].normalizeSum); + repeats.collect({paddedSeq}); + }); + fSeq +}; + + +//------audition funcs + +/* +Event.addEventType(\osc, { + if (~addr.postln.notNil) { + ~addr.sendMsg(~indexPath, ~indexMsg); + ~addr.sendMsg(~seqPath, stringifyToDepth.value(~seqMsg, 3)); + //~addr.sendMsg("/STATE/OPEN", (dir.replace("supercollider", "resources") +/+ ~idMsg +/+ ~idMsg ++ "_gui_state" ++ ".state").standardizePath.postln); + }; +}); +*/ + +Event.addEventType(\osc, { + if (~addr.notNil) { + ~msg; + ~addr.sendMsg(~path, *~msg); + }; +}); + +genPatterns = {arg inSeq, addr, oneShot = false; + var voices, durs, pbinds, res, indices, sectionDurs, msg, ids, seq; + seq = inSeq.collect({arg mSeq; mSeq[0]}); + //# voices, durs = seq.flatten2(seq.maxDepth - 5).flop; + # voices, durs = seq.flatten2(if(oneShot, {2}, {3})).flop; + pbinds = voices.flop.collect({arg voice, v; + var clumps, hdScores, freqs, fDurs, attacks, rels, amps; + clumps = voice.separate({arg a, b; a != b }); + freqs = clumps.collect({arg clump; if(clump[0] != ["Rest"], {(62.midicps * hsArrayToFreq.value(clump[0]))}, {Rest(0)})}); + fDurs = durs.clumps(clumps.collect({arg clump; clump.size})).collect({arg clump; clump.sum}); + //attacks = 2.collect({rrand(1, 3)}) ++ freqs.drop(2).collect({rrand(0.3, 0.5)}); + attacks = fDurs.collect({arg dur; dur * rrand(0.2, 0.4)}); + //rels = freqs.drop(2).collect({rrand(0.3, 0.5)}) ++ 2.collect({rrand(1, 3)}); + rels = (clumps.size - 1).collect({arg c; + if(clumps[c + 1][0] == ["Rest"], {rrand(1.0, 3.0)}, {rrand(0.3, 0.5)}); + }); + rels = rels.add(rrand(1.0, 3.0)); + amps = freqs.collect({rrand(0.6, 0.99)}); + + [ + Pbind( + \instrument, \string_model, + \group, group, + \freq, Pseq(freqs, 1), + \dur, Pseq(fDurs, 1), + \attack, Pseq(attacks, 1), + \sustain, Pseq(fDurs, 1), + \release, Pseq(rels, 1), + //\amp, Pseq(amps, 1), + \amp, Pbrown(0.5, 1, 0.5), + \busIndex, v + ), + Pbind( + \instrument, \sine, + \group, group, + \freq, Pseq(freqs, 1), + \dur, Pseq(fDurs, 1), + \sustain, Pseq(fDurs, 1), + \busIndex, v + ) + ] + }).flatten; + if(oneShot.not, { + msg = inSeq.collect({arg mSeq, m; mSeq[1..]}); + //ids = inSeq.collect({arg mSeq, m; mSeq[2]}); + sectionDurs = seq.collect({arg mSeq; mSeq.flatten2(2).flop[1].sum}); + pbinds = pbinds ++ + [ + Pbind( + \type, \osc, + \addr, addr, + \path, "/playing", + \msg, Pseq(msg, 1), + \dur, Pseq(sectionDurs, 1) + ); + ] + }); + res = Ppar(pbinds); + res +}; + +/* +genMidiPatterns = {arg seq; + var voices, durs, patterns, res, mOut, pbRange; + pbRange = 1; //semitones - change this as needed for your situation + mOut = MIDIOut.newByName("TiMidity", "TiMidity port 0").latency_(Server.default.latency); + # voices, durs = seq.flatten2(seq.maxDepth - 5).flop; + res = Ppar( + voices.flop.collect({arg voice, v; + var clumps, hdScores, freqs, fDurs; + + mOut.program(v, 70); + + clumps = voice.separate({arg a, b; a != b }); + freqs = clumps.collect({arg clump; if(clump[0] != ["Rest"], {(60.midicps * hsArrayToFreq.value(clump[0]))}, {Rest(0)})}); + fDurs = durs.clumps(clumps.collect({arg clump; clump.size})).collect({arg clump; clump.sum}); + + Pbind( + \type, \midi, + \chan, v, + \noteval, Pseq(freqs.cpsmidi - 24, 1), + \note, Pfunc({ | event | event[\noteval].floor }), + \dur, Pseq(fDurs, 1), + \midiout, mOut, + \amp, 1, + \bend, Pfunc({ + | event | + if (event[\note].isRest.not) { + var pitchbendvalue = event[\noteval].frac.linlin(0, pbRange, 8192, 8192*2).asInteger; + m.bend(v, pitchbendvalue); + }; + 0; // return something other than nil to avoid stopping the pattern + }), + ); + }); + ); + res +}; +*/ + + +//------resource management funcs + +genUID = {Date.seed.asHexString.toLower}; + +seedFunc = {arg func, seed; + var funcArgs, next; + next = Routine({loop{func.valueArray(funcArgs).yield }}); + next.randSeed_(seed); + {arg ...args; funcArgs = args; next.value} +}; + +stringifyToDepth = {arg data, maxDepth = 1; + var prettyString = "", rCount = 0, writeArray, indent; + + if(maxDepth == 0, { + data.asCompileString + }, { + indent = {arg size; size.collect({" "}).join("")}; + writeArray = {arg array; + prettyString = prettyString ++ indent.value(rCount) ++ "[\n"; + rCount = rCount + 1; + if(rCount < maxDepth, { + array.do({arg subArray; writeArray.value(subArray)}); + }, { + prettyString = prettyString ++ array.collect({arg subArray; + indent.value(rCount + 1) ++ subArray.asCompileString + }).join(",\n"); + }); + rCount = rCount - 1; + prettyString = prettyString ++ "\n" ++ indent.value(rCount) ++ "],\n"; + }; + + writeArray.value(data); + prettyString.replace(",\n\n", "\n").drop(-2); + }) +}; + +sanityCheck = {arg motif, index; + //print functions = very helpful + ("----------" + index + "------------").postln; + + motif.flatten.do({arg val, v; + if(v > 0, { + if(motif.flatten[v-1][0].hammingDistance(val[0]) > 1, {"problem 1".postln}); + if(motif.flatten[v-1][0].hammingDistance(val[0]) == 0, {"problem 2".postln}); + }); + val.postln + }); + "***********".postln; +}; + +msgInterpret = {arg in, escapeDoubleQuotes = true, escapeSingleQuotes = true; + var res; + + res = in; + if(res.isNil.not, { + if((res.isArray && res.isString.not), { + res = res.asCompileString; + res = res.replace(" ", "").replace("\n", "").replace("\t", ""); + if(escapeSingleQuotes, {res = res.replace("\'", "")}); + if(escapeDoubleQuotes, {res = res.replace("\"", "")}); + res = res.replace("Rest", "\"Rest\""); + res = res.interpret; + }, { + var tmpRes; + if(res.every({arg char; char.isDecDigit}), {tmpRes = res.asInteger}); + if(res.contains("."), {tmpRes = res.asFloat}); + if(tmpRes != nil, {res = tmpRes}); + }); + }); + res +}; + +writeResources = {arg path, dict; + var file, modelItems, resString; + file = File(path,"w"); + + modelItems = [ + seq, lastXChanges, + curUID, refUID, orderSeed, durSeed, motifSeed, + entrancesProbVals, passagesProbVals, exitsProbVals, + ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, + orders, susWeights, orderSize, passagesSize, + motifEdited, orderEdited + ]; + + resString = nameSpaces.collect({arg nameSpace; + var depth = 0, insert = " "; + if(nameSpace == "music_data", {depth = 3; insert = "\n"}); + if(nameSpace == "last_changes", {depth = 1; insert = "\n"}); + if(nameSpace == "order", {depth = 1; insert = "\n"}); + if((nameSpace == "ref_uid") && (dict[nameSpace] == nil), {dict[nameSpace] = "nil"}); + "\"" ++ nameSpace ++ "\":" ++ insert ++ stringifyToDepth.value(dict[nameSpace], depth) + }).join(",\n"); + + resString = "{\n" ++ resString ++ "\n}"; + + file.write(resString); + file.close; + resString +}; + +loadModelFile = {arg path; loadModelJSON.value(File(path, "r").readAllString.parseJSON)}; + +loadModelJSON = {arg jsonObject; + var dict; + dict = Dictionary.with(*nameSpaces.collect({arg nS; nS->msgInterpret.value(jsonObject[nS])})); + dict +}; + +setGlobalVars = {arg dict, skipLastXChanges = false; + var tmpLastXChanges; + tmpLastXChanges = lastXChanges.deepCopy; + // order really matters!!!! + # seq, lastXChanges, curUID, refUID, orderSeed, durSeed, motifSeed, + entrancesProbVals, passagesProbVals, exitsProbVals, + ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, + orders, susWeights, orderSize, passagesSize, + motifEdited, orderEdited = nameSpaces.collect({arg nS; dict[nS]}); + if(skipLastXChanges, {lastXChanges = tmpLastXChanges}); + dict +}; + +globalVarsToDict = { + var modelItems, dict; + // order really matters!!!! + modelItems = [ + seq, lastXChanges, + curUID, refUID, orderSeed, durSeed, motifSeed, + entrancesProbVals, passagesProbVals, exitsProbVals, + ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, + orders, susWeights, orderSize, passagesSize, + motifEdited, orderEdited + ]; + dict = Dictionary.with(*nameSpaces.collect({arg nS, n; nS->modelItems[n]})); +}; + +loadLedgerFile = {arg path; + ledgerPath = path; + resourceDir = path.splitext(".").drop(-1).join; + loadLedgerJSON.value(File(ledgerPath, "r").readAllString.parseJSON) +}; + +loadLedgerJSON = {arg jsonObject; ledger = jsonObject["ledger"]}; + +saveLedger = {arg ledger, path; + var file, curResourceDir; + file = File(path, "w"); + curResourceDir = resourceDir; + resourceDir = path.splitext(".").drop(-1).join; + if(curResourceDir != resourceDir, { + File.mkdir(resourceDir); + ledger.do({arg id; + File.copy(curResourceDir +/+ id, resourceDir +/+ id); + }); + }); + file.write("{\n\"ledger\":\n" ++ stringifyToDepth.value(ledger, 1) ++ "\n}"); + file.close; +}; + +//------global vars + +primes = [[2, 1], [3, 2], [5, 4], [7, 4], [11, 8], [13, 8]]; +//ranges = [[-2400, 0], [-1200, 1200], [0, 2400], [0, 2400]]; +exPath = thisProcess.nowExecutingPath; +dir = exPath.dirname; +//popSize = 4; +dims = primes.size; +tuples = genTuples.value(); +//refUID = nil; +group = Group.new; +~group = group; +loadLedgerFile.value(dir +/+ ".." +/+ "resources" +/+ "piece_ledger.json"); +resourceDir = (dir +/+ ".." +/+ "resources" +/+ "piece_ledger"); +//passagesWeights = [1, 1, 1, 1, 1]; +//susWeights = [1, 1, 1]; +// order really matters!!!! +nameSpaces = [ + "music_data", "last_changes", "cur_uid", "ref_uid", "order_seed", "dur_seed", "motifs_seed", + "entrances_probs_vals","passages_probs_vals", "exits_probs_vals", + "ranges", "step_probs_vals", "passages_weights", "hd_exp", "hd_invert", + "order", "sus_weights", "order_size", "passages_size", + "motif_edited", "order_edited" +]; + + +//------OSC funcs + +OSCdef(\load_ledger, {arg msg, time, addr, port; + loadLedgerFile.value(msg[1].asString); +}, \load_ledger); + +OSCdef(\load_model, {arg msg, time, addr, port; + var dict; + dict = loadModelFile.value(msg[1].asString); + setGlobalVars.value(dict); +}, \load_model); + +OSCdef(\save_ledger, {arg msg, time, addr, port; + msg.postln; + ledger = msgInterpret.value(msg[1].asString.parseJSON["ledger"], false).postln; + //loadLedgerJSON.value(msg[0]) + saveLedger.value(ledger, msg[2].asString); + //loadLedgerFile.value(msg[1].asString); +}, \save_ledger); + +OSCdef(\generate, {arg msg, time, addr, port; + var path, dict, durSeeds, musPath, modelString; + msg.postln; + + path = msg[1].asString; + + dict = loadModelFile.value(path); + setGlobalVars.value(dict, true); + + popSize = ranges.size; + + //refUID.postln; + + loadLedgerFile.value(ledgerPath); + if(ledger == nil, {ledger = ["tmp"]}); + if(ledger.last != "tmp", {ledger = ledger.add("tmp")}); + + if(refUID == nil, {lastXChanges = [initVoices.value().deepCopy]}); + if((refUID != nil) && (refUID != "tmp"), { + var file; + file = File((resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath, "r"); + lastXChanges = msgInterpret.value(file.readAllString.parseJSON["last_changes"]); + }); + + refUID.postln; + lastXChanges.collect({arg item; item.postln}); + + durSeeds = seedFunc.value({3.collect({rrand(100000, 999999)})}, durSeed).value.postln; + entrancesDurFunc = genDurFunc.valueArray(entrancesProbVals[..4] ++ [entrancesProbVals[5..]] ++ [durSeeds[0]]); + passagesDurFunc = genDurFunc.valueArray(passagesProbVals[..4] ++ [passagesProbVals[5..]] ++ [durSeeds[1]]); + exitsDurFunc = genDurFunc.valueArray(exitsProbVals[..4] ++ [exitsProbVals[5..]] ++ [durSeeds[2]]); + + if(orders == nil, { + orders = seedFunc.value(genOrders, orderSeed).valueArray(orderSize ++ passagesSize); + //addr.sendMsg("/order", stringifyToDepth.value(orders, 1)); + }); + + stepFunc = genStepFunc.valueArray(stepProbsVals[..1] ++ [stepProbsVals[2..]] ++ [motifSeed]); + seq = seedFunc.value(genMotif, motifSeed).value; + + lastXChanges.collect({arg item; item.postln}); + + dict = globalVarsToDict.value; + modelString = writeResources.value(path, dict); + + //addr.sendMsg("/generated", musPath, stringifyToDepth.value(seq, 3)); + //~seq = seq; + + addr.sendMsg("/generated", path, modelString, ledgerPath); +}, \generate); + + +OSCdef(\commit, {arg msg, time, addr, port; + var musicData, musicChanged, dict, newLedger, modelPath, musString, musFile, test1, test2, lastCurUID, commitType, commitPos, equalityLedger; + //msg.postln; + + /* + test1 = msg[1].asString.parseJSON; + test2 = (dir +/+ ".." +/+ "resources/tmp/tmp_music" ++ ".json").standardizePath.parseJSONFile; + msgInterpret.value(test1["music"])[0][0][0][1].class.postln; + msgInterpret.value(test2["music_data"])[0][0][0][1].class.postln; + (test1["music"] == test2["music_data"]).postln; + */ + + musicData = loadModelJSON.value(msg[1].asString.parseJSON)["music_data"].postln; + musicChanged = (musicData != seq).postln; + commitType = msg[2].asString; + commitPos = msg[3].postln.asInteger; + + lastCurUID = curUID.deepCopy; + curUID = genUID.value; + + File.mkdir((resourceDir +/+ curUID).standardizePath); + File.copy(exPath, (resourceDir +/+ curUID +/+ curUID ++ "_code" ++ ".scd").standardizePath); + + modelPath = (resourceDir +/+ curUID +/+ curUID ++ "_mus_model" ++ ".json").standardizePath; + dict = globalVarsToDict.value; + if(musicChanged, { + seq = musicData; + dict["music_data"] = seq; + dict["motif_edited"] = "true" + }); + dict["cur_uid"] = curUID; + + writeResources.value(modelPath, dict); + + File.delete(ledgerPath ++ "_bak"); + File.copy(ledgerPath, ledgerPath ++ "_bak"); + File.delete(ledgerPath); + + /* + if(commitType == "add", { + if(lastCurUID == "tmp", { + ledger = ledger.drop(-1).add(curUID); + }, { + ledger = ledger.add(curUID); + }) + }); + */ + + ledger.postln; + + if(commitType == "add", {ledger = ledger.add(curUID)}); + + if(commitType == "insert", {ledger = ledger.insert(commitPos + 1, curUID)}); + + if(commitType == "replace", {ledger = ledger.put(commitPos, curUID)}); + + equalityLedger = ledger.collect({arg item; item.asSymbol}); + if(equalityLedger.includes(\tmp).postln, {ledger.removeAt(equalityLedger.indexOf(\tmp).postln)}); + + ledger.postln; + + saveLedger.value(ledger, ledgerPath); + + addr.sendMsg("/committed", curUID, ledgerPath); + //refUID = curUID; + +}, \commit); + +OSCdef(\transport, {arg msg, time, addr, port; + msg.postln; + if(msg[1] == 0, { + group.set(\release, 2); + group.set(\gate, 0); + player.stop; + }, { + // the cued sequence can now be read from file, so this can be cleaned up + var cSize, patterns, pSeq, cuedSeek, indexStart, indexEnd, tmpLedger; + if(msg[1] == 1, { + pSeq = []; + cuedSeek = (seq != nil); + indexStart = msg[2].asInteger; + indexEnd = ledger.size - if(cuedSeek, {2}, {1}); + //ledger.postln; + if(((indexStart == (ledger.size - 1)) && cuedSeek).not, { + ledger[indexStart..indexEnd].do({arg uid, index; + var path, file; + path = (resourceDir +/+ uid +/+ uid ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.postln.parseJSON["music_data"]), path, indexStart + index, uid]); + file.close; + }); + }); + if(cuedSeek, { + var path, file; + path = (resourceDir +/+ "tmp/tmp_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.parseJSON["music_data"]), path, ledger.size - 1, "tmp"]); + file.close; + }); + patterns = genPatterns.value(pSeq, addr); + }, { + pSeq = [loadModelJSON.value(msg[2].asString.parseJSON)["music_data"].postln]; + patterns = genPatterns.value(pSeq, addr, true); + }); + player = Pfset(pattern: patterns, cleanupFunc: { + addr.sendMsg("/transport", 0); + addr.sendMsg("/one_shot", 0); + }); + player = player.play + }); +}, \transport); + + +OSCdef(\transcribe_motif, {arg msg, time, addr, port; + var tSeq, refChord, refUID; + + msg.postln; + + tSeq = [loadModelJSON.value(msg[1].asString.parseJSON)["music_data"]]; + refUID = msg[2].asString.postln; + + if((refUID != "nil") && (refUID != "tmp"), { + var file; + file = File((resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath, "r"); + refChord = msgInterpret.value(file.readAllString.parseJSON["last_changes"]).last; + file.close; + }, { + refChord = [[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0]]; + }); + + ~transcribe.value(tSeq, refChord, (dir +/+ ".." +/+ "lilypond" +/+ "includes").standardizePath, addr, "/transcribe_motif"); +}, \transcribe_motif); + + +OSCdef(\transcribe_all, {arg msg, time, addr, port; + var cSize, patterns, cuedSeek, indexStart, indexEnd, tmpLedger; + if(true, { + cuedSeek = (seq != nil); + indexStart = msg[1].asInteger; + indexEnd = ledger.size - if(cuedSeek, {2}, {1}); + + //tmp for testing transcription + //indexEnd = (indexStart+5); + + //ledger.postln; + if(((indexStart == (ledger.size - 1)) && cuedSeek).not, { + var lilyPartLedgerFiles; + + lilyPartLedgerFiles = 4.collect({arg p; + File((dir +/+ ".." +/+ "lilypond" +/+ "includes" +/+ "part_" ++ ["IV", "III", "II", "I"][p] ++ ".ly").standardizePath, "w"); + }); + + ledger[indexStart..indexEnd].do({arg uid, index; + var path, file, fileString, tSeq, refUID, refChord; + path = (resourceDir +/+ uid +/+ uid ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + fileString = file.readAllString; + tSeq = msgInterpret.value(fileString.parseJSON["music_data"]); + refUID = msgInterpret.value(fileString.parseJSON["ref_uid"]); + file.close; + + //uid.postln; + //(refUID == "nil").postln; + + refChord = [[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0]]; + + if(refUID != "nil", { + path = (resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + refChord = msgInterpret.value(file.readAllString.parseJSON["last_changes"]).last; + file.close; + }); + + if(index != indexEnd, { + ~transcribe.value(tSeq, refChord, (resourceDir +/+ uid +/+ "lilypond").standardizePath); + }, { + ~transcribe.value(tSeq, refChord, (resourceDir +/+ uid +/+ "lilypond").standardizePath, addr, "/transcribe_all"); + }); + + lilyPartLedgerFiles.do({arg f, p; + f.write("\\include \"" ++ resourceDir +/+ uid +/+ "lilypond" +/+ "part_" ++ ["IV", "III", "II", "I"][p] ++ ".ly\"\n"); + }); + + }); + + lilyPartLedgerFiles.do({arg f; + f.close + }); + }); + /* + if(cuedSeek, { + var path, file; + path = (dir +/+ ".." +/+ "resources/tmp/tmp_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.parseJSON["music_data"]), path, ledger.size - 1, "tmp"]); + file.close; + }); + */ + }, { + + }); + +}, \transcribe_all); + +) + + diff --git a/resources/string_quartet_3_rise/4dd2a130/4dd2a130_mus_model.json b/resources/string_quartet_3_rise/4dd2a130/4dd2a130_mus_model.json new file mode 100644 index 0000000..bf4b64e --- /dev/null +++ b/resources/string_quartet_3_rise/4dd2a130/4dd2a130_mus_model.json @@ -0,0 +1,65 @@ +{ +"music_data": +[ + [ + [ + [ [ [ -5, 5, 1, 2, -1, 2 ], [ -5, 3, 1, 2, -1, 2 ], [ -4, 4, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -5, 5, 1, 2, -1, 2 ], [ -5, 3, 1, 2, -1, 2 ], [ -6, 5, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -4, 4, 0, 2, -1, 2 ], [ -5, 3, 1, 2, -1, 2 ], [ -6, 5, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0 ], + [ [ [ -4, 4, 0, 2, -1, 2 ], [ -5, 3, 1, 2, -1, 2 ], [ -5, 4, 0, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -4, 4, 0, 2, -1, 2 ], [ -5, 3, 1, 2, -1, 2 ], [ -4, 3, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -4, 4, 0, 2, -1, 2 ], [ -6, 5, 1, 2, -1, 2 ], [ -4, 3, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -4, 4, 0, 2, -1, 2 ], [ -6, 5, 1, 2, -1, 2 ], [ -5, 5, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ] + ], + [ + [ [ [ -4, 4, 0, 2, -1, 2 ], [ -5, 4, 0, 2, -1, 2 ], [ -5, 5, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0 ], + [ [ [ -4, 4, 0, 2, -1, 2 ], [ -5, 4, 0, 2, -1, 2 ], [ -4, 4, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -4, 4, 0, 2, -1, 2 ], [ -6, 4, 1, 2, -1, 2 ], [ -4, 4, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ] + ], + [ + [ [ [ -4, 4, 0, 2, -1, 2 ], [ -6, 4, 1, 2, -1, 2 ], [ -5, 4, 0, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -4, 4, 0, 2, -1, 2 ], [ -5, 4, 1, 1, -1, 2 ], [ -5, 4, 0, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0 ], + [ [ [ -5, 5, 1, 2, -1, 2 ], [ -5, 4, 1, 1, -1, 2 ], [ -5, 4, 0, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ] + ], + [ + [ [ [ -5, 4, 1, 2, -1, 3 ], [ -5, 4, 1, 1, -1, 2 ], [ -5, 4, 0, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0 ], + [ [ [ -5, 4, 1, 2, -1, 3 ], [ -5, 4, -1, 2, -1, 2 ], [ -5, 4, 0, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -5, 4, 1, 2, -1, 3 ], [ -5, 3, 1, 2, -1, 2 ], [ -5, 4, 0, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0 ], + [ [ [ -4, 3, 1, 2, -1, 2 ], [ -5, 3, 1, 2, -1, 2 ], [ -5, 4, 0, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ] + ] + ] +], +"last_changes": +[ + [ [ -5, 5, 1, 2, -1, 2 ], [ -5, 4, 1, 1, -1, 2 ], [ -5, 4, 0, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], + [ [ -5, 4, 1, 2, -1, 3 ], [ -5, 4, 1, 1, -1, 2 ], [ -5, 4, 0, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], + [ [ -5, 4, 1, 2, -1, 3 ], [ -5, 4, -1, 2, -1, 2 ], [ -5, 4, 0, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], + [ [ -5, 4, 1, 2, -1, 3 ], [ -5, 3, 1, 2, -1, 2 ], [ -5, 4, 0, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], + [ [ -4, 3, 1, 2, -1, 2 ], [ -5, 3, 1, 2, -1, 2 ], [ -5, 4, 0, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ] +], +"cur_uid": "4dd2a130", +"ref_uid": "5ef20586", +"order_seed": 837263, +"dur_seed": 562162, +"motifs_seed": 328077, +"entrances_probs_vals": [ 0.78, 0, 0, 0, 1.02, 0.0098039215686275, 0, 0.20915032679739, 0, 0.24183006535948, 0.82432432432432, 0.28758169934641, 0, 1, 0 ], +"passages_probs_vals": [ 0.78, 0, 0, 0, 1.02, 0.0098039215686275, 0, 0.20915032679739, 0, 0.24183006535948, 0.82432432432432, 0.28758169934641, 0, 1, 0 ], +"exits_probs_vals": [ 0.78, 0, 0, 0, 1.02, 0.0098039215686275, 0, 0.20915032679739, 0, 0.24183006535948, 0.82432432432432, 0.28758169934641, 0, 1, 0 ], +"ranges": [ [ -2400, 1200 ], [ -1200, 1453 ], [ -702, 1694 ], [ -702, 1973 ] ], +"step_probs_vals": [ -1200, 1200, 0, 0, 0.41358024691358, 0.0056818181818177, 0.46296296296296, 0.39204545454545, 0.52057613168724, 0, 0.56378600823045, 0.85795454545455, 0.60082304526749, 0, 1, 0 ], +"passages_weights": [ 1, 0.15, 0.43, 1, 1 ], +"hd_exp": 10, +"hd_invert": 0, +"order": +[ + [ [ 3 ], [ 1, 2, 0, 2, 2, 1, 2 ], [ ] ], + [ [ 0, 3 ], [ 1, 2, 1 ], [ ] ], + [ [ 3 ], [ 2, 1, 0 ], [ ] ], + [ [ 3, 2 ], [ 0, 1, 1, 0 ], [ ] ] +], +"sus_weights": [ 0.7, 0.48, 0.49 ], +"order_size": [ 14.132653061224, 14.132653061224 ], +"passages_size": [ 0, 5 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/4dd2a130/lilypond/part_I.ly b/resources/string_quartet_3_rise/4dd2a130/lilypond/part_I.ly new file mode 100644 index 0000000..bad9fe0 --- /dev/null +++ b/resources/string_quartet_3_rise/4dd2a130/lilypond/part_I.ly @@ -0,0 +1,4 @@ +{ + { f'1^\markup { \pad-markup #0.2 "-38"}} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/4dd2a130/lilypond/part_II.ly b/resources/string_quartet_3_rise/4dd2a130/lilypond/part_II.ly new file mode 100644 index 0000000..2db37cc --- /dev/null +++ b/resources/string_quartet_3_rise/4dd2a130/lilypond/part_II.ly @@ -0,0 +1,4 @@ +{ + { f''8^\markup { \pad-markup #0.2 "-38"}[ c'8^\markup { \pad-markup #0.2 "-37"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }}] cis'8^\markup { \pad-markup #0.2 "-25"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }}[ ais'8^\markup { \pad-markup #0.2 "-40"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }}] ~ ais'8[ c''8^\markup { \pad-markup #0.2 "-37"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }}] f''4^\markup { \pad-markup #0.2 "-38"}} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/4dd2a130/lilypond/part_III.ly b/resources/string_quartet_3_rise/4dd2a130/lilypond/part_III.ly new file mode 100644 index 0000000..d7a92c3 --- /dev/null +++ b/resources/string_quartet_3_rise/4dd2a130/lilypond/part_III.ly @@ -0,0 +1,4 @@ +{ + { ais2^\markup { \pad-markup #0.2 "-40"} c'4^\markup { \pad-markup #0.2 "-37"} cis'8^\markup { \pad-markup #0.2 "-25"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }}[ f8^\markup { \pad-markup #0.2 "-38"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 1↑" }}]} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/4dd2a130/lilypond/part_IV.ly b/resources/string_quartet_3_rise/4dd2a130/lilypond/part_IV.ly new file mode 100644 index 0000000..992a465 --- /dev/null +++ b/resources/string_quartet_3_rise/4dd2a130/lilypond/part_IV.ly @@ -0,0 +1,4 @@ +{ + { c''4^\markup { \pad-markup #0.2 "-37"} cis''2.^\markup { \pad-markup #0.2 "-25"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 5↓" }}} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/50620822/50620822_code.scd b/resources/string_quartet_3_rise/50620822/50620822_code.scd new file mode 100644 index 0000000..e1a9d92 --- /dev/null +++ b/resources/string_quartet_3_rise/50620822/50620822_code.scd @@ -0,0 +1,992 @@ +( +// helper funcs +var hsArrayToCents, pDist, hdSum, hsChordalDistance, hsArrayToFreq; + +// score funcs +var isInRange, spacingScore, rangeScore, intervalScore, inclusionScore; + +// subroutines +var genTuples, initVoices, genOrders, genSubMotif, updateVoices, genDurFunc, genStepFunc; + +// primary routines +var genMotif, genSecondarySeq; + +// audition funcs +var genPatterns, genMidiPatterns; + +// resource management funcs +var seedFunc, genUID, writeResources, stringifyToDepth, setSeeds, sanityCheck, +msgInterpret, loadLedgerFile, loadLedgerJSON, loadModelFile, loadModelJSON, +setGlobalVars, globalVarsToDict, saveLedger; + +// model vars +//(model and global vars mostly set by OSC funcs +var seq, lastXChanges, +curUID, refUID, orderSeed, durSeed, motifSeed, +entrancesProbVals, passagesProbVals, exitsProbVals, +ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, +orders, susWeights, orderSize, passagesSize, +motifEdited, orderEdited; + +// model aux vars +var entrancesDurFunc, passagesDurFunc, exitsDurFunc, stepFunc; + +// other global vars +var popSize, exPath, dir, primes, dims, tuples, +group, player, resourceDir, ledgerPath, ledger, currentlyPlayingUID, +nameSpaces; + +// install JSON quark (not used) +/* +if(Quarks.isInstalled("JSONlib").not, { + Quarks.install("https://github.com/musikinformatik/JSONlib.git"); + thisProcess.recompile; + //HelpBrowser.openHelpFor("Classes/JSONlib"); +}); +*/ + + +//------helper funcs + +hsArrayToCents = { + arg hsArray; + hsArray.collect({arg dist, p; dist * 1200 * log2(primes[p][0]/primes[p][1])}).sum +}; + +pDist = { + arg array1, array2, signed = false; + var pDistance; + pDistance = hsArrayToCents.value(array2) - hsArrayToCents.value(array1); + if(signed, {pDistance}, {abs(pDistance)}) +}; + +hdSum = { + arg hsArrays; + var size, distances, mean; + size = hsArrays.size; + distances = (size - 1).collect({arg i; + ((i + 1)..(size - 1)).collect({arg j; + abs(hsArrays[i] - hsArrays[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsChordalDistance = { + arg hsArrays1, hsArrays2; + var size, distances, mean; + size = hsArrays1.size; + distances = hsArrays1.size.collect({arg i; + hsArrays2.size.collect({arg j; + abs(hsArrays1[i] - hsArrays2[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsArrayToFreq = { + arg array; + array.collect({arg dim, d; pow(primes[d][0]/primes[d][1], dim)}).product +}; + +//------score funcs + +/* +isInRange = { + arg hsArray, min, max; + var cents; + cents = hsArrayToCents.value(hsArray); + (cents >= min) && (cents <= max) +}; +*/ + +spacingScore = { + arg hsArrays, min; + var centsArray; + centsArray = hsArrays.collect({arg hsArray; hsArrayToCents.value(hsArray)}).sort({arg a, b; a < b}); + centsArray.differentiate.drop(1).collect({arg pDistance; if(pDistance >= min, {1}, {0.01})}).sum; +}; + +rangeScore = { + arg hsArray1, hsArray2, min, max, low, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + if((pDistance >= min) && (pDistance <= max), {1}, {low}); +}; + +/* +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + stepFunc.value(pDistance) +}; +*/ + +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = true; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + //if(pDistance >= 0, {stepFunc.value(abs(pDistance))}, {0.01}); + stepFunc.value(pDistance) +}; + +inclusionScore = { + arg array, test, min = 0.01; + if(array.collect({arg v; v.hash}).includes(test.hash), {min}, {1}); +}; + + +//------subroutines + +genTuples = { + var tuples; + tuples = dims.collect({[-1, 0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum <= 1) && (tuple[0] == 0)}); + //tuples = dims.collect({[0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum == 1) && (tuple[0] == 0)}); + tuples = tuples ++ tuples.collect({arg tuple; [-3, -2, -1, 1, 2, 3].collect({arg octTrans; tuple.deepCopy.put(0, octTrans)})}).flatten; +}; + +initVoices = { + var init, voicesInit; + voicesInit = popSize.collect({dims.collect({0})}); + /* + voicesInit = [dims.collect({0})]; + (popSize - 1).do({ + arg rep, new; + rep = dims.rand; + new = voicesInit.last.deepCopy; + new[rep] = new[rep] + [-1, 1].choose(); + voicesInit = voicesInit.add(new); + }); + */ + voicesInit.deepCopy; +}; + +genDurFunc = {arg chordProb, minPad, maxPad, minDur, maxDur, envData, seed; + var env, pTable, durFunc; + env = Env.pairs([[0, 0]] ++ envData.clump(2) ++ [[1, 0]]).asSignal(256).asList.asArray; + pTable = env.asRandomTable; + //[chordProb, minPad, maxPad, minDur, maxDur, envData].postln; + durFunc = {arg allowChord, pad = false; + var res; + res = if(allowChord.not, { + pTable.tableRand * (maxDur - minDur) + minDur + }, { + if(1.0.rand < chordProb, {0}, {pTable.tableRand * (maxDur - minDur) + minDur}); + }).round(0.125); + if(pad, {res = res + rrand(minPad.asFloat, maxPad.asFloat).round(0.125)}); + if(res.asInteger == res, {res = res.asInteger}); + res + }; + seedFunc.value(durFunc, seed); +}; + +genStepFunc = {arg minStep, maxStep, envData, seed; + var envDataNorm, env, pTable, stepFunc; + //[minStep, maxStep, envData].postln; + envDataNorm = ([[0, 0]] ++ envData.clump(2) ++ [[1, 0]]).flop; + envDataNorm = [envDataNorm[0].normalize(minStep, maxStep), envDataNorm[1]].flop; + env = Env.pairs(envDataNorm); + stepFunc = {arg pDist; + env.at(pDist).clip(0.001, 1); + }; + seedFunc.value(stepFunc, seed); +}; + +genOrders = {arg minMotifLength = 1, maxMotifLength = 5, minProgLength = 0, maxProgLength = 5; + ((maxMotifLength.asInteger - minMotifLength.asInteger).rand + minMotifLength.asInteger).collect({ + var noProgIns, noSusIns, noSilentIns, prog, sus, silent, order; + noSusIns = [1, 2, 3].wchoose(susWeights.normalizeSum); + + //noProgIns = (popSize - noSusIns).rand + 1; + noProgIns = (popSize - noSusIns); + noSilentIns = popSize - noSusIns - noProgIns; + + /* + noSilentIns = (popSize - noSusIns).rand.clip(0, 1); + noProgIns = popSize - noSusIns - noSilentIns; + */ + + # prog, sus, silent = (0..(popSize-1)).scramble.clumps([noProgIns, noSusIns, noSilentIns]); + + prog = (prog.scramble ++ ((maxProgLength.asInteger - minProgLength.asInteger).rand + minProgLength.asInteger).collect({prog.choose}).scramble); + //prog = ((maxProgLength.asInteger - minProgLength.asInteger).rand + minProgLength.asInteger).collect({prog.choose}); + + if(silent == nil, {silent = []}); + [sus.scramble, prog, silent.scramble] + }); +}; + +updateVoices = {arg ins, sus; + var voices, candidates, nWeights, nProbs, sel; + + voices = lastXChanges.deepCopy.last; + + candidates = sus.collect({arg v; tuples.collect({arg t; voices[v] + t})}).flatten; + candidates = difference(candidates.asSet, voices.asSet).asList; + //candidates.select({arg item; (item ++ voices).asSet.size >= 4}); + nProbs = candidates.collect({arg candidate; + var stepScore, recentlySoundedScore, isInRangeScore, regScore, hdScore; + + //stepScore = intervalScore.value(voices[ins], candidate, 30, 400, 0.1); + stepScore = intervalScore.value(voices[ins], candidate, 100, 100); + recentlySoundedScore = inclusionScore.value(lastXChanges.flop[ins], candidate, 0.01); + //recentlySoundedScore = inclusionScore.value(lastXChanges.flatten.collect({arg item; item.drop(1)}), candidate.drop(1), 0.01); + + /* + if(rangeScore.value(candidate.collect({0}), voices[ins], ranges[ins][1] - 500, ranges[ins][1], 0, true) == 1, { + isInRangeScore = 1 - rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0] + 500, ranges[ins][1] - 500, 0, true); + isInRangeScore = isInRangeScore * rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true) + }, { + isInRangeScore = rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true); + }); + */ + + + isInRangeScore = rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true); + //old way - worked but actually by accident (I think) + //isInRangeScore = rangeScore.value(candidate, candidate.collect({0}), ranges[ins][0], ranges[ins][1], 0, true); + + + regScore = spacingScore.value(voices.deepCopy.put(ins, candidate), 300); + hdScore = pow(hdSum.value(voices.deepCopy.put(ins, candidate)), hdExp); + if(hdInvert == 0, {hdScore = 1/hdScore}); + //maybe what you want here is a vector to another root and then favoring movement towards it. + //distScore = pow(hsChordalDistance.value(voices, voices.put(ins, candidate)), 2); + + [stepScore, recentlySoundedScore, isInRangeScore, regScore, hdScore] + }); + + nWeights = passagesWeights; + + //this handles nWeights of 0; mainly for testing + nProbs = nProbs.flop.select({arg scores, s; nWeights[s] != 0}).flop; + nWeights = nWeights.select({arg weight; weight != 0}); + nProbs = nProbs.flop.collect({arg scores, s; + if(scores.sum == 0, {scores}, {scores.normalizeSum * nWeights[s]}).postln; + }); + nProbs = nProbs.flop.collect({arg scores, s; scores.product}).normalizeSum; + + nProbs.round(0.001).postln; + [candidates, nProbs.round(0.001)].flop.postln; + + sel = candidates.wchoose(nProbs); + + voices[ins] = sel; + lastXChanges = lastXChanges.add(voices).keep(-5); +}; + +genSubMotif = {arg order, orderIndex, lastState, repeatLast = false, startFromLast = false, isLastOrder = false; + var sus, prog, silent, flatOrder, res, isInChord, allowChord, pad, lastXChangesHold, voices, adder; + # sus, prog, silent = order; + flatOrder = silent ++ sus ++ prog; + lastXChangesHold = lastXChanges.deepCopy; + voices = lastState.deepCopy; + isInChord = popSize.collect({false}); + allowChord = false; + pad = false; + res = []; + "------generating motif".postln; + //need to figure out here if voices move between motifs + flatOrder.do({arg ins, i; + + if(prog.includes(ins) && repeatLast.not, {updateVoices.value(ins, sus)}); + adder = if(silent.includes(ins), {["Rest"]}, {lastXChanges.last.deepCopy[ins]}); + + if(voices[ins] != adder, { + var dur; + + if((sus ++ silent).includes(ins), { + allowChord = (ins != sus.last); + pad = (ins == sus.last); + }, { + if(i < (flatOrder.size - 1), { + allowChord = (isInChord[flatOrder[i + 1]] || (ins == flatOrder[i + 1])).not; + pad = false; + }, { + allowChord = false; + pad = true + }); + }); + if((orderIndex == 0) && sus.includes(ins), { + dur = entrancesDurFunc.value(allowChord, pad); + }, { + dur = passagesDurFunc.value(allowChord, pad); + }); + if(dur == 0, {isInChord[ins] = true}, {isInChord = popSize.collect({false})}); + + voices[ins] = adder; + res = res.add([voices.deepCopy.postln, dur]); + }); + }); + + // pad ending + /* + if(orderIndex == (orders.size - 1), { + (0..(popSize-1)).scramble.do({arg ins; + if(res.last.first[ins] != ["Rest"], { + var dur; + voices[ins] = ["Rest"]; + allowChord = (voices != popSize.collect({["Rest"]})); + pad = allowChord.not; + dur = exitsDurFunc.value(allowChord, pad); + res = res.add([voices.deepCopy.postln, dur]); + }); + }); + }); + */ + + //format and return + if(startFromLast, {lastXChanges = lastXChangesHold.deepCopy}); + res; +}; + + +//------primary routines + +genMotif = { + var repeats, fSeq, fDur, durAdd; + + repeats = 1; + fSeq = []; + + repeats.do({arg index; + var motif; + + motif = []; + + orders.do({arg order, o; + var lastState, subMotif; + //lastState = if(o == 0, {popSize.collect({["Rest"]})}, {motif.last.last.first}); + lastState = if(o == 0, {lastXChanges.last.deepCopy}, {motif.last.last.first}); + subMotif = genSubMotif.value(order, o, lastState, isLastOrder: o == (orders.size - 1)); + motif = motif.add(subMotif); + + }); + + sanityCheck.value(motif, index); + + fSeq = fSeq.add(motif); + }); + + //round last duration to measure + /* + fDur = fSeq.flatten.flatten.slice(nil, 1).sum; + durAdd = fDur.round(4) - fDur; + if(durAdd < 0, {durAdd = 4 - durAdd}); + fSeq[0][orders.size - 1][fSeq[0][orders.size - 1].size - 1][1] = fSeq[0][orders.size - 1][fSeq[0][orders.size - 1].size - 1][1] + durAdd; + */ + + fSeq +}; + +genSecondarySeq = {arg seq; + var curdles, fSeq; + curdles = []; + while({curdles.sum < seq.size}, {curdles = curdles ++ [3.rand + 1]}); + + fSeq = seq.clumps(curdles).collect({arg clump, m; + var repeats, paddedSeq; + + //add rest + paddedSeq = clump.add([[[popSize.collect({["Rest"]}), 0.5.rand]]]); + + //implement repeats + repeats = [0.rand + 1, 1].wchoose([1, 0].normalizeSum); + repeats.collect({paddedSeq}); + }); + fSeq +}; + + +//------audition funcs + +/* +Event.addEventType(\osc, { + if (~addr.postln.notNil) { + ~addr.sendMsg(~indexPath, ~indexMsg); + ~addr.sendMsg(~seqPath, stringifyToDepth.value(~seqMsg, 3)); + //~addr.sendMsg("/STATE/OPEN", (dir.replace("supercollider", "resources") +/+ ~idMsg +/+ ~idMsg ++ "_gui_state" ++ ".state").standardizePath.postln); + }; +}); +*/ + +Event.addEventType(\osc, { + if (~addr.notNil) { + ~msg; + ~addr.sendMsg(~path, *~msg); + }; +}); + +genPatterns = {arg inSeq, addr, oneShot = false; + var voices, durs, pbinds, res, indices, sectionDurs, msg, ids, seq; + seq = inSeq.collect({arg mSeq; mSeq[0]}); + //# voices, durs = seq.flatten2(seq.maxDepth - 5).flop; + # voices, durs = seq.flatten2(if(oneShot, {2}, {3})).flop; + pbinds = voices.flop.collect({arg voice, v; + var clumps, hdScores, freqs, fDurs, attacks, rels, amps; + clumps = voice.separate({arg a, b; a != b }); + freqs = clumps.collect({arg clump; if(clump[0] != ["Rest"], {(62.midicps * hsArrayToFreq.value(clump[0]))}, {Rest(0)})}); + fDurs = durs.clumps(clumps.collect({arg clump; clump.size})).collect({arg clump; clump.sum}); + //attacks = 2.collect({rrand(1, 3)}) ++ freqs.drop(2).collect({rrand(0.3, 0.5)}); + attacks = fDurs.collect({arg dur; dur * rrand(0.2, 0.4)}); + //rels = freqs.drop(2).collect({rrand(0.3, 0.5)}) ++ 2.collect({rrand(1, 3)}); + rels = (clumps.size - 1).collect({arg c; + if(clumps[c + 1][0] == ["Rest"], {rrand(1.0, 3.0)}, {rrand(0.3, 0.5)}); + }); + rels = rels.add(rrand(1.0, 3.0)); + amps = freqs.collect({rrand(0.6, 0.99)}); + + [ + Pbind( + \instrument, \string_model, + \group, group, + \freq, Pseq(freqs, 1), + \dur, Pseq(fDurs, 1), + \attack, Pseq(attacks, 1), + \sustain, Pseq(fDurs, 1), + \release, Pseq(rels, 1), + //\amp, Pseq(amps, 1), + \amp, Pbrown(0.5, 1, 0.5), + \busIndex, v + ), + Pbind( + \instrument, \sine, + \group, group, + \freq, Pseq(freqs, 1), + \dur, Pseq(fDurs, 1), + \sustain, Pseq(fDurs, 1), + \busIndex, v + ) + ] + }).flatten; + if(oneShot.not, { + msg = inSeq.collect({arg mSeq, m; mSeq[1..]}); + //ids = inSeq.collect({arg mSeq, m; mSeq[2]}); + sectionDurs = seq.collect({arg mSeq; mSeq.flatten2(2).flop[1].sum}); + pbinds = pbinds ++ + [ + Pbind( + \type, \osc, + \addr, addr, + \path, "/playing", + \msg, Pseq(msg, 1), + \dur, Pseq(sectionDurs, 1) + ); + ] + }); + res = Ppar(pbinds); + res +}; + +/* +genMidiPatterns = {arg seq; + var voices, durs, patterns, res, mOut, pbRange; + pbRange = 1; //semitones - change this as needed for your situation + mOut = MIDIOut.newByName("TiMidity", "TiMidity port 0").latency_(Server.default.latency); + # voices, durs = seq.flatten2(seq.maxDepth - 5).flop; + res = Ppar( + voices.flop.collect({arg voice, v; + var clumps, hdScores, freqs, fDurs; + + mOut.program(v, 70); + + clumps = voice.separate({arg a, b; a != b }); + freqs = clumps.collect({arg clump; if(clump[0] != ["Rest"], {(60.midicps * hsArrayToFreq.value(clump[0]))}, {Rest(0)})}); + fDurs = durs.clumps(clumps.collect({arg clump; clump.size})).collect({arg clump; clump.sum}); + + Pbind( + \type, \midi, + \chan, v, + \noteval, Pseq(freqs.cpsmidi - 24, 1), + \note, Pfunc({ | event | event[\noteval].floor }), + \dur, Pseq(fDurs, 1), + \midiout, mOut, + \amp, 1, + \bend, Pfunc({ + | event | + if (event[\note].isRest.not) { + var pitchbendvalue = event[\noteval].frac.linlin(0, pbRange, 8192, 8192*2).asInteger; + m.bend(v, pitchbendvalue); + }; + 0; // return something other than nil to avoid stopping the pattern + }), + ); + }); + ); + res +}; +*/ + + +//------resource management funcs + +genUID = {Date.seed.asHexString.toLower}; + +seedFunc = {arg func, seed; + var funcArgs, next; + next = Routine({loop{func.valueArray(funcArgs).yield }}); + next.randSeed_(seed); + {arg ...args; funcArgs = args; next.value} +}; + +stringifyToDepth = {arg data, maxDepth = 1; + var prettyString = "", rCount = 0, writeArray, indent; + + if(maxDepth == 0, { + data.asCompileString + }, { + indent = {arg size; size.collect({" "}).join("")}; + writeArray = {arg array; + prettyString = prettyString ++ indent.value(rCount) ++ "[\n"; + rCount = rCount + 1; + if(rCount < maxDepth, { + array.do({arg subArray; writeArray.value(subArray)}); + }, { + prettyString = prettyString ++ array.collect({arg subArray; + indent.value(rCount + 1) ++ subArray.asCompileString + }).join(",\n"); + }); + rCount = rCount - 1; + prettyString = prettyString ++ "\n" ++ indent.value(rCount) ++ "],\n"; + }; + + writeArray.value(data); + prettyString.replace(",\n\n", "\n").drop(-2); + }) +}; + +sanityCheck = {arg motif, index; + //print functions = very helpful + ("----------" + index + "------------").postln; + + motif.flatten.do({arg val, v; + if(v > 0, { + if(motif.flatten[v-1][0].hammingDistance(val[0]) > 1, {"problem 1".postln}); + if(motif.flatten[v-1][0].hammingDistance(val[0]) == 0, {"problem 2".postln}); + }); + val.postln + }); + "***********".postln; +}; + +msgInterpret = {arg in, escapeDoubleQuotes = true, escapeSingleQuotes = true; + var res; + + res = in; + if(res.isNil.not, { + if((res.isArray && res.isString.not), { + res = res.asCompileString; + res = res.replace(" ", "").replace("\n", "").replace("\t", ""); + if(escapeSingleQuotes, {res = res.replace("\'", "")}); + if(escapeDoubleQuotes, {res = res.replace("\"", "")}); + res = res.replace("Rest", "\"Rest\""); + res = res.interpret; + }, { + var tmpRes; + if(res.every({arg char; char.isDecDigit}), {tmpRes = res.asInteger}); + if(res.contains("."), {tmpRes = res.asFloat}); + if(tmpRes != nil, {res = tmpRes}); + }); + }); + res +}; + +writeResources = {arg path, dict; + var file, modelItems, resString; + file = File(path,"w"); + + modelItems = [ + seq, lastXChanges, + curUID, refUID, orderSeed, durSeed, motifSeed, + entrancesProbVals, passagesProbVals, exitsProbVals, + ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, + orders, susWeights, orderSize, passagesSize, + motifEdited, orderEdited + ]; + + resString = nameSpaces.collect({arg nameSpace; + var depth = 0, insert = " "; + if(nameSpace == "music_data", {depth = 3; insert = "\n"}); + if(nameSpace == "last_changes", {depth = 1; insert = "\n"}); + if(nameSpace == "order", {depth = 1; insert = "\n"}); + if((nameSpace == "ref_uid") && (dict[nameSpace] == nil), {dict[nameSpace] = "nil"}); + "\"" ++ nameSpace ++ "\":" ++ insert ++ stringifyToDepth.value(dict[nameSpace], depth) + }).join(",\n"); + + resString = "{\n" ++ resString ++ "\n}"; + + file.write(resString); + file.close; + resString +}; + +loadModelFile = {arg path; loadModelJSON.value(File(path, "r").readAllString.parseJSON)}; + +loadModelJSON = {arg jsonObject; + var dict; + dict = Dictionary.with(*nameSpaces.collect({arg nS; nS->msgInterpret.value(jsonObject[nS])})); + dict +}; + +setGlobalVars = {arg dict, skipLastXChanges = false; + var tmpLastXChanges; + tmpLastXChanges = lastXChanges.deepCopy; + // order really matters!!!! + # seq, lastXChanges, curUID, refUID, orderSeed, durSeed, motifSeed, + entrancesProbVals, passagesProbVals, exitsProbVals, + ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, + orders, susWeights, orderSize, passagesSize, + motifEdited, orderEdited = nameSpaces.collect({arg nS; dict[nS]}); + if(skipLastXChanges, {lastXChanges = tmpLastXChanges}); + dict +}; + +globalVarsToDict = { + var modelItems, dict; + // order really matters!!!! + modelItems = [ + seq, lastXChanges, + curUID, refUID, orderSeed, durSeed, motifSeed, + entrancesProbVals, passagesProbVals, exitsProbVals, + ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, + orders, susWeights, orderSize, passagesSize, + motifEdited, orderEdited + ]; + dict = Dictionary.with(*nameSpaces.collect({arg nS, n; nS->modelItems[n]})); +}; + +loadLedgerFile = {arg path; + ledgerPath = path; + resourceDir = path.splitext(".").drop(-1).join; + loadLedgerJSON.value(File(ledgerPath, "r").readAllString.parseJSON) +}; + +loadLedgerJSON = {arg jsonObject; ledger = jsonObject["ledger"]}; + +saveLedger = {arg ledger, path; + var file, curResourceDir; + file = File(path, "w"); + curResourceDir = resourceDir; + resourceDir = path.splitext(".").drop(-1).join; + if(curResourceDir != resourceDir, { + File.mkdir(resourceDir); + ledger.do({arg id; + File.copy(curResourceDir +/+ id, resourceDir +/+ id); + }); + }); + file.write("{\n\"ledger\":\n" ++ stringifyToDepth.value(ledger, 1) ++ "\n}"); + file.close; +}; + +//------global vars + +primes = [[2, 1], [3, 2], [5, 4], [7, 4], [11, 8], [13, 8]]; +//ranges = [[-2400, 0], [-1200, 1200], [0, 2400], [0, 2400]]; +exPath = thisProcess.nowExecutingPath; +dir = exPath.dirname; +//popSize = 4; +dims = primes.size; +tuples = genTuples.value(); +//refUID = nil; +group = Group.new; +~group = group; +loadLedgerFile.value(dir +/+ ".." +/+ "resources" +/+ "piece_ledger.json"); +resourceDir = (dir +/+ ".." +/+ "resources" +/+ "piece_ledger"); +//passagesWeights = [1, 1, 1, 1, 1]; +//susWeights = [1, 1, 1]; +// order really matters!!!! +nameSpaces = [ + "music_data", "last_changes", "cur_uid", "ref_uid", "order_seed", "dur_seed", "motifs_seed", + "entrances_probs_vals","passages_probs_vals", "exits_probs_vals", + "ranges", "step_probs_vals", "passages_weights", "hd_exp", "hd_invert", + "order", "sus_weights", "order_size", "passages_size", + "motif_edited", "order_edited" +]; + + +//------OSC funcs + +OSCdef(\load_ledger, {arg msg, time, addr, port; + loadLedgerFile.value(msg[1].asString); +}, \load_ledger); + +OSCdef(\load_model, {arg msg, time, addr, port; + var dict; + dict = loadModelFile.value(msg[1].asString); + setGlobalVars.value(dict); +}, \load_model); + +OSCdef(\save_ledger, {arg msg, time, addr, port; + msg.postln; + ledger = msgInterpret.value(msg[1].asString.parseJSON["ledger"], false).postln; + //loadLedgerJSON.value(msg[0]) + saveLedger.value(ledger, msg[2].asString); + //loadLedgerFile.value(msg[1].asString); +}, \save_ledger); + +OSCdef(\generate, {arg msg, time, addr, port; + var path, dict, durSeeds, musPath, modelString; + msg.postln; + + path = msg[1].asString; + + dict = loadModelFile.value(path); + setGlobalVars.value(dict, true); + + popSize = ranges.size; + + //refUID.postln; + + loadLedgerFile.value(ledgerPath); + if(ledger == nil, {ledger = ["tmp"]}); + if(ledger.last != "tmp", {ledger = ledger.add("tmp")}); + + if(refUID == nil, {lastXChanges = [initVoices.value().deepCopy]}); + if((refUID != nil) && (refUID != "tmp"), { + var file; + file = File((resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath, "r"); + lastXChanges = msgInterpret.value(file.readAllString.parseJSON["last_changes"]); + }); + + refUID.postln; + lastXChanges.collect({arg item; item.postln}); + + durSeeds = seedFunc.value({3.collect({rrand(100000, 999999)})}, durSeed).value.postln; + entrancesDurFunc = genDurFunc.valueArray(entrancesProbVals[..4] ++ [entrancesProbVals[5..]] ++ [durSeeds[0]]); + passagesDurFunc = genDurFunc.valueArray(passagesProbVals[..4] ++ [passagesProbVals[5..]] ++ [durSeeds[1]]); + exitsDurFunc = genDurFunc.valueArray(exitsProbVals[..4] ++ [exitsProbVals[5..]] ++ [durSeeds[2]]); + + if(orders == nil, { + orders = seedFunc.value(genOrders, orderSeed).valueArray(orderSize ++ passagesSize); + //addr.sendMsg("/order", stringifyToDepth.value(orders, 1)); + }); + + stepFunc = genStepFunc.valueArray(stepProbsVals[..1] ++ [stepProbsVals[2..]] ++ [motifSeed]); + seq = seedFunc.value(genMotif, motifSeed).value; + + lastXChanges.collect({arg item; item.postln}); + + dict = globalVarsToDict.value; + modelString = writeResources.value(path, dict); + + //addr.sendMsg("/generated", musPath, stringifyToDepth.value(seq, 3)); + //~seq = seq; + + addr.sendMsg("/generated", path, modelString, ledgerPath); +}, \generate); + + +OSCdef(\commit, {arg msg, time, addr, port; + var musicData, musicChanged, dict, newLedger, modelPath, musString, musFile, test1, test2, lastCurUID, commitType, commitPos, equalityLedger; + //msg.postln; + + /* + test1 = msg[1].asString.parseJSON; + test2 = (dir +/+ ".." +/+ "resources/tmp/tmp_music" ++ ".json").standardizePath.parseJSONFile; + msgInterpret.value(test1["music"])[0][0][0][1].class.postln; + msgInterpret.value(test2["music_data"])[0][0][0][1].class.postln; + (test1["music"] == test2["music_data"]).postln; + */ + + musicData = loadModelJSON.value(msg[1].asString.parseJSON)["music_data"].postln; + musicChanged = (musicData != seq).postln; + commitType = msg[2].asString; + commitPos = msg[3].postln.asInteger; + + lastCurUID = curUID.deepCopy; + curUID = genUID.value; + + File.mkdir((resourceDir +/+ curUID).standardizePath); + File.copy(exPath, (resourceDir +/+ curUID +/+ curUID ++ "_code" ++ ".scd").standardizePath); + + modelPath = (resourceDir +/+ curUID +/+ curUID ++ "_mus_model" ++ ".json").standardizePath; + dict = globalVarsToDict.value; + if(musicChanged, { + seq = musicData; + dict["music_data"] = seq; + dict["motif_edited"] = "true" + }); + dict["cur_uid"] = curUID; + + writeResources.value(modelPath, dict); + + File.delete(ledgerPath ++ "_bak"); + File.copy(ledgerPath, ledgerPath ++ "_bak"); + File.delete(ledgerPath); + + /* + if(commitType == "add", { + if(lastCurUID == "tmp", { + ledger = ledger.drop(-1).add(curUID); + }, { + ledger = ledger.add(curUID); + }) + }); + */ + + ledger.postln; + + if(commitType == "add", {ledger = ledger.add(curUID)}); + + if(commitType == "insert", {ledger = ledger.insert(commitPos + 1, curUID)}); + + if(commitType == "replace", {ledger = ledger.put(commitPos, curUID)}); + + equalityLedger = ledger.collect({arg item; item.asSymbol}); + if(equalityLedger.includes(\tmp).postln, {ledger.removeAt(equalityLedger.indexOf(\tmp).postln)}); + + ledger.postln; + + saveLedger.value(ledger, ledgerPath); + + addr.sendMsg("/committed", curUID, ledgerPath); + //refUID = curUID; + +}, \commit); + +OSCdef(\transport, {arg msg, time, addr, port; + msg.postln; + if(msg[1] == 0, { + group.set(\release, 2); + group.set(\gate, 0); + player.stop; + }, { + // the cued sequence can now be read from file, so this can be cleaned up + var cSize, patterns, pSeq, cuedSeek, indexStart, indexEnd, tmpLedger; + if(msg[1] == 1, { + pSeq = []; + cuedSeek = (seq != nil); + indexStart = msg[2].asInteger; + indexEnd = ledger.size - if(cuedSeek, {2}, {1}); + //ledger.postln; + if(((indexStart == (ledger.size - 1)) && cuedSeek).not, { + ledger[indexStart..indexEnd].do({arg uid, index; + var path, file; + path = (resourceDir +/+ uid +/+ uid ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.postln.parseJSON["music_data"]), path, indexStart + index, uid]); + file.close; + }); + }); + if(cuedSeek, { + var path, file; + path = (resourceDir +/+ "tmp/tmp_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.parseJSON["music_data"]), path, ledger.size - 1, "tmp"]); + file.close; + }); + patterns = genPatterns.value(pSeq, addr); + }, { + pSeq = [loadModelJSON.value(msg[2].asString.parseJSON)["music_data"].postln]; + patterns = genPatterns.value(pSeq, addr, true); + }); + player = Pfset(pattern: patterns, cleanupFunc: { + addr.sendMsg("/transport", 0); + addr.sendMsg("/one_shot", 0); + }); + player = player.play + }); +}, \transport); + + +OSCdef(\transcribe_motif, {arg msg, time, addr, port; + var tSeq, refChord, refUID; + + msg.postln; + + tSeq = [loadModelJSON.value(msg[1].asString.parseJSON)["music_data"]]; + refUID = msg[2].asString.postln; + + if((refUID != "nil") && (refUID != "tmp"), { + var file; + file = File((resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath, "r"); + refChord = msgInterpret.value(file.readAllString.parseJSON["last_changes"]).last; + file.close; + }, { + refChord = [[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0]]; + }); + + ~transcribe.value(tSeq, refChord, (dir +/+ ".." +/+ "lilypond" +/+ "includes").standardizePath, addr, "/transcribe_motif"); +}, \transcribe_motif); + + +OSCdef(\transcribe_all, {arg msg, time, addr, port; + var cSize, patterns, cuedSeek, indexStart, indexEnd, tmpLedger; + if(true, { + cuedSeek = (seq != nil); + indexStart = msg[1].asInteger; + indexEnd = ledger.size - if(cuedSeek, {2}, {1}); + + //tmp for testing transcription + //indexEnd = (indexStart+5); + + //ledger.postln; + if(((indexStart == (ledger.size - 1)) && cuedSeek).not, { + var lilyPartLedgerFiles; + + lilyPartLedgerFiles = 4.collect({arg p; + File((dir +/+ ".." +/+ "lilypond" +/+ "includes" +/+ "part_" ++ ["IV", "III", "II", "I"][p] ++ ".ly").standardizePath, "w"); + }); + + ledger[indexStart..indexEnd].do({arg uid, index; + var path, file, fileString, tSeq, refUID, refChord; + path = (resourceDir +/+ uid +/+ uid ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + fileString = file.readAllString; + tSeq = msgInterpret.value(fileString.parseJSON["music_data"]); + refUID = msgInterpret.value(fileString.parseJSON["ref_uid"]); + file.close; + + //uid.postln; + //(refUID == "nil").postln; + + refChord = [[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0]]; + + if(refUID != "nil", { + path = (resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + refChord = msgInterpret.value(file.readAllString.parseJSON["last_changes"]).last; + file.close; + }); + + if(index != indexEnd, { + ~transcribe.value(tSeq, refChord, (resourceDir +/+ uid +/+ "lilypond").standardizePath); + }, { + ~transcribe.value(tSeq, refChord, (resourceDir +/+ uid +/+ "lilypond").standardizePath, addr, "/transcribe_all"); + }); + + lilyPartLedgerFiles.do({arg f, p; + f.write("\\include \"" ++ resourceDir +/+ uid +/+ "lilypond" +/+ "part_" ++ ["IV", "III", "II", "I"][p] ++ ".ly\"\n"); + }); + + }); + + lilyPartLedgerFiles.do({arg f; + f.close + }); + }); + /* + if(cuedSeek, { + var path, file; + path = (dir +/+ ".." +/+ "resources/tmp/tmp_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.parseJSON["music_data"]), path, ledger.size - 1, "tmp"]); + file.close; + }); + */ + }, { + + }); + +}, \transcribe_all); + +) + + diff --git a/resources/string_quartet_3_rise/50620822/50620822_mus_model.json b/resources/string_quartet_3_rise/50620822/50620822_mus_model.json new file mode 100644 index 0000000..4650062 --- /dev/null +++ b/resources/string_quartet_3_rise/50620822/50620822_mus_model.json @@ -0,0 +1,141 @@ +{ +"music_data": +[ + [ + [ + [ [ [ -5, 5, -1, 2, -2, 2 ], [ -3, 4, -2, 1, -2, 2 ], [ -3, 5, -2, 1, -2, 2 ], [ -3, 5, -1, 1, -2, 2 ] ], 0.25 ], + [ [ [ -5, 5, -1, 2, -2, 2 ], [ -4, 4, -1, 2, -2, 2 ], [ -3, 5, -2, 1, -2, 2 ], [ -3, 5, -1, 1, -2, 2 ] ], 0.25 ], + [ [ [ -5, 5, -1, 2, -2, 2 ], [ -4, 6, -2, 1, -2, 2 ], [ -3, 5, -2, 1, -2, 2 ], [ -3, 5, -1, 1, -2, 2 ] ], 0.25 ], + [ [ [ -5, 5, -1, 2, -2, 2 ], [ -3, 4, -1, 1, -2, 2 ], [ -3, 5, -2, 1, -2, 2 ], [ -3, 5, -1, 1, -2, 2 ] ], 0.25 ] + ], + [ + [ [ [ -5, 5, -1, 2, -2, 2 ], [ -3, 4, -1, 1, -2, 2 ], [ -3, 5, -2, 1, -2, 2 ], [ -3, 4, -2, 1, -2, 2 ] ], 0 ], + [ [ [ -4, 4, -1, 1, -2, 2 ], [ -3, 4, -1, 1, -2, 2 ], [ -3, 5, -2, 1, -2, 2 ], [ -3, 4, -2, 1, -2, 2 ] ], 0 ], + [ [ [ -4, 4, -1, 1, -2, 2 ], [ -3, 4, -1, 1, -2, 2 ], [ -2, 3, -1, 1, -2, 2 ], [ -3, 4, -2, 1, -2, 2 ] ], 0.25 ], + [ [ [ -4, 4, -1, 1, -2, 2 ], [ -3, 4, -1, 1, -2, 2 ], [ -3, 5, -1, 1, -2, 2 ], [ -3, 4, -2, 1, -2, 2 ] ], 0.25 ], + [ [ [ -4, 4, -1, 1, -2, 2 ], [ -3, 4, -1, 1, -2, 2 ], [ -2, 4, -2, 1, -2, 2 ], [ -3, 4, -2, 1, -2, 2 ] ], 0.25 ], + [ [ [ -4, 4, -1, 1, -2, 2 ], [ -3, 4, -1, 1, -2, 2 ], [ -3, 4, -1, 2, -2, 2 ], [ -3, 4, -2, 1, -2, 2 ] ], 0.25 ] + ], + [ + [ [ [ -4, 4, -1, 1, -2, 2 ], [ -3, 4, -1, 1, -2, 2 ], [ -3, 4, -1, 2, -2, 2 ], [ -4, 4, -1, 2, -2, 2 ] ], 0 ], + [ [ [ -4, 4, -1, 1, -2, 2 ], [ -3, 4, -1, 1, -2, 2 ], [ -2, 4, -1, 1, -2, 2 ], [ -4, 4, -1, 2, -2, 2 ] ], 0.25 ] + ], + [ + [ [ [ -4, 4, -1, 1, -2, 2 ], [ -3, 4, -1, 1, -2, 2 ], [ -4, 5, -1, 2, -2, 2 ], [ -4, 4, -1, 2, -2, 2 ] ], 0 ], + [ [ [ -4, 4, -1, 1, -2, 2 ], [ -4, 4, 0, 2, -2, 2 ], [ -4, 5, -1, 2, -2, 2 ], [ -4, 4, -1, 2, -2, 2 ] ], 0 ], + [ [ [ -5, 4, 0, 2, -2, 2 ], [ -4, 4, 0, 2, -2, 2 ], [ -4, 5, -1, 2, -2, 2 ], [ -4, 4, -1, 2, -2, 2 ] ], 0.25 ], + [ [ [ -5, 4, -1, 2, -2, 2 ], [ -4, 4, 0, 2, -2, 2 ], [ -4, 5, -1, 2, -2, 2 ], [ -4, 4, -1, 2, -2, 2 ] ], 0 ], + [ [ [ -5, 4, -1, 2, -2, 2 ], [ -4, 3, -1, 2, -2, 2 ], [ -4, 5, -1, 2, -2, 2 ], [ -4, 4, -1, 2, -2, 2 ] ], 0.25 ], + [ [ [ -5, 4, -1, 2, -2, 2 ], [ -5, 5, -1, 2, -2, 2 ], [ -4, 5, -1, 2, -2, 2 ], [ -4, 4, -1, 2, -2, 2 ] ], 0.25 ], + [ [ [ -5, 4, -1, 2, -2, 2 ], [ -4, 3, -1, 2, -2, 2 ], [ -4, 5, -1, 2, -2, 2 ], [ -4, 4, -1, 2, -2, 2 ] ], 0.25 ] + ], + [ + [ [ [ -5, 4, -1, 2, -2, 2 ], [ -5, 5, -1, 2, -2, 2 ], [ -4, 5, -1, 2, -2, 2 ], [ -4, 4, -1, 2, -2, 2 ] ], 0.25 ], + [ [ [ -5, 4, -1, 2, -2, 2 ], [ -5, 5, -1, 2, -2, 2 ], [ -3, 4, -1, 2, -2, 2 ], [ -4, 4, -1, 2, -2, 2 ] ], 0 ], + [ [ [ -4, 5, -1, 2, -2, 2 ], [ -5, 5, -1, 2, -2, 2 ], [ -3, 4, -1, 2, -2, 2 ], [ -4, 4, -1, 2, -2, 2 ] ], 0.25 ], + [ [ [ -4, 5, -1, 2, -2, 2 ], [ -5, 5, -1, 2, -2, 2 ], [ -2, 4, -1, 1, -2, 2 ], [ -4, 4, -1, 2, -2, 2 ] ], 0.25 ], + [ [ [ -4, 5, -1, 2, -2, 2 ], [ -5, 5, -1, 2, -2, 2 ], [ -3, 4, -1, 2, -2, 2 ], [ -4, 4, -1, 2, -2, 2 ] ], 0 ], + [ [ [ -4, 5, -1, 2, -2, 2 ], [ -4, 3, -1, 2, -2, 2 ], [ -3, 4, -1, 2, -2, 2 ], [ -4, 4, -1, 2, -2, 2 ] ], 0.25 ], + [ [ [ -4, 5, -1, 2, -2, 2 ], [ -4, 3, -1, 2, -2, 2 ], [ -3, 3, -1, 2, -2, 2 ], [ -4, 4, -1, 2, -2, 2 ] ], 0.25 ] + ], + [ + [ [ [ -4, 5, -1, 2, -2, 2 ], [ -5, 4, 0, 2, -2, 2 ], [ -3, 3, -1, 2, -2, 2 ], [ -4, 4, -1, 2, -2, 2 ] ], 0 ], + [ [ [ -4, 5, -1, 2, -2, 2 ], [ -5, 4, 0, 2, -2, 2 ], [ -4, 4, 0, 2, -2, 2 ], [ -4, 4, -1, 2, -2, 2 ] ], 0.25 ], + [ [ [ -4, 5, -1, 2, -2, 2 ], [ -5, 4, -1, 2, -2, 2 ], [ -4, 4, 0, 2, -2, 2 ], [ -4, 4, -1, 2, -2, 2 ] ], 0.25 ] + ], + [ + [ [ [ -4, 5, -1, 2, -2, 2 ], [ -5, 4, -1, 2, -2, 2 ], [ -5, 5, -1, 2, -2, 2 ], [ -4, 4, -1, 2, -2, 2 ] ], 0 ], + [ [ [ -4, 5, -1, 2, -2, 2 ], [ -3, 4, -1, 2, -2, 2 ], [ -5, 5, -1, 2, -2, 2 ], [ -4, 4, -1, 2, -2, 2 ] ], 0 ], + [ [ [ -3, 3, -1, 2, -2, 2 ], [ -3, 4, -1, 2, -2, 2 ], [ -5, 5, -1, 2, -2, 2 ], [ -4, 4, -1, 2, -2, 2 ] ], 0.25 ] + ], + [ + [ [ [ -4, 4, 0, 2, -2, 2 ], [ -3, 4, -1, 2, -2, 2 ], [ -5, 5, -1, 2, -2, 2 ], [ -4, 4, -1, 2, -2, 2 ] ], 0 ], + [ [ [ -4, 4, 0, 2, -2, 2 ], [ -4, 5, 0, 2, -2, 2 ], [ -5, 5, -1, 2, -2, 2 ], [ -4, 4, -1, 2, -2, 2 ] ], 0.25 ], + [ [ [ -4, 4, 0, 2, -2, 2 ], [ -4, 4, -1, 3, -2, 2 ], [ -5, 5, -1, 2, -2, 2 ], [ -4, 4, -1, 2, -2, 2 ] ], 0 ], + [ [ [ -4, 4, -1, 2, -1, 2 ], [ -4, 4, -1, 3, -2, 2 ], [ -5, 5, -1, 2, -2, 2 ], [ -4, 4, -1, 2, -2, 2 ] ], 0.25 ] + ], + [ + [ [ [ -4, 4, -1, 2, -1, 2 ], [ -5, 4, -1, 2, -2, 2 ], [ -5, 5, -1, 2, -2, 2 ], [ -4, 4, -1, 2, -2, 2 ] ], 0 ], + [ [ [ -4, 4, -1, 2, -1, 2 ], [ -5, 4, -1, 2, -2, 2 ], [ -5, 5, -1, 2, -2, 2 ], [ -5, 4, -1, 2, 0, 2 ] ], 0 ], + [ [ [ -4, 4, -1, 2, -1, 2 ], [ -5, 4, -1, 2, -2, 2 ], [ -5, 4, -1, 2, -1, 2 ], [ -5, 4, -1, 2, 0, 2 ] ], 0.25 ], + [ [ [ -4, 4, -1, 2, -1, 2 ], [ -5, 4, -1, 2, -2, 2 ], [ -4, 4, -1, 1, -1, 2 ], [ -5, 4, -1, 2, 0, 2 ] ], 0 ], + [ [ [ -4, 4, -1, 2, -1, 2 ], [ -5, 4, -1, 2, -2, 2 ], [ -4, 4, -1, 1, -1, 2 ], [ -5, 5, -1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -4, 4, -1, 2, -1, 2 ], [ -5, 4, -1, 2, -2, 2 ], [ -5, 4, 0, 2, -1, 2 ], [ -5, 5, -1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -4, 4, -1, 2, -1, 2 ], [ -6, 5, -1, 2, -1, 2 ], [ -5, 4, 0, 2, -1, 2 ], [ -5, 5, -1, 2, -1, 2 ] ], 0.25 ] + ], + [ + [ [ [ -4, 4, -1, 2, -1, 2 ], [ -6, 5, -1, 2, -1, 2 ], [ -4, 3, -1, 2, -1, 2 ], [ -5, 5, -1, 2, -1, 2 ] ], 0.25 ] + ], + [ + [ [ [ -4, 4, -1, 2, -1, 2 ], [ -5, 4, -2, 2, -1, 2 ], [ -4, 3, -1, 2, -1, 2 ], [ -5, 5, -1, 2, -1, 2 ] ], 0 ], + [ [ [ -4, 4, -1, 2, -1, 2 ], [ -5, 4, -2, 2, -1, 2 ], [ -4, 3, -1, 2, -1, 2 ], [ -4, 4, -2, 2, -1, 2 ] ], 0.25 ], + [ [ [ -4, 4, -1, 2, -1, 2 ], [ -5, 4, -2, 2, -1, 2 ], [ -4, 3, -1, 2, -1, 2 ], [ -3, 2, -1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -4, 4, -1, 2, -1, 2 ], [ -4, 2, -1, 2, -1, 2 ], [ -4, 3, -1, 2, -1, 2 ], [ -3, 2, -1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -4, 4, -1, 2, -1, 2 ], [ -5, 4, -1, 2, -1, 2 ], [ -4, 3, -1, 2, -1, 2 ], [ -3, 2, -1, 2, -1, 2 ] ], 0.25 ] + ], + [ + [ [ [ -3, 3, -1, 2, -1, 2 ], [ -5, 4, -1, 2, -1, 2 ], [ -4, 3, -1, 2, -1, 2 ], [ -3, 2, -1, 2, -1, 2 ] ], 0 ], + [ [ [ -3, 3, -1, 2, -1, 2 ], [ -5, 4, -1, 2, -1, 2 ], [ -4, 4, -1, 2, -2, 2 ], [ -3, 2, -1, 2, -1, 2 ] ], 0 ], + [ [ [ -3, 3, -1, 2, -1, 2 ], [ -5, 4, -1, 2, -1, 2 ], [ -4, 4, -1, 2, -2, 2 ], [ -4, 4, -1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -3, 3, -1, 2, -1, 2 ], [ -5, 4, -1, 2, -1, 2 ], [ -4, 4, -2, 2, -1, 2 ], [ -4, 4, -1, 2, -1, 2 ] ], 0 ], + [ [ [ -4, 4, 0, 2, -1, 2 ], [ -5, 4, -1, 2, -1, 2 ], [ -4, 4, -2, 2, -1, 2 ], [ -4, 4, -1, 2, -1, 2 ] ], 0 ], + [ [ [ -4, 4, 0, 2, -1, 2 ], [ -5, 4, -1, 2, -1, 2 ], [ -4, 4, -2, 2, -1, 2 ], [ -3, 4, -1, 1, -1, 2 ] ], 0.25 ] + ], + [ + [ [ [ -3, 4, -1, 2, -1, 1 ], [ -5, 4, -1, 2, -1, 2 ], [ -4, 4, -2, 2, -1, 2 ], [ -3, 4, -1, 1, -1, 2 ] ], 0.25 ], + [ [ [ -3, 4, -1, 2, -1, 1 ], [ -5, 4, -1, 2, -1, 2 ], [ -3, 3, -1, 1, -1, 2 ], [ -3, 4, -1, 1, -1, 2 ] ], 0 ], + [ [ [ -2, 4, -1, 0, -1, 2 ], [ -5, 4, -1, 2, -1, 2 ], [ -3, 3, -1, 1, -1, 2 ], [ -3, 4, -1, 1, -1, 2 ] ], 0.25 ], + [ [ [ -3, 3, -1, 2, -1, 2 ], [ -5, 4, -1, 2, -1, 2 ], [ -3, 3, -1, 1, -1, 2 ], [ -3, 4, -1, 1, -1, 2 ] ], 0.25 ], + [ [ [ -5, 4, -1, 1, -1, 2 ], [ -5, 4, -1, 2, -1, 2 ], [ -3, 3, -1, 1, -1, 2 ], [ -3, 4, -1, 1, -1, 2 ] ], 0.25 ], + [ [ [ -5, 4, -1, 2, -1, 1 ], [ -5, 4, -1, 2, -1, 2 ], [ -3, 3, -1, 1, -1, 2 ], [ -3, 4, -1, 1, -1, 2 ] ], 0.25 ] + ], + [ + [ [ [ -5, 4, -1, 2, -1, 1 ], [ -5, 4, -1, 2, -1, 2 ], [ -4, 4, -2, 2, -1, 2 ], [ -3, 4, -1, 1, -1, 2 ] ], 0.25 ], + [ [ [ -6, 4, -1, 2, 0, 2 ], [ -5, 4, -1, 2, -1, 2 ], [ -4, 4, -2, 2, -1, 2 ], [ -3, 4, -1, 1, -1, 2 ] ], 0.25 ] + ] + ] +], +"last_changes": +[ + [ [ -3, 3, -1, 2, -1, 2 ], [ -5, 4, -1, 2, -1, 2 ], [ -3, 3, -1, 1, -1, 2 ], [ -3, 4, -1, 1, -1, 2 ] ], + [ [ -5, 4, -1, 1, -1, 2 ], [ -5, 4, -1, 2, -1, 2 ], [ -3, 3, -1, 1, -1, 2 ], [ -3, 4, -1, 1, -1, 2 ] ], + [ [ -5, 4, -1, 2, -1, 1 ], [ -5, 4, -1, 2, -1, 2 ], [ -3, 3, -1, 1, -1, 2 ], [ -3, 4, -1, 1, -1, 2 ] ], + [ [ -5, 4, -1, 2, -1, 1 ], [ -5, 4, -1, 2, -1, 2 ], [ -4, 4, -2, 2, -1, 2 ], [ -3, 4, -1, 1, -1, 2 ] ], + [ [ -6, 4, -1, 2, 0, 2 ], [ -5, 4, -1, 2, -1, 2 ], [ -4, 4, -2, 2, -1, 2 ], [ -3, 4, -1, 1, -1, 2 ] ] +], +"cur_uid": "50620822", +"ref_uid": "61207e49", +"order_seed": 837263, +"dur_seed": 454936, +"motifs_seed": 788402, +"entrances_probs_vals": [ 0.78, 0, 0, 0, 1.02, 0.0098039215686275, 0, 0.20915032679739, 0, 0.24183006535948, 0.82432432432432, 0.28758169934641, 0, 1, 0 ], +"passages_probs_vals": [ 0.78, 0, 0, 0, 1.02, 0.0098039215686275, 0, 0.20915032679739, 0, 0.24183006535948, 0.82432432432432, 0.28758169934641, 0, 1, 0 ], +"exits_probs_vals": [ 0.78, 0, 0, 0, 1.02, 0.0098039215686275, 0, 0.20915032679739, 0, 0.24183006535948, 0.82432432432432, 0.28758169934641, 0, 1, 0 ], +"ranges": [ [ -2400, 1200 ], [ -1200, 1453 ], [ -702, 1694 ], [ -702, 1973 ] ], +"step_probs_vals": [ -1200, 1200, 0, 0, 0.41358024691358, 0.0056818181818177, 0.46913580246914, 0.67045454545455, 0.52057613168724, 0, 0.56378600823045, 0.85795454545455, 0.60082304526749, 0, 1, 0 ], +"passages_weights": [ 1, 0.15, 0.43, 1, 1 ], +"hd_exp": 10, +"hd_invert": 0, +"order": +[ + [ [ 0, 2, 3 ], [ 1, 1, 1, 1 ], [ ] ], + [ [ 1 ], [ 3, 0, 2, 2, 2, 2 ], [ ] ], + [ [ 1, 0 ], [ 3, 2 ], [ ] ], + [ [ 3 ], [ 2, 1, 0, 0, 1, 1, 1 ], [ ] ], + [ [ 3 ], [ 1, 2, 0, 2, 2, 1, 2 ], [ ] ], + [ [ 0, 3 ], [ 1, 2, 1 ], [ ] ], + [ [ 3 ], [ 2, 1, 0 ], [ ] ], + [ [ 3, 2 ], [ 0, 1, 1, 0 ], [ ] ], + [ [ 0 ], [ 1, 3, 2, 2, 3, 2, 1 ], [ ] ], + [ [ 1, 0, 3 ], [ 2 ], [ ] ], + [ [ 0, 2 ], [ 1, 3, 3, 1, 1 ], [ ] ], + [ [ 1 ], [ 0, 2, 3, 2, 0, 3 ], [ ] ], + [ [ 1, 3 ], [ 0, 2, 0, 0, 0, 0 ], [ ] ], + [ [ 3, 1 ], [ 2, 0 ], [ ] ] +], +"sus_weights": [ 0.7, 0.48, 0.49 ], +"order_size": [ 14.132653061224, 14.132653061224 ], +"passages_size": [ 0, 5 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/50c2b0ad/50c2b0ad_code.scd b/resources/string_quartet_3_rise/50c2b0ad/50c2b0ad_code.scd new file mode 100644 index 0000000..e1a9d92 --- /dev/null +++ b/resources/string_quartet_3_rise/50c2b0ad/50c2b0ad_code.scd @@ -0,0 +1,992 @@ +( +// helper funcs +var hsArrayToCents, pDist, hdSum, hsChordalDistance, hsArrayToFreq; + +// score funcs +var isInRange, spacingScore, rangeScore, intervalScore, inclusionScore; + +// subroutines +var genTuples, initVoices, genOrders, genSubMotif, updateVoices, genDurFunc, genStepFunc; + +// primary routines +var genMotif, genSecondarySeq; + +// audition funcs +var genPatterns, genMidiPatterns; + +// resource management funcs +var seedFunc, genUID, writeResources, stringifyToDepth, setSeeds, sanityCheck, +msgInterpret, loadLedgerFile, loadLedgerJSON, loadModelFile, loadModelJSON, +setGlobalVars, globalVarsToDict, saveLedger; + +// model vars +//(model and global vars mostly set by OSC funcs +var seq, lastXChanges, +curUID, refUID, orderSeed, durSeed, motifSeed, +entrancesProbVals, passagesProbVals, exitsProbVals, +ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, +orders, susWeights, orderSize, passagesSize, +motifEdited, orderEdited; + +// model aux vars +var entrancesDurFunc, passagesDurFunc, exitsDurFunc, stepFunc; + +// other global vars +var popSize, exPath, dir, primes, dims, tuples, +group, player, resourceDir, ledgerPath, ledger, currentlyPlayingUID, +nameSpaces; + +// install JSON quark (not used) +/* +if(Quarks.isInstalled("JSONlib").not, { + Quarks.install("https://github.com/musikinformatik/JSONlib.git"); + thisProcess.recompile; + //HelpBrowser.openHelpFor("Classes/JSONlib"); +}); +*/ + + +//------helper funcs + +hsArrayToCents = { + arg hsArray; + hsArray.collect({arg dist, p; dist * 1200 * log2(primes[p][0]/primes[p][1])}).sum +}; + +pDist = { + arg array1, array2, signed = false; + var pDistance; + pDistance = hsArrayToCents.value(array2) - hsArrayToCents.value(array1); + if(signed, {pDistance}, {abs(pDistance)}) +}; + +hdSum = { + arg hsArrays; + var size, distances, mean; + size = hsArrays.size; + distances = (size - 1).collect({arg i; + ((i + 1)..(size - 1)).collect({arg j; + abs(hsArrays[i] - hsArrays[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsChordalDistance = { + arg hsArrays1, hsArrays2; + var size, distances, mean; + size = hsArrays1.size; + distances = hsArrays1.size.collect({arg i; + hsArrays2.size.collect({arg j; + abs(hsArrays1[i] - hsArrays2[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsArrayToFreq = { + arg array; + array.collect({arg dim, d; pow(primes[d][0]/primes[d][1], dim)}).product +}; + +//------score funcs + +/* +isInRange = { + arg hsArray, min, max; + var cents; + cents = hsArrayToCents.value(hsArray); + (cents >= min) && (cents <= max) +}; +*/ + +spacingScore = { + arg hsArrays, min; + var centsArray; + centsArray = hsArrays.collect({arg hsArray; hsArrayToCents.value(hsArray)}).sort({arg a, b; a < b}); + centsArray.differentiate.drop(1).collect({arg pDistance; if(pDistance >= min, {1}, {0.01})}).sum; +}; + +rangeScore = { + arg hsArray1, hsArray2, min, max, low, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + if((pDistance >= min) && (pDistance <= max), {1}, {low}); +}; + +/* +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + stepFunc.value(pDistance) +}; +*/ + +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = true; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + //if(pDistance >= 0, {stepFunc.value(abs(pDistance))}, {0.01}); + stepFunc.value(pDistance) +}; + +inclusionScore = { + arg array, test, min = 0.01; + if(array.collect({arg v; v.hash}).includes(test.hash), {min}, {1}); +}; + + +//------subroutines + +genTuples = { + var tuples; + tuples = dims.collect({[-1, 0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum <= 1) && (tuple[0] == 0)}); + //tuples = dims.collect({[0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum == 1) && (tuple[0] == 0)}); + tuples = tuples ++ tuples.collect({arg tuple; [-3, -2, -1, 1, 2, 3].collect({arg octTrans; tuple.deepCopy.put(0, octTrans)})}).flatten; +}; + +initVoices = { + var init, voicesInit; + voicesInit = popSize.collect({dims.collect({0})}); + /* + voicesInit = [dims.collect({0})]; + (popSize - 1).do({ + arg rep, new; + rep = dims.rand; + new = voicesInit.last.deepCopy; + new[rep] = new[rep] + [-1, 1].choose(); + voicesInit = voicesInit.add(new); + }); + */ + voicesInit.deepCopy; +}; + +genDurFunc = {arg chordProb, minPad, maxPad, minDur, maxDur, envData, seed; + var env, pTable, durFunc; + env = Env.pairs([[0, 0]] ++ envData.clump(2) ++ [[1, 0]]).asSignal(256).asList.asArray; + pTable = env.asRandomTable; + //[chordProb, minPad, maxPad, minDur, maxDur, envData].postln; + durFunc = {arg allowChord, pad = false; + var res; + res = if(allowChord.not, { + pTable.tableRand * (maxDur - minDur) + minDur + }, { + if(1.0.rand < chordProb, {0}, {pTable.tableRand * (maxDur - minDur) + minDur}); + }).round(0.125); + if(pad, {res = res + rrand(minPad.asFloat, maxPad.asFloat).round(0.125)}); + if(res.asInteger == res, {res = res.asInteger}); + res + }; + seedFunc.value(durFunc, seed); +}; + +genStepFunc = {arg minStep, maxStep, envData, seed; + var envDataNorm, env, pTable, stepFunc; + //[minStep, maxStep, envData].postln; + envDataNorm = ([[0, 0]] ++ envData.clump(2) ++ [[1, 0]]).flop; + envDataNorm = [envDataNorm[0].normalize(minStep, maxStep), envDataNorm[1]].flop; + env = Env.pairs(envDataNorm); + stepFunc = {arg pDist; + env.at(pDist).clip(0.001, 1); + }; + seedFunc.value(stepFunc, seed); +}; + +genOrders = {arg minMotifLength = 1, maxMotifLength = 5, minProgLength = 0, maxProgLength = 5; + ((maxMotifLength.asInteger - minMotifLength.asInteger).rand + minMotifLength.asInteger).collect({ + var noProgIns, noSusIns, noSilentIns, prog, sus, silent, order; + noSusIns = [1, 2, 3].wchoose(susWeights.normalizeSum); + + //noProgIns = (popSize - noSusIns).rand + 1; + noProgIns = (popSize - noSusIns); + noSilentIns = popSize - noSusIns - noProgIns; + + /* + noSilentIns = (popSize - noSusIns).rand.clip(0, 1); + noProgIns = popSize - noSusIns - noSilentIns; + */ + + # prog, sus, silent = (0..(popSize-1)).scramble.clumps([noProgIns, noSusIns, noSilentIns]); + + prog = (prog.scramble ++ ((maxProgLength.asInteger - minProgLength.asInteger).rand + minProgLength.asInteger).collect({prog.choose}).scramble); + //prog = ((maxProgLength.asInteger - minProgLength.asInteger).rand + minProgLength.asInteger).collect({prog.choose}); + + if(silent == nil, {silent = []}); + [sus.scramble, prog, silent.scramble] + }); +}; + +updateVoices = {arg ins, sus; + var voices, candidates, nWeights, nProbs, sel; + + voices = lastXChanges.deepCopy.last; + + candidates = sus.collect({arg v; tuples.collect({arg t; voices[v] + t})}).flatten; + candidates = difference(candidates.asSet, voices.asSet).asList; + //candidates.select({arg item; (item ++ voices).asSet.size >= 4}); + nProbs = candidates.collect({arg candidate; + var stepScore, recentlySoundedScore, isInRangeScore, regScore, hdScore; + + //stepScore = intervalScore.value(voices[ins], candidate, 30, 400, 0.1); + stepScore = intervalScore.value(voices[ins], candidate, 100, 100); + recentlySoundedScore = inclusionScore.value(lastXChanges.flop[ins], candidate, 0.01); + //recentlySoundedScore = inclusionScore.value(lastXChanges.flatten.collect({arg item; item.drop(1)}), candidate.drop(1), 0.01); + + /* + if(rangeScore.value(candidate.collect({0}), voices[ins], ranges[ins][1] - 500, ranges[ins][1], 0, true) == 1, { + isInRangeScore = 1 - rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0] + 500, ranges[ins][1] - 500, 0, true); + isInRangeScore = isInRangeScore * rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true) + }, { + isInRangeScore = rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true); + }); + */ + + + isInRangeScore = rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true); + //old way - worked but actually by accident (I think) + //isInRangeScore = rangeScore.value(candidate, candidate.collect({0}), ranges[ins][0], ranges[ins][1], 0, true); + + + regScore = spacingScore.value(voices.deepCopy.put(ins, candidate), 300); + hdScore = pow(hdSum.value(voices.deepCopy.put(ins, candidate)), hdExp); + if(hdInvert == 0, {hdScore = 1/hdScore}); + //maybe what you want here is a vector to another root and then favoring movement towards it. + //distScore = pow(hsChordalDistance.value(voices, voices.put(ins, candidate)), 2); + + [stepScore, recentlySoundedScore, isInRangeScore, regScore, hdScore] + }); + + nWeights = passagesWeights; + + //this handles nWeights of 0; mainly for testing + nProbs = nProbs.flop.select({arg scores, s; nWeights[s] != 0}).flop; + nWeights = nWeights.select({arg weight; weight != 0}); + nProbs = nProbs.flop.collect({arg scores, s; + if(scores.sum == 0, {scores}, {scores.normalizeSum * nWeights[s]}).postln; + }); + nProbs = nProbs.flop.collect({arg scores, s; scores.product}).normalizeSum; + + nProbs.round(0.001).postln; + [candidates, nProbs.round(0.001)].flop.postln; + + sel = candidates.wchoose(nProbs); + + voices[ins] = sel; + lastXChanges = lastXChanges.add(voices).keep(-5); +}; + +genSubMotif = {arg order, orderIndex, lastState, repeatLast = false, startFromLast = false, isLastOrder = false; + var sus, prog, silent, flatOrder, res, isInChord, allowChord, pad, lastXChangesHold, voices, adder; + # sus, prog, silent = order; + flatOrder = silent ++ sus ++ prog; + lastXChangesHold = lastXChanges.deepCopy; + voices = lastState.deepCopy; + isInChord = popSize.collect({false}); + allowChord = false; + pad = false; + res = []; + "------generating motif".postln; + //need to figure out here if voices move between motifs + flatOrder.do({arg ins, i; + + if(prog.includes(ins) && repeatLast.not, {updateVoices.value(ins, sus)}); + adder = if(silent.includes(ins), {["Rest"]}, {lastXChanges.last.deepCopy[ins]}); + + if(voices[ins] != adder, { + var dur; + + if((sus ++ silent).includes(ins), { + allowChord = (ins != sus.last); + pad = (ins == sus.last); + }, { + if(i < (flatOrder.size - 1), { + allowChord = (isInChord[flatOrder[i + 1]] || (ins == flatOrder[i + 1])).not; + pad = false; + }, { + allowChord = false; + pad = true + }); + }); + if((orderIndex == 0) && sus.includes(ins), { + dur = entrancesDurFunc.value(allowChord, pad); + }, { + dur = passagesDurFunc.value(allowChord, pad); + }); + if(dur == 0, {isInChord[ins] = true}, {isInChord = popSize.collect({false})}); + + voices[ins] = adder; + res = res.add([voices.deepCopy.postln, dur]); + }); + }); + + // pad ending + /* + if(orderIndex == (orders.size - 1), { + (0..(popSize-1)).scramble.do({arg ins; + if(res.last.first[ins] != ["Rest"], { + var dur; + voices[ins] = ["Rest"]; + allowChord = (voices != popSize.collect({["Rest"]})); + pad = allowChord.not; + dur = exitsDurFunc.value(allowChord, pad); + res = res.add([voices.deepCopy.postln, dur]); + }); + }); + }); + */ + + //format and return + if(startFromLast, {lastXChanges = lastXChangesHold.deepCopy}); + res; +}; + + +//------primary routines + +genMotif = { + var repeats, fSeq, fDur, durAdd; + + repeats = 1; + fSeq = []; + + repeats.do({arg index; + var motif; + + motif = []; + + orders.do({arg order, o; + var lastState, subMotif; + //lastState = if(o == 0, {popSize.collect({["Rest"]})}, {motif.last.last.first}); + lastState = if(o == 0, {lastXChanges.last.deepCopy}, {motif.last.last.first}); + subMotif = genSubMotif.value(order, o, lastState, isLastOrder: o == (orders.size - 1)); + motif = motif.add(subMotif); + + }); + + sanityCheck.value(motif, index); + + fSeq = fSeq.add(motif); + }); + + //round last duration to measure + /* + fDur = fSeq.flatten.flatten.slice(nil, 1).sum; + durAdd = fDur.round(4) - fDur; + if(durAdd < 0, {durAdd = 4 - durAdd}); + fSeq[0][orders.size - 1][fSeq[0][orders.size - 1].size - 1][1] = fSeq[0][orders.size - 1][fSeq[0][orders.size - 1].size - 1][1] + durAdd; + */ + + fSeq +}; + +genSecondarySeq = {arg seq; + var curdles, fSeq; + curdles = []; + while({curdles.sum < seq.size}, {curdles = curdles ++ [3.rand + 1]}); + + fSeq = seq.clumps(curdles).collect({arg clump, m; + var repeats, paddedSeq; + + //add rest + paddedSeq = clump.add([[[popSize.collect({["Rest"]}), 0.5.rand]]]); + + //implement repeats + repeats = [0.rand + 1, 1].wchoose([1, 0].normalizeSum); + repeats.collect({paddedSeq}); + }); + fSeq +}; + + +//------audition funcs + +/* +Event.addEventType(\osc, { + if (~addr.postln.notNil) { + ~addr.sendMsg(~indexPath, ~indexMsg); + ~addr.sendMsg(~seqPath, stringifyToDepth.value(~seqMsg, 3)); + //~addr.sendMsg("/STATE/OPEN", (dir.replace("supercollider", "resources") +/+ ~idMsg +/+ ~idMsg ++ "_gui_state" ++ ".state").standardizePath.postln); + }; +}); +*/ + +Event.addEventType(\osc, { + if (~addr.notNil) { + ~msg; + ~addr.sendMsg(~path, *~msg); + }; +}); + +genPatterns = {arg inSeq, addr, oneShot = false; + var voices, durs, pbinds, res, indices, sectionDurs, msg, ids, seq; + seq = inSeq.collect({arg mSeq; mSeq[0]}); + //# voices, durs = seq.flatten2(seq.maxDepth - 5).flop; + # voices, durs = seq.flatten2(if(oneShot, {2}, {3})).flop; + pbinds = voices.flop.collect({arg voice, v; + var clumps, hdScores, freqs, fDurs, attacks, rels, amps; + clumps = voice.separate({arg a, b; a != b }); + freqs = clumps.collect({arg clump; if(clump[0] != ["Rest"], {(62.midicps * hsArrayToFreq.value(clump[0]))}, {Rest(0)})}); + fDurs = durs.clumps(clumps.collect({arg clump; clump.size})).collect({arg clump; clump.sum}); + //attacks = 2.collect({rrand(1, 3)}) ++ freqs.drop(2).collect({rrand(0.3, 0.5)}); + attacks = fDurs.collect({arg dur; dur * rrand(0.2, 0.4)}); + //rels = freqs.drop(2).collect({rrand(0.3, 0.5)}) ++ 2.collect({rrand(1, 3)}); + rels = (clumps.size - 1).collect({arg c; + if(clumps[c + 1][0] == ["Rest"], {rrand(1.0, 3.0)}, {rrand(0.3, 0.5)}); + }); + rels = rels.add(rrand(1.0, 3.0)); + amps = freqs.collect({rrand(0.6, 0.99)}); + + [ + Pbind( + \instrument, \string_model, + \group, group, + \freq, Pseq(freqs, 1), + \dur, Pseq(fDurs, 1), + \attack, Pseq(attacks, 1), + \sustain, Pseq(fDurs, 1), + \release, Pseq(rels, 1), + //\amp, Pseq(amps, 1), + \amp, Pbrown(0.5, 1, 0.5), + \busIndex, v + ), + Pbind( + \instrument, \sine, + \group, group, + \freq, Pseq(freqs, 1), + \dur, Pseq(fDurs, 1), + \sustain, Pseq(fDurs, 1), + \busIndex, v + ) + ] + }).flatten; + if(oneShot.not, { + msg = inSeq.collect({arg mSeq, m; mSeq[1..]}); + //ids = inSeq.collect({arg mSeq, m; mSeq[2]}); + sectionDurs = seq.collect({arg mSeq; mSeq.flatten2(2).flop[1].sum}); + pbinds = pbinds ++ + [ + Pbind( + \type, \osc, + \addr, addr, + \path, "/playing", + \msg, Pseq(msg, 1), + \dur, Pseq(sectionDurs, 1) + ); + ] + }); + res = Ppar(pbinds); + res +}; + +/* +genMidiPatterns = {arg seq; + var voices, durs, patterns, res, mOut, pbRange; + pbRange = 1; //semitones - change this as needed for your situation + mOut = MIDIOut.newByName("TiMidity", "TiMidity port 0").latency_(Server.default.latency); + # voices, durs = seq.flatten2(seq.maxDepth - 5).flop; + res = Ppar( + voices.flop.collect({arg voice, v; + var clumps, hdScores, freqs, fDurs; + + mOut.program(v, 70); + + clumps = voice.separate({arg a, b; a != b }); + freqs = clumps.collect({arg clump; if(clump[0] != ["Rest"], {(60.midicps * hsArrayToFreq.value(clump[0]))}, {Rest(0)})}); + fDurs = durs.clumps(clumps.collect({arg clump; clump.size})).collect({arg clump; clump.sum}); + + Pbind( + \type, \midi, + \chan, v, + \noteval, Pseq(freqs.cpsmidi - 24, 1), + \note, Pfunc({ | event | event[\noteval].floor }), + \dur, Pseq(fDurs, 1), + \midiout, mOut, + \amp, 1, + \bend, Pfunc({ + | event | + if (event[\note].isRest.not) { + var pitchbendvalue = event[\noteval].frac.linlin(0, pbRange, 8192, 8192*2).asInteger; + m.bend(v, pitchbendvalue); + }; + 0; // return something other than nil to avoid stopping the pattern + }), + ); + }); + ); + res +}; +*/ + + +//------resource management funcs + +genUID = {Date.seed.asHexString.toLower}; + +seedFunc = {arg func, seed; + var funcArgs, next; + next = Routine({loop{func.valueArray(funcArgs).yield }}); + next.randSeed_(seed); + {arg ...args; funcArgs = args; next.value} +}; + +stringifyToDepth = {arg data, maxDepth = 1; + var prettyString = "", rCount = 0, writeArray, indent; + + if(maxDepth == 0, { + data.asCompileString + }, { + indent = {arg size; size.collect({" "}).join("")}; + writeArray = {arg array; + prettyString = prettyString ++ indent.value(rCount) ++ "[\n"; + rCount = rCount + 1; + if(rCount < maxDepth, { + array.do({arg subArray; writeArray.value(subArray)}); + }, { + prettyString = prettyString ++ array.collect({arg subArray; + indent.value(rCount + 1) ++ subArray.asCompileString + }).join(",\n"); + }); + rCount = rCount - 1; + prettyString = prettyString ++ "\n" ++ indent.value(rCount) ++ "],\n"; + }; + + writeArray.value(data); + prettyString.replace(",\n\n", "\n").drop(-2); + }) +}; + +sanityCheck = {arg motif, index; + //print functions = very helpful + ("----------" + index + "------------").postln; + + motif.flatten.do({arg val, v; + if(v > 0, { + if(motif.flatten[v-1][0].hammingDistance(val[0]) > 1, {"problem 1".postln}); + if(motif.flatten[v-1][0].hammingDistance(val[0]) == 0, {"problem 2".postln}); + }); + val.postln + }); + "***********".postln; +}; + +msgInterpret = {arg in, escapeDoubleQuotes = true, escapeSingleQuotes = true; + var res; + + res = in; + if(res.isNil.not, { + if((res.isArray && res.isString.not), { + res = res.asCompileString; + res = res.replace(" ", "").replace("\n", "").replace("\t", ""); + if(escapeSingleQuotes, {res = res.replace("\'", "")}); + if(escapeDoubleQuotes, {res = res.replace("\"", "")}); + res = res.replace("Rest", "\"Rest\""); + res = res.interpret; + }, { + var tmpRes; + if(res.every({arg char; char.isDecDigit}), {tmpRes = res.asInteger}); + if(res.contains("."), {tmpRes = res.asFloat}); + if(tmpRes != nil, {res = tmpRes}); + }); + }); + res +}; + +writeResources = {arg path, dict; + var file, modelItems, resString; + file = File(path,"w"); + + modelItems = [ + seq, lastXChanges, + curUID, refUID, orderSeed, durSeed, motifSeed, + entrancesProbVals, passagesProbVals, exitsProbVals, + ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, + orders, susWeights, orderSize, passagesSize, + motifEdited, orderEdited + ]; + + resString = nameSpaces.collect({arg nameSpace; + var depth = 0, insert = " "; + if(nameSpace == "music_data", {depth = 3; insert = "\n"}); + if(nameSpace == "last_changes", {depth = 1; insert = "\n"}); + if(nameSpace == "order", {depth = 1; insert = "\n"}); + if((nameSpace == "ref_uid") && (dict[nameSpace] == nil), {dict[nameSpace] = "nil"}); + "\"" ++ nameSpace ++ "\":" ++ insert ++ stringifyToDepth.value(dict[nameSpace], depth) + }).join(",\n"); + + resString = "{\n" ++ resString ++ "\n}"; + + file.write(resString); + file.close; + resString +}; + +loadModelFile = {arg path; loadModelJSON.value(File(path, "r").readAllString.parseJSON)}; + +loadModelJSON = {arg jsonObject; + var dict; + dict = Dictionary.with(*nameSpaces.collect({arg nS; nS->msgInterpret.value(jsonObject[nS])})); + dict +}; + +setGlobalVars = {arg dict, skipLastXChanges = false; + var tmpLastXChanges; + tmpLastXChanges = lastXChanges.deepCopy; + // order really matters!!!! + # seq, lastXChanges, curUID, refUID, orderSeed, durSeed, motifSeed, + entrancesProbVals, passagesProbVals, exitsProbVals, + ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, + orders, susWeights, orderSize, passagesSize, + motifEdited, orderEdited = nameSpaces.collect({arg nS; dict[nS]}); + if(skipLastXChanges, {lastXChanges = tmpLastXChanges}); + dict +}; + +globalVarsToDict = { + var modelItems, dict; + // order really matters!!!! + modelItems = [ + seq, lastXChanges, + curUID, refUID, orderSeed, durSeed, motifSeed, + entrancesProbVals, passagesProbVals, exitsProbVals, + ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, + orders, susWeights, orderSize, passagesSize, + motifEdited, orderEdited + ]; + dict = Dictionary.with(*nameSpaces.collect({arg nS, n; nS->modelItems[n]})); +}; + +loadLedgerFile = {arg path; + ledgerPath = path; + resourceDir = path.splitext(".").drop(-1).join; + loadLedgerJSON.value(File(ledgerPath, "r").readAllString.parseJSON) +}; + +loadLedgerJSON = {arg jsonObject; ledger = jsonObject["ledger"]}; + +saveLedger = {arg ledger, path; + var file, curResourceDir; + file = File(path, "w"); + curResourceDir = resourceDir; + resourceDir = path.splitext(".").drop(-1).join; + if(curResourceDir != resourceDir, { + File.mkdir(resourceDir); + ledger.do({arg id; + File.copy(curResourceDir +/+ id, resourceDir +/+ id); + }); + }); + file.write("{\n\"ledger\":\n" ++ stringifyToDepth.value(ledger, 1) ++ "\n}"); + file.close; +}; + +//------global vars + +primes = [[2, 1], [3, 2], [5, 4], [7, 4], [11, 8], [13, 8]]; +//ranges = [[-2400, 0], [-1200, 1200], [0, 2400], [0, 2400]]; +exPath = thisProcess.nowExecutingPath; +dir = exPath.dirname; +//popSize = 4; +dims = primes.size; +tuples = genTuples.value(); +//refUID = nil; +group = Group.new; +~group = group; +loadLedgerFile.value(dir +/+ ".." +/+ "resources" +/+ "piece_ledger.json"); +resourceDir = (dir +/+ ".." +/+ "resources" +/+ "piece_ledger"); +//passagesWeights = [1, 1, 1, 1, 1]; +//susWeights = [1, 1, 1]; +// order really matters!!!! +nameSpaces = [ + "music_data", "last_changes", "cur_uid", "ref_uid", "order_seed", "dur_seed", "motifs_seed", + "entrances_probs_vals","passages_probs_vals", "exits_probs_vals", + "ranges", "step_probs_vals", "passages_weights", "hd_exp", "hd_invert", + "order", "sus_weights", "order_size", "passages_size", + "motif_edited", "order_edited" +]; + + +//------OSC funcs + +OSCdef(\load_ledger, {arg msg, time, addr, port; + loadLedgerFile.value(msg[1].asString); +}, \load_ledger); + +OSCdef(\load_model, {arg msg, time, addr, port; + var dict; + dict = loadModelFile.value(msg[1].asString); + setGlobalVars.value(dict); +}, \load_model); + +OSCdef(\save_ledger, {arg msg, time, addr, port; + msg.postln; + ledger = msgInterpret.value(msg[1].asString.parseJSON["ledger"], false).postln; + //loadLedgerJSON.value(msg[0]) + saveLedger.value(ledger, msg[2].asString); + //loadLedgerFile.value(msg[1].asString); +}, \save_ledger); + +OSCdef(\generate, {arg msg, time, addr, port; + var path, dict, durSeeds, musPath, modelString; + msg.postln; + + path = msg[1].asString; + + dict = loadModelFile.value(path); + setGlobalVars.value(dict, true); + + popSize = ranges.size; + + //refUID.postln; + + loadLedgerFile.value(ledgerPath); + if(ledger == nil, {ledger = ["tmp"]}); + if(ledger.last != "tmp", {ledger = ledger.add("tmp")}); + + if(refUID == nil, {lastXChanges = [initVoices.value().deepCopy]}); + if((refUID != nil) && (refUID != "tmp"), { + var file; + file = File((resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath, "r"); + lastXChanges = msgInterpret.value(file.readAllString.parseJSON["last_changes"]); + }); + + refUID.postln; + lastXChanges.collect({arg item; item.postln}); + + durSeeds = seedFunc.value({3.collect({rrand(100000, 999999)})}, durSeed).value.postln; + entrancesDurFunc = genDurFunc.valueArray(entrancesProbVals[..4] ++ [entrancesProbVals[5..]] ++ [durSeeds[0]]); + passagesDurFunc = genDurFunc.valueArray(passagesProbVals[..4] ++ [passagesProbVals[5..]] ++ [durSeeds[1]]); + exitsDurFunc = genDurFunc.valueArray(exitsProbVals[..4] ++ [exitsProbVals[5..]] ++ [durSeeds[2]]); + + if(orders == nil, { + orders = seedFunc.value(genOrders, orderSeed).valueArray(orderSize ++ passagesSize); + //addr.sendMsg("/order", stringifyToDepth.value(orders, 1)); + }); + + stepFunc = genStepFunc.valueArray(stepProbsVals[..1] ++ [stepProbsVals[2..]] ++ [motifSeed]); + seq = seedFunc.value(genMotif, motifSeed).value; + + lastXChanges.collect({arg item; item.postln}); + + dict = globalVarsToDict.value; + modelString = writeResources.value(path, dict); + + //addr.sendMsg("/generated", musPath, stringifyToDepth.value(seq, 3)); + //~seq = seq; + + addr.sendMsg("/generated", path, modelString, ledgerPath); +}, \generate); + + +OSCdef(\commit, {arg msg, time, addr, port; + var musicData, musicChanged, dict, newLedger, modelPath, musString, musFile, test1, test2, lastCurUID, commitType, commitPos, equalityLedger; + //msg.postln; + + /* + test1 = msg[1].asString.parseJSON; + test2 = (dir +/+ ".." +/+ "resources/tmp/tmp_music" ++ ".json").standardizePath.parseJSONFile; + msgInterpret.value(test1["music"])[0][0][0][1].class.postln; + msgInterpret.value(test2["music_data"])[0][0][0][1].class.postln; + (test1["music"] == test2["music_data"]).postln; + */ + + musicData = loadModelJSON.value(msg[1].asString.parseJSON)["music_data"].postln; + musicChanged = (musicData != seq).postln; + commitType = msg[2].asString; + commitPos = msg[3].postln.asInteger; + + lastCurUID = curUID.deepCopy; + curUID = genUID.value; + + File.mkdir((resourceDir +/+ curUID).standardizePath); + File.copy(exPath, (resourceDir +/+ curUID +/+ curUID ++ "_code" ++ ".scd").standardizePath); + + modelPath = (resourceDir +/+ curUID +/+ curUID ++ "_mus_model" ++ ".json").standardizePath; + dict = globalVarsToDict.value; + if(musicChanged, { + seq = musicData; + dict["music_data"] = seq; + dict["motif_edited"] = "true" + }); + dict["cur_uid"] = curUID; + + writeResources.value(modelPath, dict); + + File.delete(ledgerPath ++ "_bak"); + File.copy(ledgerPath, ledgerPath ++ "_bak"); + File.delete(ledgerPath); + + /* + if(commitType == "add", { + if(lastCurUID == "tmp", { + ledger = ledger.drop(-1).add(curUID); + }, { + ledger = ledger.add(curUID); + }) + }); + */ + + ledger.postln; + + if(commitType == "add", {ledger = ledger.add(curUID)}); + + if(commitType == "insert", {ledger = ledger.insert(commitPos + 1, curUID)}); + + if(commitType == "replace", {ledger = ledger.put(commitPos, curUID)}); + + equalityLedger = ledger.collect({arg item; item.asSymbol}); + if(equalityLedger.includes(\tmp).postln, {ledger.removeAt(equalityLedger.indexOf(\tmp).postln)}); + + ledger.postln; + + saveLedger.value(ledger, ledgerPath); + + addr.sendMsg("/committed", curUID, ledgerPath); + //refUID = curUID; + +}, \commit); + +OSCdef(\transport, {arg msg, time, addr, port; + msg.postln; + if(msg[1] == 0, { + group.set(\release, 2); + group.set(\gate, 0); + player.stop; + }, { + // the cued sequence can now be read from file, so this can be cleaned up + var cSize, patterns, pSeq, cuedSeek, indexStart, indexEnd, tmpLedger; + if(msg[1] == 1, { + pSeq = []; + cuedSeek = (seq != nil); + indexStart = msg[2].asInteger; + indexEnd = ledger.size - if(cuedSeek, {2}, {1}); + //ledger.postln; + if(((indexStart == (ledger.size - 1)) && cuedSeek).not, { + ledger[indexStart..indexEnd].do({arg uid, index; + var path, file; + path = (resourceDir +/+ uid +/+ uid ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.postln.parseJSON["music_data"]), path, indexStart + index, uid]); + file.close; + }); + }); + if(cuedSeek, { + var path, file; + path = (resourceDir +/+ "tmp/tmp_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.parseJSON["music_data"]), path, ledger.size - 1, "tmp"]); + file.close; + }); + patterns = genPatterns.value(pSeq, addr); + }, { + pSeq = [loadModelJSON.value(msg[2].asString.parseJSON)["music_data"].postln]; + patterns = genPatterns.value(pSeq, addr, true); + }); + player = Pfset(pattern: patterns, cleanupFunc: { + addr.sendMsg("/transport", 0); + addr.sendMsg("/one_shot", 0); + }); + player = player.play + }); +}, \transport); + + +OSCdef(\transcribe_motif, {arg msg, time, addr, port; + var tSeq, refChord, refUID; + + msg.postln; + + tSeq = [loadModelJSON.value(msg[1].asString.parseJSON)["music_data"]]; + refUID = msg[2].asString.postln; + + if((refUID != "nil") && (refUID != "tmp"), { + var file; + file = File((resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath, "r"); + refChord = msgInterpret.value(file.readAllString.parseJSON["last_changes"]).last; + file.close; + }, { + refChord = [[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0]]; + }); + + ~transcribe.value(tSeq, refChord, (dir +/+ ".." +/+ "lilypond" +/+ "includes").standardizePath, addr, "/transcribe_motif"); +}, \transcribe_motif); + + +OSCdef(\transcribe_all, {arg msg, time, addr, port; + var cSize, patterns, cuedSeek, indexStart, indexEnd, tmpLedger; + if(true, { + cuedSeek = (seq != nil); + indexStart = msg[1].asInteger; + indexEnd = ledger.size - if(cuedSeek, {2}, {1}); + + //tmp for testing transcription + //indexEnd = (indexStart+5); + + //ledger.postln; + if(((indexStart == (ledger.size - 1)) && cuedSeek).not, { + var lilyPartLedgerFiles; + + lilyPartLedgerFiles = 4.collect({arg p; + File((dir +/+ ".." +/+ "lilypond" +/+ "includes" +/+ "part_" ++ ["IV", "III", "II", "I"][p] ++ ".ly").standardizePath, "w"); + }); + + ledger[indexStart..indexEnd].do({arg uid, index; + var path, file, fileString, tSeq, refUID, refChord; + path = (resourceDir +/+ uid +/+ uid ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + fileString = file.readAllString; + tSeq = msgInterpret.value(fileString.parseJSON["music_data"]); + refUID = msgInterpret.value(fileString.parseJSON["ref_uid"]); + file.close; + + //uid.postln; + //(refUID == "nil").postln; + + refChord = [[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0]]; + + if(refUID != "nil", { + path = (resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + refChord = msgInterpret.value(file.readAllString.parseJSON["last_changes"]).last; + file.close; + }); + + if(index != indexEnd, { + ~transcribe.value(tSeq, refChord, (resourceDir +/+ uid +/+ "lilypond").standardizePath); + }, { + ~transcribe.value(tSeq, refChord, (resourceDir +/+ uid +/+ "lilypond").standardizePath, addr, "/transcribe_all"); + }); + + lilyPartLedgerFiles.do({arg f, p; + f.write("\\include \"" ++ resourceDir +/+ uid +/+ "lilypond" +/+ "part_" ++ ["IV", "III", "II", "I"][p] ++ ".ly\"\n"); + }); + + }); + + lilyPartLedgerFiles.do({arg f; + f.close + }); + }); + /* + if(cuedSeek, { + var path, file; + path = (dir +/+ ".." +/+ "resources/tmp/tmp_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.parseJSON["music_data"]), path, ledger.size - 1, "tmp"]); + file.close; + }); + */ + }, { + + }); + +}, \transcribe_all); + +) + + diff --git a/resources/string_quartet_3_rise/50c2b0ad/50c2b0ad_mus_model.json b/resources/string_quartet_3_rise/50c2b0ad/50c2b0ad_mus_model.json new file mode 100644 index 0000000..10eff06 --- /dev/null +++ b/resources/string_quartet_3_rise/50c2b0ad/50c2b0ad_mus_model.json @@ -0,0 +1,96 @@ +{ +"music_data": +[ + [ + [ + [ [ [ -5, 5, -1, 2, -2, 2 ], [ -3, 4, -2, 1, -2, 2 ], [ -3, 5, -2, 1, -2, 2 ], [ -3, 5, -1, 1, -2, 2 ] ], 0.25 ], + [ [ [ -5, 5, -1, 2, -2, 2 ], [ -4, 4, -1, 2, -2, 2 ], [ -3, 5, -2, 1, -2, 2 ], [ -3, 5, -1, 1, -2, 2 ] ], 0.25 ], + [ [ [ -5, 5, -1, 2, -2, 2 ], [ -4, 6, -2, 1, -2, 2 ], [ -3, 5, -2, 1, -2, 2 ], [ -3, 5, -1, 1, -2, 2 ] ], 0.25 ], + [ [ [ -5, 5, -1, 2, -2, 2 ], [ -3, 4, -1, 1, -2, 2 ], [ -3, 5, -2, 1, -2, 2 ], [ -3, 5, -1, 1, -2, 2 ] ], 0.25 ] + ], + [ + [ [ [ -5, 5, -1, 2, -2, 2 ], [ -3, 4, -1, 1, -2, 2 ], [ -3, 5, -2, 1, -2, 2 ], [ -3, 4, -2, 1, -2, 2 ] ], 0 ], + [ [ [ -4, 4, -1, 1, -2, 2 ], [ -3, 4, -1, 1, -2, 2 ], [ -3, 5, -2, 1, -2, 2 ], [ -3, 4, -2, 1, -2, 2 ] ], 0 ], + [ [ [ -4, 4, -1, 1, -2, 2 ], [ -3, 4, -1, 1, -2, 2 ], [ -2, 3, -1, 1, -2, 2 ], [ -3, 4, -2, 1, -2, 2 ] ], 0.25 ], + [ [ [ -4, 4, -1, 1, -2, 2 ], [ -3, 4, -1, 1, -2, 2 ], [ -3, 5, -1, 1, -2, 2 ], [ -3, 4, -2, 1, -2, 2 ] ], 0.25 ], + [ [ [ -4, 4, -1, 1, -2, 2 ], [ -3, 4, -1, 1, -2, 2 ], [ -2, 4, -2, 1, -2, 2 ], [ -3, 4, -2, 1, -2, 2 ] ], 0.25 ], + [ [ [ -4, 4, -1, 1, -2, 2 ], [ -3, 4, -1, 1, -2, 2 ], [ -3, 4, -1, 2, -2, 2 ], [ -3, 4, -2, 1, -2, 2 ] ], 0.25 ] + ], + [ + [ [ [ -4, 4, -1, 1, -2, 2 ], [ -3, 4, -1, 1, -2, 2 ], [ -3, 4, -1, 2, -2, 2 ], [ -4, 4, -1, 2, -2, 2 ] ], 0 ], + [ [ [ -4, 4, -1, 1, -2, 2 ], [ -3, 4, -1, 1, -2, 2 ], [ -2, 4, -1, 1, -2, 2 ], [ -4, 4, -1, 2, -2, 2 ] ], 0.25 ] + ], + [ + [ [ [ -4, 4, -1, 1, -2, 2 ], [ -3, 4, -1, 1, -2, 2 ], [ -4, 5, -1, 2, -2, 2 ], [ -4, 4, -1, 2, -2, 2 ] ], 0 ], + [ [ [ -4, 4, -1, 1, -2, 2 ], [ -4, 4, 0, 2, -2, 2 ], [ -4, 5, -1, 2, -2, 2 ], [ -4, 4, -1, 2, -2, 2 ] ], 0 ], + [ [ [ -5, 4, 0, 2, -2, 2 ], [ -4, 4, 0, 2, -2, 2 ], [ -4, 5, -1, 2, -2, 2 ], [ -4, 4, -1, 2, -2, 2 ] ], 0.25 ], + [ [ [ -5, 4, -1, 2, -2, 2 ], [ -4, 4, 0, 2, -2, 2 ], [ -4, 5, -1, 2, -2, 2 ], [ -4, 4, -1, 2, -2, 2 ] ], 0 ], + [ [ [ -5, 4, -1, 2, -2, 2 ], [ -4, 3, -1, 2, -2, 2 ], [ -4, 5, -1, 2, -2, 2 ], [ -4, 4, -1, 2, -2, 2 ] ], 0.25 ], + [ [ [ -5, 4, -1, 2, -2, 2 ], [ -5, 5, -1, 2, -2, 2 ], [ -4, 5, -1, 2, -2, 2 ], [ -4, 4, -1, 2, -2, 2 ] ], 0.25 ], + [ [ [ -5, 4, -1, 2, -2, 2 ], [ -4, 3, -1, 2, -2, 2 ], [ -4, 5, -1, 2, -2, 2 ], [ -4, 4, -1, 2, -2, 2 ] ], 0.25 ] + ], + [ + [ [ [ -5, 4, -1, 2, -2, 2 ], [ -5, 5, -1, 2, -2, 2 ], [ -4, 5, -1, 2, -2, 2 ], [ -4, 4, -1, 2, -2, 2 ] ], 0.25 ], + [ [ [ -5, 4, -1, 2, -2, 2 ], [ -5, 5, -1, 2, -2, 2 ], [ -3, 4, -1, 2, -2, 2 ], [ -4, 4, -1, 2, -2, 2 ] ], 0 ], + [ [ [ -4, 5, -1, 2, -2, 2 ], [ -5, 5, -1, 2, -2, 2 ], [ -3, 4, -1, 2, -2, 2 ], [ -4, 4, -1, 2, -2, 2 ] ], 0.25 ], + [ [ [ -4, 5, -1, 2, -2, 2 ], [ -5, 5, -1, 2, -2, 2 ], [ -2, 4, -1, 1, -2, 2 ], [ -4, 4, -1, 2, -2, 2 ] ], 0.25 ], + [ [ [ -4, 5, -1, 2, -2, 2 ], [ -5, 5, -1, 2, -2, 2 ], [ -3, 4, -1, 2, -2, 2 ], [ -4, 4, -1, 2, -2, 2 ] ], 0 ], + [ [ [ -4, 5, -1, 2, -2, 2 ], [ -4, 3, -1, 2, -2, 2 ], [ -3, 4, -1, 2, -2, 2 ], [ -4, 4, -1, 2, -2, 2 ] ], 0.25 ], + [ [ [ -4, 5, -1, 2, -2, 2 ], [ -4, 3, -1, 2, -2, 2 ], [ -3, 3, -1, 2, -2, 2 ], [ -4, 4, -1, 2, -2, 2 ] ], 0.25 ] + ], + [ + [ [ [ -4, 5, -1, 2, -2, 2 ], [ -5, 4, 0, 2, -2, 2 ], [ -3, 3, -1, 2, -2, 2 ], [ -4, 4, -1, 2, -2, 2 ] ], 0 ], + [ [ [ -4, 5, -1, 2, -2, 2 ], [ -5, 4, 0, 2, -2, 2 ], [ -4, 4, 0, 2, -2, 2 ], [ -4, 4, -1, 2, -2, 2 ] ], 0.25 ], + [ [ [ -4, 5, -1, 2, -2, 2 ], [ -5, 4, -1, 2, -2, 2 ], [ -4, 4, 0, 2, -2, 2 ], [ -4, 4, -1, 2, -2, 2 ] ], 0.25 ] + ], + [ + [ [ [ -4, 5, -1, 2, -2, 2 ], [ -5, 4, -1, 2, -2, 2 ], [ -5, 5, -1, 2, -2, 2 ], [ -4, 4, -1, 2, -2, 2 ] ], 0 ], + [ [ [ -4, 5, -1, 2, -2, 2 ], [ -3, 4, -1, 2, -2, 2 ], [ -5, 5, -1, 2, -2, 2 ], [ -4, 4, -1, 2, -2, 2 ] ], 0 ], + [ [ [ -3, 3, -1, 2, -2, 2 ], [ -3, 4, -1, 2, -2, 2 ], [ -5, 5, -1, 2, -2, 2 ], [ -4, 4, -1, 2, -2, 2 ] ], 0.25 ] + ], + [ + [ [ [ -4, 4, 0, 2, -2, 2 ], [ -3, 4, -1, 2, -2, 2 ], [ -5, 5, -1, 2, -2, 2 ], [ -4, 4, -1, 2, -2, 2 ] ], 0 ], + [ [ [ -4, 4, 0, 2, -2, 2 ], [ -4, 5, 0, 2, -2, 2 ], [ -5, 5, -1, 2, -2, 2 ], [ -4, 4, -1, 2, -2, 2 ] ], 0.25 ], + [ [ [ -4, 4, 0, 2, -2, 2 ], [ -4, 4, -1, 3, -2, 2 ], [ -5, 5, -1, 2, -2, 2 ], [ -4, 4, -1, 2, -2, 2 ] ], 0 ], + [ [ [ -4, 4, -1, 2, -1, 2 ], [ -4, 4, -1, 3, -2, 2 ], [ -5, 5, -1, 2, -2, 2 ], [ -4, 4, -1, 2, -2, 2 ] ], 0.25 ] + ] + ] +], +"last_changes": +[ + [ [ -3, 3, -1, 2, -2, 2 ], [ -3, 4, -1, 2, -2, 2 ], [ -5, 5, -1, 2, -2, 2 ], [ -4, 4, -1, 2, -2, 2 ] ], + [ [ -4, 4, 0, 2, -2, 2 ], [ -3, 4, -1, 2, -2, 2 ], [ -5, 5, -1, 2, -2, 2 ], [ -4, 4, -1, 2, -2, 2 ] ], + [ [ -4, 4, 0, 2, -2, 2 ], [ -4, 5, 0, 2, -2, 2 ], [ -5, 5, -1, 2, -2, 2 ], [ -4, 4, -1, 2, -2, 2 ] ], + [ [ -4, 4, 0, 2, -2, 2 ], [ -4, 4, -1, 3, -2, 2 ], [ -5, 5, -1, 2, -2, 2 ], [ -4, 4, -1, 2, -2, 2 ] ], + [ [ -4, 4, -1, 2, -1, 2 ], [ -4, 4, -1, 3, -2, 2 ], [ -5, 5, -1, 2, -2, 2 ], [ -4, 4, -1, 2, -2, 2 ] ] +], +"cur_uid": "50c2b0ad", +"ref_uid": "61207e49", +"order_seed": 837263, +"dur_seed": 454936, +"motifs_seed": 788402, +"entrances_probs_vals": [ 0.78, 0, 0, 0, 1.02, 0.0098039215686275, 0, 0.20915032679739, 0, 0.24183006535948, 0.82432432432432, 0.28758169934641, 0, 1, 0 ], +"passages_probs_vals": [ 0.78, 0, 0, 0, 1.02, 0.0098039215686275, 0, 0.20915032679739, 0, 0.24183006535948, 0.82432432432432, 0.28758169934641, 0, 1, 0 ], +"exits_probs_vals": [ 0.78, 0, 0, 0, 1.02, 0.0098039215686275, 0, 0.20915032679739, 0, 0.24183006535948, 0.82432432432432, 0.28758169934641, 0, 1, 0 ], +"ranges": [ [ -2400, 1200 ], [ -1200, 1453 ], [ -702, 1694 ], [ -702, 1973 ] ], +"step_probs_vals": [ -1200, 1200, 0, 0, 0.41358024691358, 0.0056818181818177, 0.46913580246914, 0.67045454545455, 0.52057613168724, 0, 0.56378600823045, 0.85795454545455, 0.60082304526749, 0, 1, 0 ], +"passages_weights": [ 1, 0.15, 0.43, 1, 1 ], +"hd_exp": 10, +"hd_invert": 0, +"order": +[ + [ [ 0, 2, 3 ], [ 1, 1, 1, 1 ], [ ] ], + [ [ 1 ], [ 3, 0, 2, 2, 2, 2 ], [ ] ], + [ [ 1, 0 ], [ 3, 2 ], [ ] ], + [ [ 3 ], [ 2, 1, 0, 0, 1, 1, 1 ], [ ] ], + [ [ 3 ], [ 1, 2, 0, 2, 2, 1, 2 ], [ ] ], + [ [ 0, 3 ], [ 1, 2, 1 ], [ ] ], + [ [ 3 ], [ 2, 1, 0 ], [ ] ], + [ [ 3, 2 ], [ 0, 1, 1, 0 ], [ ] ] +], +"sus_weights": [ 0.7, 0.48, 0.49 ], +"order_size": [ 14.132653061224, 14.132653061224 ], +"passages_size": [ 0, 5 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/50c2b0ad/lilypond/part_I.ly b/resources/string_quartet_3_rise/50c2b0ad/lilypond/part_I.ly new file mode 100644 index 0000000..0080f12 --- /dev/null +++ b/resources/string_quartet_3_rise/50c2b0ad/lilypond/part_I.ly @@ -0,0 +1,6 @@ +{ + { cis''2^\markup { \pad-markup #0.2 "-29"} d'2^\markup { \pad-markup #0.2 "-18"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 5↓" }} } + \bar "|" + { dis'1^\markup { \pad-markup #0.2 "+38"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 1↑" }}} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/50c2b0ad/lilypond/part_II.ly b/resources/string_quartet_3_rise/50c2b0ad/lilypond/part_II.ly new file mode 100644 index 0000000..bf238e6 --- /dev/null +++ b/resources/string_quartet_3_rise/50c2b0ad/lilypond/part_II.ly @@ -0,0 +1,6 @@ +{ + { a'2^\markup { \pad-markup #0.2 "-16"} b'8^\markup { \pad-markup #0.2 "-33"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↓" }}[ cis''8^\markup { \pad-markup #0.2 "-29"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↑" }}] d''8^\markup { \pad-markup #0.2 "-18"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 1↑" }}[ dis''8^\markup { \pad-markup #0.2 "+38"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 7↑" }}] } + \bar "|" + { fis''8^\markup { \pad-markup #0.2 "-31"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }}[ ais'8^\markup { \pad-markup #0.2 "+40"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 3↑" }}] ~ ais'2 dis''8^\markup { \pad-markup #0.2 "+38"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }}[ fis''8^\markup { \pad-markup #0.2 "-31"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 7↓" }}]} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/50c2b0ad/lilypond/part_III.ly b/resources/string_quartet_3_rise/50c2b0ad/lilypond/part_III.ly new file mode 100644 index 0000000..db9e500 --- /dev/null +++ b/resources/string_quartet_3_rise/50c2b0ad/lilypond/part_III.ly @@ -0,0 +1,6 @@ +{ + { d'8^\markup { \pad-markup #0.2 "-18"}[ dis'8^\markup { \pad-markup #0.2 "+38"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↓" }}] e'8^\markup { \pad-markup #0.2 "-14"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 3↑" }}[ fis'8^\markup { \pad-markup #0.2 "-31"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 3↓" }}] ~ fis'2 ~ } + \bar "|" + { fis'8[ g'8^\markup { \pad-markup #0.2 "+24"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 5↑" }}] gis8^\markup { \pad-markup #0.2 "+36"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↓" }}[ ais8^\markup { \pad-markup #0.2 "+40"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 1↑" }}] gis8^\markup { \pad-markup #0.2 "+36"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↓" }}[ ais8^\markup { \pad-markup #0.2 "+40"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 1↑" }}] ~ ais4} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/50c2b0ad/lilypond/part_IV.ly b/resources/string_quartet_3_rise/50c2b0ad/lilypond/part_IV.ly new file mode 100644 index 0000000..5ff227c --- /dev/null +++ b/resources/string_quartet_3_rise/50c2b0ad/lilypond/part_IV.ly @@ -0,0 +1,6 @@ +{ + { ais2^\markup { \pad-markup #0.2 "+40"} fis2^\markup { \pad-markup #0.2 "-31"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }} ~ } + \bar "|" + { fis8[ g8^\markup { \pad-markup #0.2 "+24"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }}] dis2^\markup { \pad-markup #0.2 "+38"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 1↑" }} ais'4^\markup { \pad-markup #0.2 "+40"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }}} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/536cac90/536cac90_code.scd b/resources/string_quartet_3_rise/536cac90/536cac90_code.scd new file mode 100644 index 0000000..e1a9d92 --- /dev/null +++ b/resources/string_quartet_3_rise/536cac90/536cac90_code.scd @@ -0,0 +1,992 @@ +( +// helper funcs +var hsArrayToCents, pDist, hdSum, hsChordalDistance, hsArrayToFreq; + +// score funcs +var isInRange, spacingScore, rangeScore, intervalScore, inclusionScore; + +// subroutines +var genTuples, initVoices, genOrders, genSubMotif, updateVoices, genDurFunc, genStepFunc; + +// primary routines +var genMotif, genSecondarySeq; + +// audition funcs +var genPatterns, genMidiPatterns; + +// resource management funcs +var seedFunc, genUID, writeResources, stringifyToDepth, setSeeds, sanityCheck, +msgInterpret, loadLedgerFile, loadLedgerJSON, loadModelFile, loadModelJSON, +setGlobalVars, globalVarsToDict, saveLedger; + +// model vars +//(model and global vars mostly set by OSC funcs +var seq, lastXChanges, +curUID, refUID, orderSeed, durSeed, motifSeed, +entrancesProbVals, passagesProbVals, exitsProbVals, +ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, +orders, susWeights, orderSize, passagesSize, +motifEdited, orderEdited; + +// model aux vars +var entrancesDurFunc, passagesDurFunc, exitsDurFunc, stepFunc; + +// other global vars +var popSize, exPath, dir, primes, dims, tuples, +group, player, resourceDir, ledgerPath, ledger, currentlyPlayingUID, +nameSpaces; + +// install JSON quark (not used) +/* +if(Quarks.isInstalled("JSONlib").not, { + Quarks.install("https://github.com/musikinformatik/JSONlib.git"); + thisProcess.recompile; + //HelpBrowser.openHelpFor("Classes/JSONlib"); +}); +*/ + + +//------helper funcs + +hsArrayToCents = { + arg hsArray; + hsArray.collect({arg dist, p; dist * 1200 * log2(primes[p][0]/primes[p][1])}).sum +}; + +pDist = { + arg array1, array2, signed = false; + var pDistance; + pDistance = hsArrayToCents.value(array2) - hsArrayToCents.value(array1); + if(signed, {pDistance}, {abs(pDistance)}) +}; + +hdSum = { + arg hsArrays; + var size, distances, mean; + size = hsArrays.size; + distances = (size - 1).collect({arg i; + ((i + 1)..(size - 1)).collect({arg j; + abs(hsArrays[i] - hsArrays[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsChordalDistance = { + arg hsArrays1, hsArrays2; + var size, distances, mean; + size = hsArrays1.size; + distances = hsArrays1.size.collect({arg i; + hsArrays2.size.collect({arg j; + abs(hsArrays1[i] - hsArrays2[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsArrayToFreq = { + arg array; + array.collect({arg dim, d; pow(primes[d][0]/primes[d][1], dim)}).product +}; + +//------score funcs + +/* +isInRange = { + arg hsArray, min, max; + var cents; + cents = hsArrayToCents.value(hsArray); + (cents >= min) && (cents <= max) +}; +*/ + +spacingScore = { + arg hsArrays, min; + var centsArray; + centsArray = hsArrays.collect({arg hsArray; hsArrayToCents.value(hsArray)}).sort({arg a, b; a < b}); + centsArray.differentiate.drop(1).collect({arg pDistance; if(pDistance >= min, {1}, {0.01})}).sum; +}; + +rangeScore = { + arg hsArray1, hsArray2, min, max, low, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + if((pDistance >= min) && (pDistance <= max), {1}, {low}); +}; + +/* +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + stepFunc.value(pDistance) +}; +*/ + +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = true; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + //if(pDistance >= 0, {stepFunc.value(abs(pDistance))}, {0.01}); + stepFunc.value(pDistance) +}; + +inclusionScore = { + arg array, test, min = 0.01; + if(array.collect({arg v; v.hash}).includes(test.hash), {min}, {1}); +}; + + +//------subroutines + +genTuples = { + var tuples; + tuples = dims.collect({[-1, 0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum <= 1) && (tuple[0] == 0)}); + //tuples = dims.collect({[0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum == 1) && (tuple[0] == 0)}); + tuples = tuples ++ tuples.collect({arg tuple; [-3, -2, -1, 1, 2, 3].collect({arg octTrans; tuple.deepCopy.put(0, octTrans)})}).flatten; +}; + +initVoices = { + var init, voicesInit; + voicesInit = popSize.collect({dims.collect({0})}); + /* + voicesInit = [dims.collect({0})]; + (popSize - 1).do({ + arg rep, new; + rep = dims.rand; + new = voicesInit.last.deepCopy; + new[rep] = new[rep] + [-1, 1].choose(); + voicesInit = voicesInit.add(new); + }); + */ + voicesInit.deepCopy; +}; + +genDurFunc = {arg chordProb, minPad, maxPad, minDur, maxDur, envData, seed; + var env, pTable, durFunc; + env = Env.pairs([[0, 0]] ++ envData.clump(2) ++ [[1, 0]]).asSignal(256).asList.asArray; + pTable = env.asRandomTable; + //[chordProb, minPad, maxPad, minDur, maxDur, envData].postln; + durFunc = {arg allowChord, pad = false; + var res; + res = if(allowChord.not, { + pTable.tableRand * (maxDur - minDur) + minDur + }, { + if(1.0.rand < chordProb, {0}, {pTable.tableRand * (maxDur - minDur) + minDur}); + }).round(0.125); + if(pad, {res = res + rrand(minPad.asFloat, maxPad.asFloat).round(0.125)}); + if(res.asInteger == res, {res = res.asInteger}); + res + }; + seedFunc.value(durFunc, seed); +}; + +genStepFunc = {arg minStep, maxStep, envData, seed; + var envDataNorm, env, pTable, stepFunc; + //[minStep, maxStep, envData].postln; + envDataNorm = ([[0, 0]] ++ envData.clump(2) ++ [[1, 0]]).flop; + envDataNorm = [envDataNorm[0].normalize(minStep, maxStep), envDataNorm[1]].flop; + env = Env.pairs(envDataNorm); + stepFunc = {arg pDist; + env.at(pDist).clip(0.001, 1); + }; + seedFunc.value(stepFunc, seed); +}; + +genOrders = {arg minMotifLength = 1, maxMotifLength = 5, minProgLength = 0, maxProgLength = 5; + ((maxMotifLength.asInteger - minMotifLength.asInteger).rand + minMotifLength.asInteger).collect({ + var noProgIns, noSusIns, noSilentIns, prog, sus, silent, order; + noSusIns = [1, 2, 3].wchoose(susWeights.normalizeSum); + + //noProgIns = (popSize - noSusIns).rand + 1; + noProgIns = (popSize - noSusIns); + noSilentIns = popSize - noSusIns - noProgIns; + + /* + noSilentIns = (popSize - noSusIns).rand.clip(0, 1); + noProgIns = popSize - noSusIns - noSilentIns; + */ + + # prog, sus, silent = (0..(popSize-1)).scramble.clumps([noProgIns, noSusIns, noSilentIns]); + + prog = (prog.scramble ++ ((maxProgLength.asInteger - minProgLength.asInteger).rand + minProgLength.asInteger).collect({prog.choose}).scramble); + //prog = ((maxProgLength.asInteger - minProgLength.asInteger).rand + minProgLength.asInteger).collect({prog.choose}); + + if(silent == nil, {silent = []}); + [sus.scramble, prog, silent.scramble] + }); +}; + +updateVoices = {arg ins, sus; + var voices, candidates, nWeights, nProbs, sel; + + voices = lastXChanges.deepCopy.last; + + candidates = sus.collect({arg v; tuples.collect({arg t; voices[v] + t})}).flatten; + candidates = difference(candidates.asSet, voices.asSet).asList; + //candidates.select({arg item; (item ++ voices).asSet.size >= 4}); + nProbs = candidates.collect({arg candidate; + var stepScore, recentlySoundedScore, isInRangeScore, regScore, hdScore; + + //stepScore = intervalScore.value(voices[ins], candidate, 30, 400, 0.1); + stepScore = intervalScore.value(voices[ins], candidate, 100, 100); + recentlySoundedScore = inclusionScore.value(lastXChanges.flop[ins], candidate, 0.01); + //recentlySoundedScore = inclusionScore.value(lastXChanges.flatten.collect({arg item; item.drop(1)}), candidate.drop(1), 0.01); + + /* + if(rangeScore.value(candidate.collect({0}), voices[ins], ranges[ins][1] - 500, ranges[ins][1], 0, true) == 1, { + isInRangeScore = 1 - rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0] + 500, ranges[ins][1] - 500, 0, true); + isInRangeScore = isInRangeScore * rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true) + }, { + isInRangeScore = rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true); + }); + */ + + + isInRangeScore = rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true); + //old way - worked but actually by accident (I think) + //isInRangeScore = rangeScore.value(candidate, candidate.collect({0}), ranges[ins][0], ranges[ins][1], 0, true); + + + regScore = spacingScore.value(voices.deepCopy.put(ins, candidate), 300); + hdScore = pow(hdSum.value(voices.deepCopy.put(ins, candidate)), hdExp); + if(hdInvert == 0, {hdScore = 1/hdScore}); + //maybe what you want here is a vector to another root and then favoring movement towards it. + //distScore = pow(hsChordalDistance.value(voices, voices.put(ins, candidate)), 2); + + [stepScore, recentlySoundedScore, isInRangeScore, regScore, hdScore] + }); + + nWeights = passagesWeights; + + //this handles nWeights of 0; mainly for testing + nProbs = nProbs.flop.select({arg scores, s; nWeights[s] != 0}).flop; + nWeights = nWeights.select({arg weight; weight != 0}); + nProbs = nProbs.flop.collect({arg scores, s; + if(scores.sum == 0, {scores}, {scores.normalizeSum * nWeights[s]}).postln; + }); + nProbs = nProbs.flop.collect({arg scores, s; scores.product}).normalizeSum; + + nProbs.round(0.001).postln; + [candidates, nProbs.round(0.001)].flop.postln; + + sel = candidates.wchoose(nProbs); + + voices[ins] = sel; + lastXChanges = lastXChanges.add(voices).keep(-5); +}; + +genSubMotif = {arg order, orderIndex, lastState, repeatLast = false, startFromLast = false, isLastOrder = false; + var sus, prog, silent, flatOrder, res, isInChord, allowChord, pad, lastXChangesHold, voices, adder; + # sus, prog, silent = order; + flatOrder = silent ++ sus ++ prog; + lastXChangesHold = lastXChanges.deepCopy; + voices = lastState.deepCopy; + isInChord = popSize.collect({false}); + allowChord = false; + pad = false; + res = []; + "------generating motif".postln; + //need to figure out here if voices move between motifs + flatOrder.do({arg ins, i; + + if(prog.includes(ins) && repeatLast.not, {updateVoices.value(ins, sus)}); + adder = if(silent.includes(ins), {["Rest"]}, {lastXChanges.last.deepCopy[ins]}); + + if(voices[ins] != adder, { + var dur; + + if((sus ++ silent).includes(ins), { + allowChord = (ins != sus.last); + pad = (ins == sus.last); + }, { + if(i < (flatOrder.size - 1), { + allowChord = (isInChord[flatOrder[i + 1]] || (ins == flatOrder[i + 1])).not; + pad = false; + }, { + allowChord = false; + pad = true + }); + }); + if((orderIndex == 0) && sus.includes(ins), { + dur = entrancesDurFunc.value(allowChord, pad); + }, { + dur = passagesDurFunc.value(allowChord, pad); + }); + if(dur == 0, {isInChord[ins] = true}, {isInChord = popSize.collect({false})}); + + voices[ins] = adder; + res = res.add([voices.deepCopy.postln, dur]); + }); + }); + + // pad ending + /* + if(orderIndex == (orders.size - 1), { + (0..(popSize-1)).scramble.do({arg ins; + if(res.last.first[ins] != ["Rest"], { + var dur; + voices[ins] = ["Rest"]; + allowChord = (voices != popSize.collect({["Rest"]})); + pad = allowChord.not; + dur = exitsDurFunc.value(allowChord, pad); + res = res.add([voices.deepCopy.postln, dur]); + }); + }); + }); + */ + + //format and return + if(startFromLast, {lastXChanges = lastXChangesHold.deepCopy}); + res; +}; + + +//------primary routines + +genMotif = { + var repeats, fSeq, fDur, durAdd; + + repeats = 1; + fSeq = []; + + repeats.do({arg index; + var motif; + + motif = []; + + orders.do({arg order, o; + var lastState, subMotif; + //lastState = if(o == 0, {popSize.collect({["Rest"]})}, {motif.last.last.first}); + lastState = if(o == 0, {lastXChanges.last.deepCopy}, {motif.last.last.first}); + subMotif = genSubMotif.value(order, o, lastState, isLastOrder: o == (orders.size - 1)); + motif = motif.add(subMotif); + + }); + + sanityCheck.value(motif, index); + + fSeq = fSeq.add(motif); + }); + + //round last duration to measure + /* + fDur = fSeq.flatten.flatten.slice(nil, 1).sum; + durAdd = fDur.round(4) - fDur; + if(durAdd < 0, {durAdd = 4 - durAdd}); + fSeq[0][orders.size - 1][fSeq[0][orders.size - 1].size - 1][1] = fSeq[0][orders.size - 1][fSeq[0][orders.size - 1].size - 1][1] + durAdd; + */ + + fSeq +}; + +genSecondarySeq = {arg seq; + var curdles, fSeq; + curdles = []; + while({curdles.sum < seq.size}, {curdles = curdles ++ [3.rand + 1]}); + + fSeq = seq.clumps(curdles).collect({arg clump, m; + var repeats, paddedSeq; + + //add rest + paddedSeq = clump.add([[[popSize.collect({["Rest"]}), 0.5.rand]]]); + + //implement repeats + repeats = [0.rand + 1, 1].wchoose([1, 0].normalizeSum); + repeats.collect({paddedSeq}); + }); + fSeq +}; + + +//------audition funcs + +/* +Event.addEventType(\osc, { + if (~addr.postln.notNil) { + ~addr.sendMsg(~indexPath, ~indexMsg); + ~addr.sendMsg(~seqPath, stringifyToDepth.value(~seqMsg, 3)); + //~addr.sendMsg("/STATE/OPEN", (dir.replace("supercollider", "resources") +/+ ~idMsg +/+ ~idMsg ++ "_gui_state" ++ ".state").standardizePath.postln); + }; +}); +*/ + +Event.addEventType(\osc, { + if (~addr.notNil) { + ~msg; + ~addr.sendMsg(~path, *~msg); + }; +}); + +genPatterns = {arg inSeq, addr, oneShot = false; + var voices, durs, pbinds, res, indices, sectionDurs, msg, ids, seq; + seq = inSeq.collect({arg mSeq; mSeq[0]}); + //# voices, durs = seq.flatten2(seq.maxDepth - 5).flop; + # voices, durs = seq.flatten2(if(oneShot, {2}, {3})).flop; + pbinds = voices.flop.collect({arg voice, v; + var clumps, hdScores, freqs, fDurs, attacks, rels, amps; + clumps = voice.separate({arg a, b; a != b }); + freqs = clumps.collect({arg clump; if(clump[0] != ["Rest"], {(62.midicps * hsArrayToFreq.value(clump[0]))}, {Rest(0)})}); + fDurs = durs.clumps(clumps.collect({arg clump; clump.size})).collect({arg clump; clump.sum}); + //attacks = 2.collect({rrand(1, 3)}) ++ freqs.drop(2).collect({rrand(0.3, 0.5)}); + attacks = fDurs.collect({arg dur; dur * rrand(0.2, 0.4)}); + //rels = freqs.drop(2).collect({rrand(0.3, 0.5)}) ++ 2.collect({rrand(1, 3)}); + rels = (clumps.size - 1).collect({arg c; + if(clumps[c + 1][0] == ["Rest"], {rrand(1.0, 3.0)}, {rrand(0.3, 0.5)}); + }); + rels = rels.add(rrand(1.0, 3.0)); + amps = freqs.collect({rrand(0.6, 0.99)}); + + [ + Pbind( + \instrument, \string_model, + \group, group, + \freq, Pseq(freqs, 1), + \dur, Pseq(fDurs, 1), + \attack, Pseq(attacks, 1), + \sustain, Pseq(fDurs, 1), + \release, Pseq(rels, 1), + //\amp, Pseq(amps, 1), + \amp, Pbrown(0.5, 1, 0.5), + \busIndex, v + ), + Pbind( + \instrument, \sine, + \group, group, + \freq, Pseq(freqs, 1), + \dur, Pseq(fDurs, 1), + \sustain, Pseq(fDurs, 1), + \busIndex, v + ) + ] + }).flatten; + if(oneShot.not, { + msg = inSeq.collect({arg mSeq, m; mSeq[1..]}); + //ids = inSeq.collect({arg mSeq, m; mSeq[2]}); + sectionDurs = seq.collect({arg mSeq; mSeq.flatten2(2).flop[1].sum}); + pbinds = pbinds ++ + [ + Pbind( + \type, \osc, + \addr, addr, + \path, "/playing", + \msg, Pseq(msg, 1), + \dur, Pseq(sectionDurs, 1) + ); + ] + }); + res = Ppar(pbinds); + res +}; + +/* +genMidiPatterns = {arg seq; + var voices, durs, patterns, res, mOut, pbRange; + pbRange = 1; //semitones - change this as needed for your situation + mOut = MIDIOut.newByName("TiMidity", "TiMidity port 0").latency_(Server.default.latency); + # voices, durs = seq.flatten2(seq.maxDepth - 5).flop; + res = Ppar( + voices.flop.collect({arg voice, v; + var clumps, hdScores, freqs, fDurs; + + mOut.program(v, 70); + + clumps = voice.separate({arg a, b; a != b }); + freqs = clumps.collect({arg clump; if(clump[0] != ["Rest"], {(60.midicps * hsArrayToFreq.value(clump[0]))}, {Rest(0)})}); + fDurs = durs.clumps(clumps.collect({arg clump; clump.size})).collect({arg clump; clump.sum}); + + Pbind( + \type, \midi, + \chan, v, + \noteval, Pseq(freqs.cpsmidi - 24, 1), + \note, Pfunc({ | event | event[\noteval].floor }), + \dur, Pseq(fDurs, 1), + \midiout, mOut, + \amp, 1, + \bend, Pfunc({ + | event | + if (event[\note].isRest.not) { + var pitchbendvalue = event[\noteval].frac.linlin(0, pbRange, 8192, 8192*2).asInteger; + m.bend(v, pitchbendvalue); + }; + 0; // return something other than nil to avoid stopping the pattern + }), + ); + }); + ); + res +}; +*/ + + +//------resource management funcs + +genUID = {Date.seed.asHexString.toLower}; + +seedFunc = {arg func, seed; + var funcArgs, next; + next = Routine({loop{func.valueArray(funcArgs).yield }}); + next.randSeed_(seed); + {arg ...args; funcArgs = args; next.value} +}; + +stringifyToDepth = {arg data, maxDepth = 1; + var prettyString = "", rCount = 0, writeArray, indent; + + if(maxDepth == 0, { + data.asCompileString + }, { + indent = {arg size; size.collect({" "}).join("")}; + writeArray = {arg array; + prettyString = prettyString ++ indent.value(rCount) ++ "[\n"; + rCount = rCount + 1; + if(rCount < maxDepth, { + array.do({arg subArray; writeArray.value(subArray)}); + }, { + prettyString = prettyString ++ array.collect({arg subArray; + indent.value(rCount + 1) ++ subArray.asCompileString + }).join(",\n"); + }); + rCount = rCount - 1; + prettyString = prettyString ++ "\n" ++ indent.value(rCount) ++ "],\n"; + }; + + writeArray.value(data); + prettyString.replace(",\n\n", "\n").drop(-2); + }) +}; + +sanityCheck = {arg motif, index; + //print functions = very helpful + ("----------" + index + "------------").postln; + + motif.flatten.do({arg val, v; + if(v > 0, { + if(motif.flatten[v-1][0].hammingDistance(val[0]) > 1, {"problem 1".postln}); + if(motif.flatten[v-1][0].hammingDistance(val[0]) == 0, {"problem 2".postln}); + }); + val.postln + }); + "***********".postln; +}; + +msgInterpret = {arg in, escapeDoubleQuotes = true, escapeSingleQuotes = true; + var res; + + res = in; + if(res.isNil.not, { + if((res.isArray && res.isString.not), { + res = res.asCompileString; + res = res.replace(" ", "").replace("\n", "").replace("\t", ""); + if(escapeSingleQuotes, {res = res.replace("\'", "")}); + if(escapeDoubleQuotes, {res = res.replace("\"", "")}); + res = res.replace("Rest", "\"Rest\""); + res = res.interpret; + }, { + var tmpRes; + if(res.every({arg char; char.isDecDigit}), {tmpRes = res.asInteger}); + if(res.contains("."), {tmpRes = res.asFloat}); + if(tmpRes != nil, {res = tmpRes}); + }); + }); + res +}; + +writeResources = {arg path, dict; + var file, modelItems, resString; + file = File(path,"w"); + + modelItems = [ + seq, lastXChanges, + curUID, refUID, orderSeed, durSeed, motifSeed, + entrancesProbVals, passagesProbVals, exitsProbVals, + ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, + orders, susWeights, orderSize, passagesSize, + motifEdited, orderEdited + ]; + + resString = nameSpaces.collect({arg nameSpace; + var depth = 0, insert = " "; + if(nameSpace == "music_data", {depth = 3; insert = "\n"}); + if(nameSpace == "last_changes", {depth = 1; insert = "\n"}); + if(nameSpace == "order", {depth = 1; insert = "\n"}); + if((nameSpace == "ref_uid") && (dict[nameSpace] == nil), {dict[nameSpace] = "nil"}); + "\"" ++ nameSpace ++ "\":" ++ insert ++ stringifyToDepth.value(dict[nameSpace], depth) + }).join(",\n"); + + resString = "{\n" ++ resString ++ "\n}"; + + file.write(resString); + file.close; + resString +}; + +loadModelFile = {arg path; loadModelJSON.value(File(path, "r").readAllString.parseJSON)}; + +loadModelJSON = {arg jsonObject; + var dict; + dict = Dictionary.with(*nameSpaces.collect({arg nS; nS->msgInterpret.value(jsonObject[nS])})); + dict +}; + +setGlobalVars = {arg dict, skipLastXChanges = false; + var tmpLastXChanges; + tmpLastXChanges = lastXChanges.deepCopy; + // order really matters!!!! + # seq, lastXChanges, curUID, refUID, orderSeed, durSeed, motifSeed, + entrancesProbVals, passagesProbVals, exitsProbVals, + ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, + orders, susWeights, orderSize, passagesSize, + motifEdited, orderEdited = nameSpaces.collect({arg nS; dict[nS]}); + if(skipLastXChanges, {lastXChanges = tmpLastXChanges}); + dict +}; + +globalVarsToDict = { + var modelItems, dict; + // order really matters!!!! + modelItems = [ + seq, lastXChanges, + curUID, refUID, orderSeed, durSeed, motifSeed, + entrancesProbVals, passagesProbVals, exitsProbVals, + ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, + orders, susWeights, orderSize, passagesSize, + motifEdited, orderEdited + ]; + dict = Dictionary.with(*nameSpaces.collect({arg nS, n; nS->modelItems[n]})); +}; + +loadLedgerFile = {arg path; + ledgerPath = path; + resourceDir = path.splitext(".").drop(-1).join; + loadLedgerJSON.value(File(ledgerPath, "r").readAllString.parseJSON) +}; + +loadLedgerJSON = {arg jsonObject; ledger = jsonObject["ledger"]}; + +saveLedger = {arg ledger, path; + var file, curResourceDir; + file = File(path, "w"); + curResourceDir = resourceDir; + resourceDir = path.splitext(".").drop(-1).join; + if(curResourceDir != resourceDir, { + File.mkdir(resourceDir); + ledger.do({arg id; + File.copy(curResourceDir +/+ id, resourceDir +/+ id); + }); + }); + file.write("{\n\"ledger\":\n" ++ stringifyToDepth.value(ledger, 1) ++ "\n}"); + file.close; +}; + +//------global vars + +primes = [[2, 1], [3, 2], [5, 4], [7, 4], [11, 8], [13, 8]]; +//ranges = [[-2400, 0], [-1200, 1200], [0, 2400], [0, 2400]]; +exPath = thisProcess.nowExecutingPath; +dir = exPath.dirname; +//popSize = 4; +dims = primes.size; +tuples = genTuples.value(); +//refUID = nil; +group = Group.new; +~group = group; +loadLedgerFile.value(dir +/+ ".." +/+ "resources" +/+ "piece_ledger.json"); +resourceDir = (dir +/+ ".." +/+ "resources" +/+ "piece_ledger"); +//passagesWeights = [1, 1, 1, 1, 1]; +//susWeights = [1, 1, 1]; +// order really matters!!!! +nameSpaces = [ + "music_data", "last_changes", "cur_uid", "ref_uid", "order_seed", "dur_seed", "motifs_seed", + "entrances_probs_vals","passages_probs_vals", "exits_probs_vals", + "ranges", "step_probs_vals", "passages_weights", "hd_exp", "hd_invert", + "order", "sus_weights", "order_size", "passages_size", + "motif_edited", "order_edited" +]; + + +//------OSC funcs + +OSCdef(\load_ledger, {arg msg, time, addr, port; + loadLedgerFile.value(msg[1].asString); +}, \load_ledger); + +OSCdef(\load_model, {arg msg, time, addr, port; + var dict; + dict = loadModelFile.value(msg[1].asString); + setGlobalVars.value(dict); +}, \load_model); + +OSCdef(\save_ledger, {arg msg, time, addr, port; + msg.postln; + ledger = msgInterpret.value(msg[1].asString.parseJSON["ledger"], false).postln; + //loadLedgerJSON.value(msg[0]) + saveLedger.value(ledger, msg[2].asString); + //loadLedgerFile.value(msg[1].asString); +}, \save_ledger); + +OSCdef(\generate, {arg msg, time, addr, port; + var path, dict, durSeeds, musPath, modelString; + msg.postln; + + path = msg[1].asString; + + dict = loadModelFile.value(path); + setGlobalVars.value(dict, true); + + popSize = ranges.size; + + //refUID.postln; + + loadLedgerFile.value(ledgerPath); + if(ledger == nil, {ledger = ["tmp"]}); + if(ledger.last != "tmp", {ledger = ledger.add("tmp")}); + + if(refUID == nil, {lastXChanges = [initVoices.value().deepCopy]}); + if((refUID != nil) && (refUID != "tmp"), { + var file; + file = File((resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath, "r"); + lastXChanges = msgInterpret.value(file.readAllString.parseJSON["last_changes"]); + }); + + refUID.postln; + lastXChanges.collect({arg item; item.postln}); + + durSeeds = seedFunc.value({3.collect({rrand(100000, 999999)})}, durSeed).value.postln; + entrancesDurFunc = genDurFunc.valueArray(entrancesProbVals[..4] ++ [entrancesProbVals[5..]] ++ [durSeeds[0]]); + passagesDurFunc = genDurFunc.valueArray(passagesProbVals[..4] ++ [passagesProbVals[5..]] ++ [durSeeds[1]]); + exitsDurFunc = genDurFunc.valueArray(exitsProbVals[..4] ++ [exitsProbVals[5..]] ++ [durSeeds[2]]); + + if(orders == nil, { + orders = seedFunc.value(genOrders, orderSeed).valueArray(orderSize ++ passagesSize); + //addr.sendMsg("/order", stringifyToDepth.value(orders, 1)); + }); + + stepFunc = genStepFunc.valueArray(stepProbsVals[..1] ++ [stepProbsVals[2..]] ++ [motifSeed]); + seq = seedFunc.value(genMotif, motifSeed).value; + + lastXChanges.collect({arg item; item.postln}); + + dict = globalVarsToDict.value; + modelString = writeResources.value(path, dict); + + //addr.sendMsg("/generated", musPath, stringifyToDepth.value(seq, 3)); + //~seq = seq; + + addr.sendMsg("/generated", path, modelString, ledgerPath); +}, \generate); + + +OSCdef(\commit, {arg msg, time, addr, port; + var musicData, musicChanged, dict, newLedger, modelPath, musString, musFile, test1, test2, lastCurUID, commitType, commitPos, equalityLedger; + //msg.postln; + + /* + test1 = msg[1].asString.parseJSON; + test2 = (dir +/+ ".." +/+ "resources/tmp/tmp_music" ++ ".json").standardizePath.parseJSONFile; + msgInterpret.value(test1["music"])[0][0][0][1].class.postln; + msgInterpret.value(test2["music_data"])[0][0][0][1].class.postln; + (test1["music"] == test2["music_data"]).postln; + */ + + musicData = loadModelJSON.value(msg[1].asString.parseJSON)["music_data"].postln; + musicChanged = (musicData != seq).postln; + commitType = msg[2].asString; + commitPos = msg[3].postln.asInteger; + + lastCurUID = curUID.deepCopy; + curUID = genUID.value; + + File.mkdir((resourceDir +/+ curUID).standardizePath); + File.copy(exPath, (resourceDir +/+ curUID +/+ curUID ++ "_code" ++ ".scd").standardizePath); + + modelPath = (resourceDir +/+ curUID +/+ curUID ++ "_mus_model" ++ ".json").standardizePath; + dict = globalVarsToDict.value; + if(musicChanged, { + seq = musicData; + dict["music_data"] = seq; + dict["motif_edited"] = "true" + }); + dict["cur_uid"] = curUID; + + writeResources.value(modelPath, dict); + + File.delete(ledgerPath ++ "_bak"); + File.copy(ledgerPath, ledgerPath ++ "_bak"); + File.delete(ledgerPath); + + /* + if(commitType == "add", { + if(lastCurUID == "tmp", { + ledger = ledger.drop(-1).add(curUID); + }, { + ledger = ledger.add(curUID); + }) + }); + */ + + ledger.postln; + + if(commitType == "add", {ledger = ledger.add(curUID)}); + + if(commitType == "insert", {ledger = ledger.insert(commitPos + 1, curUID)}); + + if(commitType == "replace", {ledger = ledger.put(commitPos, curUID)}); + + equalityLedger = ledger.collect({arg item; item.asSymbol}); + if(equalityLedger.includes(\tmp).postln, {ledger.removeAt(equalityLedger.indexOf(\tmp).postln)}); + + ledger.postln; + + saveLedger.value(ledger, ledgerPath); + + addr.sendMsg("/committed", curUID, ledgerPath); + //refUID = curUID; + +}, \commit); + +OSCdef(\transport, {arg msg, time, addr, port; + msg.postln; + if(msg[1] == 0, { + group.set(\release, 2); + group.set(\gate, 0); + player.stop; + }, { + // the cued sequence can now be read from file, so this can be cleaned up + var cSize, patterns, pSeq, cuedSeek, indexStart, indexEnd, tmpLedger; + if(msg[1] == 1, { + pSeq = []; + cuedSeek = (seq != nil); + indexStart = msg[2].asInteger; + indexEnd = ledger.size - if(cuedSeek, {2}, {1}); + //ledger.postln; + if(((indexStart == (ledger.size - 1)) && cuedSeek).not, { + ledger[indexStart..indexEnd].do({arg uid, index; + var path, file; + path = (resourceDir +/+ uid +/+ uid ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.postln.parseJSON["music_data"]), path, indexStart + index, uid]); + file.close; + }); + }); + if(cuedSeek, { + var path, file; + path = (resourceDir +/+ "tmp/tmp_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.parseJSON["music_data"]), path, ledger.size - 1, "tmp"]); + file.close; + }); + patterns = genPatterns.value(pSeq, addr); + }, { + pSeq = [loadModelJSON.value(msg[2].asString.parseJSON)["music_data"].postln]; + patterns = genPatterns.value(pSeq, addr, true); + }); + player = Pfset(pattern: patterns, cleanupFunc: { + addr.sendMsg("/transport", 0); + addr.sendMsg("/one_shot", 0); + }); + player = player.play + }); +}, \transport); + + +OSCdef(\transcribe_motif, {arg msg, time, addr, port; + var tSeq, refChord, refUID; + + msg.postln; + + tSeq = [loadModelJSON.value(msg[1].asString.parseJSON)["music_data"]]; + refUID = msg[2].asString.postln; + + if((refUID != "nil") && (refUID != "tmp"), { + var file; + file = File((resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath, "r"); + refChord = msgInterpret.value(file.readAllString.parseJSON["last_changes"]).last; + file.close; + }, { + refChord = [[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0]]; + }); + + ~transcribe.value(tSeq, refChord, (dir +/+ ".." +/+ "lilypond" +/+ "includes").standardizePath, addr, "/transcribe_motif"); +}, \transcribe_motif); + + +OSCdef(\transcribe_all, {arg msg, time, addr, port; + var cSize, patterns, cuedSeek, indexStart, indexEnd, tmpLedger; + if(true, { + cuedSeek = (seq != nil); + indexStart = msg[1].asInteger; + indexEnd = ledger.size - if(cuedSeek, {2}, {1}); + + //tmp for testing transcription + //indexEnd = (indexStart+5); + + //ledger.postln; + if(((indexStart == (ledger.size - 1)) && cuedSeek).not, { + var lilyPartLedgerFiles; + + lilyPartLedgerFiles = 4.collect({arg p; + File((dir +/+ ".." +/+ "lilypond" +/+ "includes" +/+ "part_" ++ ["IV", "III", "II", "I"][p] ++ ".ly").standardizePath, "w"); + }); + + ledger[indexStart..indexEnd].do({arg uid, index; + var path, file, fileString, tSeq, refUID, refChord; + path = (resourceDir +/+ uid +/+ uid ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + fileString = file.readAllString; + tSeq = msgInterpret.value(fileString.parseJSON["music_data"]); + refUID = msgInterpret.value(fileString.parseJSON["ref_uid"]); + file.close; + + //uid.postln; + //(refUID == "nil").postln; + + refChord = [[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0]]; + + if(refUID != "nil", { + path = (resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + refChord = msgInterpret.value(file.readAllString.parseJSON["last_changes"]).last; + file.close; + }); + + if(index != indexEnd, { + ~transcribe.value(tSeq, refChord, (resourceDir +/+ uid +/+ "lilypond").standardizePath); + }, { + ~transcribe.value(tSeq, refChord, (resourceDir +/+ uid +/+ "lilypond").standardizePath, addr, "/transcribe_all"); + }); + + lilyPartLedgerFiles.do({arg f, p; + f.write("\\include \"" ++ resourceDir +/+ uid +/+ "lilypond" +/+ "part_" ++ ["IV", "III", "II", "I"][p] ++ ".ly\"\n"); + }); + + }); + + lilyPartLedgerFiles.do({arg f; + f.close + }); + }); + /* + if(cuedSeek, { + var path, file; + path = (dir +/+ ".." +/+ "resources/tmp/tmp_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.parseJSON["music_data"]), path, ledger.size - 1, "tmp"]); + file.close; + }); + */ + }, { + + }); + +}, \transcribe_all); + +) + + diff --git a/resources/string_quartet_3_rise/536cac90/536cac90_mus_model.json b/resources/string_quartet_3_rise/536cac90/536cac90_mus_model.json new file mode 100644 index 0000000..c479c84 --- /dev/null +++ b/resources/string_quartet_3_rise/536cac90/536cac90_mus_model.json @@ -0,0 +1,96 @@ +{ +"music_data": +[ + [ + [ + [ [ [ -6, 4, 1, 2, -1, 2 ], [ -6, 4, 1, 2, 0, 2 ], [ -6, 4, 1, 3, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -6, 4, 1, 2, -1, 2 ], [ -6, 5, 1, 2, -1, 2 ], [ -6, 4, 1, 3, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -6, 4, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -2, 2 ], [ -6, 4, 1, 3, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -6, 4, 1, 2, -1, 2 ], [ -5, 3, 1, 2, -1, 2 ], [ -6, 4, 1, 3, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ] + ], + [ + [ [ [ -6, 4, 1, 2, -1, 2 ], [ -5, 3, 1, 2, -1, 2 ], [ -6, 4, 1, 3, -1, 2 ], [ -6, 3, 1, 3, -1, 2 ] ], 0.25 ], + [ [ [ -6, 3, 1, 2, -1, 2 ], [ -5, 3, 1, 2, -1, 2 ], [ -6, 4, 1, 3, -1, 2 ], [ -6, 3, 1, 3, -1, 2 ] ], 0 ], + [ [ [ -6, 3, 1, 2, -1, 2 ], [ -5, 3, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ], [ -6, 3, 1, 3, -1, 2 ] ], 0.25 ], + [ [ [ -6, 3, 1, 2, -1, 2 ], [ -5, 3, 1, 2, -1, 2 ], [ -5, 3, 1, 2, 0, 2 ], [ -6, 3, 1, 3, -1, 2 ] ], 0.25 ], + [ [ [ -6, 3, 1, 2, -1, 2 ], [ -5, 3, 1, 2, -1, 2 ], [ -4, 2, 1, 2, -1, 2 ], [ -6, 3, 1, 3, -1, 2 ] ], 0.25 ], + [ [ [ -6, 3, 1, 2, -1, 2 ], [ -5, 3, 1, 2, -1, 2 ], [ -5, 3, 2, 2, -1, 2 ], [ -6, 3, 1, 3, -1, 2 ] ], 0.25 ] + ], + [ + [ [ [ -6, 3, 1, 2, -1, 2 ], [ -5, 3, 1, 2, -1, 2 ], [ -5, 3, 2, 2, -1, 2 ], [ -4, 3, 1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -6, 3, 1, 2, -1, 2 ], [ -5, 3, 1, 2, -1, 2 ], [ -4, 3, 1, 1, -1, 2 ], [ -4, 3, 1, 2, -1, 2 ] ], 0.25 ] + ], + [ + [ [ [ -6, 3, 1, 2, -1, 2 ], [ -5, 3, 1, 2, -1, 2 ], [ -4, 3, 0, 2, -1, 2 ], [ -4, 3, 1, 2, -1, 2 ] ], 0 ], + [ [ [ -6, 3, 1, 2, -1, 2 ], [ -4, 3, 1, 1, -1, 2 ], [ -4, 3, 0, 2, -1, 2 ], [ -4, 3, 1, 2, -1, 2 ] ], 0 ], + [ [ [ -5, 3, 1, 1, -1, 2 ], [ -4, 3, 1, 1, -1, 2 ], [ -4, 3, 0, 2, -1, 2 ], [ -4, 3, 1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -5, 3, 1, 2, -1, 1 ], [ -4, 3, 1, 1, -1, 2 ], [ -4, 3, 0, 2, -1, 2 ], [ -4, 3, 1, 2, -1, 2 ] ], 0 ], + [ [ [ -5, 3, 1, 2, -1, 1 ], [ -5, 3, 2, 2, -1, 2 ], [ -4, 3, 0, 2, -1, 2 ], [ -4, 3, 1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -5, 3, 1, 2, -1, 1 ], [ -4, 2, 1, 2, -1, 2 ], [ -4, 3, 0, 2, -1, 2 ], [ -4, 3, 1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -5, 3, 1, 2, -1, 1 ], [ -4, 3, 1, 2, -1, 1 ], [ -4, 3, 0, 2, -1, 2 ], [ -4, 3, 1, 2, -1, 2 ] ], 0.25 ] + ], + [ + [ [ [ -5, 3, 1, 2, -1, 1 ], [ -5, 3, 1, 2, -1, 2 ], [ -4, 3, 0, 2, -1, 2 ], [ -4, 3, 1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -5, 3, 1, 2, -1, 1 ], [ -5, 3, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ], [ -4, 3, 1, 2, -1, 2 ] ], 0 ], + [ [ [ -5, 2, 1, 2, -1, 2 ], [ -5, 3, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ], [ -4, 3, 1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -5, 2, 1, 2, -1, 2 ], [ -5, 3, 1, 2, -1, 2 ], [ -4, 2, 1, 2, -1, 2 ], [ -4, 3, 1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -5, 2, 1, 2, -1, 2 ], [ -5, 3, 1, 2, -1, 2 ], [ -5, 3, 2, 2, -1, 2 ], [ -4, 3, 1, 2, -1, 2 ] ], 0 ], + [ [ [ -5, 2, 1, 2, -1, 2 ], [ -4, 3, 1, 1, -1, 2 ], [ -5, 3, 2, 2, -1, 2 ], [ -4, 3, 1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -5, 2, 1, 2, -1, 2 ], [ -4, 3, 1, 1, -1, 2 ], [ -5, 3, 1, 2, 0, 2 ], [ -4, 3, 1, 2, -1, 2 ] ], 0.25 ] + ], + [ + [ [ [ -5, 2, 1, 2, -1, 2 ], [ -5, 3, 2, 2, -1, 2 ], [ -5, 3, 1, 2, 0, 2 ], [ -4, 3, 1, 2, -1, 2 ] ], 0 ], + [ [ [ -5, 2, 1, 2, -1, 2 ], [ -5, 3, 2, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ], [ -4, 3, 1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -5, 2, 1, 2, -1, 2 ], [ -4, 2, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ], [ -4, 3, 1, 2, -1, 2 ] ], 0.25 ] + ], + [ + [ [ [ -5, 2, 1, 2, -1, 2 ], [ -4, 2, 1, 2, -1, 2 ], [ -3, 2, 1, 2, -1, 2 ], [ -4, 3, 1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -5, 2, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ], [ -3, 2, 1, 2, -1, 2 ], [ -4, 3, 1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -6, 4, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ], [ -3, 2, 1, 2, -1, 2 ], [ -4, 3, 1, 2, -1, 2 ] ], 0.25 ] + ], + [ + [ [ [ -6, 3, 1, 2, 0, 2 ], [ -5, 4, 1, 2, -1, 2 ], [ -3, 2, 1, 2, -1, 2 ], [ -4, 3, 1, 2, -1, 2 ] ], 0 ], + [ [ [ -6, 3, 1, 2, 0, 2 ], [ -5, 3, 1, 2, 0, 2 ], [ -3, 2, 1, 2, -1, 2 ], [ -4, 3, 1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -6, 3, 1, 2, 0, 2 ], [ -4, 3, 1, 2, -2, 2 ], [ -3, 2, 1, 2, -1, 2 ], [ -4, 3, 1, 2, -1, 2 ] ], 0 ], + [ [ [ -5, 3, 1, 2, -2, 2 ], [ -4, 3, 1, 2, -2, 2 ], [ -3, 2, 1, 2, -1, 2 ], [ -4, 3, 1, 2, -1, 2 ] ], 0.25 ] + ] + ] +], +"last_changes": +[ + [ [ -6, 4, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ], [ -3, 2, 1, 2, -1, 2 ], [ -4, 3, 1, 2, -1, 2 ] ], + [ [ -6, 3, 1, 2, 0, 2 ], [ -5, 4, 1, 2, -1, 2 ], [ -3, 2, 1, 2, -1, 2 ], [ -4, 3, 1, 2, -1, 2 ] ], + [ [ -6, 3, 1, 2, 0, 2 ], [ -5, 3, 1, 2, 0, 2 ], [ -3, 2, 1, 2, -1, 2 ], [ -4, 3, 1, 2, -1, 2 ] ], + [ [ -6, 3, 1, 2, 0, 2 ], [ -4, 3, 1, 2, -2, 2 ], [ -3, 2, 1, 2, -1, 2 ], [ -4, 3, 1, 2, -1, 2 ] ], + [ [ -5, 3, 1, 2, -2, 2 ], [ -4, 3, 1, 2, -2, 2 ], [ -3, 2, 1, 2, -1, 2 ], [ -4, 3, 1, 2, -1, 2 ] ] +], +"cur_uid": "536cac90", +"ref_uid": "767e70f0", +"order_seed": 837263, +"dur_seed": 210094, +"motifs_seed": 299013, +"entrances_probs_vals": [ 0.78, 0, 0, 0, 1.02, 0.0098039215686275, 0, 0.20915032679739, 0, 0.24183006535948, 0.82432432432432, 0.28758169934641, 0, 1, 0 ], +"passages_probs_vals": [ 0.78, 0, 0, 0, 1.02, 0.0098039215686275, 0, 0.20915032679739, 0, 0.24183006535948, 0.82432432432432, 0.28758169934641, 0, 1, 0 ], +"exits_probs_vals": [ 0.78, 0, 0, 0, 1.02, 0.0098039215686275, 0, 0.20915032679739, 0, 0.24183006535948, 0.82432432432432, 0.28758169934641, 0, 1, 0 ], +"ranges": [ [ -2400, 1200 ], [ -1200, 1453 ], [ -702, 1694 ], [ -702, 1973 ] ], +"step_probs_vals": [ -1200, 1200, 0, 0, 0.41358024691358, 0.0056818181818177, 0.46913580246914, 0.67045454545455, 0.52057613168724, 0, 0.56378600823045, 0.85795454545455, 0.60082304526749, 0, 1, 0 ], +"passages_weights": [ 1, 0.15, 0.43, 1, 1 ], +"hd_exp": 10, +"hd_invert": 0, +"order": +[ + [ [ 0, 2, 3 ], [ 1, 1, 1, 1 ], [ ] ], + [ [ 1 ], [ 3, 0, 2, 2, 2, 2 ], [ ] ], + [ [ 1, 0 ], [ 3, 2 ], [ ] ], + [ [ 3 ], [ 2, 1, 0, 0, 1, 1, 1 ], [ ] ], + [ [ 3 ], [ 1, 2, 0, 2, 2, 1, 2 ], [ ] ], + [ [ 0, 3 ], [ 1, 2, 1 ], [ ] ], + [ [ 3 ], [ 2, 1, 0 ], [ ] ], + [ [ 3, 2 ], [ 0, 1, 1, 0 ], [ ] ] +], +"sus_weights": [ 0.7, 0.48, 0.49 ], +"order_size": [ 14.132653061224, 14.132653061224 ], +"passages_size": [ 0, 5 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/536cac90/lilypond/part_I.ly b/resources/string_quartet_3_rise/536cac90/lilypond/part_I.ly new file mode 100644 index 0000000..5413433 --- /dev/null +++ b/resources/string_quartet_3_rise/536cac90/lilypond/part_I.ly @@ -0,0 +1,8 @@ +{ + { f'2^\markup { \pad-markup #0.2 "-38"} g2^\markup { \pad-markup #0.2 "+28"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 7↑" }} ~ } + \bar "|" + { g8[ ais'8^\markup { \pad-markup #0.2 "-40"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }}] ~ ais'2. ~ } + \bar "|" + { ais'1} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/536cac90/lilypond/part_II.ly b/resources/string_quartet_3_rise/536cac90/lilypond/part_II.ly new file mode 100644 index 0000000..6d3b793 --- /dev/null +++ b/resources/string_quartet_3_rise/536cac90/lilypond/part_II.ly @@ -0,0 +1,8 @@ +{ + { d'2^\markup { \pad-markup #0.2 "+30"} ~ d'8[ f'8^\markup { \pad-markup #0.2 "-38"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↑" }}] dis'8^\markup { \pad-markup #0.2 "+11"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 11↑" }}[ dis'8^\markup { \pad-markup #0.2 "-42"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↓" }}] } + \bar "|" + { cis'4^\markup { \pad-markup #0.2 "+46"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↑" }} c'8^\markup { \pad-markup #0.2 "-9"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 7↓" }}[ fis'8^\markup { \pad-markup #0.2 "-27"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↓" }}] ~ fis'2 } + \bar "|" + { f'8^\markup { \pad-markup #0.2 "-38"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 3↑" }}[ dis'8^\markup { \pad-markup #0.2 "-42"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }}] cis'8^\markup { \pad-markup #0.2 "+46"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 5↑" }}[ dis'8^\markup { \pad-markup #0.2 "+11"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 11↑" }}] f'4^\markup { \pad-markup #0.2 "-38"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 3↑" }} dis''4^\markup { \pad-markup #0.2 "-42"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }}} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/536cac90/lilypond/part_III.ly b/resources/string_quartet_3_rise/536cac90/lilypond/part_III.ly new file mode 100644 index 0000000..fe3d344 --- /dev/null +++ b/resources/string_quartet_3_rise/536cac90/lilypond/part_III.ly @@ -0,0 +1,8 @@ +{ + { ais8^\markup { \pad-markup #0.2 "+13"}[ c'8^\markup { \pad-markup #0.2 "-37"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↑" }}] b8^\markup { \pad-markup #0.2 "+10"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 11↓" }}[ ais8^\markup { \pad-markup #0.2 "-40"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↓" }}] ~ ais2 ~ } + \bar "|" + { ais4 ~ ais8[ c'8^\markup { \pad-markup #0.2 "-9"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 7↓" }}] cis'8^\markup { \pad-markup #0.2 "+46"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 5↑" }}[ dis'8^\markup { \pad-markup #0.2 "-42"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 3↓" }}] cis'8^\markup { \pad-markup #0.2 "+19"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }}[ ais8^\markup { \pad-markup #0.2 "-40"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 1↑" }}] ~ } + \bar "|" + { ais4 c'4^\markup { \pad-markup #0.2 "-9"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 7↓" }} cis'8^\markup { \pad-markup #0.2 "+46"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 5↑" }}[ dis'8^\markup { \pad-markup #0.2 "-42"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }}] ~ dis'8[ f'8^\markup { \pad-markup #0.2 "-38"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 3↑" }}]} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/536cac90/lilypond/part_IV.ly b/resources/string_quartet_3_rise/536cac90/lilypond/part_IV.ly new file mode 100644 index 0000000..5aed56f --- /dev/null +++ b/resources/string_quartet_3_rise/536cac90/lilypond/part_IV.ly @@ -0,0 +1,8 @@ +{ + { f2^\markup { \pad-markup #0.2 "-38"} ~ f8[ ais,8^\markup { \pad-markup #0.2 "-40"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }}] ~ ais,4 ~ } + \bar "|" + { ais,4 ~ ais,8[ c8^\markup { \pad-markup #0.2 "-9"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }}] cis2^\markup { \pad-markup #0.2 "+19"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 13↓" }} } + \bar "|" + { dis1^\markup { \pad-markup #0.2 "-42"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 3↓" }}} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/567a9375/567a9375_code.scd b/resources/string_quartet_3_rise/567a9375/567a9375_code.scd new file mode 100644 index 0000000..e1a9d92 --- /dev/null +++ b/resources/string_quartet_3_rise/567a9375/567a9375_code.scd @@ -0,0 +1,992 @@ +( +// helper funcs +var hsArrayToCents, pDist, hdSum, hsChordalDistance, hsArrayToFreq; + +// score funcs +var isInRange, spacingScore, rangeScore, intervalScore, inclusionScore; + +// subroutines +var genTuples, initVoices, genOrders, genSubMotif, updateVoices, genDurFunc, genStepFunc; + +// primary routines +var genMotif, genSecondarySeq; + +// audition funcs +var genPatterns, genMidiPatterns; + +// resource management funcs +var seedFunc, genUID, writeResources, stringifyToDepth, setSeeds, sanityCheck, +msgInterpret, loadLedgerFile, loadLedgerJSON, loadModelFile, loadModelJSON, +setGlobalVars, globalVarsToDict, saveLedger; + +// model vars +//(model and global vars mostly set by OSC funcs +var seq, lastXChanges, +curUID, refUID, orderSeed, durSeed, motifSeed, +entrancesProbVals, passagesProbVals, exitsProbVals, +ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, +orders, susWeights, orderSize, passagesSize, +motifEdited, orderEdited; + +// model aux vars +var entrancesDurFunc, passagesDurFunc, exitsDurFunc, stepFunc; + +// other global vars +var popSize, exPath, dir, primes, dims, tuples, +group, player, resourceDir, ledgerPath, ledger, currentlyPlayingUID, +nameSpaces; + +// install JSON quark (not used) +/* +if(Quarks.isInstalled("JSONlib").not, { + Quarks.install("https://github.com/musikinformatik/JSONlib.git"); + thisProcess.recompile; + //HelpBrowser.openHelpFor("Classes/JSONlib"); +}); +*/ + + +//------helper funcs + +hsArrayToCents = { + arg hsArray; + hsArray.collect({arg dist, p; dist * 1200 * log2(primes[p][0]/primes[p][1])}).sum +}; + +pDist = { + arg array1, array2, signed = false; + var pDistance; + pDistance = hsArrayToCents.value(array2) - hsArrayToCents.value(array1); + if(signed, {pDistance}, {abs(pDistance)}) +}; + +hdSum = { + arg hsArrays; + var size, distances, mean; + size = hsArrays.size; + distances = (size - 1).collect({arg i; + ((i + 1)..(size - 1)).collect({arg j; + abs(hsArrays[i] - hsArrays[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsChordalDistance = { + arg hsArrays1, hsArrays2; + var size, distances, mean; + size = hsArrays1.size; + distances = hsArrays1.size.collect({arg i; + hsArrays2.size.collect({arg j; + abs(hsArrays1[i] - hsArrays2[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsArrayToFreq = { + arg array; + array.collect({arg dim, d; pow(primes[d][0]/primes[d][1], dim)}).product +}; + +//------score funcs + +/* +isInRange = { + arg hsArray, min, max; + var cents; + cents = hsArrayToCents.value(hsArray); + (cents >= min) && (cents <= max) +}; +*/ + +spacingScore = { + arg hsArrays, min; + var centsArray; + centsArray = hsArrays.collect({arg hsArray; hsArrayToCents.value(hsArray)}).sort({arg a, b; a < b}); + centsArray.differentiate.drop(1).collect({arg pDistance; if(pDistance >= min, {1}, {0.01})}).sum; +}; + +rangeScore = { + arg hsArray1, hsArray2, min, max, low, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + if((pDistance >= min) && (pDistance <= max), {1}, {low}); +}; + +/* +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + stepFunc.value(pDistance) +}; +*/ + +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = true; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + //if(pDistance >= 0, {stepFunc.value(abs(pDistance))}, {0.01}); + stepFunc.value(pDistance) +}; + +inclusionScore = { + arg array, test, min = 0.01; + if(array.collect({arg v; v.hash}).includes(test.hash), {min}, {1}); +}; + + +//------subroutines + +genTuples = { + var tuples; + tuples = dims.collect({[-1, 0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum <= 1) && (tuple[0] == 0)}); + //tuples = dims.collect({[0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum == 1) && (tuple[0] == 0)}); + tuples = tuples ++ tuples.collect({arg tuple; [-3, -2, -1, 1, 2, 3].collect({arg octTrans; tuple.deepCopy.put(0, octTrans)})}).flatten; +}; + +initVoices = { + var init, voicesInit; + voicesInit = popSize.collect({dims.collect({0})}); + /* + voicesInit = [dims.collect({0})]; + (popSize - 1).do({ + arg rep, new; + rep = dims.rand; + new = voicesInit.last.deepCopy; + new[rep] = new[rep] + [-1, 1].choose(); + voicesInit = voicesInit.add(new); + }); + */ + voicesInit.deepCopy; +}; + +genDurFunc = {arg chordProb, minPad, maxPad, minDur, maxDur, envData, seed; + var env, pTable, durFunc; + env = Env.pairs([[0, 0]] ++ envData.clump(2) ++ [[1, 0]]).asSignal(256).asList.asArray; + pTable = env.asRandomTable; + //[chordProb, minPad, maxPad, minDur, maxDur, envData].postln; + durFunc = {arg allowChord, pad = false; + var res; + res = if(allowChord.not, { + pTable.tableRand * (maxDur - minDur) + minDur + }, { + if(1.0.rand < chordProb, {0}, {pTable.tableRand * (maxDur - minDur) + minDur}); + }).round(0.125); + if(pad, {res = res + rrand(minPad.asFloat, maxPad.asFloat).round(0.125)}); + if(res.asInteger == res, {res = res.asInteger}); + res + }; + seedFunc.value(durFunc, seed); +}; + +genStepFunc = {arg minStep, maxStep, envData, seed; + var envDataNorm, env, pTable, stepFunc; + //[minStep, maxStep, envData].postln; + envDataNorm = ([[0, 0]] ++ envData.clump(2) ++ [[1, 0]]).flop; + envDataNorm = [envDataNorm[0].normalize(minStep, maxStep), envDataNorm[1]].flop; + env = Env.pairs(envDataNorm); + stepFunc = {arg pDist; + env.at(pDist).clip(0.001, 1); + }; + seedFunc.value(stepFunc, seed); +}; + +genOrders = {arg minMotifLength = 1, maxMotifLength = 5, minProgLength = 0, maxProgLength = 5; + ((maxMotifLength.asInteger - minMotifLength.asInteger).rand + minMotifLength.asInteger).collect({ + var noProgIns, noSusIns, noSilentIns, prog, sus, silent, order; + noSusIns = [1, 2, 3].wchoose(susWeights.normalizeSum); + + //noProgIns = (popSize - noSusIns).rand + 1; + noProgIns = (popSize - noSusIns); + noSilentIns = popSize - noSusIns - noProgIns; + + /* + noSilentIns = (popSize - noSusIns).rand.clip(0, 1); + noProgIns = popSize - noSusIns - noSilentIns; + */ + + # prog, sus, silent = (0..(popSize-1)).scramble.clumps([noProgIns, noSusIns, noSilentIns]); + + prog = (prog.scramble ++ ((maxProgLength.asInteger - minProgLength.asInteger).rand + minProgLength.asInteger).collect({prog.choose}).scramble); + //prog = ((maxProgLength.asInteger - minProgLength.asInteger).rand + minProgLength.asInteger).collect({prog.choose}); + + if(silent == nil, {silent = []}); + [sus.scramble, prog, silent.scramble] + }); +}; + +updateVoices = {arg ins, sus; + var voices, candidates, nWeights, nProbs, sel; + + voices = lastXChanges.deepCopy.last; + + candidates = sus.collect({arg v; tuples.collect({arg t; voices[v] + t})}).flatten; + candidates = difference(candidates.asSet, voices.asSet).asList; + //candidates.select({arg item; (item ++ voices).asSet.size >= 4}); + nProbs = candidates.collect({arg candidate; + var stepScore, recentlySoundedScore, isInRangeScore, regScore, hdScore; + + //stepScore = intervalScore.value(voices[ins], candidate, 30, 400, 0.1); + stepScore = intervalScore.value(voices[ins], candidate, 100, 100); + recentlySoundedScore = inclusionScore.value(lastXChanges.flop[ins], candidate, 0.01); + //recentlySoundedScore = inclusionScore.value(lastXChanges.flatten.collect({arg item; item.drop(1)}), candidate.drop(1), 0.01); + + /* + if(rangeScore.value(candidate.collect({0}), voices[ins], ranges[ins][1] - 500, ranges[ins][1], 0, true) == 1, { + isInRangeScore = 1 - rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0] + 500, ranges[ins][1] - 500, 0, true); + isInRangeScore = isInRangeScore * rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true) + }, { + isInRangeScore = rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true); + }); + */ + + + isInRangeScore = rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true); + //old way - worked but actually by accident (I think) + //isInRangeScore = rangeScore.value(candidate, candidate.collect({0}), ranges[ins][0], ranges[ins][1], 0, true); + + + regScore = spacingScore.value(voices.deepCopy.put(ins, candidate), 300); + hdScore = pow(hdSum.value(voices.deepCopy.put(ins, candidate)), hdExp); + if(hdInvert == 0, {hdScore = 1/hdScore}); + //maybe what you want here is a vector to another root and then favoring movement towards it. + //distScore = pow(hsChordalDistance.value(voices, voices.put(ins, candidate)), 2); + + [stepScore, recentlySoundedScore, isInRangeScore, regScore, hdScore] + }); + + nWeights = passagesWeights; + + //this handles nWeights of 0; mainly for testing + nProbs = nProbs.flop.select({arg scores, s; nWeights[s] != 0}).flop; + nWeights = nWeights.select({arg weight; weight != 0}); + nProbs = nProbs.flop.collect({arg scores, s; + if(scores.sum == 0, {scores}, {scores.normalizeSum * nWeights[s]}).postln; + }); + nProbs = nProbs.flop.collect({arg scores, s; scores.product}).normalizeSum; + + nProbs.round(0.001).postln; + [candidates, nProbs.round(0.001)].flop.postln; + + sel = candidates.wchoose(nProbs); + + voices[ins] = sel; + lastXChanges = lastXChanges.add(voices).keep(-5); +}; + +genSubMotif = {arg order, orderIndex, lastState, repeatLast = false, startFromLast = false, isLastOrder = false; + var sus, prog, silent, flatOrder, res, isInChord, allowChord, pad, lastXChangesHold, voices, adder; + # sus, prog, silent = order; + flatOrder = silent ++ sus ++ prog; + lastXChangesHold = lastXChanges.deepCopy; + voices = lastState.deepCopy; + isInChord = popSize.collect({false}); + allowChord = false; + pad = false; + res = []; + "------generating motif".postln; + //need to figure out here if voices move between motifs + flatOrder.do({arg ins, i; + + if(prog.includes(ins) && repeatLast.not, {updateVoices.value(ins, sus)}); + adder = if(silent.includes(ins), {["Rest"]}, {lastXChanges.last.deepCopy[ins]}); + + if(voices[ins] != adder, { + var dur; + + if((sus ++ silent).includes(ins), { + allowChord = (ins != sus.last); + pad = (ins == sus.last); + }, { + if(i < (flatOrder.size - 1), { + allowChord = (isInChord[flatOrder[i + 1]] || (ins == flatOrder[i + 1])).not; + pad = false; + }, { + allowChord = false; + pad = true + }); + }); + if((orderIndex == 0) && sus.includes(ins), { + dur = entrancesDurFunc.value(allowChord, pad); + }, { + dur = passagesDurFunc.value(allowChord, pad); + }); + if(dur == 0, {isInChord[ins] = true}, {isInChord = popSize.collect({false})}); + + voices[ins] = adder; + res = res.add([voices.deepCopy.postln, dur]); + }); + }); + + // pad ending + /* + if(orderIndex == (orders.size - 1), { + (0..(popSize-1)).scramble.do({arg ins; + if(res.last.first[ins] != ["Rest"], { + var dur; + voices[ins] = ["Rest"]; + allowChord = (voices != popSize.collect({["Rest"]})); + pad = allowChord.not; + dur = exitsDurFunc.value(allowChord, pad); + res = res.add([voices.deepCopy.postln, dur]); + }); + }); + }); + */ + + //format and return + if(startFromLast, {lastXChanges = lastXChangesHold.deepCopy}); + res; +}; + + +//------primary routines + +genMotif = { + var repeats, fSeq, fDur, durAdd; + + repeats = 1; + fSeq = []; + + repeats.do({arg index; + var motif; + + motif = []; + + orders.do({arg order, o; + var lastState, subMotif; + //lastState = if(o == 0, {popSize.collect({["Rest"]})}, {motif.last.last.first}); + lastState = if(o == 0, {lastXChanges.last.deepCopy}, {motif.last.last.first}); + subMotif = genSubMotif.value(order, o, lastState, isLastOrder: o == (orders.size - 1)); + motif = motif.add(subMotif); + + }); + + sanityCheck.value(motif, index); + + fSeq = fSeq.add(motif); + }); + + //round last duration to measure + /* + fDur = fSeq.flatten.flatten.slice(nil, 1).sum; + durAdd = fDur.round(4) - fDur; + if(durAdd < 0, {durAdd = 4 - durAdd}); + fSeq[0][orders.size - 1][fSeq[0][orders.size - 1].size - 1][1] = fSeq[0][orders.size - 1][fSeq[0][orders.size - 1].size - 1][1] + durAdd; + */ + + fSeq +}; + +genSecondarySeq = {arg seq; + var curdles, fSeq; + curdles = []; + while({curdles.sum < seq.size}, {curdles = curdles ++ [3.rand + 1]}); + + fSeq = seq.clumps(curdles).collect({arg clump, m; + var repeats, paddedSeq; + + //add rest + paddedSeq = clump.add([[[popSize.collect({["Rest"]}), 0.5.rand]]]); + + //implement repeats + repeats = [0.rand + 1, 1].wchoose([1, 0].normalizeSum); + repeats.collect({paddedSeq}); + }); + fSeq +}; + + +//------audition funcs + +/* +Event.addEventType(\osc, { + if (~addr.postln.notNil) { + ~addr.sendMsg(~indexPath, ~indexMsg); + ~addr.sendMsg(~seqPath, stringifyToDepth.value(~seqMsg, 3)); + //~addr.sendMsg("/STATE/OPEN", (dir.replace("supercollider", "resources") +/+ ~idMsg +/+ ~idMsg ++ "_gui_state" ++ ".state").standardizePath.postln); + }; +}); +*/ + +Event.addEventType(\osc, { + if (~addr.notNil) { + ~msg; + ~addr.sendMsg(~path, *~msg); + }; +}); + +genPatterns = {arg inSeq, addr, oneShot = false; + var voices, durs, pbinds, res, indices, sectionDurs, msg, ids, seq; + seq = inSeq.collect({arg mSeq; mSeq[0]}); + //# voices, durs = seq.flatten2(seq.maxDepth - 5).flop; + # voices, durs = seq.flatten2(if(oneShot, {2}, {3})).flop; + pbinds = voices.flop.collect({arg voice, v; + var clumps, hdScores, freqs, fDurs, attacks, rels, amps; + clumps = voice.separate({arg a, b; a != b }); + freqs = clumps.collect({arg clump; if(clump[0] != ["Rest"], {(62.midicps * hsArrayToFreq.value(clump[0]))}, {Rest(0)})}); + fDurs = durs.clumps(clumps.collect({arg clump; clump.size})).collect({arg clump; clump.sum}); + //attacks = 2.collect({rrand(1, 3)}) ++ freqs.drop(2).collect({rrand(0.3, 0.5)}); + attacks = fDurs.collect({arg dur; dur * rrand(0.2, 0.4)}); + //rels = freqs.drop(2).collect({rrand(0.3, 0.5)}) ++ 2.collect({rrand(1, 3)}); + rels = (clumps.size - 1).collect({arg c; + if(clumps[c + 1][0] == ["Rest"], {rrand(1.0, 3.0)}, {rrand(0.3, 0.5)}); + }); + rels = rels.add(rrand(1.0, 3.0)); + amps = freqs.collect({rrand(0.6, 0.99)}); + + [ + Pbind( + \instrument, \string_model, + \group, group, + \freq, Pseq(freqs, 1), + \dur, Pseq(fDurs, 1), + \attack, Pseq(attacks, 1), + \sustain, Pseq(fDurs, 1), + \release, Pseq(rels, 1), + //\amp, Pseq(amps, 1), + \amp, Pbrown(0.5, 1, 0.5), + \busIndex, v + ), + Pbind( + \instrument, \sine, + \group, group, + \freq, Pseq(freqs, 1), + \dur, Pseq(fDurs, 1), + \sustain, Pseq(fDurs, 1), + \busIndex, v + ) + ] + }).flatten; + if(oneShot.not, { + msg = inSeq.collect({arg mSeq, m; mSeq[1..]}); + //ids = inSeq.collect({arg mSeq, m; mSeq[2]}); + sectionDurs = seq.collect({arg mSeq; mSeq.flatten2(2).flop[1].sum}); + pbinds = pbinds ++ + [ + Pbind( + \type, \osc, + \addr, addr, + \path, "/playing", + \msg, Pseq(msg, 1), + \dur, Pseq(sectionDurs, 1) + ); + ] + }); + res = Ppar(pbinds); + res +}; + +/* +genMidiPatterns = {arg seq; + var voices, durs, patterns, res, mOut, pbRange; + pbRange = 1; //semitones - change this as needed for your situation + mOut = MIDIOut.newByName("TiMidity", "TiMidity port 0").latency_(Server.default.latency); + # voices, durs = seq.flatten2(seq.maxDepth - 5).flop; + res = Ppar( + voices.flop.collect({arg voice, v; + var clumps, hdScores, freqs, fDurs; + + mOut.program(v, 70); + + clumps = voice.separate({arg a, b; a != b }); + freqs = clumps.collect({arg clump; if(clump[0] != ["Rest"], {(60.midicps * hsArrayToFreq.value(clump[0]))}, {Rest(0)})}); + fDurs = durs.clumps(clumps.collect({arg clump; clump.size})).collect({arg clump; clump.sum}); + + Pbind( + \type, \midi, + \chan, v, + \noteval, Pseq(freqs.cpsmidi - 24, 1), + \note, Pfunc({ | event | event[\noteval].floor }), + \dur, Pseq(fDurs, 1), + \midiout, mOut, + \amp, 1, + \bend, Pfunc({ + | event | + if (event[\note].isRest.not) { + var pitchbendvalue = event[\noteval].frac.linlin(0, pbRange, 8192, 8192*2).asInteger; + m.bend(v, pitchbendvalue); + }; + 0; // return something other than nil to avoid stopping the pattern + }), + ); + }); + ); + res +}; +*/ + + +//------resource management funcs + +genUID = {Date.seed.asHexString.toLower}; + +seedFunc = {arg func, seed; + var funcArgs, next; + next = Routine({loop{func.valueArray(funcArgs).yield }}); + next.randSeed_(seed); + {arg ...args; funcArgs = args; next.value} +}; + +stringifyToDepth = {arg data, maxDepth = 1; + var prettyString = "", rCount = 0, writeArray, indent; + + if(maxDepth == 0, { + data.asCompileString + }, { + indent = {arg size; size.collect({" "}).join("")}; + writeArray = {arg array; + prettyString = prettyString ++ indent.value(rCount) ++ "[\n"; + rCount = rCount + 1; + if(rCount < maxDepth, { + array.do({arg subArray; writeArray.value(subArray)}); + }, { + prettyString = prettyString ++ array.collect({arg subArray; + indent.value(rCount + 1) ++ subArray.asCompileString + }).join(",\n"); + }); + rCount = rCount - 1; + prettyString = prettyString ++ "\n" ++ indent.value(rCount) ++ "],\n"; + }; + + writeArray.value(data); + prettyString.replace(",\n\n", "\n").drop(-2); + }) +}; + +sanityCheck = {arg motif, index; + //print functions = very helpful + ("----------" + index + "------------").postln; + + motif.flatten.do({arg val, v; + if(v > 0, { + if(motif.flatten[v-1][0].hammingDistance(val[0]) > 1, {"problem 1".postln}); + if(motif.flatten[v-1][0].hammingDistance(val[0]) == 0, {"problem 2".postln}); + }); + val.postln + }); + "***********".postln; +}; + +msgInterpret = {arg in, escapeDoubleQuotes = true, escapeSingleQuotes = true; + var res; + + res = in; + if(res.isNil.not, { + if((res.isArray && res.isString.not), { + res = res.asCompileString; + res = res.replace(" ", "").replace("\n", "").replace("\t", ""); + if(escapeSingleQuotes, {res = res.replace("\'", "")}); + if(escapeDoubleQuotes, {res = res.replace("\"", "")}); + res = res.replace("Rest", "\"Rest\""); + res = res.interpret; + }, { + var tmpRes; + if(res.every({arg char; char.isDecDigit}), {tmpRes = res.asInteger}); + if(res.contains("."), {tmpRes = res.asFloat}); + if(tmpRes != nil, {res = tmpRes}); + }); + }); + res +}; + +writeResources = {arg path, dict; + var file, modelItems, resString; + file = File(path,"w"); + + modelItems = [ + seq, lastXChanges, + curUID, refUID, orderSeed, durSeed, motifSeed, + entrancesProbVals, passagesProbVals, exitsProbVals, + ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, + orders, susWeights, orderSize, passagesSize, + motifEdited, orderEdited + ]; + + resString = nameSpaces.collect({arg nameSpace; + var depth = 0, insert = " "; + if(nameSpace == "music_data", {depth = 3; insert = "\n"}); + if(nameSpace == "last_changes", {depth = 1; insert = "\n"}); + if(nameSpace == "order", {depth = 1; insert = "\n"}); + if((nameSpace == "ref_uid") && (dict[nameSpace] == nil), {dict[nameSpace] = "nil"}); + "\"" ++ nameSpace ++ "\":" ++ insert ++ stringifyToDepth.value(dict[nameSpace], depth) + }).join(",\n"); + + resString = "{\n" ++ resString ++ "\n}"; + + file.write(resString); + file.close; + resString +}; + +loadModelFile = {arg path; loadModelJSON.value(File(path, "r").readAllString.parseJSON)}; + +loadModelJSON = {arg jsonObject; + var dict; + dict = Dictionary.with(*nameSpaces.collect({arg nS; nS->msgInterpret.value(jsonObject[nS])})); + dict +}; + +setGlobalVars = {arg dict, skipLastXChanges = false; + var tmpLastXChanges; + tmpLastXChanges = lastXChanges.deepCopy; + // order really matters!!!! + # seq, lastXChanges, curUID, refUID, orderSeed, durSeed, motifSeed, + entrancesProbVals, passagesProbVals, exitsProbVals, + ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, + orders, susWeights, orderSize, passagesSize, + motifEdited, orderEdited = nameSpaces.collect({arg nS; dict[nS]}); + if(skipLastXChanges, {lastXChanges = tmpLastXChanges}); + dict +}; + +globalVarsToDict = { + var modelItems, dict; + // order really matters!!!! + modelItems = [ + seq, lastXChanges, + curUID, refUID, orderSeed, durSeed, motifSeed, + entrancesProbVals, passagesProbVals, exitsProbVals, + ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, + orders, susWeights, orderSize, passagesSize, + motifEdited, orderEdited + ]; + dict = Dictionary.with(*nameSpaces.collect({arg nS, n; nS->modelItems[n]})); +}; + +loadLedgerFile = {arg path; + ledgerPath = path; + resourceDir = path.splitext(".").drop(-1).join; + loadLedgerJSON.value(File(ledgerPath, "r").readAllString.parseJSON) +}; + +loadLedgerJSON = {arg jsonObject; ledger = jsonObject["ledger"]}; + +saveLedger = {arg ledger, path; + var file, curResourceDir; + file = File(path, "w"); + curResourceDir = resourceDir; + resourceDir = path.splitext(".").drop(-1).join; + if(curResourceDir != resourceDir, { + File.mkdir(resourceDir); + ledger.do({arg id; + File.copy(curResourceDir +/+ id, resourceDir +/+ id); + }); + }); + file.write("{\n\"ledger\":\n" ++ stringifyToDepth.value(ledger, 1) ++ "\n}"); + file.close; +}; + +//------global vars + +primes = [[2, 1], [3, 2], [5, 4], [7, 4], [11, 8], [13, 8]]; +//ranges = [[-2400, 0], [-1200, 1200], [0, 2400], [0, 2400]]; +exPath = thisProcess.nowExecutingPath; +dir = exPath.dirname; +//popSize = 4; +dims = primes.size; +tuples = genTuples.value(); +//refUID = nil; +group = Group.new; +~group = group; +loadLedgerFile.value(dir +/+ ".." +/+ "resources" +/+ "piece_ledger.json"); +resourceDir = (dir +/+ ".." +/+ "resources" +/+ "piece_ledger"); +//passagesWeights = [1, 1, 1, 1, 1]; +//susWeights = [1, 1, 1]; +// order really matters!!!! +nameSpaces = [ + "music_data", "last_changes", "cur_uid", "ref_uid", "order_seed", "dur_seed", "motifs_seed", + "entrances_probs_vals","passages_probs_vals", "exits_probs_vals", + "ranges", "step_probs_vals", "passages_weights", "hd_exp", "hd_invert", + "order", "sus_weights", "order_size", "passages_size", + "motif_edited", "order_edited" +]; + + +//------OSC funcs + +OSCdef(\load_ledger, {arg msg, time, addr, port; + loadLedgerFile.value(msg[1].asString); +}, \load_ledger); + +OSCdef(\load_model, {arg msg, time, addr, port; + var dict; + dict = loadModelFile.value(msg[1].asString); + setGlobalVars.value(dict); +}, \load_model); + +OSCdef(\save_ledger, {arg msg, time, addr, port; + msg.postln; + ledger = msgInterpret.value(msg[1].asString.parseJSON["ledger"], false).postln; + //loadLedgerJSON.value(msg[0]) + saveLedger.value(ledger, msg[2].asString); + //loadLedgerFile.value(msg[1].asString); +}, \save_ledger); + +OSCdef(\generate, {arg msg, time, addr, port; + var path, dict, durSeeds, musPath, modelString; + msg.postln; + + path = msg[1].asString; + + dict = loadModelFile.value(path); + setGlobalVars.value(dict, true); + + popSize = ranges.size; + + //refUID.postln; + + loadLedgerFile.value(ledgerPath); + if(ledger == nil, {ledger = ["tmp"]}); + if(ledger.last != "tmp", {ledger = ledger.add("tmp")}); + + if(refUID == nil, {lastXChanges = [initVoices.value().deepCopy]}); + if((refUID != nil) && (refUID != "tmp"), { + var file; + file = File((resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath, "r"); + lastXChanges = msgInterpret.value(file.readAllString.parseJSON["last_changes"]); + }); + + refUID.postln; + lastXChanges.collect({arg item; item.postln}); + + durSeeds = seedFunc.value({3.collect({rrand(100000, 999999)})}, durSeed).value.postln; + entrancesDurFunc = genDurFunc.valueArray(entrancesProbVals[..4] ++ [entrancesProbVals[5..]] ++ [durSeeds[0]]); + passagesDurFunc = genDurFunc.valueArray(passagesProbVals[..4] ++ [passagesProbVals[5..]] ++ [durSeeds[1]]); + exitsDurFunc = genDurFunc.valueArray(exitsProbVals[..4] ++ [exitsProbVals[5..]] ++ [durSeeds[2]]); + + if(orders == nil, { + orders = seedFunc.value(genOrders, orderSeed).valueArray(orderSize ++ passagesSize); + //addr.sendMsg("/order", stringifyToDepth.value(orders, 1)); + }); + + stepFunc = genStepFunc.valueArray(stepProbsVals[..1] ++ [stepProbsVals[2..]] ++ [motifSeed]); + seq = seedFunc.value(genMotif, motifSeed).value; + + lastXChanges.collect({arg item; item.postln}); + + dict = globalVarsToDict.value; + modelString = writeResources.value(path, dict); + + //addr.sendMsg("/generated", musPath, stringifyToDepth.value(seq, 3)); + //~seq = seq; + + addr.sendMsg("/generated", path, modelString, ledgerPath); +}, \generate); + + +OSCdef(\commit, {arg msg, time, addr, port; + var musicData, musicChanged, dict, newLedger, modelPath, musString, musFile, test1, test2, lastCurUID, commitType, commitPos, equalityLedger; + //msg.postln; + + /* + test1 = msg[1].asString.parseJSON; + test2 = (dir +/+ ".." +/+ "resources/tmp/tmp_music" ++ ".json").standardizePath.parseJSONFile; + msgInterpret.value(test1["music"])[0][0][0][1].class.postln; + msgInterpret.value(test2["music_data"])[0][0][0][1].class.postln; + (test1["music"] == test2["music_data"]).postln; + */ + + musicData = loadModelJSON.value(msg[1].asString.parseJSON)["music_data"].postln; + musicChanged = (musicData != seq).postln; + commitType = msg[2].asString; + commitPos = msg[3].postln.asInteger; + + lastCurUID = curUID.deepCopy; + curUID = genUID.value; + + File.mkdir((resourceDir +/+ curUID).standardizePath); + File.copy(exPath, (resourceDir +/+ curUID +/+ curUID ++ "_code" ++ ".scd").standardizePath); + + modelPath = (resourceDir +/+ curUID +/+ curUID ++ "_mus_model" ++ ".json").standardizePath; + dict = globalVarsToDict.value; + if(musicChanged, { + seq = musicData; + dict["music_data"] = seq; + dict["motif_edited"] = "true" + }); + dict["cur_uid"] = curUID; + + writeResources.value(modelPath, dict); + + File.delete(ledgerPath ++ "_bak"); + File.copy(ledgerPath, ledgerPath ++ "_bak"); + File.delete(ledgerPath); + + /* + if(commitType == "add", { + if(lastCurUID == "tmp", { + ledger = ledger.drop(-1).add(curUID); + }, { + ledger = ledger.add(curUID); + }) + }); + */ + + ledger.postln; + + if(commitType == "add", {ledger = ledger.add(curUID)}); + + if(commitType == "insert", {ledger = ledger.insert(commitPos + 1, curUID)}); + + if(commitType == "replace", {ledger = ledger.put(commitPos, curUID)}); + + equalityLedger = ledger.collect({arg item; item.asSymbol}); + if(equalityLedger.includes(\tmp).postln, {ledger.removeAt(equalityLedger.indexOf(\tmp).postln)}); + + ledger.postln; + + saveLedger.value(ledger, ledgerPath); + + addr.sendMsg("/committed", curUID, ledgerPath); + //refUID = curUID; + +}, \commit); + +OSCdef(\transport, {arg msg, time, addr, port; + msg.postln; + if(msg[1] == 0, { + group.set(\release, 2); + group.set(\gate, 0); + player.stop; + }, { + // the cued sequence can now be read from file, so this can be cleaned up + var cSize, patterns, pSeq, cuedSeek, indexStart, indexEnd, tmpLedger; + if(msg[1] == 1, { + pSeq = []; + cuedSeek = (seq != nil); + indexStart = msg[2].asInteger; + indexEnd = ledger.size - if(cuedSeek, {2}, {1}); + //ledger.postln; + if(((indexStart == (ledger.size - 1)) && cuedSeek).not, { + ledger[indexStart..indexEnd].do({arg uid, index; + var path, file; + path = (resourceDir +/+ uid +/+ uid ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.postln.parseJSON["music_data"]), path, indexStart + index, uid]); + file.close; + }); + }); + if(cuedSeek, { + var path, file; + path = (resourceDir +/+ "tmp/tmp_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.parseJSON["music_data"]), path, ledger.size - 1, "tmp"]); + file.close; + }); + patterns = genPatterns.value(pSeq, addr); + }, { + pSeq = [loadModelJSON.value(msg[2].asString.parseJSON)["music_data"].postln]; + patterns = genPatterns.value(pSeq, addr, true); + }); + player = Pfset(pattern: patterns, cleanupFunc: { + addr.sendMsg("/transport", 0); + addr.sendMsg("/one_shot", 0); + }); + player = player.play + }); +}, \transport); + + +OSCdef(\transcribe_motif, {arg msg, time, addr, port; + var tSeq, refChord, refUID; + + msg.postln; + + tSeq = [loadModelJSON.value(msg[1].asString.parseJSON)["music_data"]]; + refUID = msg[2].asString.postln; + + if((refUID != "nil") && (refUID != "tmp"), { + var file; + file = File((resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath, "r"); + refChord = msgInterpret.value(file.readAllString.parseJSON["last_changes"]).last; + file.close; + }, { + refChord = [[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0]]; + }); + + ~transcribe.value(tSeq, refChord, (dir +/+ ".." +/+ "lilypond" +/+ "includes").standardizePath, addr, "/transcribe_motif"); +}, \transcribe_motif); + + +OSCdef(\transcribe_all, {arg msg, time, addr, port; + var cSize, patterns, cuedSeek, indexStart, indexEnd, tmpLedger; + if(true, { + cuedSeek = (seq != nil); + indexStart = msg[1].asInteger; + indexEnd = ledger.size - if(cuedSeek, {2}, {1}); + + //tmp for testing transcription + //indexEnd = (indexStart+5); + + //ledger.postln; + if(((indexStart == (ledger.size - 1)) && cuedSeek).not, { + var lilyPartLedgerFiles; + + lilyPartLedgerFiles = 4.collect({arg p; + File((dir +/+ ".." +/+ "lilypond" +/+ "includes" +/+ "part_" ++ ["IV", "III", "II", "I"][p] ++ ".ly").standardizePath, "w"); + }); + + ledger[indexStart..indexEnd].do({arg uid, index; + var path, file, fileString, tSeq, refUID, refChord; + path = (resourceDir +/+ uid +/+ uid ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + fileString = file.readAllString; + tSeq = msgInterpret.value(fileString.parseJSON["music_data"]); + refUID = msgInterpret.value(fileString.parseJSON["ref_uid"]); + file.close; + + //uid.postln; + //(refUID == "nil").postln; + + refChord = [[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0]]; + + if(refUID != "nil", { + path = (resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + refChord = msgInterpret.value(file.readAllString.parseJSON["last_changes"]).last; + file.close; + }); + + if(index != indexEnd, { + ~transcribe.value(tSeq, refChord, (resourceDir +/+ uid +/+ "lilypond").standardizePath); + }, { + ~transcribe.value(tSeq, refChord, (resourceDir +/+ uid +/+ "lilypond").standardizePath, addr, "/transcribe_all"); + }); + + lilyPartLedgerFiles.do({arg f, p; + f.write("\\include \"" ++ resourceDir +/+ uid +/+ "lilypond" +/+ "part_" ++ ["IV", "III", "II", "I"][p] ++ ".ly\"\n"); + }); + + }); + + lilyPartLedgerFiles.do({arg f; + f.close + }); + }); + /* + if(cuedSeek, { + var path, file; + path = (dir +/+ ".." +/+ "resources/tmp/tmp_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.parseJSON["music_data"]), path, ledger.size - 1, "tmp"]); + file.close; + }); + */ + }, { + + }); + +}, \transcribe_all); + +) + + diff --git a/resources/string_quartet_3_rise/567a9375/567a9375_mus_model.json b/resources/string_quartet_3_rise/567a9375/567a9375_mus_model.json new file mode 100644 index 0000000..22703b5 --- /dev/null +++ b/resources/string_quartet_3_rise/567a9375/567a9375_mus_model.json @@ -0,0 +1,65 @@ +{ +"music_data": +[ + [ + [ + [ [ [ -7, 4, 0, 3, -1, 2 ], [ -7, 4, 1, 3, -1, 2 ], [ -6, 4, 1, 3, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0 ], + [ [ [ -7, 4, 0, 3, -1, 2 ], [ -7, 4, 1, 3, -1, 2 ], [ -5, 4, 0, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0 ], + [ [ [ -6, 3, 1, 2, -1, 2 ], [ -7, 4, 1, 3, -1, 2 ], [ -5, 4, 0, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -6, 3, 1, 2, -1, 2 ], [ -7, 4, 1, 3, -1, 2 ], [ -5, 4, 1, 2, -2, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -6, 3, 1, 2, -1, 2 ], [ -7, 4, 1, 3, -1, 2 ], [ -5, 3, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0 ], + [ [ [ -6, 3, 1, 2, -1, 2 ], [ -6, 4, 1, 2, -1, 2 ], [ -5, 3, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -6, 3, 1, 2, -1, 2 ], [ -6, 4, 1, 2, -1, 2 ], [ -6, 4, 2, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ] + ], + [ + [ [ [ -6, 3, 1, 2, -1, 2 ], [ -5, 3, 1, 2, -2, 2 ], [ -6, 4, 2, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0 ], + [ [ [ -6, 3, 1, 2, -1, 2 ], [ -5, 3, 1, 2, -2, 2 ], [ -5, 3, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -6, 3, 1, 2, -1, 2 ], [ -5, 2, 1, 2, -1, 2 ], [ -5, 3, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ] + ], + [ + [ [ [ -6, 3, 1, 2, -1, 2 ], [ -5, 2, 1, 2, -1, 2 ], [ -6, 5, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0 ], + [ [ [ -6, 3, 1, 2, -1, 2 ], [ -6, 4, 1, 2, -1, 2 ], [ -6, 5, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0 ], + [ [ [ -7, 5, 1, 2, -1, 2 ], [ -6, 4, 1, 2, -1, 2 ], [ -6, 5, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ] + ], + [ + [ [ [ -6, 4, 0, 2, -1, 2 ], [ -6, 4, 1, 2, -1, 2 ], [ -6, 5, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -6, 4, 0, 2, -1, 2 ], [ -7, 6, 1, 2, -1, 2 ], [ -6, 5, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -6, 4, 0, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 1 ], [ -6, 5, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0 ], + [ [ [ -6, 5, 1, 1, -1, 2 ], [ -5, 4, 1, 2, -1, 1 ], [ -6, 5, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ] + ] + ] +], +"last_changes": +[ + [ [ -7, 5, 1, 2, -1, 2 ], [ -6, 4, 1, 2, -1, 2 ], [ -6, 5, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], + [ [ -6, 4, 0, 2, -1, 2 ], [ -6, 4, 1, 2, -1, 2 ], [ -6, 5, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], + [ [ -6, 4, 0, 2, -1, 2 ], [ -7, 6, 1, 2, -1, 2 ], [ -6, 5, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], + [ [ -6, 4, 0, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 1 ], [ -6, 5, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], + [ [ -6, 5, 1, 1, -1, 2 ], [ -5, 4, 1, 2, -1, 1 ], [ -6, 5, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ] +], +"cur_uid": "567a9375", +"ref_uid": "65120e88", +"order_seed": 837263, +"dur_seed": 805746, +"motifs_seed": 806271, +"entrances_probs_vals": [ 0.78, 0, 0, 0, 1.02, 0.0098039215686275, 0, 0.20915032679739, 0, 0.24183006535948, 0.82432432432432, 0.28758169934641, 0, 1, 0 ], +"passages_probs_vals": [ 0.78, 0, 0, 0, 1.02, 0.0098039215686275, 0, 0.20915032679739, 0, 0.24183006535948, 0.82432432432432, 0.28758169934641, 0, 1, 0 ], +"exits_probs_vals": [ 0.78, 0, 0, 0, 1.02, 0.0098039215686275, 0, 0.20915032679739, 0, 0.24183006535948, 0.82432432432432, 0.28758169934641, 0, 1, 0 ], +"ranges": [ [ -2400, 1200 ], [ -1200, 1453 ], [ -702, 1694 ], [ -702, 1973 ] ], +"step_probs_vals": [ -1200, 1200, 0, 0, 0.41358024691358, 0.0056818181818177, 0.46913580246914, 0.67045454545455, 0.52057613168724, 0, 0.56378600823045, 0.85795454545455, 0.60082304526749, 0, 1, 0 ], +"passages_weights": [ 1, 0.15, 0.43, 1, 1 ], +"hd_exp": 10, +"hd_invert": 0, +"order": +[ + [ [ 3 ], [ 1, 2, 0, 2, 2, 1, 2 ], [ ] ], + [ [ 0, 3 ], [ 1, 2, 1 ], [ ] ], + [ [ 3 ], [ 2, 1, 0 ], [ ] ], + [ [ 3, 2 ], [ 0, 1, 1, 0 ], [ ] ] +], +"sus_weights": [ 0.7, 0.48, 0.49 ], +"order_size": [ 14.132653061224, 14.132653061224 ], +"passages_size": [ 0, 5 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/567a9375/lilypond/part_I.ly b/resources/string_quartet_3_rise/567a9375/lilypond/part_I.ly new file mode 100644 index 0000000..bad9fe0 --- /dev/null +++ b/resources/string_quartet_3_rise/567a9375/lilypond/part_I.ly @@ -0,0 +1,4 @@ +{ + { f'1^\markup { \pad-markup #0.2 "-38"}} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/567a9375/lilypond/part_II.ly b/resources/string_quartet_3_rise/567a9375/lilypond/part_II.ly new file mode 100644 index 0000000..d716806 --- /dev/null +++ b/resources/string_quartet_3_rise/567a9375/lilypond/part_II.ly @@ -0,0 +1,4 @@ +{ + { cis'8^\markup { \pad-markup #0.2 "-25"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 7↓" }}[ b8^\markup { \pad-markup #0.2 "+10"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 11↓" }}] ais8^\markup { \pad-markup #0.2 "-40"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }}[ gis8^\markup { \pad-markup #0.2 "+48"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 5↑" }}] ais4^\markup { \pad-markup #0.2 "-40"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }} c'4^\markup { \pad-markup #0.2 "-37"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 3↑" }}} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/567a9375/lilypond/part_III.ly b/resources/string_quartet_3_rise/567a9375/lilypond/part_III.ly new file mode 100644 index 0000000..9ad3e08 --- /dev/null +++ b/resources/string_quartet_3_rise/567a9375/lilypond/part_III.ly @@ -0,0 +1,4 @@ +{ + { d4^\markup { \pad-markup #0.2 "+30"} f4^\markup { \pad-markup #0.2 "-38"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 1↑" }} e8^\markup { \pad-markup #0.2 "+8"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 11↓" }}[ dis8^\markup { \pad-markup #0.2 "-42"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↓" }}] f4^\markup { \pad-markup #0.2 "-38"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 1↑" }}} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/567a9375/lilypond/part_IV.ly b/resources/string_quartet_3_rise/567a9375/lilypond/part_IV.ly new file mode 100644 index 0000000..7580093 --- /dev/null +++ b/resources/string_quartet_3_rise/567a9375/lilypond/part_IV.ly @@ -0,0 +1,4 @@ +{ + { ais,2.^\markup { \pad-markup #0.2 "-40"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 3↓" }} c8^\markup { \pad-markup #0.2 "-37"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 1↑" }}[ cis8^\markup { \pad-markup #0.2 "-25"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 5↓" }}]} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/5ef20586/5ef20586_code.scd b/resources/string_quartet_3_rise/5ef20586/5ef20586_code.scd new file mode 100644 index 0000000..e1a9d92 --- /dev/null +++ b/resources/string_quartet_3_rise/5ef20586/5ef20586_code.scd @@ -0,0 +1,992 @@ +( +// helper funcs +var hsArrayToCents, pDist, hdSum, hsChordalDistance, hsArrayToFreq; + +// score funcs +var isInRange, spacingScore, rangeScore, intervalScore, inclusionScore; + +// subroutines +var genTuples, initVoices, genOrders, genSubMotif, updateVoices, genDurFunc, genStepFunc; + +// primary routines +var genMotif, genSecondarySeq; + +// audition funcs +var genPatterns, genMidiPatterns; + +// resource management funcs +var seedFunc, genUID, writeResources, stringifyToDepth, setSeeds, sanityCheck, +msgInterpret, loadLedgerFile, loadLedgerJSON, loadModelFile, loadModelJSON, +setGlobalVars, globalVarsToDict, saveLedger; + +// model vars +//(model and global vars mostly set by OSC funcs +var seq, lastXChanges, +curUID, refUID, orderSeed, durSeed, motifSeed, +entrancesProbVals, passagesProbVals, exitsProbVals, +ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, +orders, susWeights, orderSize, passagesSize, +motifEdited, orderEdited; + +// model aux vars +var entrancesDurFunc, passagesDurFunc, exitsDurFunc, stepFunc; + +// other global vars +var popSize, exPath, dir, primes, dims, tuples, +group, player, resourceDir, ledgerPath, ledger, currentlyPlayingUID, +nameSpaces; + +// install JSON quark (not used) +/* +if(Quarks.isInstalled("JSONlib").not, { + Quarks.install("https://github.com/musikinformatik/JSONlib.git"); + thisProcess.recompile; + //HelpBrowser.openHelpFor("Classes/JSONlib"); +}); +*/ + + +//------helper funcs + +hsArrayToCents = { + arg hsArray; + hsArray.collect({arg dist, p; dist * 1200 * log2(primes[p][0]/primes[p][1])}).sum +}; + +pDist = { + arg array1, array2, signed = false; + var pDistance; + pDistance = hsArrayToCents.value(array2) - hsArrayToCents.value(array1); + if(signed, {pDistance}, {abs(pDistance)}) +}; + +hdSum = { + arg hsArrays; + var size, distances, mean; + size = hsArrays.size; + distances = (size - 1).collect({arg i; + ((i + 1)..(size - 1)).collect({arg j; + abs(hsArrays[i] - hsArrays[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsChordalDistance = { + arg hsArrays1, hsArrays2; + var size, distances, mean; + size = hsArrays1.size; + distances = hsArrays1.size.collect({arg i; + hsArrays2.size.collect({arg j; + abs(hsArrays1[i] - hsArrays2[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsArrayToFreq = { + arg array; + array.collect({arg dim, d; pow(primes[d][0]/primes[d][1], dim)}).product +}; + +//------score funcs + +/* +isInRange = { + arg hsArray, min, max; + var cents; + cents = hsArrayToCents.value(hsArray); + (cents >= min) && (cents <= max) +}; +*/ + +spacingScore = { + arg hsArrays, min; + var centsArray; + centsArray = hsArrays.collect({arg hsArray; hsArrayToCents.value(hsArray)}).sort({arg a, b; a < b}); + centsArray.differentiate.drop(1).collect({arg pDistance; if(pDistance >= min, {1}, {0.01})}).sum; +}; + +rangeScore = { + arg hsArray1, hsArray2, min, max, low, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + if((pDistance >= min) && (pDistance <= max), {1}, {low}); +}; + +/* +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + stepFunc.value(pDistance) +}; +*/ + +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = true; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + //if(pDistance >= 0, {stepFunc.value(abs(pDistance))}, {0.01}); + stepFunc.value(pDistance) +}; + +inclusionScore = { + arg array, test, min = 0.01; + if(array.collect({arg v; v.hash}).includes(test.hash), {min}, {1}); +}; + + +//------subroutines + +genTuples = { + var tuples; + tuples = dims.collect({[-1, 0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum <= 1) && (tuple[0] == 0)}); + //tuples = dims.collect({[0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum == 1) && (tuple[0] == 0)}); + tuples = tuples ++ tuples.collect({arg tuple; [-3, -2, -1, 1, 2, 3].collect({arg octTrans; tuple.deepCopy.put(0, octTrans)})}).flatten; +}; + +initVoices = { + var init, voicesInit; + voicesInit = popSize.collect({dims.collect({0})}); + /* + voicesInit = [dims.collect({0})]; + (popSize - 1).do({ + arg rep, new; + rep = dims.rand; + new = voicesInit.last.deepCopy; + new[rep] = new[rep] + [-1, 1].choose(); + voicesInit = voicesInit.add(new); + }); + */ + voicesInit.deepCopy; +}; + +genDurFunc = {arg chordProb, minPad, maxPad, minDur, maxDur, envData, seed; + var env, pTable, durFunc; + env = Env.pairs([[0, 0]] ++ envData.clump(2) ++ [[1, 0]]).asSignal(256).asList.asArray; + pTable = env.asRandomTable; + //[chordProb, minPad, maxPad, minDur, maxDur, envData].postln; + durFunc = {arg allowChord, pad = false; + var res; + res = if(allowChord.not, { + pTable.tableRand * (maxDur - minDur) + minDur + }, { + if(1.0.rand < chordProb, {0}, {pTable.tableRand * (maxDur - minDur) + minDur}); + }).round(0.125); + if(pad, {res = res + rrand(minPad.asFloat, maxPad.asFloat).round(0.125)}); + if(res.asInteger == res, {res = res.asInteger}); + res + }; + seedFunc.value(durFunc, seed); +}; + +genStepFunc = {arg minStep, maxStep, envData, seed; + var envDataNorm, env, pTable, stepFunc; + //[minStep, maxStep, envData].postln; + envDataNorm = ([[0, 0]] ++ envData.clump(2) ++ [[1, 0]]).flop; + envDataNorm = [envDataNorm[0].normalize(minStep, maxStep), envDataNorm[1]].flop; + env = Env.pairs(envDataNorm); + stepFunc = {arg pDist; + env.at(pDist).clip(0.001, 1); + }; + seedFunc.value(stepFunc, seed); +}; + +genOrders = {arg minMotifLength = 1, maxMotifLength = 5, minProgLength = 0, maxProgLength = 5; + ((maxMotifLength.asInteger - minMotifLength.asInteger).rand + minMotifLength.asInteger).collect({ + var noProgIns, noSusIns, noSilentIns, prog, sus, silent, order; + noSusIns = [1, 2, 3].wchoose(susWeights.normalizeSum); + + //noProgIns = (popSize - noSusIns).rand + 1; + noProgIns = (popSize - noSusIns); + noSilentIns = popSize - noSusIns - noProgIns; + + /* + noSilentIns = (popSize - noSusIns).rand.clip(0, 1); + noProgIns = popSize - noSusIns - noSilentIns; + */ + + # prog, sus, silent = (0..(popSize-1)).scramble.clumps([noProgIns, noSusIns, noSilentIns]); + + prog = (prog.scramble ++ ((maxProgLength.asInteger - minProgLength.asInteger).rand + minProgLength.asInteger).collect({prog.choose}).scramble); + //prog = ((maxProgLength.asInteger - minProgLength.asInteger).rand + minProgLength.asInteger).collect({prog.choose}); + + if(silent == nil, {silent = []}); + [sus.scramble, prog, silent.scramble] + }); +}; + +updateVoices = {arg ins, sus; + var voices, candidates, nWeights, nProbs, sel; + + voices = lastXChanges.deepCopy.last; + + candidates = sus.collect({arg v; tuples.collect({arg t; voices[v] + t})}).flatten; + candidates = difference(candidates.asSet, voices.asSet).asList; + //candidates.select({arg item; (item ++ voices).asSet.size >= 4}); + nProbs = candidates.collect({arg candidate; + var stepScore, recentlySoundedScore, isInRangeScore, regScore, hdScore; + + //stepScore = intervalScore.value(voices[ins], candidate, 30, 400, 0.1); + stepScore = intervalScore.value(voices[ins], candidate, 100, 100); + recentlySoundedScore = inclusionScore.value(lastXChanges.flop[ins], candidate, 0.01); + //recentlySoundedScore = inclusionScore.value(lastXChanges.flatten.collect({arg item; item.drop(1)}), candidate.drop(1), 0.01); + + /* + if(rangeScore.value(candidate.collect({0}), voices[ins], ranges[ins][1] - 500, ranges[ins][1], 0, true) == 1, { + isInRangeScore = 1 - rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0] + 500, ranges[ins][1] - 500, 0, true); + isInRangeScore = isInRangeScore * rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true) + }, { + isInRangeScore = rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true); + }); + */ + + + isInRangeScore = rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true); + //old way - worked but actually by accident (I think) + //isInRangeScore = rangeScore.value(candidate, candidate.collect({0}), ranges[ins][0], ranges[ins][1], 0, true); + + + regScore = spacingScore.value(voices.deepCopy.put(ins, candidate), 300); + hdScore = pow(hdSum.value(voices.deepCopy.put(ins, candidate)), hdExp); + if(hdInvert == 0, {hdScore = 1/hdScore}); + //maybe what you want here is a vector to another root and then favoring movement towards it. + //distScore = pow(hsChordalDistance.value(voices, voices.put(ins, candidate)), 2); + + [stepScore, recentlySoundedScore, isInRangeScore, regScore, hdScore] + }); + + nWeights = passagesWeights; + + //this handles nWeights of 0; mainly for testing + nProbs = nProbs.flop.select({arg scores, s; nWeights[s] != 0}).flop; + nWeights = nWeights.select({arg weight; weight != 0}); + nProbs = nProbs.flop.collect({arg scores, s; + if(scores.sum == 0, {scores}, {scores.normalizeSum * nWeights[s]}).postln; + }); + nProbs = nProbs.flop.collect({arg scores, s; scores.product}).normalizeSum; + + nProbs.round(0.001).postln; + [candidates, nProbs.round(0.001)].flop.postln; + + sel = candidates.wchoose(nProbs); + + voices[ins] = sel; + lastXChanges = lastXChanges.add(voices).keep(-5); +}; + +genSubMotif = {arg order, orderIndex, lastState, repeatLast = false, startFromLast = false, isLastOrder = false; + var sus, prog, silent, flatOrder, res, isInChord, allowChord, pad, lastXChangesHold, voices, adder; + # sus, prog, silent = order; + flatOrder = silent ++ sus ++ prog; + lastXChangesHold = lastXChanges.deepCopy; + voices = lastState.deepCopy; + isInChord = popSize.collect({false}); + allowChord = false; + pad = false; + res = []; + "------generating motif".postln; + //need to figure out here if voices move between motifs + flatOrder.do({arg ins, i; + + if(prog.includes(ins) && repeatLast.not, {updateVoices.value(ins, sus)}); + adder = if(silent.includes(ins), {["Rest"]}, {lastXChanges.last.deepCopy[ins]}); + + if(voices[ins] != adder, { + var dur; + + if((sus ++ silent).includes(ins), { + allowChord = (ins != sus.last); + pad = (ins == sus.last); + }, { + if(i < (flatOrder.size - 1), { + allowChord = (isInChord[flatOrder[i + 1]] || (ins == flatOrder[i + 1])).not; + pad = false; + }, { + allowChord = false; + pad = true + }); + }); + if((orderIndex == 0) && sus.includes(ins), { + dur = entrancesDurFunc.value(allowChord, pad); + }, { + dur = passagesDurFunc.value(allowChord, pad); + }); + if(dur == 0, {isInChord[ins] = true}, {isInChord = popSize.collect({false})}); + + voices[ins] = adder; + res = res.add([voices.deepCopy.postln, dur]); + }); + }); + + // pad ending + /* + if(orderIndex == (orders.size - 1), { + (0..(popSize-1)).scramble.do({arg ins; + if(res.last.first[ins] != ["Rest"], { + var dur; + voices[ins] = ["Rest"]; + allowChord = (voices != popSize.collect({["Rest"]})); + pad = allowChord.not; + dur = exitsDurFunc.value(allowChord, pad); + res = res.add([voices.deepCopy.postln, dur]); + }); + }); + }); + */ + + //format and return + if(startFromLast, {lastXChanges = lastXChangesHold.deepCopy}); + res; +}; + + +//------primary routines + +genMotif = { + var repeats, fSeq, fDur, durAdd; + + repeats = 1; + fSeq = []; + + repeats.do({arg index; + var motif; + + motif = []; + + orders.do({arg order, o; + var lastState, subMotif; + //lastState = if(o == 0, {popSize.collect({["Rest"]})}, {motif.last.last.first}); + lastState = if(o == 0, {lastXChanges.last.deepCopy}, {motif.last.last.first}); + subMotif = genSubMotif.value(order, o, lastState, isLastOrder: o == (orders.size - 1)); + motif = motif.add(subMotif); + + }); + + sanityCheck.value(motif, index); + + fSeq = fSeq.add(motif); + }); + + //round last duration to measure + /* + fDur = fSeq.flatten.flatten.slice(nil, 1).sum; + durAdd = fDur.round(4) - fDur; + if(durAdd < 0, {durAdd = 4 - durAdd}); + fSeq[0][orders.size - 1][fSeq[0][orders.size - 1].size - 1][1] = fSeq[0][orders.size - 1][fSeq[0][orders.size - 1].size - 1][1] + durAdd; + */ + + fSeq +}; + +genSecondarySeq = {arg seq; + var curdles, fSeq; + curdles = []; + while({curdles.sum < seq.size}, {curdles = curdles ++ [3.rand + 1]}); + + fSeq = seq.clumps(curdles).collect({arg clump, m; + var repeats, paddedSeq; + + //add rest + paddedSeq = clump.add([[[popSize.collect({["Rest"]}), 0.5.rand]]]); + + //implement repeats + repeats = [0.rand + 1, 1].wchoose([1, 0].normalizeSum); + repeats.collect({paddedSeq}); + }); + fSeq +}; + + +//------audition funcs + +/* +Event.addEventType(\osc, { + if (~addr.postln.notNil) { + ~addr.sendMsg(~indexPath, ~indexMsg); + ~addr.sendMsg(~seqPath, stringifyToDepth.value(~seqMsg, 3)); + //~addr.sendMsg("/STATE/OPEN", (dir.replace("supercollider", "resources") +/+ ~idMsg +/+ ~idMsg ++ "_gui_state" ++ ".state").standardizePath.postln); + }; +}); +*/ + +Event.addEventType(\osc, { + if (~addr.notNil) { + ~msg; + ~addr.sendMsg(~path, *~msg); + }; +}); + +genPatterns = {arg inSeq, addr, oneShot = false; + var voices, durs, pbinds, res, indices, sectionDurs, msg, ids, seq; + seq = inSeq.collect({arg mSeq; mSeq[0]}); + //# voices, durs = seq.flatten2(seq.maxDepth - 5).flop; + # voices, durs = seq.flatten2(if(oneShot, {2}, {3})).flop; + pbinds = voices.flop.collect({arg voice, v; + var clumps, hdScores, freqs, fDurs, attacks, rels, amps; + clumps = voice.separate({arg a, b; a != b }); + freqs = clumps.collect({arg clump; if(clump[0] != ["Rest"], {(62.midicps * hsArrayToFreq.value(clump[0]))}, {Rest(0)})}); + fDurs = durs.clumps(clumps.collect({arg clump; clump.size})).collect({arg clump; clump.sum}); + //attacks = 2.collect({rrand(1, 3)}) ++ freqs.drop(2).collect({rrand(0.3, 0.5)}); + attacks = fDurs.collect({arg dur; dur * rrand(0.2, 0.4)}); + //rels = freqs.drop(2).collect({rrand(0.3, 0.5)}) ++ 2.collect({rrand(1, 3)}); + rels = (clumps.size - 1).collect({arg c; + if(clumps[c + 1][0] == ["Rest"], {rrand(1.0, 3.0)}, {rrand(0.3, 0.5)}); + }); + rels = rels.add(rrand(1.0, 3.0)); + amps = freqs.collect({rrand(0.6, 0.99)}); + + [ + Pbind( + \instrument, \string_model, + \group, group, + \freq, Pseq(freqs, 1), + \dur, Pseq(fDurs, 1), + \attack, Pseq(attacks, 1), + \sustain, Pseq(fDurs, 1), + \release, Pseq(rels, 1), + //\amp, Pseq(amps, 1), + \amp, Pbrown(0.5, 1, 0.5), + \busIndex, v + ), + Pbind( + \instrument, \sine, + \group, group, + \freq, Pseq(freqs, 1), + \dur, Pseq(fDurs, 1), + \sustain, Pseq(fDurs, 1), + \busIndex, v + ) + ] + }).flatten; + if(oneShot.not, { + msg = inSeq.collect({arg mSeq, m; mSeq[1..]}); + //ids = inSeq.collect({arg mSeq, m; mSeq[2]}); + sectionDurs = seq.collect({arg mSeq; mSeq.flatten2(2).flop[1].sum}); + pbinds = pbinds ++ + [ + Pbind( + \type, \osc, + \addr, addr, + \path, "/playing", + \msg, Pseq(msg, 1), + \dur, Pseq(sectionDurs, 1) + ); + ] + }); + res = Ppar(pbinds); + res +}; + +/* +genMidiPatterns = {arg seq; + var voices, durs, patterns, res, mOut, pbRange; + pbRange = 1; //semitones - change this as needed for your situation + mOut = MIDIOut.newByName("TiMidity", "TiMidity port 0").latency_(Server.default.latency); + # voices, durs = seq.flatten2(seq.maxDepth - 5).flop; + res = Ppar( + voices.flop.collect({arg voice, v; + var clumps, hdScores, freqs, fDurs; + + mOut.program(v, 70); + + clumps = voice.separate({arg a, b; a != b }); + freqs = clumps.collect({arg clump; if(clump[0] != ["Rest"], {(60.midicps * hsArrayToFreq.value(clump[0]))}, {Rest(0)})}); + fDurs = durs.clumps(clumps.collect({arg clump; clump.size})).collect({arg clump; clump.sum}); + + Pbind( + \type, \midi, + \chan, v, + \noteval, Pseq(freqs.cpsmidi - 24, 1), + \note, Pfunc({ | event | event[\noteval].floor }), + \dur, Pseq(fDurs, 1), + \midiout, mOut, + \amp, 1, + \bend, Pfunc({ + | event | + if (event[\note].isRest.not) { + var pitchbendvalue = event[\noteval].frac.linlin(0, pbRange, 8192, 8192*2).asInteger; + m.bend(v, pitchbendvalue); + }; + 0; // return something other than nil to avoid stopping the pattern + }), + ); + }); + ); + res +}; +*/ + + +//------resource management funcs + +genUID = {Date.seed.asHexString.toLower}; + +seedFunc = {arg func, seed; + var funcArgs, next; + next = Routine({loop{func.valueArray(funcArgs).yield }}); + next.randSeed_(seed); + {arg ...args; funcArgs = args; next.value} +}; + +stringifyToDepth = {arg data, maxDepth = 1; + var prettyString = "", rCount = 0, writeArray, indent; + + if(maxDepth == 0, { + data.asCompileString + }, { + indent = {arg size; size.collect({" "}).join("")}; + writeArray = {arg array; + prettyString = prettyString ++ indent.value(rCount) ++ "[\n"; + rCount = rCount + 1; + if(rCount < maxDepth, { + array.do({arg subArray; writeArray.value(subArray)}); + }, { + prettyString = prettyString ++ array.collect({arg subArray; + indent.value(rCount + 1) ++ subArray.asCompileString + }).join(",\n"); + }); + rCount = rCount - 1; + prettyString = prettyString ++ "\n" ++ indent.value(rCount) ++ "],\n"; + }; + + writeArray.value(data); + prettyString.replace(",\n\n", "\n").drop(-2); + }) +}; + +sanityCheck = {arg motif, index; + //print functions = very helpful + ("----------" + index + "------------").postln; + + motif.flatten.do({arg val, v; + if(v > 0, { + if(motif.flatten[v-1][0].hammingDistance(val[0]) > 1, {"problem 1".postln}); + if(motif.flatten[v-1][0].hammingDistance(val[0]) == 0, {"problem 2".postln}); + }); + val.postln + }); + "***********".postln; +}; + +msgInterpret = {arg in, escapeDoubleQuotes = true, escapeSingleQuotes = true; + var res; + + res = in; + if(res.isNil.not, { + if((res.isArray && res.isString.not), { + res = res.asCompileString; + res = res.replace(" ", "").replace("\n", "").replace("\t", ""); + if(escapeSingleQuotes, {res = res.replace("\'", "")}); + if(escapeDoubleQuotes, {res = res.replace("\"", "")}); + res = res.replace("Rest", "\"Rest\""); + res = res.interpret; + }, { + var tmpRes; + if(res.every({arg char; char.isDecDigit}), {tmpRes = res.asInteger}); + if(res.contains("."), {tmpRes = res.asFloat}); + if(tmpRes != nil, {res = tmpRes}); + }); + }); + res +}; + +writeResources = {arg path, dict; + var file, modelItems, resString; + file = File(path,"w"); + + modelItems = [ + seq, lastXChanges, + curUID, refUID, orderSeed, durSeed, motifSeed, + entrancesProbVals, passagesProbVals, exitsProbVals, + ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, + orders, susWeights, orderSize, passagesSize, + motifEdited, orderEdited + ]; + + resString = nameSpaces.collect({arg nameSpace; + var depth = 0, insert = " "; + if(nameSpace == "music_data", {depth = 3; insert = "\n"}); + if(nameSpace == "last_changes", {depth = 1; insert = "\n"}); + if(nameSpace == "order", {depth = 1; insert = "\n"}); + if((nameSpace == "ref_uid") && (dict[nameSpace] == nil), {dict[nameSpace] = "nil"}); + "\"" ++ nameSpace ++ "\":" ++ insert ++ stringifyToDepth.value(dict[nameSpace], depth) + }).join(",\n"); + + resString = "{\n" ++ resString ++ "\n}"; + + file.write(resString); + file.close; + resString +}; + +loadModelFile = {arg path; loadModelJSON.value(File(path, "r").readAllString.parseJSON)}; + +loadModelJSON = {arg jsonObject; + var dict; + dict = Dictionary.with(*nameSpaces.collect({arg nS; nS->msgInterpret.value(jsonObject[nS])})); + dict +}; + +setGlobalVars = {arg dict, skipLastXChanges = false; + var tmpLastXChanges; + tmpLastXChanges = lastXChanges.deepCopy; + // order really matters!!!! + # seq, lastXChanges, curUID, refUID, orderSeed, durSeed, motifSeed, + entrancesProbVals, passagesProbVals, exitsProbVals, + ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, + orders, susWeights, orderSize, passagesSize, + motifEdited, orderEdited = nameSpaces.collect({arg nS; dict[nS]}); + if(skipLastXChanges, {lastXChanges = tmpLastXChanges}); + dict +}; + +globalVarsToDict = { + var modelItems, dict; + // order really matters!!!! + modelItems = [ + seq, lastXChanges, + curUID, refUID, orderSeed, durSeed, motifSeed, + entrancesProbVals, passagesProbVals, exitsProbVals, + ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, + orders, susWeights, orderSize, passagesSize, + motifEdited, orderEdited + ]; + dict = Dictionary.with(*nameSpaces.collect({arg nS, n; nS->modelItems[n]})); +}; + +loadLedgerFile = {arg path; + ledgerPath = path; + resourceDir = path.splitext(".").drop(-1).join; + loadLedgerJSON.value(File(ledgerPath, "r").readAllString.parseJSON) +}; + +loadLedgerJSON = {arg jsonObject; ledger = jsonObject["ledger"]}; + +saveLedger = {arg ledger, path; + var file, curResourceDir; + file = File(path, "w"); + curResourceDir = resourceDir; + resourceDir = path.splitext(".").drop(-1).join; + if(curResourceDir != resourceDir, { + File.mkdir(resourceDir); + ledger.do({arg id; + File.copy(curResourceDir +/+ id, resourceDir +/+ id); + }); + }); + file.write("{\n\"ledger\":\n" ++ stringifyToDepth.value(ledger, 1) ++ "\n}"); + file.close; +}; + +//------global vars + +primes = [[2, 1], [3, 2], [5, 4], [7, 4], [11, 8], [13, 8]]; +//ranges = [[-2400, 0], [-1200, 1200], [0, 2400], [0, 2400]]; +exPath = thisProcess.nowExecutingPath; +dir = exPath.dirname; +//popSize = 4; +dims = primes.size; +tuples = genTuples.value(); +//refUID = nil; +group = Group.new; +~group = group; +loadLedgerFile.value(dir +/+ ".." +/+ "resources" +/+ "piece_ledger.json"); +resourceDir = (dir +/+ ".." +/+ "resources" +/+ "piece_ledger"); +//passagesWeights = [1, 1, 1, 1, 1]; +//susWeights = [1, 1, 1]; +// order really matters!!!! +nameSpaces = [ + "music_data", "last_changes", "cur_uid", "ref_uid", "order_seed", "dur_seed", "motifs_seed", + "entrances_probs_vals","passages_probs_vals", "exits_probs_vals", + "ranges", "step_probs_vals", "passages_weights", "hd_exp", "hd_invert", + "order", "sus_weights", "order_size", "passages_size", + "motif_edited", "order_edited" +]; + + +//------OSC funcs + +OSCdef(\load_ledger, {arg msg, time, addr, port; + loadLedgerFile.value(msg[1].asString); +}, \load_ledger); + +OSCdef(\load_model, {arg msg, time, addr, port; + var dict; + dict = loadModelFile.value(msg[1].asString); + setGlobalVars.value(dict); +}, \load_model); + +OSCdef(\save_ledger, {arg msg, time, addr, port; + msg.postln; + ledger = msgInterpret.value(msg[1].asString.parseJSON["ledger"], false).postln; + //loadLedgerJSON.value(msg[0]) + saveLedger.value(ledger, msg[2].asString); + //loadLedgerFile.value(msg[1].asString); +}, \save_ledger); + +OSCdef(\generate, {arg msg, time, addr, port; + var path, dict, durSeeds, musPath, modelString; + msg.postln; + + path = msg[1].asString; + + dict = loadModelFile.value(path); + setGlobalVars.value(dict, true); + + popSize = ranges.size; + + //refUID.postln; + + loadLedgerFile.value(ledgerPath); + if(ledger == nil, {ledger = ["tmp"]}); + if(ledger.last != "tmp", {ledger = ledger.add("tmp")}); + + if(refUID == nil, {lastXChanges = [initVoices.value().deepCopy]}); + if((refUID != nil) && (refUID != "tmp"), { + var file; + file = File((resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath, "r"); + lastXChanges = msgInterpret.value(file.readAllString.parseJSON["last_changes"]); + }); + + refUID.postln; + lastXChanges.collect({arg item; item.postln}); + + durSeeds = seedFunc.value({3.collect({rrand(100000, 999999)})}, durSeed).value.postln; + entrancesDurFunc = genDurFunc.valueArray(entrancesProbVals[..4] ++ [entrancesProbVals[5..]] ++ [durSeeds[0]]); + passagesDurFunc = genDurFunc.valueArray(passagesProbVals[..4] ++ [passagesProbVals[5..]] ++ [durSeeds[1]]); + exitsDurFunc = genDurFunc.valueArray(exitsProbVals[..4] ++ [exitsProbVals[5..]] ++ [durSeeds[2]]); + + if(orders == nil, { + orders = seedFunc.value(genOrders, orderSeed).valueArray(orderSize ++ passagesSize); + //addr.sendMsg("/order", stringifyToDepth.value(orders, 1)); + }); + + stepFunc = genStepFunc.valueArray(stepProbsVals[..1] ++ [stepProbsVals[2..]] ++ [motifSeed]); + seq = seedFunc.value(genMotif, motifSeed).value; + + lastXChanges.collect({arg item; item.postln}); + + dict = globalVarsToDict.value; + modelString = writeResources.value(path, dict); + + //addr.sendMsg("/generated", musPath, stringifyToDepth.value(seq, 3)); + //~seq = seq; + + addr.sendMsg("/generated", path, modelString, ledgerPath); +}, \generate); + + +OSCdef(\commit, {arg msg, time, addr, port; + var musicData, musicChanged, dict, newLedger, modelPath, musString, musFile, test1, test2, lastCurUID, commitType, commitPos, equalityLedger; + //msg.postln; + + /* + test1 = msg[1].asString.parseJSON; + test2 = (dir +/+ ".." +/+ "resources/tmp/tmp_music" ++ ".json").standardizePath.parseJSONFile; + msgInterpret.value(test1["music"])[0][0][0][1].class.postln; + msgInterpret.value(test2["music_data"])[0][0][0][1].class.postln; + (test1["music"] == test2["music_data"]).postln; + */ + + musicData = loadModelJSON.value(msg[1].asString.parseJSON)["music_data"].postln; + musicChanged = (musicData != seq).postln; + commitType = msg[2].asString; + commitPos = msg[3].postln.asInteger; + + lastCurUID = curUID.deepCopy; + curUID = genUID.value; + + File.mkdir((resourceDir +/+ curUID).standardizePath); + File.copy(exPath, (resourceDir +/+ curUID +/+ curUID ++ "_code" ++ ".scd").standardizePath); + + modelPath = (resourceDir +/+ curUID +/+ curUID ++ "_mus_model" ++ ".json").standardizePath; + dict = globalVarsToDict.value; + if(musicChanged, { + seq = musicData; + dict["music_data"] = seq; + dict["motif_edited"] = "true" + }); + dict["cur_uid"] = curUID; + + writeResources.value(modelPath, dict); + + File.delete(ledgerPath ++ "_bak"); + File.copy(ledgerPath, ledgerPath ++ "_bak"); + File.delete(ledgerPath); + + /* + if(commitType == "add", { + if(lastCurUID == "tmp", { + ledger = ledger.drop(-1).add(curUID); + }, { + ledger = ledger.add(curUID); + }) + }); + */ + + ledger.postln; + + if(commitType == "add", {ledger = ledger.add(curUID)}); + + if(commitType == "insert", {ledger = ledger.insert(commitPos + 1, curUID)}); + + if(commitType == "replace", {ledger = ledger.put(commitPos, curUID)}); + + equalityLedger = ledger.collect({arg item; item.asSymbol}); + if(equalityLedger.includes(\tmp).postln, {ledger.removeAt(equalityLedger.indexOf(\tmp).postln)}); + + ledger.postln; + + saveLedger.value(ledger, ledgerPath); + + addr.sendMsg("/committed", curUID, ledgerPath); + //refUID = curUID; + +}, \commit); + +OSCdef(\transport, {arg msg, time, addr, port; + msg.postln; + if(msg[1] == 0, { + group.set(\release, 2); + group.set(\gate, 0); + player.stop; + }, { + // the cued sequence can now be read from file, so this can be cleaned up + var cSize, patterns, pSeq, cuedSeek, indexStart, indexEnd, tmpLedger; + if(msg[1] == 1, { + pSeq = []; + cuedSeek = (seq != nil); + indexStart = msg[2].asInteger; + indexEnd = ledger.size - if(cuedSeek, {2}, {1}); + //ledger.postln; + if(((indexStart == (ledger.size - 1)) && cuedSeek).not, { + ledger[indexStart..indexEnd].do({arg uid, index; + var path, file; + path = (resourceDir +/+ uid +/+ uid ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.postln.parseJSON["music_data"]), path, indexStart + index, uid]); + file.close; + }); + }); + if(cuedSeek, { + var path, file; + path = (resourceDir +/+ "tmp/tmp_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.parseJSON["music_data"]), path, ledger.size - 1, "tmp"]); + file.close; + }); + patterns = genPatterns.value(pSeq, addr); + }, { + pSeq = [loadModelJSON.value(msg[2].asString.parseJSON)["music_data"].postln]; + patterns = genPatterns.value(pSeq, addr, true); + }); + player = Pfset(pattern: patterns, cleanupFunc: { + addr.sendMsg("/transport", 0); + addr.sendMsg("/one_shot", 0); + }); + player = player.play + }); +}, \transport); + + +OSCdef(\transcribe_motif, {arg msg, time, addr, port; + var tSeq, refChord, refUID; + + msg.postln; + + tSeq = [loadModelJSON.value(msg[1].asString.parseJSON)["music_data"]]; + refUID = msg[2].asString.postln; + + if((refUID != "nil") && (refUID != "tmp"), { + var file; + file = File((resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath, "r"); + refChord = msgInterpret.value(file.readAllString.parseJSON["last_changes"]).last; + file.close; + }, { + refChord = [[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0]]; + }); + + ~transcribe.value(tSeq, refChord, (dir +/+ ".." +/+ "lilypond" +/+ "includes").standardizePath, addr, "/transcribe_motif"); +}, \transcribe_motif); + + +OSCdef(\transcribe_all, {arg msg, time, addr, port; + var cSize, patterns, cuedSeek, indexStart, indexEnd, tmpLedger; + if(true, { + cuedSeek = (seq != nil); + indexStart = msg[1].asInteger; + indexEnd = ledger.size - if(cuedSeek, {2}, {1}); + + //tmp for testing transcription + //indexEnd = (indexStart+5); + + //ledger.postln; + if(((indexStart == (ledger.size - 1)) && cuedSeek).not, { + var lilyPartLedgerFiles; + + lilyPartLedgerFiles = 4.collect({arg p; + File((dir +/+ ".." +/+ "lilypond" +/+ "includes" +/+ "part_" ++ ["IV", "III", "II", "I"][p] ++ ".ly").standardizePath, "w"); + }); + + ledger[indexStart..indexEnd].do({arg uid, index; + var path, file, fileString, tSeq, refUID, refChord; + path = (resourceDir +/+ uid +/+ uid ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + fileString = file.readAllString; + tSeq = msgInterpret.value(fileString.parseJSON["music_data"]); + refUID = msgInterpret.value(fileString.parseJSON["ref_uid"]); + file.close; + + //uid.postln; + //(refUID == "nil").postln; + + refChord = [[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0]]; + + if(refUID != "nil", { + path = (resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + refChord = msgInterpret.value(file.readAllString.parseJSON["last_changes"]).last; + file.close; + }); + + if(index != indexEnd, { + ~transcribe.value(tSeq, refChord, (resourceDir +/+ uid +/+ "lilypond").standardizePath); + }, { + ~transcribe.value(tSeq, refChord, (resourceDir +/+ uid +/+ "lilypond").standardizePath, addr, "/transcribe_all"); + }); + + lilyPartLedgerFiles.do({arg f, p; + f.write("\\include \"" ++ resourceDir +/+ uid +/+ "lilypond" +/+ "part_" ++ ["IV", "III", "II", "I"][p] ++ ".ly\"\n"); + }); + + }); + + lilyPartLedgerFiles.do({arg f; + f.close + }); + }); + /* + if(cuedSeek, { + var path, file; + path = (dir +/+ ".." +/+ "resources/tmp/tmp_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.parseJSON["music_data"]), path, ledger.size - 1, "tmp"]); + file.close; + }); + */ + }, { + + }); + +}, \transcribe_all); + +) + + diff --git a/resources/string_quartet_3_rise/5ef20586/5ef20586_mus_model.json b/resources/string_quartet_3_rise/5ef20586/5ef20586_mus_model.json new file mode 100644 index 0000000..bee7f3b --- /dev/null +++ b/resources/string_quartet_3_rise/5ef20586/5ef20586_mus_model.json @@ -0,0 +1,65 @@ +{ +"music_data": +[ + [ + [ + [ [ [ -6, 4, 1, 2, -1, 2 ], [ -5, 4, 0, 2, -1, 2 ], [ -4, 4, 0, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0 ], + [ [ [ -6, 4, 1, 2, -1, 2 ], [ -5, 4, 0, 2, -1, 2 ], [ -5, 4, 1, 3, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0 ], + [ [ [ -4, 3, 1, 2, -1, 2 ], [ -5, 4, 0, 2, -1, 2 ], [ -5, 4, 1, 3, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -4, 3, 1, 2, -1, 2 ], [ -5, 4, 0, 2, -1, 2 ], [ -4, 4, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -4, 3, 1, 2, -1, 2 ], [ -5, 4, 0, 2, -1, 2 ], [ -3, 4, 1, 1, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0 ], + [ [ [ -4, 3, 1, 2, -1, 2 ], [ -6, 4, 1, 3, -1, 2 ], [ -3, 4, 1, 1, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -4, 3, 1, 2, -1, 2 ], [ -6, 4, 1, 3, -1, 2 ], [ -5, 3, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ] + ], + [ + [ [ [ -4, 3, 1, 2, -1, 2 ], [ -5, 3, 2, 2, -1, 2 ], [ -5, 3, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0 ], + [ [ [ -4, 3, 1, 2, -1, 2 ], [ -5, 3, 2, 2, -1, 2 ], [ -6, 4, 2, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -4, 3, 1, 2, -1, 2 ], [ -5, 3, 1, 2, -1, 2 ], [ -6, 4, 2, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ] + ], + [ + [ [ [ -4, 3, 1, 2, -1, 2 ], [ -5, 3, 1, 2, -1, 2 ], [ -4, 4, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -4, 3, 1, 2, -1, 2 ], [ -6, 4, 2, 2, -1, 2 ], [ -4, 4, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0 ], + [ [ [ -5, 4, 2, 2, -1, 2 ], [ -6, 4, 2, 2, -1, 2 ], [ -4, 4, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ] + ], + [ + [ [ [ -5, 4, 1, 2, 0, 2 ], [ -6, 4, 2, 2, -1, 2 ], [ -4, 4, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0 ], + [ [ [ -5, 4, 1, 2, 0, 2 ], [ -6, 4, 1, 2, 0, 2 ], [ -4, 4, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -5, 4, 1, 2, 0, 2 ], [ -6, 5, 1, 2, -1, 2 ], [ -4, 4, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0 ], + [ [ [ -5, 5, 1, 2, -1, 2 ], [ -6, 5, 1, 2, -1, 2 ], [ -4, 4, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ] + ] + ] +], +"last_changes": +[ + [ [ -5, 4, 2, 2, -1, 2 ], [ -6, 4, 2, 2, -1, 2 ], [ -4, 4, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], + [ [ -5, 4, 1, 2, 0, 2 ], [ -6, 4, 2, 2, -1, 2 ], [ -4, 4, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], + [ [ -5, 4, 1, 2, 0, 2 ], [ -6, 4, 1, 2, 0, 2 ], [ -4, 4, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], + [ [ -5, 4, 1, 2, 0, 2 ], [ -6, 5, 1, 2, -1, 2 ], [ -4, 4, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], + [ [ -5, 5, 1, 2, -1, 2 ], [ -6, 5, 1, 2, -1, 2 ], [ -4, 4, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ] +], +"cur_uid": "5ef20586", +"ref_uid": "5f0075ab", +"order_seed": 837263, +"dur_seed": 148789, +"motifs_seed": 771884, +"entrances_probs_vals": [ 0.78, 0, 0, 0, 1.02, 0.0098039215686275, 0, 0.20915032679739, 0, 0.24183006535948, 0.82432432432432, 0.28758169934641, 0, 1, 0 ], +"passages_probs_vals": [ 0.78, 0, 0, 0, 1.02, 0.0098039215686275, 0, 0.20915032679739, 0, 0.24183006535948, 0.82432432432432, 0.28758169934641, 0, 1, 0 ], +"exits_probs_vals": [ 0.78, 0, 0, 0, 1.02, 0.0098039215686275, 0, 0.20915032679739, 0, 0.24183006535948, 0.82432432432432, 0.28758169934641, 0, 1, 0 ], +"ranges": [ [ -2400, 1200 ], [ -1200, 1453 ], [ -702, 1694 ], [ -702, 1973 ] ], +"step_probs_vals": [ -1200, 1200, 0, 0, 0.41358024691358, 0.0056818181818177, 0.45884773662551, 0.5, 0.52057613168724, 0, 0.56378600823045, 0.85795454545455, 0.60082304526749, 0, 1, 0 ], +"passages_weights": [ 1, 0.15, 0.43, 1, 1 ], +"hd_exp": 10, +"hd_invert": 0, +"order": +[ + [ [ 3 ], [ 1, 2, 0, 2, 2, 1, 2 ], [ ] ], + [ [ 0, 3 ], [ 1, 2, 1 ], [ ] ], + [ [ 3 ], [ 2, 1, 0 ], [ ] ], + [ [ 3, 2 ], [ 0, 1, 1, 0 ], [ ] ] +], +"sus_weights": [ 0.7, 0.48, 0.49 ], +"order_size": [ 14.132653061224, 14.132653061224 ], +"passages_size": [ 0, 5 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/5ef20586/lilypond/part_I.ly b/resources/string_quartet_3_rise/5ef20586/lilypond/part_I.ly new file mode 100644 index 0000000..bad9fe0 --- /dev/null +++ b/resources/string_quartet_3_rise/5ef20586/lilypond/part_I.ly @@ -0,0 +1,4 @@ +{ + { f'1^\markup { \pad-markup #0.2 "-38"}} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/5ef20586/lilypond/part_II.ly b/resources/string_quartet_3_rise/5ef20586/lilypond/part_II.ly new file mode 100644 index 0000000..16f25e1 --- /dev/null +++ b/resources/string_quartet_3_rise/5ef20586/lilypond/part_II.ly @@ -0,0 +1,4 @@ +{ + { d''8^\markup { \pad-markup #0.2 "+30"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 7↑" }}[ f''8^\markup { \pad-markup #0.2 "-38"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 1↑" }}] g''8^\markup { \pad-markup #0.2 "-7"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 7↓" }}[ ais8^\markup { \pad-markup #0.2 "-40"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }}] gis4^\markup { \pad-markup #0.2 "+48"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 3↑" }} f''4^\markup { \pad-markup #0.2 "-38"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 1↑" }}} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/5ef20586/lilypond/part_III.ly b/resources/string_quartet_3_rise/5ef20586/lilypond/part_III.ly new file mode 100644 index 0000000..9bfeeca --- /dev/null +++ b/resources/string_quartet_3_rise/5ef20586/lilypond/part_III.ly @@ -0,0 +1,4 @@ +{ + { cis'4^\markup { \pad-markup #0.2 "-25"} d'4^\markup { \pad-markup #0.2 "+30"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 7↑" }} cis'8^\markup { \pad-markup #0.2 "+46"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↑" }}[ ais8^\markup { \pad-markup #0.2 "-40"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }}] ~ ais8[ gis8^\markup { \pad-markup #0.2 "+48"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 5↑" }}]} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/5ef20586/lilypond/part_IV.ly b/resources/string_quartet_3_rise/5ef20586/lilypond/part_IV.ly new file mode 100644 index 0000000..2a64c15 --- /dev/null +++ b/resources/string_quartet_3_rise/5ef20586/lilypond/part_IV.ly @@ -0,0 +1,4 @@ +{ + { ais'2.^\markup { \pad-markup #0.2 "-40"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 3↓" }} ~ ais'8[ gis'8^\markup { \pad-markup #0.2 "+48"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }}]} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/5f0075ab/5f0075ab_code.scd b/resources/string_quartet_3_rise/5f0075ab/5f0075ab_code.scd new file mode 100644 index 0000000..e1a9d92 --- /dev/null +++ b/resources/string_quartet_3_rise/5f0075ab/5f0075ab_code.scd @@ -0,0 +1,992 @@ +( +// helper funcs +var hsArrayToCents, pDist, hdSum, hsChordalDistance, hsArrayToFreq; + +// score funcs +var isInRange, spacingScore, rangeScore, intervalScore, inclusionScore; + +// subroutines +var genTuples, initVoices, genOrders, genSubMotif, updateVoices, genDurFunc, genStepFunc; + +// primary routines +var genMotif, genSecondarySeq; + +// audition funcs +var genPatterns, genMidiPatterns; + +// resource management funcs +var seedFunc, genUID, writeResources, stringifyToDepth, setSeeds, sanityCheck, +msgInterpret, loadLedgerFile, loadLedgerJSON, loadModelFile, loadModelJSON, +setGlobalVars, globalVarsToDict, saveLedger; + +// model vars +//(model and global vars mostly set by OSC funcs +var seq, lastXChanges, +curUID, refUID, orderSeed, durSeed, motifSeed, +entrancesProbVals, passagesProbVals, exitsProbVals, +ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, +orders, susWeights, orderSize, passagesSize, +motifEdited, orderEdited; + +// model aux vars +var entrancesDurFunc, passagesDurFunc, exitsDurFunc, stepFunc; + +// other global vars +var popSize, exPath, dir, primes, dims, tuples, +group, player, resourceDir, ledgerPath, ledger, currentlyPlayingUID, +nameSpaces; + +// install JSON quark (not used) +/* +if(Quarks.isInstalled("JSONlib").not, { + Quarks.install("https://github.com/musikinformatik/JSONlib.git"); + thisProcess.recompile; + //HelpBrowser.openHelpFor("Classes/JSONlib"); +}); +*/ + + +//------helper funcs + +hsArrayToCents = { + arg hsArray; + hsArray.collect({arg dist, p; dist * 1200 * log2(primes[p][0]/primes[p][1])}).sum +}; + +pDist = { + arg array1, array2, signed = false; + var pDistance; + pDistance = hsArrayToCents.value(array2) - hsArrayToCents.value(array1); + if(signed, {pDistance}, {abs(pDistance)}) +}; + +hdSum = { + arg hsArrays; + var size, distances, mean; + size = hsArrays.size; + distances = (size - 1).collect({arg i; + ((i + 1)..(size - 1)).collect({arg j; + abs(hsArrays[i] - hsArrays[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsChordalDistance = { + arg hsArrays1, hsArrays2; + var size, distances, mean; + size = hsArrays1.size; + distances = hsArrays1.size.collect({arg i; + hsArrays2.size.collect({arg j; + abs(hsArrays1[i] - hsArrays2[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsArrayToFreq = { + arg array; + array.collect({arg dim, d; pow(primes[d][0]/primes[d][1], dim)}).product +}; + +//------score funcs + +/* +isInRange = { + arg hsArray, min, max; + var cents; + cents = hsArrayToCents.value(hsArray); + (cents >= min) && (cents <= max) +}; +*/ + +spacingScore = { + arg hsArrays, min; + var centsArray; + centsArray = hsArrays.collect({arg hsArray; hsArrayToCents.value(hsArray)}).sort({arg a, b; a < b}); + centsArray.differentiate.drop(1).collect({arg pDistance; if(pDistance >= min, {1}, {0.01})}).sum; +}; + +rangeScore = { + arg hsArray1, hsArray2, min, max, low, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + if((pDistance >= min) && (pDistance <= max), {1}, {low}); +}; + +/* +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + stepFunc.value(pDistance) +}; +*/ + +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = true; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + //if(pDistance >= 0, {stepFunc.value(abs(pDistance))}, {0.01}); + stepFunc.value(pDistance) +}; + +inclusionScore = { + arg array, test, min = 0.01; + if(array.collect({arg v; v.hash}).includes(test.hash), {min}, {1}); +}; + + +//------subroutines + +genTuples = { + var tuples; + tuples = dims.collect({[-1, 0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum <= 1) && (tuple[0] == 0)}); + //tuples = dims.collect({[0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum == 1) && (tuple[0] == 0)}); + tuples = tuples ++ tuples.collect({arg tuple; [-3, -2, -1, 1, 2, 3].collect({arg octTrans; tuple.deepCopy.put(0, octTrans)})}).flatten; +}; + +initVoices = { + var init, voicesInit; + voicesInit = popSize.collect({dims.collect({0})}); + /* + voicesInit = [dims.collect({0})]; + (popSize - 1).do({ + arg rep, new; + rep = dims.rand; + new = voicesInit.last.deepCopy; + new[rep] = new[rep] + [-1, 1].choose(); + voicesInit = voicesInit.add(new); + }); + */ + voicesInit.deepCopy; +}; + +genDurFunc = {arg chordProb, minPad, maxPad, minDur, maxDur, envData, seed; + var env, pTable, durFunc; + env = Env.pairs([[0, 0]] ++ envData.clump(2) ++ [[1, 0]]).asSignal(256).asList.asArray; + pTable = env.asRandomTable; + //[chordProb, minPad, maxPad, minDur, maxDur, envData].postln; + durFunc = {arg allowChord, pad = false; + var res; + res = if(allowChord.not, { + pTable.tableRand * (maxDur - minDur) + minDur + }, { + if(1.0.rand < chordProb, {0}, {pTable.tableRand * (maxDur - minDur) + minDur}); + }).round(0.125); + if(pad, {res = res + rrand(minPad.asFloat, maxPad.asFloat).round(0.125)}); + if(res.asInteger == res, {res = res.asInteger}); + res + }; + seedFunc.value(durFunc, seed); +}; + +genStepFunc = {arg minStep, maxStep, envData, seed; + var envDataNorm, env, pTable, stepFunc; + //[minStep, maxStep, envData].postln; + envDataNorm = ([[0, 0]] ++ envData.clump(2) ++ [[1, 0]]).flop; + envDataNorm = [envDataNorm[0].normalize(minStep, maxStep), envDataNorm[1]].flop; + env = Env.pairs(envDataNorm); + stepFunc = {arg pDist; + env.at(pDist).clip(0.001, 1); + }; + seedFunc.value(stepFunc, seed); +}; + +genOrders = {arg minMotifLength = 1, maxMotifLength = 5, minProgLength = 0, maxProgLength = 5; + ((maxMotifLength.asInteger - minMotifLength.asInteger).rand + minMotifLength.asInteger).collect({ + var noProgIns, noSusIns, noSilentIns, prog, sus, silent, order; + noSusIns = [1, 2, 3].wchoose(susWeights.normalizeSum); + + //noProgIns = (popSize - noSusIns).rand + 1; + noProgIns = (popSize - noSusIns); + noSilentIns = popSize - noSusIns - noProgIns; + + /* + noSilentIns = (popSize - noSusIns).rand.clip(0, 1); + noProgIns = popSize - noSusIns - noSilentIns; + */ + + # prog, sus, silent = (0..(popSize-1)).scramble.clumps([noProgIns, noSusIns, noSilentIns]); + + prog = (prog.scramble ++ ((maxProgLength.asInteger - minProgLength.asInteger).rand + minProgLength.asInteger).collect({prog.choose}).scramble); + //prog = ((maxProgLength.asInteger - minProgLength.asInteger).rand + minProgLength.asInteger).collect({prog.choose}); + + if(silent == nil, {silent = []}); + [sus.scramble, prog, silent.scramble] + }); +}; + +updateVoices = {arg ins, sus; + var voices, candidates, nWeights, nProbs, sel; + + voices = lastXChanges.deepCopy.last; + + candidates = sus.collect({arg v; tuples.collect({arg t; voices[v] + t})}).flatten; + candidates = difference(candidates.asSet, voices.asSet).asList; + //candidates.select({arg item; (item ++ voices).asSet.size >= 4}); + nProbs = candidates.collect({arg candidate; + var stepScore, recentlySoundedScore, isInRangeScore, regScore, hdScore; + + //stepScore = intervalScore.value(voices[ins], candidate, 30, 400, 0.1); + stepScore = intervalScore.value(voices[ins], candidate, 100, 100); + recentlySoundedScore = inclusionScore.value(lastXChanges.flop[ins], candidate, 0.01); + //recentlySoundedScore = inclusionScore.value(lastXChanges.flatten.collect({arg item; item.drop(1)}), candidate.drop(1), 0.01); + + /* + if(rangeScore.value(candidate.collect({0}), voices[ins], ranges[ins][1] - 500, ranges[ins][1], 0, true) == 1, { + isInRangeScore = 1 - rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0] + 500, ranges[ins][1] - 500, 0, true); + isInRangeScore = isInRangeScore * rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true) + }, { + isInRangeScore = rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true); + }); + */ + + + isInRangeScore = rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true); + //old way - worked but actually by accident (I think) + //isInRangeScore = rangeScore.value(candidate, candidate.collect({0}), ranges[ins][0], ranges[ins][1], 0, true); + + + regScore = spacingScore.value(voices.deepCopy.put(ins, candidate), 300); + hdScore = pow(hdSum.value(voices.deepCopy.put(ins, candidate)), hdExp); + if(hdInvert == 0, {hdScore = 1/hdScore}); + //maybe what you want here is a vector to another root and then favoring movement towards it. + //distScore = pow(hsChordalDistance.value(voices, voices.put(ins, candidate)), 2); + + [stepScore, recentlySoundedScore, isInRangeScore, regScore, hdScore] + }); + + nWeights = passagesWeights; + + //this handles nWeights of 0; mainly for testing + nProbs = nProbs.flop.select({arg scores, s; nWeights[s] != 0}).flop; + nWeights = nWeights.select({arg weight; weight != 0}); + nProbs = nProbs.flop.collect({arg scores, s; + if(scores.sum == 0, {scores}, {scores.normalizeSum * nWeights[s]}).postln; + }); + nProbs = nProbs.flop.collect({arg scores, s; scores.product}).normalizeSum; + + nProbs.round(0.001).postln; + [candidates, nProbs.round(0.001)].flop.postln; + + sel = candidates.wchoose(nProbs); + + voices[ins] = sel; + lastXChanges = lastXChanges.add(voices).keep(-5); +}; + +genSubMotif = {arg order, orderIndex, lastState, repeatLast = false, startFromLast = false, isLastOrder = false; + var sus, prog, silent, flatOrder, res, isInChord, allowChord, pad, lastXChangesHold, voices, adder; + # sus, prog, silent = order; + flatOrder = silent ++ sus ++ prog; + lastXChangesHold = lastXChanges.deepCopy; + voices = lastState.deepCopy; + isInChord = popSize.collect({false}); + allowChord = false; + pad = false; + res = []; + "------generating motif".postln; + //need to figure out here if voices move between motifs + flatOrder.do({arg ins, i; + + if(prog.includes(ins) && repeatLast.not, {updateVoices.value(ins, sus)}); + adder = if(silent.includes(ins), {["Rest"]}, {lastXChanges.last.deepCopy[ins]}); + + if(voices[ins] != adder, { + var dur; + + if((sus ++ silent).includes(ins), { + allowChord = (ins != sus.last); + pad = (ins == sus.last); + }, { + if(i < (flatOrder.size - 1), { + allowChord = (isInChord[flatOrder[i + 1]] || (ins == flatOrder[i + 1])).not; + pad = false; + }, { + allowChord = false; + pad = true + }); + }); + if((orderIndex == 0) && sus.includes(ins), { + dur = entrancesDurFunc.value(allowChord, pad); + }, { + dur = passagesDurFunc.value(allowChord, pad); + }); + if(dur == 0, {isInChord[ins] = true}, {isInChord = popSize.collect({false})}); + + voices[ins] = adder; + res = res.add([voices.deepCopy.postln, dur]); + }); + }); + + // pad ending + /* + if(orderIndex == (orders.size - 1), { + (0..(popSize-1)).scramble.do({arg ins; + if(res.last.first[ins] != ["Rest"], { + var dur; + voices[ins] = ["Rest"]; + allowChord = (voices != popSize.collect({["Rest"]})); + pad = allowChord.not; + dur = exitsDurFunc.value(allowChord, pad); + res = res.add([voices.deepCopy.postln, dur]); + }); + }); + }); + */ + + //format and return + if(startFromLast, {lastXChanges = lastXChangesHold.deepCopy}); + res; +}; + + +//------primary routines + +genMotif = { + var repeats, fSeq, fDur, durAdd; + + repeats = 1; + fSeq = []; + + repeats.do({arg index; + var motif; + + motif = []; + + orders.do({arg order, o; + var lastState, subMotif; + //lastState = if(o == 0, {popSize.collect({["Rest"]})}, {motif.last.last.first}); + lastState = if(o == 0, {lastXChanges.last.deepCopy}, {motif.last.last.first}); + subMotif = genSubMotif.value(order, o, lastState, isLastOrder: o == (orders.size - 1)); + motif = motif.add(subMotif); + + }); + + sanityCheck.value(motif, index); + + fSeq = fSeq.add(motif); + }); + + //round last duration to measure + /* + fDur = fSeq.flatten.flatten.slice(nil, 1).sum; + durAdd = fDur.round(4) - fDur; + if(durAdd < 0, {durAdd = 4 - durAdd}); + fSeq[0][orders.size - 1][fSeq[0][orders.size - 1].size - 1][1] = fSeq[0][orders.size - 1][fSeq[0][orders.size - 1].size - 1][1] + durAdd; + */ + + fSeq +}; + +genSecondarySeq = {arg seq; + var curdles, fSeq; + curdles = []; + while({curdles.sum < seq.size}, {curdles = curdles ++ [3.rand + 1]}); + + fSeq = seq.clumps(curdles).collect({arg clump, m; + var repeats, paddedSeq; + + //add rest + paddedSeq = clump.add([[[popSize.collect({["Rest"]}), 0.5.rand]]]); + + //implement repeats + repeats = [0.rand + 1, 1].wchoose([1, 0].normalizeSum); + repeats.collect({paddedSeq}); + }); + fSeq +}; + + +//------audition funcs + +/* +Event.addEventType(\osc, { + if (~addr.postln.notNil) { + ~addr.sendMsg(~indexPath, ~indexMsg); + ~addr.sendMsg(~seqPath, stringifyToDepth.value(~seqMsg, 3)); + //~addr.sendMsg("/STATE/OPEN", (dir.replace("supercollider", "resources") +/+ ~idMsg +/+ ~idMsg ++ "_gui_state" ++ ".state").standardizePath.postln); + }; +}); +*/ + +Event.addEventType(\osc, { + if (~addr.notNil) { + ~msg; + ~addr.sendMsg(~path, *~msg); + }; +}); + +genPatterns = {arg inSeq, addr, oneShot = false; + var voices, durs, pbinds, res, indices, sectionDurs, msg, ids, seq; + seq = inSeq.collect({arg mSeq; mSeq[0]}); + //# voices, durs = seq.flatten2(seq.maxDepth - 5).flop; + # voices, durs = seq.flatten2(if(oneShot, {2}, {3})).flop; + pbinds = voices.flop.collect({arg voice, v; + var clumps, hdScores, freqs, fDurs, attacks, rels, amps; + clumps = voice.separate({arg a, b; a != b }); + freqs = clumps.collect({arg clump; if(clump[0] != ["Rest"], {(62.midicps * hsArrayToFreq.value(clump[0]))}, {Rest(0)})}); + fDurs = durs.clumps(clumps.collect({arg clump; clump.size})).collect({arg clump; clump.sum}); + //attacks = 2.collect({rrand(1, 3)}) ++ freqs.drop(2).collect({rrand(0.3, 0.5)}); + attacks = fDurs.collect({arg dur; dur * rrand(0.2, 0.4)}); + //rels = freqs.drop(2).collect({rrand(0.3, 0.5)}) ++ 2.collect({rrand(1, 3)}); + rels = (clumps.size - 1).collect({arg c; + if(clumps[c + 1][0] == ["Rest"], {rrand(1.0, 3.0)}, {rrand(0.3, 0.5)}); + }); + rels = rels.add(rrand(1.0, 3.0)); + amps = freqs.collect({rrand(0.6, 0.99)}); + + [ + Pbind( + \instrument, \string_model, + \group, group, + \freq, Pseq(freqs, 1), + \dur, Pseq(fDurs, 1), + \attack, Pseq(attacks, 1), + \sustain, Pseq(fDurs, 1), + \release, Pseq(rels, 1), + //\amp, Pseq(amps, 1), + \amp, Pbrown(0.5, 1, 0.5), + \busIndex, v + ), + Pbind( + \instrument, \sine, + \group, group, + \freq, Pseq(freqs, 1), + \dur, Pseq(fDurs, 1), + \sustain, Pseq(fDurs, 1), + \busIndex, v + ) + ] + }).flatten; + if(oneShot.not, { + msg = inSeq.collect({arg mSeq, m; mSeq[1..]}); + //ids = inSeq.collect({arg mSeq, m; mSeq[2]}); + sectionDurs = seq.collect({arg mSeq; mSeq.flatten2(2).flop[1].sum}); + pbinds = pbinds ++ + [ + Pbind( + \type, \osc, + \addr, addr, + \path, "/playing", + \msg, Pseq(msg, 1), + \dur, Pseq(sectionDurs, 1) + ); + ] + }); + res = Ppar(pbinds); + res +}; + +/* +genMidiPatterns = {arg seq; + var voices, durs, patterns, res, mOut, pbRange; + pbRange = 1; //semitones - change this as needed for your situation + mOut = MIDIOut.newByName("TiMidity", "TiMidity port 0").latency_(Server.default.latency); + # voices, durs = seq.flatten2(seq.maxDepth - 5).flop; + res = Ppar( + voices.flop.collect({arg voice, v; + var clumps, hdScores, freqs, fDurs; + + mOut.program(v, 70); + + clumps = voice.separate({arg a, b; a != b }); + freqs = clumps.collect({arg clump; if(clump[0] != ["Rest"], {(60.midicps * hsArrayToFreq.value(clump[0]))}, {Rest(0)})}); + fDurs = durs.clumps(clumps.collect({arg clump; clump.size})).collect({arg clump; clump.sum}); + + Pbind( + \type, \midi, + \chan, v, + \noteval, Pseq(freqs.cpsmidi - 24, 1), + \note, Pfunc({ | event | event[\noteval].floor }), + \dur, Pseq(fDurs, 1), + \midiout, mOut, + \amp, 1, + \bend, Pfunc({ + | event | + if (event[\note].isRest.not) { + var pitchbendvalue = event[\noteval].frac.linlin(0, pbRange, 8192, 8192*2).asInteger; + m.bend(v, pitchbendvalue); + }; + 0; // return something other than nil to avoid stopping the pattern + }), + ); + }); + ); + res +}; +*/ + + +//------resource management funcs + +genUID = {Date.seed.asHexString.toLower}; + +seedFunc = {arg func, seed; + var funcArgs, next; + next = Routine({loop{func.valueArray(funcArgs).yield }}); + next.randSeed_(seed); + {arg ...args; funcArgs = args; next.value} +}; + +stringifyToDepth = {arg data, maxDepth = 1; + var prettyString = "", rCount = 0, writeArray, indent; + + if(maxDepth == 0, { + data.asCompileString + }, { + indent = {arg size; size.collect({" "}).join("")}; + writeArray = {arg array; + prettyString = prettyString ++ indent.value(rCount) ++ "[\n"; + rCount = rCount + 1; + if(rCount < maxDepth, { + array.do({arg subArray; writeArray.value(subArray)}); + }, { + prettyString = prettyString ++ array.collect({arg subArray; + indent.value(rCount + 1) ++ subArray.asCompileString + }).join(",\n"); + }); + rCount = rCount - 1; + prettyString = prettyString ++ "\n" ++ indent.value(rCount) ++ "],\n"; + }; + + writeArray.value(data); + prettyString.replace(",\n\n", "\n").drop(-2); + }) +}; + +sanityCheck = {arg motif, index; + //print functions = very helpful + ("----------" + index + "------------").postln; + + motif.flatten.do({arg val, v; + if(v > 0, { + if(motif.flatten[v-1][0].hammingDistance(val[0]) > 1, {"problem 1".postln}); + if(motif.flatten[v-1][0].hammingDistance(val[0]) == 0, {"problem 2".postln}); + }); + val.postln + }); + "***********".postln; +}; + +msgInterpret = {arg in, escapeDoubleQuotes = true, escapeSingleQuotes = true; + var res; + + res = in; + if(res.isNil.not, { + if((res.isArray && res.isString.not), { + res = res.asCompileString; + res = res.replace(" ", "").replace("\n", "").replace("\t", ""); + if(escapeSingleQuotes, {res = res.replace("\'", "")}); + if(escapeDoubleQuotes, {res = res.replace("\"", "")}); + res = res.replace("Rest", "\"Rest\""); + res = res.interpret; + }, { + var tmpRes; + if(res.every({arg char; char.isDecDigit}), {tmpRes = res.asInteger}); + if(res.contains("."), {tmpRes = res.asFloat}); + if(tmpRes != nil, {res = tmpRes}); + }); + }); + res +}; + +writeResources = {arg path, dict; + var file, modelItems, resString; + file = File(path,"w"); + + modelItems = [ + seq, lastXChanges, + curUID, refUID, orderSeed, durSeed, motifSeed, + entrancesProbVals, passagesProbVals, exitsProbVals, + ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, + orders, susWeights, orderSize, passagesSize, + motifEdited, orderEdited + ]; + + resString = nameSpaces.collect({arg nameSpace; + var depth = 0, insert = " "; + if(nameSpace == "music_data", {depth = 3; insert = "\n"}); + if(nameSpace == "last_changes", {depth = 1; insert = "\n"}); + if(nameSpace == "order", {depth = 1; insert = "\n"}); + if((nameSpace == "ref_uid") && (dict[nameSpace] == nil), {dict[nameSpace] = "nil"}); + "\"" ++ nameSpace ++ "\":" ++ insert ++ stringifyToDepth.value(dict[nameSpace], depth) + }).join(",\n"); + + resString = "{\n" ++ resString ++ "\n}"; + + file.write(resString); + file.close; + resString +}; + +loadModelFile = {arg path; loadModelJSON.value(File(path, "r").readAllString.parseJSON)}; + +loadModelJSON = {arg jsonObject; + var dict; + dict = Dictionary.with(*nameSpaces.collect({arg nS; nS->msgInterpret.value(jsonObject[nS])})); + dict +}; + +setGlobalVars = {arg dict, skipLastXChanges = false; + var tmpLastXChanges; + tmpLastXChanges = lastXChanges.deepCopy; + // order really matters!!!! + # seq, lastXChanges, curUID, refUID, orderSeed, durSeed, motifSeed, + entrancesProbVals, passagesProbVals, exitsProbVals, + ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, + orders, susWeights, orderSize, passagesSize, + motifEdited, orderEdited = nameSpaces.collect({arg nS; dict[nS]}); + if(skipLastXChanges, {lastXChanges = tmpLastXChanges}); + dict +}; + +globalVarsToDict = { + var modelItems, dict; + // order really matters!!!! + modelItems = [ + seq, lastXChanges, + curUID, refUID, orderSeed, durSeed, motifSeed, + entrancesProbVals, passagesProbVals, exitsProbVals, + ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, + orders, susWeights, orderSize, passagesSize, + motifEdited, orderEdited + ]; + dict = Dictionary.with(*nameSpaces.collect({arg nS, n; nS->modelItems[n]})); +}; + +loadLedgerFile = {arg path; + ledgerPath = path; + resourceDir = path.splitext(".").drop(-1).join; + loadLedgerJSON.value(File(ledgerPath, "r").readAllString.parseJSON) +}; + +loadLedgerJSON = {arg jsonObject; ledger = jsonObject["ledger"]}; + +saveLedger = {arg ledger, path; + var file, curResourceDir; + file = File(path, "w"); + curResourceDir = resourceDir; + resourceDir = path.splitext(".").drop(-1).join; + if(curResourceDir != resourceDir, { + File.mkdir(resourceDir); + ledger.do({arg id; + File.copy(curResourceDir +/+ id, resourceDir +/+ id); + }); + }); + file.write("{\n\"ledger\":\n" ++ stringifyToDepth.value(ledger, 1) ++ "\n}"); + file.close; +}; + +//------global vars + +primes = [[2, 1], [3, 2], [5, 4], [7, 4], [11, 8], [13, 8]]; +//ranges = [[-2400, 0], [-1200, 1200], [0, 2400], [0, 2400]]; +exPath = thisProcess.nowExecutingPath; +dir = exPath.dirname; +//popSize = 4; +dims = primes.size; +tuples = genTuples.value(); +//refUID = nil; +group = Group.new; +~group = group; +loadLedgerFile.value(dir +/+ ".." +/+ "resources" +/+ "piece_ledger.json"); +resourceDir = (dir +/+ ".." +/+ "resources" +/+ "piece_ledger"); +//passagesWeights = [1, 1, 1, 1, 1]; +//susWeights = [1, 1, 1]; +// order really matters!!!! +nameSpaces = [ + "music_data", "last_changes", "cur_uid", "ref_uid", "order_seed", "dur_seed", "motifs_seed", + "entrances_probs_vals","passages_probs_vals", "exits_probs_vals", + "ranges", "step_probs_vals", "passages_weights", "hd_exp", "hd_invert", + "order", "sus_weights", "order_size", "passages_size", + "motif_edited", "order_edited" +]; + + +//------OSC funcs + +OSCdef(\load_ledger, {arg msg, time, addr, port; + loadLedgerFile.value(msg[1].asString); +}, \load_ledger); + +OSCdef(\load_model, {arg msg, time, addr, port; + var dict; + dict = loadModelFile.value(msg[1].asString); + setGlobalVars.value(dict); +}, \load_model); + +OSCdef(\save_ledger, {arg msg, time, addr, port; + msg.postln; + ledger = msgInterpret.value(msg[1].asString.parseJSON["ledger"], false).postln; + //loadLedgerJSON.value(msg[0]) + saveLedger.value(ledger, msg[2].asString); + //loadLedgerFile.value(msg[1].asString); +}, \save_ledger); + +OSCdef(\generate, {arg msg, time, addr, port; + var path, dict, durSeeds, musPath, modelString; + msg.postln; + + path = msg[1].asString; + + dict = loadModelFile.value(path); + setGlobalVars.value(dict, true); + + popSize = ranges.size; + + //refUID.postln; + + loadLedgerFile.value(ledgerPath); + if(ledger == nil, {ledger = ["tmp"]}); + if(ledger.last != "tmp", {ledger = ledger.add("tmp")}); + + if(refUID == nil, {lastXChanges = [initVoices.value().deepCopy]}); + if((refUID != nil) && (refUID != "tmp"), { + var file; + file = File((resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath, "r"); + lastXChanges = msgInterpret.value(file.readAllString.parseJSON["last_changes"]); + }); + + refUID.postln; + lastXChanges.collect({arg item; item.postln}); + + durSeeds = seedFunc.value({3.collect({rrand(100000, 999999)})}, durSeed).value.postln; + entrancesDurFunc = genDurFunc.valueArray(entrancesProbVals[..4] ++ [entrancesProbVals[5..]] ++ [durSeeds[0]]); + passagesDurFunc = genDurFunc.valueArray(passagesProbVals[..4] ++ [passagesProbVals[5..]] ++ [durSeeds[1]]); + exitsDurFunc = genDurFunc.valueArray(exitsProbVals[..4] ++ [exitsProbVals[5..]] ++ [durSeeds[2]]); + + if(orders == nil, { + orders = seedFunc.value(genOrders, orderSeed).valueArray(orderSize ++ passagesSize); + //addr.sendMsg("/order", stringifyToDepth.value(orders, 1)); + }); + + stepFunc = genStepFunc.valueArray(stepProbsVals[..1] ++ [stepProbsVals[2..]] ++ [motifSeed]); + seq = seedFunc.value(genMotif, motifSeed).value; + + lastXChanges.collect({arg item; item.postln}); + + dict = globalVarsToDict.value; + modelString = writeResources.value(path, dict); + + //addr.sendMsg("/generated", musPath, stringifyToDepth.value(seq, 3)); + //~seq = seq; + + addr.sendMsg("/generated", path, modelString, ledgerPath); +}, \generate); + + +OSCdef(\commit, {arg msg, time, addr, port; + var musicData, musicChanged, dict, newLedger, modelPath, musString, musFile, test1, test2, lastCurUID, commitType, commitPos, equalityLedger; + //msg.postln; + + /* + test1 = msg[1].asString.parseJSON; + test2 = (dir +/+ ".." +/+ "resources/tmp/tmp_music" ++ ".json").standardizePath.parseJSONFile; + msgInterpret.value(test1["music"])[0][0][0][1].class.postln; + msgInterpret.value(test2["music_data"])[0][0][0][1].class.postln; + (test1["music"] == test2["music_data"]).postln; + */ + + musicData = loadModelJSON.value(msg[1].asString.parseJSON)["music_data"].postln; + musicChanged = (musicData != seq).postln; + commitType = msg[2].asString; + commitPos = msg[3].postln.asInteger; + + lastCurUID = curUID.deepCopy; + curUID = genUID.value; + + File.mkdir((resourceDir +/+ curUID).standardizePath); + File.copy(exPath, (resourceDir +/+ curUID +/+ curUID ++ "_code" ++ ".scd").standardizePath); + + modelPath = (resourceDir +/+ curUID +/+ curUID ++ "_mus_model" ++ ".json").standardizePath; + dict = globalVarsToDict.value; + if(musicChanged, { + seq = musicData; + dict["music_data"] = seq; + dict["motif_edited"] = "true" + }); + dict["cur_uid"] = curUID; + + writeResources.value(modelPath, dict); + + File.delete(ledgerPath ++ "_bak"); + File.copy(ledgerPath, ledgerPath ++ "_bak"); + File.delete(ledgerPath); + + /* + if(commitType == "add", { + if(lastCurUID == "tmp", { + ledger = ledger.drop(-1).add(curUID); + }, { + ledger = ledger.add(curUID); + }) + }); + */ + + ledger.postln; + + if(commitType == "add", {ledger = ledger.add(curUID)}); + + if(commitType == "insert", {ledger = ledger.insert(commitPos + 1, curUID)}); + + if(commitType == "replace", {ledger = ledger.put(commitPos, curUID)}); + + equalityLedger = ledger.collect({arg item; item.asSymbol}); + if(equalityLedger.includes(\tmp).postln, {ledger.removeAt(equalityLedger.indexOf(\tmp).postln)}); + + ledger.postln; + + saveLedger.value(ledger, ledgerPath); + + addr.sendMsg("/committed", curUID, ledgerPath); + //refUID = curUID; + +}, \commit); + +OSCdef(\transport, {arg msg, time, addr, port; + msg.postln; + if(msg[1] == 0, { + group.set(\release, 2); + group.set(\gate, 0); + player.stop; + }, { + // the cued sequence can now be read from file, so this can be cleaned up + var cSize, patterns, pSeq, cuedSeek, indexStart, indexEnd, tmpLedger; + if(msg[1] == 1, { + pSeq = []; + cuedSeek = (seq != nil); + indexStart = msg[2].asInteger; + indexEnd = ledger.size - if(cuedSeek, {2}, {1}); + //ledger.postln; + if(((indexStart == (ledger.size - 1)) && cuedSeek).not, { + ledger[indexStart..indexEnd].do({arg uid, index; + var path, file; + path = (resourceDir +/+ uid +/+ uid ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.postln.parseJSON["music_data"]), path, indexStart + index, uid]); + file.close; + }); + }); + if(cuedSeek, { + var path, file; + path = (resourceDir +/+ "tmp/tmp_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.parseJSON["music_data"]), path, ledger.size - 1, "tmp"]); + file.close; + }); + patterns = genPatterns.value(pSeq, addr); + }, { + pSeq = [loadModelJSON.value(msg[2].asString.parseJSON)["music_data"].postln]; + patterns = genPatterns.value(pSeq, addr, true); + }); + player = Pfset(pattern: patterns, cleanupFunc: { + addr.sendMsg("/transport", 0); + addr.sendMsg("/one_shot", 0); + }); + player = player.play + }); +}, \transport); + + +OSCdef(\transcribe_motif, {arg msg, time, addr, port; + var tSeq, refChord, refUID; + + msg.postln; + + tSeq = [loadModelJSON.value(msg[1].asString.parseJSON)["music_data"]]; + refUID = msg[2].asString.postln; + + if((refUID != "nil") && (refUID != "tmp"), { + var file; + file = File((resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath, "r"); + refChord = msgInterpret.value(file.readAllString.parseJSON["last_changes"]).last; + file.close; + }, { + refChord = [[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0]]; + }); + + ~transcribe.value(tSeq, refChord, (dir +/+ ".." +/+ "lilypond" +/+ "includes").standardizePath, addr, "/transcribe_motif"); +}, \transcribe_motif); + + +OSCdef(\transcribe_all, {arg msg, time, addr, port; + var cSize, patterns, cuedSeek, indexStart, indexEnd, tmpLedger; + if(true, { + cuedSeek = (seq != nil); + indexStart = msg[1].asInteger; + indexEnd = ledger.size - if(cuedSeek, {2}, {1}); + + //tmp for testing transcription + //indexEnd = (indexStart+5); + + //ledger.postln; + if(((indexStart == (ledger.size - 1)) && cuedSeek).not, { + var lilyPartLedgerFiles; + + lilyPartLedgerFiles = 4.collect({arg p; + File((dir +/+ ".." +/+ "lilypond" +/+ "includes" +/+ "part_" ++ ["IV", "III", "II", "I"][p] ++ ".ly").standardizePath, "w"); + }); + + ledger[indexStart..indexEnd].do({arg uid, index; + var path, file, fileString, tSeq, refUID, refChord; + path = (resourceDir +/+ uid +/+ uid ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + fileString = file.readAllString; + tSeq = msgInterpret.value(fileString.parseJSON["music_data"]); + refUID = msgInterpret.value(fileString.parseJSON["ref_uid"]); + file.close; + + //uid.postln; + //(refUID == "nil").postln; + + refChord = [[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0]]; + + if(refUID != "nil", { + path = (resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + refChord = msgInterpret.value(file.readAllString.parseJSON["last_changes"]).last; + file.close; + }); + + if(index != indexEnd, { + ~transcribe.value(tSeq, refChord, (resourceDir +/+ uid +/+ "lilypond").standardizePath); + }, { + ~transcribe.value(tSeq, refChord, (resourceDir +/+ uid +/+ "lilypond").standardizePath, addr, "/transcribe_all"); + }); + + lilyPartLedgerFiles.do({arg f, p; + f.write("\\include \"" ++ resourceDir +/+ uid +/+ "lilypond" +/+ "part_" ++ ["IV", "III", "II", "I"][p] ++ ".ly\"\n"); + }); + + }); + + lilyPartLedgerFiles.do({arg f; + f.close + }); + }); + /* + if(cuedSeek, { + var path, file; + path = (dir +/+ ".." +/+ "resources/tmp/tmp_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.parseJSON["music_data"]), path, ledger.size - 1, "tmp"]); + file.close; + }); + */ + }, { + + }); + +}, \transcribe_all); + +) + + diff --git a/resources/string_quartet_3_rise/5f0075ab/5f0075ab_mus_model.json b/resources/string_quartet_3_rise/5f0075ab/5f0075ab_mus_model.json new file mode 100644 index 0000000..11843ff --- /dev/null +++ b/resources/string_quartet_3_rise/5f0075ab/5f0075ab_mus_model.json @@ -0,0 +1,65 @@ +{ +"music_data": +[ + [ + [ + [ [ [ -7, 4, 1, 2, -1, 2 ], [ -5, 4, 1, 1, -1, 2 ], [ -5, 5, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -7, 4, 1, 2, -1, 2 ], [ -5, 4, 1, 1, -1, 2 ], [ -5, 4, 1, 2, 0, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -6, 4, 1, 1, -1, 2 ], [ -5, 4, 1, 1, -1, 2 ], [ -5, 4, 1, 2, 0, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0 ], + [ [ [ -6, 4, 1, 1, -1, 2 ], [ -5, 4, 1, 1, -1, 2 ], [ -4, 4, 1, 1, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -6, 4, 1, 1, -1, 2 ], [ -5, 4, 1, 1, -1, 2 ], [ -5, 4, 2, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0 ], + [ [ [ -6, 4, 1, 1, -1, 2 ], [ -6, 4, 2, 2, -1, 2 ], [ -5, 4, 2, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -6, 4, 1, 1, -1, 2 ], [ -6, 4, 2, 2, -1, 2 ], [ -4, 3, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ] + ], + [ + [ [ [ -6, 4, 1, 1, -1, 2 ], [ -5, 3, 1, 2, -1, 2 ], [ -4, 3, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -6, 4, 1, 1, -1, 2 ], [ -5, 3, 1, 2, -1, 2 ], [ -5, 5, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0 ], + [ [ [ -6, 4, 1, 1, -1, 2 ], [ -5, 4, 2, 1, -1, 2 ], [ -5, 5, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ] + ], + [ + [ [ [ -6, 4, 1, 1, -1, 2 ], [ -5, 4, 2, 1, -1, 2 ], [ -4, 4, 0, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0 ], + [ [ [ -6, 4, 1, 1, -1, 2 ], [ -5, 4, 0, 2, -1, 2 ], [ -4, 4, 0, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0 ], + [ [ [ -7, 4, 1, 2, -1, 2 ], [ -5, 4, 0, 2, -1, 2 ], [ -4, 4, 0, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ] + ], + [ + [ [ [ -5, 3, 0, 2, -1, 2 ], [ -5, 4, 0, 2, -1, 2 ], [ -4, 4, 0, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0 ], + [ [ [ -5, 3, 0, 2, -1, 2 ], [ -5, 3, 1, 2, -1, 2 ], [ -4, 4, 0, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -5, 3, 0, 2, -1, 2 ], [ -6, 5, 1, 2, -1, 2 ], [ -4, 4, 0, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0 ], + [ [ [ -6, 4, 1, 2, -1, 2 ], [ -6, 5, 1, 2, -1, 2 ], [ -4, 4, 0, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ] + ] + ] +], +"last_changes": +[ + [ [ -7, 4, 1, 2, -1, 2 ], [ -5, 4, 0, 2, -1, 2 ], [ -4, 4, 0, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], + [ [ -5, 3, 0, 2, -1, 2 ], [ -5, 4, 0, 2, -1, 2 ], [ -4, 4, 0, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], + [ [ -5, 3, 0, 2, -1, 2 ], [ -5, 3, 1, 2, -1, 2 ], [ -4, 4, 0, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], + [ [ -5, 3, 0, 2, -1, 2 ], [ -6, 5, 1, 2, -1, 2 ], [ -4, 4, 0, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], + [ [ -6, 4, 1, 2, -1, 2 ], [ -6, 5, 1, 2, -1, 2 ], [ -4, 4, 0, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ] +], +"cur_uid": "5f0075ab", +"ref_uid": "736745da", +"order_seed": 837263, +"dur_seed": 820526, +"motifs_seed": 826853, +"entrances_probs_vals": [ 0.78, 0, 0, 0, 1.02, 0.0098039215686275, 0, 0.20915032679739, 0, 0.24183006535948, 0.82432432432432, 0.28758169934641, 0, 1, 0 ], +"passages_probs_vals": [ 0.78, 0, 0, 0, 1.02, 0.0098039215686275, 0, 0.20915032679739, 0, 0.24183006535948, 0.82432432432432, 0.28758169934641, 0, 1, 0 ], +"exits_probs_vals": [ 0.78, 0, 0, 0, 1.02, 0.0098039215686275, 0, 0.20915032679739, 0, 0.24183006535948, 0.82432432432432, 0.28758169934641, 0, 1, 0 ], +"ranges": [ [ -2400, 1200 ], [ -1200, 1453 ], [ -702, 1694 ], [ -702, 1973 ] ], +"step_probs_vals": [ -1200, 1200, 0, 0, 0.41358024691358, 0.0056818181818177, 0.45884773662551, 0.5, 0.52057613168724, 0, 0.56378600823045, 0.85795454545455, 0.60082304526749, 0, 1, 0 ], +"passages_weights": [ 1, 0.15, 0.43, 1, 1 ], +"hd_exp": 10, +"hd_invert": 0, +"order": +[ + [ [ 3 ], [ 1, 2, 0, 2, 2, 1, 2 ], [ ] ], + [ [ 0, 3 ], [ 1, 2, 1 ], [ ] ], + [ [ 3 ], [ 2, 1, 0 ], [ ] ], + [ [ 3, 2 ], [ 0, 1, 1, 0 ], [ ] ] +], +"sus_weights": [ 0.7, 0.48, 0.49 ], +"order_size": [ 14.132653061224, 14.132653061224 ], +"passages_size": [ 0, 5 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/5f0075ab/lilypond/part_I.ly b/resources/string_quartet_3_rise/5f0075ab/lilypond/part_I.ly new file mode 100644 index 0000000..bad9fe0 --- /dev/null +++ b/resources/string_quartet_3_rise/5f0075ab/lilypond/part_I.ly @@ -0,0 +1,4 @@ +{ + { f'1^\markup { \pad-markup #0.2 "-38"}} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/5f0075ab/lilypond/part_II.ly b/resources/string_quartet_3_rise/5f0075ab/lilypond/part_II.ly new file mode 100644 index 0000000..c0f7b74 --- /dev/null +++ b/resources/string_quartet_3_rise/5f0075ab/lilypond/part_II.ly @@ -0,0 +1,4 @@ +{ + { c''8^\markup { \pad-markup #0.2 "-37"}[ ais'8^\markup { \pad-markup #0.2 "+13"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 11↑" }}] g'8^\markup { \pad-markup #0.2 "-7"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }}[ gis'8^\markup { \pad-markup #0.2 "+48"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 5↑" }}] ais'4^\markup { \pad-markup #0.2 "-40"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 3↓" }} c''8^\markup { \pad-markup #0.2 "-37"}[ cis''8^\markup { \pad-markup #0.2 "-25"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 5↓" }}]} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/5f0075ab/lilypond/part_III.ly b/resources/string_quartet_3_rise/5f0075ab/lilypond/part_III.ly new file mode 100644 index 0000000..3492e4e --- /dev/null +++ b/resources/string_quartet_3_rise/5f0075ab/lilypond/part_III.ly @@ -0,0 +1,4 @@ +{ + { g4^\markup { \pad-markup #0.2 "-7"} ~ g8[ gis8^\markup { \pad-markup #0.2 "+48"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 1↑" }}] ~ gis8[ ais8^\markup { \pad-markup #0.2 "-40"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 1↑" }}] b8^\markup { \pad-markup #0.2 "-21"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↑" }}[ cis'8^\markup { \pad-markup #0.2 "-25"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 1↑" }}]} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/5f0075ab/lilypond/part_IV.ly b/resources/string_quartet_3_rise/5f0075ab/lilypond/part_IV.ly new file mode 100644 index 0000000..abd31d1 --- /dev/null +++ b/resources/string_quartet_3_rise/5f0075ab/lilypond/part_IV.ly @@ -0,0 +1,4 @@ +{ + { f,4^\markup { \pad-markup #0.2 "-38"} g,2^\markup { \pad-markup #0.2 "-7"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }} ~ g,8[ f,8^\markup { \pad-markup #0.2 "-38"}]} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/61207e49/61207e49_code.scd b/resources/string_quartet_3_rise/61207e49/61207e49_code.scd new file mode 100644 index 0000000..e1a9d92 --- /dev/null +++ b/resources/string_quartet_3_rise/61207e49/61207e49_code.scd @@ -0,0 +1,992 @@ +( +// helper funcs +var hsArrayToCents, pDist, hdSum, hsChordalDistance, hsArrayToFreq; + +// score funcs +var isInRange, spacingScore, rangeScore, intervalScore, inclusionScore; + +// subroutines +var genTuples, initVoices, genOrders, genSubMotif, updateVoices, genDurFunc, genStepFunc; + +// primary routines +var genMotif, genSecondarySeq; + +// audition funcs +var genPatterns, genMidiPatterns; + +// resource management funcs +var seedFunc, genUID, writeResources, stringifyToDepth, setSeeds, sanityCheck, +msgInterpret, loadLedgerFile, loadLedgerJSON, loadModelFile, loadModelJSON, +setGlobalVars, globalVarsToDict, saveLedger; + +// model vars +//(model and global vars mostly set by OSC funcs +var seq, lastXChanges, +curUID, refUID, orderSeed, durSeed, motifSeed, +entrancesProbVals, passagesProbVals, exitsProbVals, +ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, +orders, susWeights, orderSize, passagesSize, +motifEdited, orderEdited; + +// model aux vars +var entrancesDurFunc, passagesDurFunc, exitsDurFunc, stepFunc; + +// other global vars +var popSize, exPath, dir, primes, dims, tuples, +group, player, resourceDir, ledgerPath, ledger, currentlyPlayingUID, +nameSpaces; + +// install JSON quark (not used) +/* +if(Quarks.isInstalled("JSONlib").not, { + Quarks.install("https://github.com/musikinformatik/JSONlib.git"); + thisProcess.recompile; + //HelpBrowser.openHelpFor("Classes/JSONlib"); +}); +*/ + + +//------helper funcs + +hsArrayToCents = { + arg hsArray; + hsArray.collect({arg dist, p; dist * 1200 * log2(primes[p][0]/primes[p][1])}).sum +}; + +pDist = { + arg array1, array2, signed = false; + var pDistance; + pDistance = hsArrayToCents.value(array2) - hsArrayToCents.value(array1); + if(signed, {pDistance}, {abs(pDistance)}) +}; + +hdSum = { + arg hsArrays; + var size, distances, mean; + size = hsArrays.size; + distances = (size - 1).collect({arg i; + ((i + 1)..(size - 1)).collect({arg j; + abs(hsArrays[i] - hsArrays[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsChordalDistance = { + arg hsArrays1, hsArrays2; + var size, distances, mean; + size = hsArrays1.size; + distances = hsArrays1.size.collect({arg i; + hsArrays2.size.collect({arg j; + abs(hsArrays1[i] - hsArrays2[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsArrayToFreq = { + arg array; + array.collect({arg dim, d; pow(primes[d][0]/primes[d][1], dim)}).product +}; + +//------score funcs + +/* +isInRange = { + arg hsArray, min, max; + var cents; + cents = hsArrayToCents.value(hsArray); + (cents >= min) && (cents <= max) +}; +*/ + +spacingScore = { + arg hsArrays, min; + var centsArray; + centsArray = hsArrays.collect({arg hsArray; hsArrayToCents.value(hsArray)}).sort({arg a, b; a < b}); + centsArray.differentiate.drop(1).collect({arg pDistance; if(pDistance >= min, {1}, {0.01})}).sum; +}; + +rangeScore = { + arg hsArray1, hsArray2, min, max, low, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + if((pDistance >= min) && (pDistance <= max), {1}, {low}); +}; + +/* +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + stepFunc.value(pDistance) +}; +*/ + +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = true; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + //if(pDistance >= 0, {stepFunc.value(abs(pDistance))}, {0.01}); + stepFunc.value(pDistance) +}; + +inclusionScore = { + arg array, test, min = 0.01; + if(array.collect({arg v; v.hash}).includes(test.hash), {min}, {1}); +}; + + +//------subroutines + +genTuples = { + var tuples; + tuples = dims.collect({[-1, 0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum <= 1) && (tuple[0] == 0)}); + //tuples = dims.collect({[0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum == 1) && (tuple[0] == 0)}); + tuples = tuples ++ tuples.collect({arg tuple; [-3, -2, -1, 1, 2, 3].collect({arg octTrans; tuple.deepCopy.put(0, octTrans)})}).flatten; +}; + +initVoices = { + var init, voicesInit; + voicesInit = popSize.collect({dims.collect({0})}); + /* + voicesInit = [dims.collect({0})]; + (popSize - 1).do({ + arg rep, new; + rep = dims.rand; + new = voicesInit.last.deepCopy; + new[rep] = new[rep] + [-1, 1].choose(); + voicesInit = voicesInit.add(new); + }); + */ + voicesInit.deepCopy; +}; + +genDurFunc = {arg chordProb, minPad, maxPad, minDur, maxDur, envData, seed; + var env, pTable, durFunc; + env = Env.pairs([[0, 0]] ++ envData.clump(2) ++ [[1, 0]]).asSignal(256).asList.asArray; + pTable = env.asRandomTable; + //[chordProb, minPad, maxPad, minDur, maxDur, envData].postln; + durFunc = {arg allowChord, pad = false; + var res; + res = if(allowChord.not, { + pTable.tableRand * (maxDur - minDur) + minDur + }, { + if(1.0.rand < chordProb, {0}, {pTable.tableRand * (maxDur - minDur) + minDur}); + }).round(0.125); + if(pad, {res = res + rrand(minPad.asFloat, maxPad.asFloat).round(0.125)}); + if(res.asInteger == res, {res = res.asInteger}); + res + }; + seedFunc.value(durFunc, seed); +}; + +genStepFunc = {arg minStep, maxStep, envData, seed; + var envDataNorm, env, pTable, stepFunc; + //[minStep, maxStep, envData].postln; + envDataNorm = ([[0, 0]] ++ envData.clump(2) ++ [[1, 0]]).flop; + envDataNorm = [envDataNorm[0].normalize(minStep, maxStep), envDataNorm[1]].flop; + env = Env.pairs(envDataNorm); + stepFunc = {arg pDist; + env.at(pDist).clip(0.001, 1); + }; + seedFunc.value(stepFunc, seed); +}; + +genOrders = {arg minMotifLength = 1, maxMotifLength = 5, minProgLength = 0, maxProgLength = 5; + ((maxMotifLength.asInteger - minMotifLength.asInteger).rand + minMotifLength.asInteger).collect({ + var noProgIns, noSusIns, noSilentIns, prog, sus, silent, order; + noSusIns = [1, 2, 3].wchoose(susWeights.normalizeSum); + + //noProgIns = (popSize - noSusIns).rand + 1; + noProgIns = (popSize - noSusIns); + noSilentIns = popSize - noSusIns - noProgIns; + + /* + noSilentIns = (popSize - noSusIns).rand.clip(0, 1); + noProgIns = popSize - noSusIns - noSilentIns; + */ + + # prog, sus, silent = (0..(popSize-1)).scramble.clumps([noProgIns, noSusIns, noSilentIns]); + + prog = (prog.scramble ++ ((maxProgLength.asInteger - minProgLength.asInteger).rand + minProgLength.asInteger).collect({prog.choose}).scramble); + //prog = ((maxProgLength.asInteger - minProgLength.asInteger).rand + minProgLength.asInteger).collect({prog.choose}); + + if(silent == nil, {silent = []}); + [sus.scramble, prog, silent.scramble] + }); +}; + +updateVoices = {arg ins, sus; + var voices, candidates, nWeights, nProbs, sel; + + voices = lastXChanges.deepCopy.last; + + candidates = sus.collect({arg v; tuples.collect({arg t; voices[v] + t})}).flatten; + candidates = difference(candidates.asSet, voices.asSet).asList; + //candidates.select({arg item; (item ++ voices).asSet.size >= 4}); + nProbs = candidates.collect({arg candidate; + var stepScore, recentlySoundedScore, isInRangeScore, regScore, hdScore; + + //stepScore = intervalScore.value(voices[ins], candidate, 30, 400, 0.1); + stepScore = intervalScore.value(voices[ins], candidate, 100, 100); + recentlySoundedScore = inclusionScore.value(lastXChanges.flop[ins], candidate, 0.01); + //recentlySoundedScore = inclusionScore.value(lastXChanges.flatten.collect({arg item; item.drop(1)}), candidate.drop(1), 0.01); + + /* + if(rangeScore.value(candidate.collect({0}), voices[ins], ranges[ins][1] - 500, ranges[ins][1], 0, true) == 1, { + isInRangeScore = 1 - rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0] + 500, ranges[ins][1] - 500, 0, true); + isInRangeScore = isInRangeScore * rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true) + }, { + isInRangeScore = rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true); + }); + */ + + + isInRangeScore = rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true); + //old way - worked but actually by accident (I think) + //isInRangeScore = rangeScore.value(candidate, candidate.collect({0}), ranges[ins][0], ranges[ins][1], 0, true); + + + regScore = spacingScore.value(voices.deepCopy.put(ins, candidate), 300); + hdScore = pow(hdSum.value(voices.deepCopy.put(ins, candidate)), hdExp); + if(hdInvert == 0, {hdScore = 1/hdScore}); + //maybe what you want here is a vector to another root and then favoring movement towards it. + //distScore = pow(hsChordalDistance.value(voices, voices.put(ins, candidate)), 2); + + [stepScore, recentlySoundedScore, isInRangeScore, regScore, hdScore] + }); + + nWeights = passagesWeights; + + //this handles nWeights of 0; mainly for testing + nProbs = nProbs.flop.select({arg scores, s; nWeights[s] != 0}).flop; + nWeights = nWeights.select({arg weight; weight != 0}); + nProbs = nProbs.flop.collect({arg scores, s; + if(scores.sum == 0, {scores}, {scores.normalizeSum * nWeights[s]}).postln; + }); + nProbs = nProbs.flop.collect({arg scores, s; scores.product}).normalizeSum; + + nProbs.round(0.001).postln; + [candidates, nProbs.round(0.001)].flop.postln; + + sel = candidates.wchoose(nProbs); + + voices[ins] = sel; + lastXChanges = lastXChanges.add(voices).keep(-5); +}; + +genSubMotif = {arg order, orderIndex, lastState, repeatLast = false, startFromLast = false, isLastOrder = false; + var sus, prog, silent, flatOrder, res, isInChord, allowChord, pad, lastXChangesHold, voices, adder; + # sus, prog, silent = order; + flatOrder = silent ++ sus ++ prog; + lastXChangesHold = lastXChanges.deepCopy; + voices = lastState.deepCopy; + isInChord = popSize.collect({false}); + allowChord = false; + pad = false; + res = []; + "------generating motif".postln; + //need to figure out here if voices move between motifs + flatOrder.do({arg ins, i; + + if(prog.includes(ins) && repeatLast.not, {updateVoices.value(ins, sus)}); + adder = if(silent.includes(ins), {["Rest"]}, {lastXChanges.last.deepCopy[ins]}); + + if(voices[ins] != adder, { + var dur; + + if((sus ++ silent).includes(ins), { + allowChord = (ins != sus.last); + pad = (ins == sus.last); + }, { + if(i < (flatOrder.size - 1), { + allowChord = (isInChord[flatOrder[i + 1]] || (ins == flatOrder[i + 1])).not; + pad = false; + }, { + allowChord = false; + pad = true + }); + }); + if((orderIndex == 0) && sus.includes(ins), { + dur = entrancesDurFunc.value(allowChord, pad); + }, { + dur = passagesDurFunc.value(allowChord, pad); + }); + if(dur == 0, {isInChord[ins] = true}, {isInChord = popSize.collect({false})}); + + voices[ins] = adder; + res = res.add([voices.deepCopy.postln, dur]); + }); + }); + + // pad ending + /* + if(orderIndex == (orders.size - 1), { + (0..(popSize-1)).scramble.do({arg ins; + if(res.last.first[ins] != ["Rest"], { + var dur; + voices[ins] = ["Rest"]; + allowChord = (voices != popSize.collect({["Rest"]})); + pad = allowChord.not; + dur = exitsDurFunc.value(allowChord, pad); + res = res.add([voices.deepCopy.postln, dur]); + }); + }); + }); + */ + + //format and return + if(startFromLast, {lastXChanges = lastXChangesHold.deepCopy}); + res; +}; + + +//------primary routines + +genMotif = { + var repeats, fSeq, fDur, durAdd; + + repeats = 1; + fSeq = []; + + repeats.do({arg index; + var motif; + + motif = []; + + orders.do({arg order, o; + var lastState, subMotif; + //lastState = if(o == 0, {popSize.collect({["Rest"]})}, {motif.last.last.first}); + lastState = if(o == 0, {lastXChanges.last.deepCopy}, {motif.last.last.first}); + subMotif = genSubMotif.value(order, o, lastState, isLastOrder: o == (orders.size - 1)); + motif = motif.add(subMotif); + + }); + + sanityCheck.value(motif, index); + + fSeq = fSeq.add(motif); + }); + + //round last duration to measure + /* + fDur = fSeq.flatten.flatten.slice(nil, 1).sum; + durAdd = fDur.round(4) - fDur; + if(durAdd < 0, {durAdd = 4 - durAdd}); + fSeq[0][orders.size - 1][fSeq[0][orders.size - 1].size - 1][1] = fSeq[0][orders.size - 1][fSeq[0][orders.size - 1].size - 1][1] + durAdd; + */ + + fSeq +}; + +genSecondarySeq = {arg seq; + var curdles, fSeq; + curdles = []; + while({curdles.sum < seq.size}, {curdles = curdles ++ [3.rand + 1]}); + + fSeq = seq.clumps(curdles).collect({arg clump, m; + var repeats, paddedSeq; + + //add rest + paddedSeq = clump.add([[[popSize.collect({["Rest"]}), 0.5.rand]]]); + + //implement repeats + repeats = [0.rand + 1, 1].wchoose([1, 0].normalizeSum); + repeats.collect({paddedSeq}); + }); + fSeq +}; + + +//------audition funcs + +/* +Event.addEventType(\osc, { + if (~addr.postln.notNil) { + ~addr.sendMsg(~indexPath, ~indexMsg); + ~addr.sendMsg(~seqPath, stringifyToDepth.value(~seqMsg, 3)); + //~addr.sendMsg("/STATE/OPEN", (dir.replace("supercollider", "resources") +/+ ~idMsg +/+ ~idMsg ++ "_gui_state" ++ ".state").standardizePath.postln); + }; +}); +*/ + +Event.addEventType(\osc, { + if (~addr.notNil) { + ~msg; + ~addr.sendMsg(~path, *~msg); + }; +}); + +genPatterns = {arg inSeq, addr, oneShot = false; + var voices, durs, pbinds, res, indices, sectionDurs, msg, ids, seq; + seq = inSeq.collect({arg mSeq; mSeq[0]}); + //# voices, durs = seq.flatten2(seq.maxDepth - 5).flop; + # voices, durs = seq.flatten2(if(oneShot, {2}, {3})).flop; + pbinds = voices.flop.collect({arg voice, v; + var clumps, hdScores, freqs, fDurs, attacks, rels, amps; + clumps = voice.separate({arg a, b; a != b }); + freqs = clumps.collect({arg clump; if(clump[0] != ["Rest"], {(62.midicps * hsArrayToFreq.value(clump[0]))}, {Rest(0)})}); + fDurs = durs.clumps(clumps.collect({arg clump; clump.size})).collect({arg clump; clump.sum}); + //attacks = 2.collect({rrand(1, 3)}) ++ freqs.drop(2).collect({rrand(0.3, 0.5)}); + attacks = fDurs.collect({arg dur; dur * rrand(0.2, 0.4)}); + //rels = freqs.drop(2).collect({rrand(0.3, 0.5)}) ++ 2.collect({rrand(1, 3)}); + rels = (clumps.size - 1).collect({arg c; + if(clumps[c + 1][0] == ["Rest"], {rrand(1.0, 3.0)}, {rrand(0.3, 0.5)}); + }); + rels = rels.add(rrand(1.0, 3.0)); + amps = freqs.collect({rrand(0.6, 0.99)}); + + [ + Pbind( + \instrument, \string_model, + \group, group, + \freq, Pseq(freqs, 1), + \dur, Pseq(fDurs, 1), + \attack, Pseq(attacks, 1), + \sustain, Pseq(fDurs, 1), + \release, Pseq(rels, 1), + //\amp, Pseq(amps, 1), + \amp, Pbrown(0.5, 1, 0.5), + \busIndex, v + ), + Pbind( + \instrument, \sine, + \group, group, + \freq, Pseq(freqs, 1), + \dur, Pseq(fDurs, 1), + \sustain, Pseq(fDurs, 1), + \busIndex, v + ) + ] + }).flatten; + if(oneShot.not, { + msg = inSeq.collect({arg mSeq, m; mSeq[1..]}); + //ids = inSeq.collect({arg mSeq, m; mSeq[2]}); + sectionDurs = seq.collect({arg mSeq; mSeq.flatten2(2).flop[1].sum}); + pbinds = pbinds ++ + [ + Pbind( + \type, \osc, + \addr, addr, + \path, "/playing", + \msg, Pseq(msg, 1), + \dur, Pseq(sectionDurs, 1) + ); + ] + }); + res = Ppar(pbinds); + res +}; + +/* +genMidiPatterns = {arg seq; + var voices, durs, patterns, res, mOut, pbRange; + pbRange = 1; //semitones - change this as needed for your situation + mOut = MIDIOut.newByName("TiMidity", "TiMidity port 0").latency_(Server.default.latency); + # voices, durs = seq.flatten2(seq.maxDepth - 5).flop; + res = Ppar( + voices.flop.collect({arg voice, v; + var clumps, hdScores, freqs, fDurs; + + mOut.program(v, 70); + + clumps = voice.separate({arg a, b; a != b }); + freqs = clumps.collect({arg clump; if(clump[0] != ["Rest"], {(60.midicps * hsArrayToFreq.value(clump[0]))}, {Rest(0)})}); + fDurs = durs.clumps(clumps.collect({arg clump; clump.size})).collect({arg clump; clump.sum}); + + Pbind( + \type, \midi, + \chan, v, + \noteval, Pseq(freqs.cpsmidi - 24, 1), + \note, Pfunc({ | event | event[\noteval].floor }), + \dur, Pseq(fDurs, 1), + \midiout, mOut, + \amp, 1, + \bend, Pfunc({ + | event | + if (event[\note].isRest.not) { + var pitchbendvalue = event[\noteval].frac.linlin(0, pbRange, 8192, 8192*2).asInteger; + m.bend(v, pitchbendvalue); + }; + 0; // return something other than nil to avoid stopping the pattern + }), + ); + }); + ); + res +}; +*/ + + +//------resource management funcs + +genUID = {Date.seed.asHexString.toLower}; + +seedFunc = {arg func, seed; + var funcArgs, next; + next = Routine({loop{func.valueArray(funcArgs).yield }}); + next.randSeed_(seed); + {arg ...args; funcArgs = args; next.value} +}; + +stringifyToDepth = {arg data, maxDepth = 1; + var prettyString = "", rCount = 0, writeArray, indent; + + if(maxDepth == 0, { + data.asCompileString + }, { + indent = {arg size; size.collect({" "}).join("")}; + writeArray = {arg array; + prettyString = prettyString ++ indent.value(rCount) ++ "[\n"; + rCount = rCount + 1; + if(rCount < maxDepth, { + array.do({arg subArray; writeArray.value(subArray)}); + }, { + prettyString = prettyString ++ array.collect({arg subArray; + indent.value(rCount + 1) ++ subArray.asCompileString + }).join(",\n"); + }); + rCount = rCount - 1; + prettyString = prettyString ++ "\n" ++ indent.value(rCount) ++ "],\n"; + }; + + writeArray.value(data); + prettyString.replace(",\n\n", "\n").drop(-2); + }) +}; + +sanityCheck = {arg motif, index; + //print functions = very helpful + ("----------" + index + "------------").postln; + + motif.flatten.do({arg val, v; + if(v > 0, { + if(motif.flatten[v-1][0].hammingDistance(val[0]) > 1, {"problem 1".postln}); + if(motif.flatten[v-1][0].hammingDistance(val[0]) == 0, {"problem 2".postln}); + }); + val.postln + }); + "***********".postln; +}; + +msgInterpret = {arg in, escapeDoubleQuotes = true, escapeSingleQuotes = true; + var res; + + res = in; + if(res.isNil.not, { + if((res.isArray && res.isString.not), { + res = res.asCompileString; + res = res.replace(" ", "").replace("\n", "").replace("\t", ""); + if(escapeSingleQuotes, {res = res.replace("\'", "")}); + if(escapeDoubleQuotes, {res = res.replace("\"", "")}); + res = res.replace("Rest", "\"Rest\""); + res = res.interpret; + }, { + var tmpRes; + if(res.every({arg char; char.isDecDigit}), {tmpRes = res.asInteger}); + if(res.contains("."), {tmpRes = res.asFloat}); + if(tmpRes != nil, {res = tmpRes}); + }); + }); + res +}; + +writeResources = {arg path, dict; + var file, modelItems, resString; + file = File(path,"w"); + + modelItems = [ + seq, lastXChanges, + curUID, refUID, orderSeed, durSeed, motifSeed, + entrancesProbVals, passagesProbVals, exitsProbVals, + ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, + orders, susWeights, orderSize, passagesSize, + motifEdited, orderEdited + ]; + + resString = nameSpaces.collect({arg nameSpace; + var depth = 0, insert = " "; + if(nameSpace == "music_data", {depth = 3; insert = "\n"}); + if(nameSpace == "last_changes", {depth = 1; insert = "\n"}); + if(nameSpace == "order", {depth = 1; insert = "\n"}); + if((nameSpace == "ref_uid") && (dict[nameSpace] == nil), {dict[nameSpace] = "nil"}); + "\"" ++ nameSpace ++ "\":" ++ insert ++ stringifyToDepth.value(dict[nameSpace], depth) + }).join(",\n"); + + resString = "{\n" ++ resString ++ "\n}"; + + file.write(resString); + file.close; + resString +}; + +loadModelFile = {arg path; loadModelJSON.value(File(path, "r").readAllString.parseJSON)}; + +loadModelJSON = {arg jsonObject; + var dict; + dict = Dictionary.with(*nameSpaces.collect({arg nS; nS->msgInterpret.value(jsonObject[nS])})); + dict +}; + +setGlobalVars = {arg dict, skipLastXChanges = false; + var tmpLastXChanges; + tmpLastXChanges = lastXChanges.deepCopy; + // order really matters!!!! + # seq, lastXChanges, curUID, refUID, orderSeed, durSeed, motifSeed, + entrancesProbVals, passagesProbVals, exitsProbVals, + ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, + orders, susWeights, orderSize, passagesSize, + motifEdited, orderEdited = nameSpaces.collect({arg nS; dict[nS]}); + if(skipLastXChanges, {lastXChanges = tmpLastXChanges}); + dict +}; + +globalVarsToDict = { + var modelItems, dict; + // order really matters!!!! + modelItems = [ + seq, lastXChanges, + curUID, refUID, orderSeed, durSeed, motifSeed, + entrancesProbVals, passagesProbVals, exitsProbVals, + ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, + orders, susWeights, orderSize, passagesSize, + motifEdited, orderEdited + ]; + dict = Dictionary.with(*nameSpaces.collect({arg nS, n; nS->modelItems[n]})); +}; + +loadLedgerFile = {arg path; + ledgerPath = path; + resourceDir = path.splitext(".").drop(-1).join; + loadLedgerJSON.value(File(ledgerPath, "r").readAllString.parseJSON) +}; + +loadLedgerJSON = {arg jsonObject; ledger = jsonObject["ledger"]}; + +saveLedger = {arg ledger, path; + var file, curResourceDir; + file = File(path, "w"); + curResourceDir = resourceDir; + resourceDir = path.splitext(".").drop(-1).join; + if(curResourceDir != resourceDir, { + File.mkdir(resourceDir); + ledger.do({arg id; + File.copy(curResourceDir +/+ id, resourceDir +/+ id); + }); + }); + file.write("{\n\"ledger\":\n" ++ stringifyToDepth.value(ledger, 1) ++ "\n}"); + file.close; +}; + +//------global vars + +primes = [[2, 1], [3, 2], [5, 4], [7, 4], [11, 8], [13, 8]]; +//ranges = [[-2400, 0], [-1200, 1200], [0, 2400], [0, 2400]]; +exPath = thisProcess.nowExecutingPath; +dir = exPath.dirname; +//popSize = 4; +dims = primes.size; +tuples = genTuples.value(); +//refUID = nil; +group = Group.new; +~group = group; +loadLedgerFile.value(dir +/+ ".." +/+ "resources" +/+ "piece_ledger.json"); +resourceDir = (dir +/+ ".." +/+ "resources" +/+ "piece_ledger"); +//passagesWeights = [1, 1, 1, 1, 1]; +//susWeights = [1, 1, 1]; +// order really matters!!!! +nameSpaces = [ + "music_data", "last_changes", "cur_uid", "ref_uid", "order_seed", "dur_seed", "motifs_seed", + "entrances_probs_vals","passages_probs_vals", "exits_probs_vals", + "ranges", "step_probs_vals", "passages_weights", "hd_exp", "hd_invert", + "order", "sus_weights", "order_size", "passages_size", + "motif_edited", "order_edited" +]; + + +//------OSC funcs + +OSCdef(\load_ledger, {arg msg, time, addr, port; + loadLedgerFile.value(msg[1].asString); +}, \load_ledger); + +OSCdef(\load_model, {arg msg, time, addr, port; + var dict; + dict = loadModelFile.value(msg[1].asString); + setGlobalVars.value(dict); +}, \load_model); + +OSCdef(\save_ledger, {arg msg, time, addr, port; + msg.postln; + ledger = msgInterpret.value(msg[1].asString.parseJSON["ledger"], false).postln; + //loadLedgerJSON.value(msg[0]) + saveLedger.value(ledger, msg[2].asString); + //loadLedgerFile.value(msg[1].asString); +}, \save_ledger); + +OSCdef(\generate, {arg msg, time, addr, port; + var path, dict, durSeeds, musPath, modelString; + msg.postln; + + path = msg[1].asString; + + dict = loadModelFile.value(path); + setGlobalVars.value(dict, true); + + popSize = ranges.size; + + //refUID.postln; + + loadLedgerFile.value(ledgerPath); + if(ledger == nil, {ledger = ["tmp"]}); + if(ledger.last != "tmp", {ledger = ledger.add("tmp")}); + + if(refUID == nil, {lastXChanges = [initVoices.value().deepCopy]}); + if((refUID != nil) && (refUID != "tmp"), { + var file; + file = File((resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath, "r"); + lastXChanges = msgInterpret.value(file.readAllString.parseJSON["last_changes"]); + }); + + refUID.postln; + lastXChanges.collect({arg item; item.postln}); + + durSeeds = seedFunc.value({3.collect({rrand(100000, 999999)})}, durSeed).value.postln; + entrancesDurFunc = genDurFunc.valueArray(entrancesProbVals[..4] ++ [entrancesProbVals[5..]] ++ [durSeeds[0]]); + passagesDurFunc = genDurFunc.valueArray(passagesProbVals[..4] ++ [passagesProbVals[5..]] ++ [durSeeds[1]]); + exitsDurFunc = genDurFunc.valueArray(exitsProbVals[..4] ++ [exitsProbVals[5..]] ++ [durSeeds[2]]); + + if(orders == nil, { + orders = seedFunc.value(genOrders, orderSeed).valueArray(orderSize ++ passagesSize); + //addr.sendMsg("/order", stringifyToDepth.value(orders, 1)); + }); + + stepFunc = genStepFunc.valueArray(stepProbsVals[..1] ++ [stepProbsVals[2..]] ++ [motifSeed]); + seq = seedFunc.value(genMotif, motifSeed).value; + + lastXChanges.collect({arg item; item.postln}); + + dict = globalVarsToDict.value; + modelString = writeResources.value(path, dict); + + //addr.sendMsg("/generated", musPath, stringifyToDepth.value(seq, 3)); + //~seq = seq; + + addr.sendMsg("/generated", path, modelString, ledgerPath); +}, \generate); + + +OSCdef(\commit, {arg msg, time, addr, port; + var musicData, musicChanged, dict, newLedger, modelPath, musString, musFile, test1, test2, lastCurUID, commitType, commitPos, equalityLedger; + //msg.postln; + + /* + test1 = msg[1].asString.parseJSON; + test2 = (dir +/+ ".." +/+ "resources/tmp/tmp_music" ++ ".json").standardizePath.parseJSONFile; + msgInterpret.value(test1["music"])[0][0][0][1].class.postln; + msgInterpret.value(test2["music_data"])[0][0][0][1].class.postln; + (test1["music"] == test2["music_data"]).postln; + */ + + musicData = loadModelJSON.value(msg[1].asString.parseJSON)["music_data"].postln; + musicChanged = (musicData != seq).postln; + commitType = msg[2].asString; + commitPos = msg[3].postln.asInteger; + + lastCurUID = curUID.deepCopy; + curUID = genUID.value; + + File.mkdir((resourceDir +/+ curUID).standardizePath); + File.copy(exPath, (resourceDir +/+ curUID +/+ curUID ++ "_code" ++ ".scd").standardizePath); + + modelPath = (resourceDir +/+ curUID +/+ curUID ++ "_mus_model" ++ ".json").standardizePath; + dict = globalVarsToDict.value; + if(musicChanged, { + seq = musicData; + dict["music_data"] = seq; + dict["motif_edited"] = "true" + }); + dict["cur_uid"] = curUID; + + writeResources.value(modelPath, dict); + + File.delete(ledgerPath ++ "_bak"); + File.copy(ledgerPath, ledgerPath ++ "_bak"); + File.delete(ledgerPath); + + /* + if(commitType == "add", { + if(lastCurUID == "tmp", { + ledger = ledger.drop(-1).add(curUID); + }, { + ledger = ledger.add(curUID); + }) + }); + */ + + ledger.postln; + + if(commitType == "add", {ledger = ledger.add(curUID)}); + + if(commitType == "insert", {ledger = ledger.insert(commitPos + 1, curUID)}); + + if(commitType == "replace", {ledger = ledger.put(commitPos, curUID)}); + + equalityLedger = ledger.collect({arg item; item.asSymbol}); + if(equalityLedger.includes(\tmp).postln, {ledger.removeAt(equalityLedger.indexOf(\tmp).postln)}); + + ledger.postln; + + saveLedger.value(ledger, ledgerPath); + + addr.sendMsg("/committed", curUID, ledgerPath); + //refUID = curUID; + +}, \commit); + +OSCdef(\transport, {arg msg, time, addr, port; + msg.postln; + if(msg[1] == 0, { + group.set(\release, 2); + group.set(\gate, 0); + player.stop; + }, { + // the cued sequence can now be read from file, so this can be cleaned up + var cSize, patterns, pSeq, cuedSeek, indexStart, indexEnd, tmpLedger; + if(msg[1] == 1, { + pSeq = []; + cuedSeek = (seq != nil); + indexStart = msg[2].asInteger; + indexEnd = ledger.size - if(cuedSeek, {2}, {1}); + //ledger.postln; + if(((indexStart == (ledger.size - 1)) && cuedSeek).not, { + ledger[indexStart..indexEnd].do({arg uid, index; + var path, file; + path = (resourceDir +/+ uid +/+ uid ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.postln.parseJSON["music_data"]), path, indexStart + index, uid]); + file.close; + }); + }); + if(cuedSeek, { + var path, file; + path = (resourceDir +/+ "tmp/tmp_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.parseJSON["music_data"]), path, ledger.size - 1, "tmp"]); + file.close; + }); + patterns = genPatterns.value(pSeq, addr); + }, { + pSeq = [loadModelJSON.value(msg[2].asString.parseJSON)["music_data"].postln]; + patterns = genPatterns.value(pSeq, addr, true); + }); + player = Pfset(pattern: patterns, cleanupFunc: { + addr.sendMsg("/transport", 0); + addr.sendMsg("/one_shot", 0); + }); + player = player.play + }); +}, \transport); + + +OSCdef(\transcribe_motif, {arg msg, time, addr, port; + var tSeq, refChord, refUID; + + msg.postln; + + tSeq = [loadModelJSON.value(msg[1].asString.parseJSON)["music_data"]]; + refUID = msg[2].asString.postln; + + if((refUID != "nil") && (refUID != "tmp"), { + var file; + file = File((resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath, "r"); + refChord = msgInterpret.value(file.readAllString.parseJSON["last_changes"]).last; + file.close; + }, { + refChord = [[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0]]; + }); + + ~transcribe.value(tSeq, refChord, (dir +/+ ".." +/+ "lilypond" +/+ "includes").standardizePath, addr, "/transcribe_motif"); +}, \transcribe_motif); + + +OSCdef(\transcribe_all, {arg msg, time, addr, port; + var cSize, patterns, cuedSeek, indexStart, indexEnd, tmpLedger; + if(true, { + cuedSeek = (seq != nil); + indexStart = msg[1].asInteger; + indexEnd = ledger.size - if(cuedSeek, {2}, {1}); + + //tmp for testing transcription + //indexEnd = (indexStart+5); + + //ledger.postln; + if(((indexStart == (ledger.size - 1)) && cuedSeek).not, { + var lilyPartLedgerFiles; + + lilyPartLedgerFiles = 4.collect({arg p; + File((dir +/+ ".." +/+ "lilypond" +/+ "includes" +/+ "part_" ++ ["IV", "III", "II", "I"][p] ++ ".ly").standardizePath, "w"); + }); + + ledger[indexStart..indexEnd].do({arg uid, index; + var path, file, fileString, tSeq, refUID, refChord; + path = (resourceDir +/+ uid +/+ uid ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + fileString = file.readAllString; + tSeq = msgInterpret.value(fileString.parseJSON["music_data"]); + refUID = msgInterpret.value(fileString.parseJSON["ref_uid"]); + file.close; + + //uid.postln; + //(refUID == "nil").postln; + + refChord = [[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0]]; + + if(refUID != "nil", { + path = (resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + refChord = msgInterpret.value(file.readAllString.parseJSON["last_changes"]).last; + file.close; + }); + + if(index != indexEnd, { + ~transcribe.value(tSeq, refChord, (resourceDir +/+ uid +/+ "lilypond").standardizePath); + }, { + ~transcribe.value(tSeq, refChord, (resourceDir +/+ uid +/+ "lilypond").standardizePath, addr, "/transcribe_all"); + }); + + lilyPartLedgerFiles.do({arg f, p; + f.write("\\include \"" ++ resourceDir +/+ uid +/+ "lilypond" +/+ "part_" ++ ["IV", "III", "II", "I"][p] ++ ".ly\"\n"); + }); + + }); + + lilyPartLedgerFiles.do({arg f; + f.close + }); + }); + /* + if(cuedSeek, { + var path, file; + path = (dir +/+ ".." +/+ "resources/tmp/tmp_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.parseJSON["music_data"]), path, ledger.size - 1, "tmp"]); + file.close; + }); + */ + }, { + + }); + +}, \transcribe_all); + +) + + diff --git a/resources/string_quartet_3_rise/61207e49/61207e49_mus_model.json b/resources/string_quartet_3_rise/61207e49/61207e49_mus_model.json new file mode 100644 index 0000000..55a3e7c --- /dev/null +++ b/resources/string_quartet_3_rise/61207e49/61207e49_mus_model.json @@ -0,0 +1,141 @@ +{ +"music_data": +[ + [ + [ + [ [ [ -6, 6, -1, 1, -2, 2 ], [ -5, 7, -1, 1, -2, 2 ], [ -4, 5, -1, 1, -2, 2 ], [ -5, 6, -1, 1, -2, 2 ] ], 0.25 ], + [ [ [ -6, 6, -1, 1, -2, 2 ], [ -4, 6, -2, 1, -2, 2 ], [ -4, 5, -1, 1, -2, 2 ], [ -5, 6, -1, 1, -2, 2 ] ], 0.25 ], + [ [ [ -6, 6, -1, 1, -2, 2 ], [ -5, 6, -1, 2, -2, 2 ], [ -4, 5, -1, 1, -2, 2 ], [ -5, 6, -1, 1, -2, 2 ] ], 0.25 ], + [ [ [ -6, 6, -1, 1, -2, 2 ], [ -4, 6, -1, 1, -2, 2 ], [ -4, 5, -1, 1, -2, 2 ], [ -5, 6, -1, 1, -2, 2 ] ], 0.25 ] + ], + [ + [ [ [ -6, 6, -1, 1, -2, 2 ], [ -4, 6, -1, 1, -2, 2 ], [ -4, 5, -1, 1, -2, 2 ], [ -3, 6, -1, 1, -2, 2 ] ], 0 ], + [ [ [ -5, 6, -1, 1, -2, 2 ], [ -4, 6, -1, 1, -2, 2 ], [ -4, 5, -1, 1, -2, 2 ], [ -3, 6, -1, 1, -2, 2 ] ], 0 ], + [ [ [ -5, 6, -1, 1, -2, 2 ], [ -4, 6, -1, 1, -2, 2 ], [ -5, 7, -1, 1, -2, 2 ], [ -3, 6, -1, 1, -2, 2 ] ], 0.25 ], + [ [ [ -5, 6, -1, 1, -2, 2 ], [ -4, 6, -1, 1, -2, 2 ], [ -4, 6, -2, 1, -2, 2 ], [ -3, 6, -1, 1, -2, 2 ] ], 0.25 ], + [ [ [ -5, 6, -1, 1, -2, 2 ], [ -4, 6, -1, 1, -2, 2 ], [ -5, 6, -1, 2, -2, 2 ], [ -3, 6, -1, 1, -2, 2 ] ], 0.25 ], + [ [ [ -5, 6, -1, 1, -2, 2 ], [ -4, 6, -1, 1, -2, 2 ], [ -4, 7, -1, 1, -2, 2 ], [ -3, 6, -1, 1, -2, 2 ] ], 0.25 ] + ], + [ + [ [ [ -5, 6, -1, 1, -2, 2 ], [ -4, 6, -1, 1, -2, 2 ], [ -4, 7, -1, 1, -2, 2 ], [ -5, 7, -1, 1, -2, 2 ] ], 0 ], + [ [ [ -5, 6, -1, 1, -2, 2 ], [ -4, 6, -1, 1, -2, 2 ], [ -4, 6, -1, 1, -2, 3 ], [ -5, 7, -1, 1, -2, 2 ] ], 0.25 ] + ], + [ + [ [ [ -5, 6, -1, 1, -2, 2 ], [ -4, 6, -1, 1, -2, 2 ], [ -4, 7, -1, 1, -2, 2 ], [ -5, 7, -1, 1, -2, 2 ] ], 0 ], + [ [ [ -5, 6, -1, 1, -2, 2 ], [ -5, 8, -1, 1, -2, 2 ], [ -4, 7, -1, 1, -2, 2 ], [ -5, 7, -1, 1, -2, 2 ] ], 0 ], + [ [ [ -6, 8, -1, 1, -2, 2 ], [ -5, 8, -1, 1, -2, 2 ], [ -4, 7, -1, 1, -2, 2 ], [ -5, 7, -1, 1, -2, 2 ] ], 0.25 ], + [ [ [ -5, 7, -2, 1, -2, 2 ], [ -5, 8, -1, 1, -2, 2 ], [ -4, 7, -1, 1, -2, 2 ], [ -5, 7, -1, 1, -2, 2 ] ], 0 ], + [ [ [ -5, 7, -2, 1, -2, 2 ], [ -4, 7, -2, 1, -2, 2 ], [ -4, 7, -1, 1, -2, 2 ], [ -5, 7, -1, 1, -2, 2 ] ], 0.25 ], + [ [ [ -5, 7, -2, 1, -2, 2 ], [ -5, 7, -1, 2, -2, 2 ], [ -4, 7, -1, 1, -2, 2 ], [ -5, 7, -1, 1, -2, 2 ] ], 0.25 ], + [ [ [ -5, 7, -2, 1, -2, 2 ], [ -5, 7, -1, 1, -2, 3 ], [ -4, 7, -1, 1, -2, 2 ], [ -5, 7, -1, 1, -2, 2 ] ], 0.25 ] + ], + [ + [ [ [ -5, 7, -2, 1, -2, 2 ], [ -4, 6, -1, 1, -2, 2 ], [ -4, 7, -1, 1, -2, 2 ], [ -5, 7, -1, 1, -2, 2 ] ], 0.25 ], + [ [ [ -5, 7, -2, 1, -2, 2 ], [ -4, 6, -1, 1, -2, 2 ], [ -3, 7, -1, 0, -2, 2 ], [ -5, 7, -1, 1, -2, 2 ] ], 0 ], + [ [ [ -6, 8, -1, 1, -2, 2 ], [ -4, 6, -1, 1, -2, 2 ], [ -3, 7, -1, 0, -2, 2 ], [ -5, 7, -1, 1, -2, 2 ] ], 0.25 ], + [ [ [ -6, 8, -1, 1, -2, 2 ], [ -4, 6, -1, 1, -2, 2 ], [ -4, 7, 0, 1, -2, 2 ], [ -5, 7, -1, 1, -2, 2 ] ], 0.25 ], + [ [ [ -6, 8, -1, 1, -2, 2 ], [ -4, 6, -1, 1, -2, 2 ], [ -5, 8, -1, 1, -2, 2 ], [ -5, 7, -1, 1, -2, 2 ] ], 0 ], + [ [ [ -6, 8, -1, 1, -2, 2 ], [ -5, 7, 0, 1, -2, 2 ], [ -5, 8, -1, 1, -2, 2 ], [ -5, 7, -1, 1, -2, 2 ] ], 0.25 ], + [ [ [ -6, 8, -1, 1, -2, 2 ], [ -5, 7, 0, 1, -2, 2 ], [ -5, 7, -1, 1, -2, 3 ], [ -5, 7, -1, 1, -2, 2 ] ], 0.25 ] + ], + [ + [ [ [ -6, 8, -1, 1, -2, 2 ], [ -6, 8, -1, 1, -2, 3 ], [ -5, 7, -1, 1, -2, 3 ], [ -5, 7, -1, 1, -2, 2 ] ], 0 ], + [ [ [ -6, 8, -1, 1, -2, 2 ], [ -6, 8, -1, 1, -2, 3 ], [ -5, 8, -1, 1, -2, 2 ], [ -5, 7, -1, 1, -2, 2 ] ], 0.25 ], + [ [ [ -6, 8, -1, 1, -2, 2 ], [ -6, 9, -1, 1, -2, 2 ], [ -5, 8, -1, 1, -2, 2 ], [ -5, 7, -1, 1, -2, 2 ] ], 0.25 ] + ], + [ + [ [ [ -6, 8, -1, 1, -2, 2 ], [ -6, 9, -1, 1, -2, 2 ], [ -4, 7, -2, 1, -2, 2 ], [ -5, 7, -1, 1, -2, 2 ] ], 0 ], + [ [ [ -6, 8, -1, 1, -2, 2 ], [ -4, 7, -1, 1, -2, 1 ], [ -4, 7, -2, 1, -2, 2 ], [ -5, 7, -1, 1, -2, 2 ] ], 0 ], + [ [ [ -5, 7, -2, 1, -2, 2 ], [ -4, 7, -1, 1, -2, 1 ], [ -4, 7, -2, 1, -2, 2 ], [ -5, 7, -1, 1, -2, 2 ] ], 0.25 ] + ], + [ + [ [ [ -6, 7, -1, 1, -1, 2 ], [ -4, 7, -1, 1, -2, 1 ], [ -4, 7, -2, 1, -2, 2 ], [ -5, 7, -1, 1, -2, 2 ] ], 0 ], + [ [ [ -6, 7, -1, 1, -1, 2 ], [ -5, 7, -1, 1, -1, 2 ], [ -4, 7, -2, 1, -2, 2 ], [ -5, 7, -1, 1, -2, 2 ] ], 0.25 ], + [ [ [ -6, 7, -1, 1, -1, 2 ], [ -5, 8, -1, 1, -2, 2 ], [ -4, 7, -2, 1, -2, 2 ], [ -5, 7, -1, 1, -2, 2 ] ], 0 ], + [ [ [ -5, 6, -1, 1, -2, 2 ], [ -5, 8, -1, 1, -2, 2 ], [ -4, 7, -2, 1, -2, 2 ], [ -5, 7, -1, 1, -2, 2 ] ], 0.25 ] + ], + [ + [ [ [ -5, 6, -1, 1, -2, 2 ], [ -3, 6, -1, 1, -2, 1 ], [ -4, 7, -2, 1, -2, 2 ], [ -5, 7, -1, 1, -2, 2 ] ], 0 ], + [ [ [ -5, 6, -1, 1, -2, 2 ], [ -3, 6, -1, 1, -2, 1 ], [ -4, 7, -2, 1, -2, 2 ], [ -5, 6, -1, 1, -2, 3 ] ], 0 ], + [ [ [ -5, 6, -1, 1, -2, 2 ], [ -3, 6, -1, 1, -2, 1 ], [ -3, 5, -1, 1, -2, 2 ], [ -5, 6, -1, 1, -2, 3 ] ], 0.25 ], + [ [ [ -5, 6, -1, 1, -2, 2 ], [ -3, 6, -1, 1, -2, 1 ], [ -4, 7, -1, 1, -2, 2 ], [ -5, 6, -1, 1, -2, 3 ] ], 0 ], + [ [ [ -5, 6, -1, 1, -2, 2 ], [ -3, 6, -1, 1, -2, 1 ], [ -4, 7, -1, 1, -2, 2 ], [ -5, 6, 0, 1, -2, 2 ] ], 0.25 ], + [ [ [ -5, 6, -1, 1, -2, 2 ], [ -3, 6, -1, 1, -2, 1 ], [ -3, 6, -2, 1, -2, 2 ], [ -5, 6, 0, 1, -2, 2 ] ], 0.25 ], + [ [ [ -5, 6, -1, 1, -2, 2 ], [ -3, 5, -1, 1, -2, 2 ], [ -3, 6, -2, 1, -2, 2 ], [ -5, 6, 0, 1, -2, 2 ] ], 0.25 ] + ], + [ + [ [ [ -5, 6, -1, 1, -2, 2 ], [ -3, 5, -1, 1, -2, 2 ], [ -3, 6, -1, 1, -3, 2 ], [ -5, 6, 0, 1, -2, 2 ] ], 0.25 ] + ], + [ + [ [ [ -5, 6, -1, 1, -2, 2 ], [ -4, 6, 0, 1, -2, 2 ], [ -3, 6, -1, 1, -3, 2 ], [ -5, 6, 0, 1, -2, 2 ] ], 0 ], + [ [ [ -5, 6, -1, 1, -2, 2 ], [ -4, 6, 0, 1, -2, 2 ], [ -3, 6, -1, 1, -3, 2 ], [ -4, 6, -2, 1, -3, 2 ] ], 0.25 ], + [ [ [ -5, 6, -1, 1, -2, 2 ], [ -4, 6, 0, 1, -2, 2 ], [ -3, 6, -1, 1, -3, 2 ], [ -5, 7, -1, 1, -3, 2 ] ], 0.25 ], + [ [ [ -5, 6, -1, 1, -2, 2 ], [ -4, 7, -1, 1, -2, 2 ], [ -3, 6, -1, 1, -3, 2 ], [ -5, 7, -1, 1, -3, 2 ] ], 0.25 ], + [ [ [ -5, 6, -1, 1, -2, 2 ], [ -4, 5, -1, 1, -2, 2 ], [ -3, 6, -1, 1, -3, 2 ], [ -5, 7, -1, 1, -3, 2 ] ], 0.25 ] + ], + [ + [ [ [ -4, 5, -1, 1, -3, 2 ], [ -4, 5, -1, 1, -2, 2 ], [ -3, 6, -1, 1, -3, 2 ], [ -5, 7, -1, 1, -3, 2 ] ], 0 ], + [ [ [ -4, 5, -1, 1, -3, 2 ], [ -4, 5, -1, 1, -2, 2 ], [ -3, 5, -1, 1, -3, 2 ], [ -5, 7, -1, 1, -3, 2 ] ], 0 ], + [ [ [ -4, 5, -1, 1, -3, 2 ], [ -4, 5, -1, 1, -2, 2 ], [ -3, 5, -1, 1, -3, 2 ], [ -5, 6, -1, 1, -2, 2 ] ], 0.25 ], + [ [ [ -4, 5, -1, 1, -3, 2 ], [ -4, 5, -1, 1, -2, 2 ], [ -3, 4, -1, 1, -2, 2 ], [ -5, 6, -1, 1, -2, 2 ] ], 0 ], + [ [ [ -4, 4, -1, 1, -2, 2 ], [ -4, 5, -1, 1, -2, 2 ], [ -3, 4, -1, 1, -2, 2 ], [ -5, 6, -1, 1, -2, 2 ] ], 0 ], + [ [ [ -4, 4, -1, 1, -2, 2 ], [ -4, 5, -1, 1, -2, 2 ], [ -3, 4, -1, 1, -2, 2 ], [ -3, 5, -1, 1, -2, 2 ] ], 0.25 ] + ], + [ + [ [ [ -5, 5, -1, 1, -2, 2 ], [ -4, 5, -1, 1, -2, 2 ], [ -3, 4, -1, 1, -2, 2 ], [ -3, 5, -1, 1, -2, 2 ] ], 0.25 ], + [ [ [ -5, 5, -1, 1, -2, 2 ], [ -4, 5, -1, 1, -2, 2 ], [ -4, 6, -1, 1, -2, 2 ], [ -3, 5, -1, 1, -2, 2 ] ], 0 ], + [ [ [ -4, 5, -1, 0, -2, 2 ], [ -4, 5, -1, 1, -2, 2 ], [ -4, 6, -1, 1, -2, 2 ], [ -3, 5, -1, 1, -2, 2 ] ], 0.25 ], + [ [ [ -5, 5, 0, 1, -2, 2 ], [ -4, 5, -1, 1, -2, 2 ], [ -4, 6, -1, 1, -2, 2 ], [ -3, 5, -1, 1, -2, 2 ] ], 0.25 ], + [ [ [ -5, 6, -1, 1, -2, 2 ], [ -4, 5, -1, 1, -2, 2 ], [ -4, 6, -1, 1, -2, 2 ], [ -3, 5, -1, 1, -2, 2 ] ], 0.25 ], + [ [ [ -4, 5, -2, 1, -2, 2 ], [ -4, 5, -1, 1, -2, 2 ], [ -4, 6, -1, 1, -2, 2 ], [ -3, 5, -1, 1, -2, 2 ] ], 0.25 ] + ], + [ + [ [ [ -4, 5, -2, 1, -2, 2 ], [ -4, 5, -1, 1, -2, 2 ], [ -3, 5, -2, 1, -2, 2 ], [ -3, 5, -1, 1, -2, 2 ] ], 0.25 ], + [ [ [ -5, 5, -1, 2, -2, 2 ], [ -4, 5, -1, 1, -2, 2 ], [ -3, 5, -2, 1, -2, 2 ], [ -3, 5, -1, 1, -2, 2 ] ], 0.25 ] + ] + ] +], +"last_changes": +[ + [ [ -5, 5, 0, 1, -2, 2 ], [ -4, 5, -1, 1, -2, 2 ], [ -4, 6, -1, 1, -2, 2 ], [ -3, 5, -1, 1, -2, 2 ] ], + [ [ -5, 6, -1, 1, -2, 2 ], [ -4, 5, -1, 1, -2, 2 ], [ -4, 6, -1, 1, -2, 2 ], [ -3, 5, -1, 1, -2, 2 ] ], + [ [ -4, 5, -2, 1, -2, 2 ], [ -4, 5, -1, 1, -2, 2 ], [ -4, 6, -1, 1, -2, 2 ], [ -3, 5, -1, 1, -2, 2 ] ], + [ [ -4, 5, -2, 1, -2, 2 ], [ -4, 5, -1, 1, -2, 2 ], [ -3, 5, -2, 1, -2, 2 ], [ -3, 5, -1, 1, -2, 2 ] ], + [ [ -5, 5, -1, 2, -2, 2 ], [ -4, 5, -1, 1, -2, 2 ], [ -3, 5, -2, 1, -2, 2 ], [ -3, 5, -1, 1, -2, 2 ] ] +], +"cur_uid": "61207e49", +"ref_uid": "624f7439", +"order_seed": 716491, +"dur_seed": 454936, +"motifs_seed": 126393, +"entrances_probs_vals": [ 0.78, 0, 0, 0, 1.02, 0.0098039215686275, 0, 0.20915032679739, 0, 0.24183006535948, 0.82432432432432, 0.28758169934641, 0, 1, 0 ], +"passages_probs_vals": [ 0.78, 0, 0, 0, 1.02, 0.0098039215686275, 0, 0.20915032679739, 0, 0.24183006535948, 0.82432432432432, 0.28758169934641, 0, 1, 0 ], +"exits_probs_vals": [ 0.78, 0, 0, 0, 1.02, 0.0098039215686275, 0, 0.20915032679739, 0, 0.24183006535948, 0.82432432432432, 0.28758169934641, 0, 1, 0 ], +"ranges": [ [ -2400, 1200 ], [ -1200, 1453 ], [ -702, 1694 ], [ -702, 1973 ] ], +"step_probs_vals": [ -1200, 1200, 0, 0, 0.41358024691358, 0.0056818181818177, 0.46913580246914, 0.67045454545455, 0.52057613168724, 0, 0.56378600823045, 0.85795454545455, 0.60082304526749, 0, 1, 0 ], +"passages_weights": [ 1, 0.15, 0.43, 1, 1 ], +"hd_exp": 10, +"hd_invert": 0, +"order": +[ + [ [ 0, 2, 3 ], [ 1, 1, 1, 1 ], [ ] ], + [ [ 1 ], [ 3, 0, 2, 2, 2, 2 ], [ ] ], + [ [ 1, 0 ], [ 3, 2 ], [ ] ], + [ [ 3 ], [ 2, 1, 0, 0, 1, 1, 1 ], [ ] ], + [ [ 3 ], [ 1, 2, 0, 2, 2, 1, 2 ], [ ] ], + [ [ 0, 3 ], [ 1, 2, 1 ], [ ] ], + [ [ 3 ], [ 2, 1, 0 ], [ ] ], + [ [ 3, 2 ], [ 0, 1, 1, 0 ], [ ] ], + [ [ 0 ], [ 1, 3, 2, 2, 3, 2, 1 ], [ ] ], + [ [ 1, 0, 3 ], [ 2 ], [ ] ], + [ [ 0, 2 ], [ 1, 3, 3, 1, 1 ], [ ] ], + [ [ 1 ], [ 0, 2, 3, 2, 0, 3 ], [ ] ], + [ [ 1, 3 ], [ 0, 2, 0, 0, 0, 0 ], [ ] ], + [ [ 3, 1 ], [ 2, 0 ], [ ] ] +], +"sus_weights": [ 0.7, 0.48, 0.49 ], +"order_size": [ 14, 14.132653061224 ], +"passages_size": [ 0, 5 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/61207e49/lilypond/part_I.ly b/resources/string_quartet_3_rise/61207e49/lilypond/part_I.ly new file mode 100644 index 0000000..4e56cbb --- /dev/null +++ b/resources/string_quartet_3_rise/61207e49/lilypond/part_I.ly @@ -0,0 +1,12 @@ +{ + { gis2^\markup { \pad-markup #0.2 "-27"} gis''2^\markup { \pad-markup #0.2 "-27"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }} } + \bar "|" + { dis'1^\markup { \pad-markup #0.2 "-25"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 1↑" }} ~ } + \bar "|" + { dis'2. ~ dis'8[ e'8^\markup { \pad-markup #0.2 "+13"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 13↑" }}] } + \bar "|" + { c'2^\markup { \pad-markup #0.2 "-41"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↑" }} ais8^\markup { \pad-markup #0.2 "+35"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 5↓" }}[ a8^\markup { \pad-markup #0.2 "+23"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 3↑" }}] ~ a4 } + \bar "|" + { gis8^\markup { \pad-markup #0.2 "-27"}[ cis''8^\markup { \pad-markup #0.2 "-29"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }}] ~ cis''2.} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/61207e49/lilypond/part_II.ly b/resources/string_quartet_3_rise/61207e49/lilypond/part_II.ly new file mode 100644 index 0000000..bb3b6e3 --- /dev/null +++ b/resources/string_quartet_3_rise/61207e49/lilypond/part_II.ly @@ -0,0 +1,12 @@ +{ + { cis'2^\markup { \pad-markup #0.2 "-29"} dis'8^\markup { \pad-markup #0.2 "-25"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↑" }}[ e'8^\markup { \pad-markup #0.2 "-14"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↓" }}] f'8^\markup { \pad-markup #0.2 "+41"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 7↑" }}[ dis''8^\markup { \pad-markup #0.2 "-25"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↑" }}] } + \bar "|" + { e''8^\markup { \pad-markup #0.2 "+13"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 13↑" }}[ dis''8^\markup { \pad-markup #0.2 "-25"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 1↑" }}] ~ dis''2 f''8^\markup { \pad-markup #0.2 "+6"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 7↓" }}[ g''8^\markup { \pad-markup #0.2 "-39"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 5↑" }}] } + \bar "|" + { ais'8^\markup { \pad-markup #0.2 "-23"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }}[ b'8^\markup { \pad-markup #0.2 "+15"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 13↑" }}] ais'4^\markup { \pad-markup #0.2 "-23"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }} b'4^\markup { \pad-markup #0.2 "-12"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 5↓" }} ~ b'8[ cis''8^\markup { \pad-markup #0.2 "-29"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↓" }}] } + \bar "|" + { dis''8^\markup { \pad-markup #0.2 "-25"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↑" }}[ e''8^\markup { \pad-markup #0.2 "-14"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↓" }}] ~ e''8[ d''8^\markup { \pad-markup #0.2 "+21"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 11↓" }}] ~ d''2 } + \bar "|" + { g'8^\markup { \pad-markup #0.2 "+19"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }}[ fis'8^\markup { \pad-markup #0.2 "-31"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 3↓" }}] ~ fis'8[ gis'8^\markup { \pad-markup #0.2 "-27"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↑" }}] ~ gis'4 ~ gis'8[ a'8^\markup { \pad-markup #0.2 "-16"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }}]} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/61207e49/lilypond/part_III.ly b/resources/string_quartet_3_rise/61207e49/lilypond/part_III.ly new file mode 100644 index 0000000..2858d1c --- /dev/null +++ b/resources/string_quartet_3_rise/61207e49/lilypond/part_III.ly @@ -0,0 +1,12 @@ +{ + { dis'8^\markup { \pad-markup #0.2 "-25"}[ e'8^\markup { \pad-markup #0.2 "-14"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↓" }}] f'8^\markup { \pad-markup #0.2 "+41"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 7↑" }}[ gis'8^\markup { \pad-markup #0.2 "-27"}] ~ gis'2 ~ } + \bar "|" + { gis'8[ ais'8^\markup { \pad-markup #0.2 "-23"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 3↑" }}] b'8^\markup { \pad-markup #0.2 "-12"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }}[ c''8^\markup { \pad-markup #0.2 "+43"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 7↑" }}] b'8^\markup { \pad-markup #0.2 "+15"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 13↑" }}[ gis'8^\markup { \pad-markup #0.2 "-27"}] ~ gis'4 } + \bar "|" + { g'4^\markup { \pad-markup #0.2 "-39"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 5↑" }} fis'8^\markup { \pad-markup #0.2 "+17"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 13↑" }}[ f'8^\markup { \pad-markup #0.2 "-21"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↑" }}] fis'8^\markup { \pad-markup #0.2 "+34"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 13↓" }}[ gis'8^\markup { \pad-markup #0.2 "+26"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }}] ais'8^\markup { \pad-markup #0.2 "-23"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 3↑" }}[ b'8^\markup { \pad-markup #0.2 "+32"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 13↓" }}] ~ } + \bar "|" + { b'4 cis''4^\markup { \pad-markup #0.2 "-29"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↓" }} c''4^\markup { \pad-markup #0.2 "-41"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 1↑" }} dis''8^\markup { \pad-markup #0.2 "-25"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↑" }}[ cis'8^\markup { \pad-markup #0.2 "-29"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↓" }}] ~ } + \bar "|" + { cis'1} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/61207e49/lilypond/part_IV.ly b/resources/string_quartet_3_rise/61207e49/lilypond/part_IV.ly new file mode 100644 index 0000000..d180278 --- /dev/null +++ b/resources/string_quartet_3_rise/61207e49/lilypond/part_IV.ly @@ -0,0 +1,12 @@ +{ + { gis,2^\markup { \pad-markup #0.2 "-27"} gis2^\markup { \pad-markup #0.2 "-27"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }} ~ } + \bar "|" + { gis8[ ais8^\markup { \pad-markup #0.2 "-23"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }}] b2^\markup { \pad-markup #0.2 "-12"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 5↓" }} ais4^\markup { \pad-markup #0.2 "-23"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 3↑" }} ~ } + \bar "|" + { ais2 b8^\markup { \pad-markup #0.2 "-12"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 1↑" }}[ gis8^\markup { \pad-markup #0.2 "+26"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 11↑" }}] gis4^\markup { \pad-markup #0.2 "-27"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 3↓" }} ~ } + \bar "|" + { gis1 } + \bar "|" + { g8^\markup { \pad-markup #0.2 "+19"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 11↓" }}[ fis8^\markup { \pad-markup #0.2 "-31"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 1↑" }}] cis8^\markup { \pad-markup #0.2 "-29"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }}[ dis8^\markup { \pad-markup #0.2 "+2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 7↓" }}] f8^\markup { \pad-markup #0.2 "-43"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 5↑" }}[ gis8^\markup { \pad-markup #0.2 "-27"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 1↑" }}] a4^\markup { \pad-markup #0.2 "-16"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 5↓" }}} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/624f7439/624f7439_mus_model.json b/resources/string_quartet_3_rise/624f7439/624f7439_mus_model.json index 1692c9e..835be59 100644 --- a/resources/string_quartet_3_rise/624f7439/624f7439_mus_model.json +++ b/resources/string_quartet_3_rise/624f7439/624f7439_mus_model.json @@ -97,11 +97,11 @@ ], "last_changes": [ - [ [ -4, 5, -1, 1, -2, 2 ], [ -3, 5, -1, 1, -2, 2 ], [ -3, 4, -1, 1, -2, 2 ], [ -4, 6, -1, 1, -2, 2 ] ], - [ [ -5, 6, 0, 1, -2, 2 ], [ -3, 5, -1, 1, -2, 2 ], [ -3, 4, -1, 1, -2, 2 ], [ -4, 6, -1, 1, -2, 2 ] ], - [ [ -4, 5, -1, 1, -2, 2 ], [ -3, 5, -1, 1, -2, 2 ], [ -3, 4, -1, 1, -2, 2 ], [ -4, 6, -1, 1, -2, 2 ] ], - [ [ -4, 5, -1, 1, -2, 2 ], [ -3, 5, -1, 1, -2, 2 ], [ -4, 5, 0, 1, -2, 2 ], [ -4, 6, -1, 1, -2, 2 ] ], - [ [ -4, 6, -1, 1, -3, 2 ], [ -3, 5, -1, 1, -2, 2 ], [ -4, 5, 0, 1, -2, 2 ], [ -4, 6, -1, 1, -2, 2 ] ] + [ [ -3, 5, -2, 1, -2, 2 ], [ -3, 4, -1, 1, -2, 2 ], [ -4, 5, -1, 1, -2, 2 ], [ -5, 5, -1, 2, -2, 2 ] ], + [ [ -3, 5, -2, 1, -2, 2 ], [ -3, 4, -1, 1, -2, 2 ], [ -4, 5, -1, 1, -2, 2 ], [ -4, 5, -2, 1, -2, 2 ] ], + [ [ -3, 5, -2, 1, -2, 2 ], [ -3, 4, -1, 1, -2, 2 ], [ -4, 5, -1, 1, -2, 2 ], [ -5, 6, -1, 1, -2, 2 ] ], + [ [ -3, 5, -2, 1, -2, 2 ], [ -4, 6, -1, 1, -2, 2 ], [ -4, 5, -1, 1, -2, 2 ], [ -5, 6, -1, 1, -2, 2 ] ], + [ [ -6, 6, -1, 1, -2, 2 ], [ -4, 6, -1, 1, -2, 2 ], [ -4, 5, -1, 1, -2, 2 ], [ -5, 6, -1, 1, -2, 2 ] ] ], "cur_uid": "624f7439", "ref_uid": "4f53a446", diff --git a/resources/string_quartet_3_rise/624f7439/lilypond/part_I.ly b/resources/string_quartet_3_rise/624f7439/lilypond/part_I.ly new file mode 100644 index 0000000..1a5d2e7 --- /dev/null +++ b/resources/string_quartet_3_rise/624f7439/lilypond/part_I.ly @@ -0,0 +1,12 @@ +{ + { gis2^\markup { \pad-markup #0.2 "-27"} gis''2^\markup { \pad-markup #0.2 "-27"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }} } + \bar "|" + { gis1^\markup { \pad-markup #0.2 "-27"} ~ } + \bar "|" + { gis2. ~ gis8[ a8^\markup { \pad-markup #0.2 "-16"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }}] } + \bar "|" + { gis'2^\markup { \pad-markup #0.2 "-27"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↑" }} a'8^\markup { \pad-markup #0.2 "-16"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↓" }}[ g'8^\markup { \pad-markup #0.2 "+19"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 11↓" }}] ~ g'4 } + \bar "|" + { a'8^\markup { \pad-markup #0.2 "-16"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }}[ gis'8^\markup { \pad-markup #0.2 "-27"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }}] ~ gis'2.} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/624f7439/lilypond/part_II.ly b/resources/string_quartet_3_rise/624f7439/lilypond/part_II.ly new file mode 100644 index 0000000..7e6b00a --- /dev/null +++ b/resources/string_quartet_3_rise/624f7439/lilypond/part_II.ly @@ -0,0 +1,12 @@ +{ + { cis'2^\markup { \pad-markup #0.2 "-29"} gis8^\markup { \pad-markup #0.2 "-27"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }}[ ais8^\markup { \pad-markup #0.2 "+4"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 7↓" }}] c'8^\markup { \pad-markup #0.2 "-41"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 5↑" }}[ cis'8^\markup { \pad-markup #0.2 "-29"}] } + \bar "|" + { dis'8^\markup { \pad-markup #0.2 "-25"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 3↑" }}[ e'8^\markup { \pad-markup #0.2 "-14"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 5↓" }}] ~ e'2 dis'8^\markup { \pad-markup #0.2 "-25"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 3↑" }}[ c''8^\markup { \pad-markup #0.2 "-41"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }}] } + \bar "|" + { cis''8^\markup { \pad-markup #0.2 "-29"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 3↓" }}[ dis''8^\markup { \pad-markup #0.2 "-25"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 3↑" }}] gis'4^\markup { \pad-markup #0.2 "-27"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 1↑" }} c'4^\markup { \pad-markup #0.2 "-41"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }} ~ c'8[ cis'8^\markup { \pad-markup #0.2 "-29"}] } + \bar "|" + { f'8^\markup { \pad-markup #0.2 "-43"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↑" }}[ fis'8^\markup { \pad-markup #0.2 "-31"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↓" }}] ~ fis'8[ e'8^\markup { \pad-markup #0.2 "-14"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 5↓" }}] ~ e'2 } + \bar "|" + { dis'8^\markup { \pad-markup #0.2 "+2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 7↓" }}[ f'8^\markup { \pad-markup #0.2 "-43"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 5↑" }}] ~ f'8[ fis'8^\markup { \pad-markup #0.2 "-31"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 3↓" }}] ~ fis'4 ~ fis'8[ f'8^\markup { \pad-markup #0.2 "-43"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↑" }}]} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/624f7439/lilypond/part_III.ly b/resources/string_quartet_3_rise/624f7439/lilypond/part_III.ly new file mode 100644 index 0000000..c1f073f --- /dev/null +++ b/resources/string_quartet_3_rise/624f7439/lilypond/part_III.ly @@ -0,0 +1,12 @@ +{ + { a'8^\markup { \pad-markup #0.2 "-16"}[ gis'8^\markup { \pad-markup #0.2 "-27"}] a'8^\markup { \pad-markup #0.2 "+11"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 13↑" }}[ gis'8^\markup { \pad-markup #0.2 "-27"}] ~ gis'2 ~ } + \bar "|" + { gis'8[ cis'8^\markup { \pad-markup #0.2 "-29"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }}] d'8^\markup { \pad-markup #0.2 "+21"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 11↓" }}[ dis'8^\markup { \pad-markup #0.2 "-25"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 3↑" }}] cis'8^\markup { \pad-markup #0.2 "+24"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 11↑" }}[ c'8^\markup { \pad-markup #0.2 "-41"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 5↑" }}] ~ c'4 } + \bar "|" + { cis'4^\markup { \pad-markup #0.2 "-29"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 1↑" }} dis'8^\markup { \pad-markup #0.2 "-25"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 1↑" }}[ f'8^\markup { \pad-markup #0.2 "-43"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↓" }}] f'8^\markup { \pad-markup #0.2 "+41"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 7↑" }}[ g'8^\markup { \pad-markup #0.2 "-39"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↑" }}] gis'8^\markup { \pad-markup #0.2 "-27"}[ a'8^\markup { \pad-markup #0.2 "-16"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↓" }}] ~ } + \bar "|" + { a'4 ais'4^\markup { \pad-markup #0.2 "+40"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 7↑" }} cis''4^\markup { \pad-markup #0.2 "-29"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }} d''8^\markup { \pad-markup #0.2 "-45"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 7↑" }}[ cis''8^\markup { \pad-markup #0.2 "-29"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }}] ~ } + \bar "|" + { cis''1} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/624f7439/lilypond/part_IV.ly b/resources/string_quartet_3_rise/624f7439/lilypond/part_IV.ly new file mode 100644 index 0000000..e59cdcc --- /dev/null +++ b/resources/string_quartet_3_rise/624f7439/lilypond/part_IV.ly @@ -0,0 +1,12 @@ +{ + { gis,2^\markup { \pad-markup #0.2 "-27"} cis''2^\markup { \pad-markup #0.2 "-29"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 1↑" }} ~ } + \bar "|" + { cis''8[ gis'8^\markup { \pad-markup #0.2 "-27"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 1↑" }}] ais,2^\markup { \pad-markup #0.2 "+4"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 7↓" }} c4^\markup { \pad-markup #0.2 "-41"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }} ~ } + \bar "|" + { c2 cis8^\markup { \pad-markup #0.2 "+24"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 11↑" }}[ dis8^\markup { \pad-markup #0.2 "-25"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 3↑" }}] cis4^\markup { \pad-markup #0.2 "-29"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 3↓" }} ~ } + \bar "|" + { cis1 } + \bar "|" + { a8^\markup { \pad-markup #0.2 "-16"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 5↓" }}[ gis8^\markup { \pad-markup #0.2 "-27"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 3↑" }}] g8^\markup { \pad-markup #0.2 "+19"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 11↓" }}[ fis8^\markup { \pad-markup #0.2 "-31"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 1↑" }}] cis'8^\markup { \pad-markup #0.2 "-29"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }}[ c'8^\markup { \pad-markup #0.2 "-41"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 5↑" }}] cis'4^\markup { \pad-markup #0.2 "-29"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }}} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/65120e88/65120e88_code.scd b/resources/string_quartet_3_rise/65120e88/65120e88_code.scd new file mode 100644 index 0000000..e1a9d92 --- /dev/null +++ b/resources/string_quartet_3_rise/65120e88/65120e88_code.scd @@ -0,0 +1,992 @@ +( +// helper funcs +var hsArrayToCents, pDist, hdSum, hsChordalDistance, hsArrayToFreq; + +// score funcs +var isInRange, spacingScore, rangeScore, intervalScore, inclusionScore; + +// subroutines +var genTuples, initVoices, genOrders, genSubMotif, updateVoices, genDurFunc, genStepFunc; + +// primary routines +var genMotif, genSecondarySeq; + +// audition funcs +var genPatterns, genMidiPatterns; + +// resource management funcs +var seedFunc, genUID, writeResources, stringifyToDepth, setSeeds, sanityCheck, +msgInterpret, loadLedgerFile, loadLedgerJSON, loadModelFile, loadModelJSON, +setGlobalVars, globalVarsToDict, saveLedger; + +// model vars +//(model and global vars mostly set by OSC funcs +var seq, lastXChanges, +curUID, refUID, orderSeed, durSeed, motifSeed, +entrancesProbVals, passagesProbVals, exitsProbVals, +ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, +orders, susWeights, orderSize, passagesSize, +motifEdited, orderEdited; + +// model aux vars +var entrancesDurFunc, passagesDurFunc, exitsDurFunc, stepFunc; + +// other global vars +var popSize, exPath, dir, primes, dims, tuples, +group, player, resourceDir, ledgerPath, ledger, currentlyPlayingUID, +nameSpaces; + +// install JSON quark (not used) +/* +if(Quarks.isInstalled("JSONlib").not, { + Quarks.install("https://github.com/musikinformatik/JSONlib.git"); + thisProcess.recompile; + //HelpBrowser.openHelpFor("Classes/JSONlib"); +}); +*/ + + +//------helper funcs + +hsArrayToCents = { + arg hsArray; + hsArray.collect({arg dist, p; dist * 1200 * log2(primes[p][0]/primes[p][1])}).sum +}; + +pDist = { + arg array1, array2, signed = false; + var pDistance; + pDistance = hsArrayToCents.value(array2) - hsArrayToCents.value(array1); + if(signed, {pDistance}, {abs(pDistance)}) +}; + +hdSum = { + arg hsArrays; + var size, distances, mean; + size = hsArrays.size; + distances = (size - 1).collect({arg i; + ((i + 1)..(size - 1)).collect({arg j; + abs(hsArrays[i] - hsArrays[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsChordalDistance = { + arg hsArrays1, hsArrays2; + var size, distances, mean; + size = hsArrays1.size; + distances = hsArrays1.size.collect({arg i; + hsArrays2.size.collect({arg j; + abs(hsArrays1[i] - hsArrays2[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsArrayToFreq = { + arg array; + array.collect({arg dim, d; pow(primes[d][0]/primes[d][1], dim)}).product +}; + +//------score funcs + +/* +isInRange = { + arg hsArray, min, max; + var cents; + cents = hsArrayToCents.value(hsArray); + (cents >= min) && (cents <= max) +}; +*/ + +spacingScore = { + arg hsArrays, min; + var centsArray; + centsArray = hsArrays.collect({arg hsArray; hsArrayToCents.value(hsArray)}).sort({arg a, b; a < b}); + centsArray.differentiate.drop(1).collect({arg pDistance; if(pDistance >= min, {1}, {0.01})}).sum; +}; + +rangeScore = { + arg hsArray1, hsArray2, min, max, low, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + if((pDistance >= min) && (pDistance <= max), {1}, {low}); +}; + +/* +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + stepFunc.value(pDistance) +}; +*/ + +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = true; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + //if(pDistance >= 0, {stepFunc.value(abs(pDistance))}, {0.01}); + stepFunc.value(pDistance) +}; + +inclusionScore = { + arg array, test, min = 0.01; + if(array.collect({arg v; v.hash}).includes(test.hash), {min}, {1}); +}; + + +//------subroutines + +genTuples = { + var tuples; + tuples = dims.collect({[-1, 0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum <= 1) && (tuple[0] == 0)}); + //tuples = dims.collect({[0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum == 1) && (tuple[0] == 0)}); + tuples = tuples ++ tuples.collect({arg tuple; [-3, -2, -1, 1, 2, 3].collect({arg octTrans; tuple.deepCopy.put(0, octTrans)})}).flatten; +}; + +initVoices = { + var init, voicesInit; + voicesInit = popSize.collect({dims.collect({0})}); + /* + voicesInit = [dims.collect({0})]; + (popSize - 1).do({ + arg rep, new; + rep = dims.rand; + new = voicesInit.last.deepCopy; + new[rep] = new[rep] + [-1, 1].choose(); + voicesInit = voicesInit.add(new); + }); + */ + voicesInit.deepCopy; +}; + +genDurFunc = {arg chordProb, minPad, maxPad, minDur, maxDur, envData, seed; + var env, pTable, durFunc; + env = Env.pairs([[0, 0]] ++ envData.clump(2) ++ [[1, 0]]).asSignal(256).asList.asArray; + pTable = env.asRandomTable; + //[chordProb, minPad, maxPad, minDur, maxDur, envData].postln; + durFunc = {arg allowChord, pad = false; + var res; + res = if(allowChord.not, { + pTable.tableRand * (maxDur - minDur) + minDur + }, { + if(1.0.rand < chordProb, {0}, {pTable.tableRand * (maxDur - minDur) + minDur}); + }).round(0.125); + if(pad, {res = res + rrand(minPad.asFloat, maxPad.asFloat).round(0.125)}); + if(res.asInteger == res, {res = res.asInteger}); + res + }; + seedFunc.value(durFunc, seed); +}; + +genStepFunc = {arg minStep, maxStep, envData, seed; + var envDataNorm, env, pTable, stepFunc; + //[minStep, maxStep, envData].postln; + envDataNorm = ([[0, 0]] ++ envData.clump(2) ++ [[1, 0]]).flop; + envDataNorm = [envDataNorm[0].normalize(minStep, maxStep), envDataNorm[1]].flop; + env = Env.pairs(envDataNorm); + stepFunc = {arg pDist; + env.at(pDist).clip(0.001, 1); + }; + seedFunc.value(stepFunc, seed); +}; + +genOrders = {arg minMotifLength = 1, maxMotifLength = 5, minProgLength = 0, maxProgLength = 5; + ((maxMotifLength.asInteger - minMotifLength.asInteger).rand + minMotifLength.asInteger).collect({ + var noProgIns, noSusIns, noSilentIns, prog, sus, silent, order; + noSusIns = [1, 2, 3].wchoose(susWeights.normalizeSum); + + //noProgIns = (popSize - noSusIns).rand + 1; + noProgIns = (popSize - noSusIns); + noSilentIns = popSize - noSusIns - noProgIns; + + /* + noSilentIns = (popSize - noSusIns).rand.clip(0, 1); + noProgIns = popSize - noSusIns - noSilentIns; + */ + + # prog, sus, silent = (0..(popSize-1)).scramble.clumps([noProgIns, noSusIns, noSilentIns]); + + prog = (prog.scramble ++ ((maxProgLength.asInteger - minProgLength.asInteger).rand + minProgLength.asInteger).collect({prog.choose}).scramble); + //prog = ((maxProgLength.asInteger - minProgLength.asInteger).rand + minProgLength.asInteger).collect({prog.choose}); + + if(silent == nil, {silent = []}); + [sus.scramble, prog, silent.scramble] + }); +}; + +updateVoices = {arg ins, sus; + var voices, candidates, nWeights, nProbs, sel; + + voices = lastXChanges.deepCopy.last; + + candidates = sus.collect({arg v; tuples.collect({arg t; voices[v] + t})}).flatten; + candidates = difference(candidates.asSet, voices.asSet).asList; + //candidates.select({arg item; (item ++ voices).asSet.size >= 4}); + nProbs = candidates.collect({arg candidate; + var stepScore, recentlySoundedScore, isInRangeScore, regScore, hdScore; + + //stepScore = intervalScore.value(voices[ins], candidate, 30, 400, 0.1); + stepScore = intervalScore.value(voices[ins], candidate, 100, 100); + recentlySoundedScore = inclusionScore.value(lastXChanges.flop[ins], candidate, 0.01); + //recentlySoundedScore = inclusionScore.value(lastXChanges.flatten.collect({arg item; item.drop(1)}), candidate.drop(1), 0.01); + + /* + if(rangeScore.value(candidate.collect({0}), voices[ins], ranges[ins][1] - 500, ranges[ins][1], 0, true) == 1, { + isInRangeScore = 1 - rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0] + 500, ranges[ins][1] - 500, 0, true); + isInRangeScore = isInRangeScore * rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true) + }, { + isInRangeScore = rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true); + }); + */ + + + isInRangeScore = rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true); + //old way - worked but actually by accident (I think) + //isInRangeScore = rangeScore.value(candidate, candidate.collect({0}), ranges[ins][0], ranges[ins][1], 0, true); + + + regScore = spacingScore.value(voices.deepCopy.put(ins, candidate), 300); + hdScore = pow(hdSum.value(voices.deepCopy.put(ins, candidate)), hdExp); + if(hdInvert == 0, {hdScore = 1/hdScore}); + //maybe what you want here is a vector to another root and then favoring movement towards it. + //distScore = pow(hsChordalDistance.value(voices, voices.put(ins, candidate)), 2); + + [stepScore, recentlySoundedScore, isInRangeScore, regScore, hdScore] + }); + + nWeights = passagesWeights; + + //this handles nWeights of 0; mainly for testing + nProbs = nProbs.flop.select({arg scores, s; nWeights[s] != 0}).flop; + nWeights = nWeights.select({arg weight; weight != 0}); + nProbs = nProbs.flop.collect({arg scores, s; + if(scores.sum == 0, {scores}, {scores.normalizeSum * nWeights[s]}).postln; + }); + nProbs = nProbs.flop.collect({arg scores, s; scores.product}).normalizeSum; + + nProbs.round(0.001).postln; + [candidates, nProbs.round(0.001)].flop.postln; + + sel = candidates.wchoose(nProbs); + + voices[ins] = sel; + lastXChanges = lastXChanges.add(voices).keep(-5); +}; + +genSubMotif = {arg order, orderIndex, lastState, repeatLast = false, startFromLast = false, isLastOrder = false; + var sus, prog, silent, flatOrder, res, isInChord, allowChord, pad, lastXChangesHold, voices, adder; + # sus, prog, silent = order; + flatOrder = silent ++ sus ++ prog; + lastXChangesHold = lastXChanges.deepCopy; + voices = lastState.deepCopy; + isInChord = popSize.collect({false}); + allowChord = false; + pad = false; + res = []; + "------generating motif".postln; + //need to figure out here if voices move between motifs + flatOrder.do({arg ins, i; + + if(prog.includes(ins) && repeatLast.not, {updateVoices.value(ins, sus)}); + adder = if(silent.includes(ins), {["Rest"]}, {lastXChanges.last.deepCopy[ins]}); + + if(voices[ins] != adder, { + var dur; + + if((sus ++ silent).includes(ins), { + allowChord = (ins != sus.last); + pad = (ins == sus.last); + }, { + if(i < (flatOrder.size - 1), { + allowChord = (isInChord[flatOrder[i + 1]] || (ins == flatOrder[i + 1])).not; + pad = false; + }, { + allowChord = false; + pad = true + }); + }); + if((orderIndex == 0) && sus.includes(ins), { + dur = entrancesDurFunc.value(allowChord, pad); + }, { + dur = passagesDurFunc.value(allowChord, pad); + }); + if(dur == 0, {isInChord[ins] = true}, {isInChord = popSize.collect({false})}); + + voices[ins] = adder; + res = res.add([voices.deepCopy.postln, dur]); + }); + }); + + // pad ending + /* + if(orderIndex == (orders.size - 1), { + (0..(popSize-1)).scramble.do({arg ins; + if(res.last.first[ins] != ["Rest"], { + var dur; + voices[ins] = ["Rest"]; + allowChord = (voices != popSize.collect({["Rest"]})); + pad = allowChord.not; + dur = exitsDurFunc.value(allowChord, pad); + res = res.add([voices.deepCopy.postln, dur]); + }); + }); + }); + */ + + //format and return + if(startFromLast, {lastXChanges = lastXChangesHold.deepCopy}); + res; +}; + + +//------primary routines + +genMotif = { + var repeats, fSeq, fDur, durAdd; + + repeats = 1; + fSeq = []; + + repeats.do({arg index; + var motif; + + motif = []; + + orders.do({arg order, o; + var lastState, subMotif; + //lastState = if(o == 0, {popSize.collect({["Rest"]})}, {motif.last.last.first}); + lastState = if(o == 0, {lastXChanges.last.deepCopy}, {motif.last.last.first}); + subMotif = genSubMotif.value(order, o, lastState, isLastOrder: o == (orders.size - 1)); + motif = motif.add(subMotif); + + }); + + sanityCheck.value(motif, index); + + fSeq = fSeq.add(motif); + }); + + //round last duration to measure + /* + fDur = fSeq.flatten.flatten.slice(nil, 1).sum; + durAdd = fDur.round(4) - fDur; + if(durAdd < 0, {durAdd = 4 - durAdd}); + fSeq[0][orders.size - 1][fSeq[0][orders.size - 1].size - 1][1] = fSeq[0][orders.size - 1][fSeq[0][orders.size - 1].size - 1][1] + durAdd; + */ + + fSeq +}; + +genSecondarySeq = {arg seq; + var curdles, fSeq; + curdles = []; + while({curdles.sum < seq.size}, {curdles = curdles ++ [3.rand + 1]}); + + fSeq = seq.clumps(curdles).collect({arg clump, m; + var repeats, paddedSeq; + + //add rest + paddedSeq = clump.add([[[popSize.collect({["Rest"]}), 0.5.rand]]]); + + //implement repeats + repeats = [0.rand + 1, 1].wchoose([1, 0].normalizeSum); + repeats.collect({paddedSeq}); + }); + fSeq +}; + + +//------audition funcs + +/* +Event.addEventType(\osc, { + if (~addr.postln.notNil) { + ~addr.sendMsg(~indexPath, ~indexMsg); + ~addr.sendMsg(~seqPath, stringifyToDepth.value(~seqMsg, 3)); + //~addr.sendMsg("/STATE/OPEN", (dir.replace("supercollider", "resources") +/+ ~idMsg +/+ ~idMsg ++ "_gui_state" ++ ".state").standardizePath.postln); + }; +}); +*/ + +Event.addEventType(\osc, { + if (~addr.notNil) { + ~msg; + ~addr.sendMsg(~path, *~msg); + }; +}); + +genPatterns = {arg inSeq, addr, oneShot = false; + var voices, durs, pbinds, res, indices, sectionDurs, msg, ids, seq; + seq = inSeq.collect({arg mSeq; mSeq[0]}); + //# voices, durs = seq.flatten2(seq.maxDepth - 5).flop; + # voices, durs = seq.flatten2(if(oneShot, {2}, {3})).flop; + pbinds = voices.flop.collect({arg voice, v; + var clumps, hdScores, freqs, fDurs, attacks, rels, amps; + clumps = voice.separate({arg a, b; a != b }); + freqs = clumps.collect({arg clump; if(clump[0] != ["Rest"], {(62.midicps * hsArrayToFreq.value(clump[0]))}, {Rest(0)})}); + fDurs = durs.clumps(clumps.collect({arg clump; clump.size})).collect({arg clump; clump.sum}); + //attacks = 2.collect({rrand(1, 3)}) ++ freqs.drop(2).collect({rrand(0.3, 0.5)}); + attacks = fDurs.collect({arg dur; dur * rrand(0.2, 0.4)}); + //rels = freqs.drop(2).collect({rrand(0.3, 0.5)}) ++ 2.collect({rrand(1, 3)}); + rels = (clumps.size - 1).collect({arg c; + if(clumps[c + 1][0] == ["Rest"], {rrand(1.0, 3.0)}, {rrand(0.3, 0.5)}); + }); + rels = rels.add(rrand(1.0, 3.0)); + amps = freqs.collect({rrand(0.6, 0.99)}); + + [ + Pbind( + \instrument, \string_model, + \group, group, + \freq, Pseq(freqs, 1), + \dur, Pseq(fDurs, 1), + \attack, Pseq(attacks, 1), + \sustain, Pseq(fDurs, 1), + \release, Pseq(rels, 1), + //\amp, Pseq(amps, 1), + \amp, Pbrown(0.5, 1, 0.5), + \busIndex, v + ), + Pbind( + \instrument, \sine, + \group, group, + \freq, Pseq(freqs, 1), + \dur, Pseq(fDurs, 1), + \sustain, Pseq(fDurs, 1), + \busIndex, v + ) + ] + }).flatten; + if(oneShot.not, { + msg = inSeq.collect({arg mSeq, m; mSeq[1..]}); + //ids = inSeq.collect({arg mSeq, m; mSeq[2]}); + sectionDurs = seq.collect({arg mSeq; mSeq.flatten2(2).flop[1].sum}); + pbinds = pbinds ++ + [ + Pbind( + \type, \osc, + \addr, addr, + \path, "/playing", + \msg, Pseq(msg, 1), + \dur, Pseq(sectionDurs, 1) + ); + ] + }); + res = Ppar(pbinds); + res +}; + +/* +genMidiPatterns = {arg seq; + var voices, durs, patterns, res, mOut, pbRange; + pbRange = 1; //semitones - change this as needed for your situation + mOut = MIDIOut.newByName("TiMidity", "TiMidity port 0").latency_(Server.default.latency); + # voices, durs = seq.flatten2(seq.maxDepth - 5).flop; + res = Ppar( + voices.flop.collect({arg voice, v; + var clumps, hdScores, freqs, fDurs; + + mOut.program(v, 70); + + clumps = voice.separate({arg a, b; a != b }); + freqs = clumps.collect({arg clump; if(clump[0] != ["Rest"], {(60.midicps * hsArrayToFreq.value(clump[0]))}, {Rest(0)})}); + fDurs = durs.clumps(clumps.collect({arg clump; clump.size})).collect({arg clump; clump.sum}); + + Pbind( + \type, \midi, + \chan, v, + \noteval, Pseq(freqs.cpsmidi - 24, 1), + \note, Pfunc({ | event | event[\noteval].floor }), + \dur, Pseq(fDurs, 1), + \midiout, mOut, + \amp, 1, + \bend, Pfunc({ + | event | + if (event[\note].isRest.not) { + var pitchbendvalue = event[\noteval].frac.linlin(0, pbRange, 8192, 8192*2).asInteger; + m.bend(v, pitchbendvalue); + }; + 0; // return something other than nil to avoid stopping the pattern + }), + ); + }); + ); + res +}; +*/ + + +//------resource management funcs + +genUID = {Date.seed.asHexString.toLower}; + +seedFunc = {arg func, seed; + var funcArgs, next; + next = Routine({loop{func.valueArray(funcArgs).yield }}); + next.randSeed_(seed); + {arg ...args; funcArgs = args; next.value} +}; + +stringifyToDepth = {arg data, maxDepth = 1; + var prettyString = "", rCount = 0, writeArray, indent; + + if(maxDepth == 0, { + data.asCompileString + }, { + indent = {arg size; size.collect({" "}).join("")}; + writeArray = {arg array; + prettyString = prettyString ++ indent.value(rCount) ++ "[\n"; + rCount = rCount + 1; + if(rCount < maxDepth, { + array.do({arg subArray; writeArray.value(subArray)}); + }, { + prettyString = prettyString ++ array.collect({arg subArray; + indent.value(rCount + 1) ++ subArray.asCompileString + }).join(",\n"); + }); + rCount = rCount - 1; + prettyString = prettyString ++ "\n" ++ indent.value(rCount) ++ "],\n"; + }; + + writeArray.value(data); + prettyString.replace(",\n\n", "\n").drop(-2); + }) +}; + +sanityCheck = {arg motif, index; + //print functions = very helpful + ("----------" + index + "------------").postln; + + motif.flatten.do({arg val, v; + if(v > 0, { + if(motif.flatten[v-1][0].hammingDistance(val[0]) > 1, {"problem 1".postln}); + if(motif.flatten[v-1][0].hammingDistance(val[0]) == 0, {"problem 2".postln}); + }); + val.postln + }); + "***********".postln; +}; + +msgInterpret = {arg in, escapeDoubleQuotes = true, escapeSingleQuotes = true; + var res; + + res = in; + if(res.isNil.not, { + if((res.isArray && res.isString.not), { + res = res.asCompileString; + res = res.replace(" ", "").replace("\n", "").replace("\t", ""); + if(escapeSingleQuotes, {res = res.replace("\'", "")}); + if(escapeDoubleQuotes, {res = res.replace("\"", "")}); + res = res.replace("Rest", "\"Rest\""); + res = res.interpret; + }, { + var tmpRes; + if(res.every({arg char; char.isDecDigit}), {tmpRes = res.asInteger}); + if(res.contains("."), {tmpRes = res.asFloat}); + if(tmpRes != nil, {res = tmpRes}); + }); + }); + res +}; + +writeResources = {arg path, dict; + var file, modelItems, resString; + file = File(path,"w"); + + modelItems = [ + seq, lastXChanges, + curUID, refUID, orderSeed, durSeed, motifSeed, + entrancesProbVals, passagesProbVals, exitsProbVals, + ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, + orders, susWeights, orderSize, passagesSize, + motifEdited, orderEdited + ]; + + resString = nameSpaces.collect({arg nameSpace; + var depth = 0, insert = " "; + if(nameSpace == "music_data", {depth = 3; insert = "\n"}); + if(nameSpace == "last_changes", {depth = 1; insert = "\n"}); + if(nameSpace == "order", {depth = 1; insert = "\n"}); + if((nameSpace == "ref_uid") && (dict[nameSpace] == nil), {dict[nameSpace] = "nil"}); + "\"" ++ nameSpace ++ "\":" ++ insert ++ stringifyToDepth.value(dict[nameSpace], depth) + }).join(",\n"); + + resString = "{\n" ++ resString ++ "\n}"; + + file.write(resString); + file.close; + resString +}; + +loadModelFile = {arg path; loadModelJSON.value(File(path, "r").readAllString.parseJSON)}; + +loadModelJSON = {arg jsonObject; + var dict; + dict = Dictionary.with(*nameSpaces.collect({arg nS; nS->msgInterpret.value(jsonObject[nS])})); + dict +}; + +setGlobalVars = {arg dict, skipLastXChanges = false; + var tmpLastXChanges; + tmpLastXChanges = lastXChanges.deepCopy; + // order really matters!!!! + # seq, lastXChanges, curUID, refUID, orderSeed, durSeed, motifSeed, + entrancesProbVals, passagesProbVals, exitsProbVals, + ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, + orders, susWeights, orderSize, passagesSize, + motifEdited, orderEdited = nameSpaces.collect({arg nS; dict[nS]}); + if(skipLastXChanges, {lastXChanges = tmpLastXChanges}); + dict +}; + +globalVarsToDict = { + var modelItems, dict; + // order really matters!!!! + modelItems = [ + seq, lastXChanges, + curUID, refUID, orderSeed, durSeed, motifSeed, + entrancesProbVals, passagesProbVals, exitsProbVals, + ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, + orders, susWeights, orderSize, passagesSize, + motifEdited, orderEdited + ]; + dict = Dictionary.with(*nameSpaces.collect({arg nS, n; nS->modelItems[n]})); +}; + +loadLedgerFile = {arg path; + ledgerPath = path; + resourceDir = path.splitext(".").drop(-1).join; + loadLedgerJSON.value(File(ledgerPath, "r").readAllString.parseJSON) +}; + +loadLedgerJSON = {arg jsonObject; ledger = jsonObject["ledger"]}; + +saveLedger = {arg ledger, path; + var file, curResourceDir; + file = File(path, "w"); + curResourceDir = resourceDir; + resourceDir = path.splitext(".").drop(-1).join; + if(curResourceDir != resourceDir, { + File.mkdir(resourceDir); + ledger.do({arg id; + File.copy(curResourceDir +/+ id, resourceDir +/+ id); + }); + }); + file.write("{\n\"ledger\":\n" ++ stringifyToDepth.value(ledger, 1) ++ "\n}"); + file.close; +}; + +//------global vars + +primes = [[2, 1], [3, 2], [5, 4], [7, 4], [11, 8], [13, 8]]; +//ranges = [[-2400, 0], [-1200, 1200], [0, 2400], [0, 2400]]; +exPath = thisProcess.nowExecutingPath; +dir = exPath.dirname; +//popSize = 4; +dims = primes.size; +tuples = genTuples.value(); +//refUID = nil; +group = Group.new; +~group = group; +loadLedgerFile.value(dir +/+ ".." +/+ "resources" +/+ "piece_ledger.json"); +resourceDir = (dir +/+ ".." +/+ "resources" +/+ "piece_ledger"); +//passagesWeights = [1, 1, 1, 1, 1]; +//susWeights = [1, 1, 1]; +// order really matters!!!! +nameSpaces = [ + "music_data", "last_changes", "cur_uid", "ref_uid", "order_seed", "dur_seed", "motifs_seed", + "entrances_probs_vals","passages_probs_vals", "exits_probs_vals", + "ranges", "step_probs_vals", "passages_weights", "hd_exp", "hd_invert", + "order", "sus_weights", "order_size", "passages_size", + "motif_edited", "order_edited" +]; + + +//------OSC funcs + +OSCdef(\load_ledger, {arg msg, time, addr, port; + loadLedgerFile.value(msg[1].asString); +}, \load_ledger); + +OSCdef(\load_model, {arg msg, time, addr, port; + var dict; + dict = loadModelFile.value(msg[1].asString); + setGlobalVars.value(dict); +}, \load_model); + +OSCdef(\save_ledger, {arg msg, time, addr, port; + msg.postln; + ledger = msgInterpret.value(msg[1].asString.parseJSON["ledger"], false).postln; + //loadLedgerJSON.value(msg[0]) + saveLedger.value(ledger, msg[2].asString); + //loadLedgerFile.value(msg[1].asString); +}, \save_ledger); + +OSCdef(\generate, {arg msg, time, addr, port; + var path, dict, durSeeds, musPath, modelString; + msg.postln; + + path = msg[1].asString; + + dict = loadModelFile.value(path); + setGlobalVars.value(dict, true); + + popSize = ranges.size; + + //refUID.postln; + + loadLedgerFile.value(ledgerPath); + if(ledger == nil, {ledger = ["tmp"]}); + if(ledger.last != "tmp", {ledger = ledger.add("tmp")}); + + if(refUID == nil, {lastXChanges = [initVoices.value().deepCopy]}); + if((refUID != nil) && (refUID != "tmp"), { + var file; + file = File((resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath, "r"); + lastXChanges = msgInterpret.value(file.readAllString.parseJSON["last_changes"]); + }); + + refUID.postln; + lastXChanges.collect({arg item; item.postln}); + + durSeeds = seedFunc.value({3.collect({rrand(100000, 999999)})}, durSeed).value.postln; + entrancesDurFunc = genDurFunc.valueArray(entrancesProbVals[..4] ++ [entrancesProbVals[5..]] ++ [durSeeds[0]]); + passagesDurFunc = genDurFunc.valueArray(passagesProbVals[..4] ++ [passagesProbVals[5..]] ++ [durSeeds[1]]); + exitsDurFunc = genDurFunc.valueArray(exitsProbVals[..4] ++ [exitsProbVals[5..]] ++ [durSeeds[2]]); + + if(orders == nil, { + orders = seedFunc.value(genOrders, orderSeed).valueArray(orderSize ++ passagesSize); + //addr.sendMsg("/order", stringifyToDepth.value(orders, 1)); + }); + + stepFunc = genStepFunc.valueArray(stepProbsVals[..1] ++ [stepProbsVals[2..]] ++ [motifSeed]); + seq = seedFunc.value(genMotif, motifSeed).value; + + lastXChanges.collect({arg item; item.postln}); + + dict = globalVarsToDict.value; + modelString = writeResources.value(path, dict); + + //addr.sendMsg("/generated", musPath, stringifyToDepth.value(seq, 3)); + //~seq = seq; + + addr.sendMsg("/generated", path, modelString, ledgerPath); +}, \generate); + + +OSCdef(\commit, {arg msg, time, addr, port; + var musicData, musicChanged, dict, newLedger, modelPath, musString, musFile, test1, test2, lastCurUID, commitType, commitPos, equalityLedger; + //msg.postln; + + /* + test1 = msg[1].asString.parseJSON; + test2 = (dir +/+ ".." +/+ "resources/tmp/tmp_music" ++ ".json").standardizePath.parseJSONFile; + msgInterpret.value(test1["music"])[0][0][0][1].class.postln; + msgInterpret.value(test2["music_data"])[0][0][0][1].class.postln; + (test1["music"] == test2["music_data"]).postln; + */ + + musicData = loadModelJSON.value(msg[1].asString.parseJSON)["music_data"].postln; + musicChanged = (musicData != seq).postln; + commitType = msg[2].asString; + commitPos = msg[3].postln.asInteger; + + lastCurUID = curUID.deepCopy; + curUID = genUID.value; + + File.mkdir((resourceDir +/+ curUID).standardizePath); + File.copy(exPath, (resourceDir +/+ curUID +/+ curUID ++ "_code" ++ ".scd").standardizePath); + + modelPath = (resourceDir +/+ curUID +/+ curUID ++ "_mus_model" ++ ".json").standardizePath; + dict = globalVarsToDict.value; + if(musicChanged, { + seq = musicData; + dict["music_data"] = seq; + dict["motif_edited"] = "true" + }); + dict["cur_uid"] = curUID; + + writeResources.value(modelPath, dict); + + File.delete(ledgerPath ++ "_bak"); + File.copy(ledgerPath, ledgerPath ++ "_bak"); + File.delete(ledgerPath); + + /* + if(commitType == "add", { + if(lastCurUID == "tmp", { + ledger = ledger.drop(-1).add(curUID); + }, { + ledger = ledger.add(curUID); + }) + }); + */ + + ledger.postln; + + if(commitType == "add", {ledger = ledger.add(curUID)}); + + if(commitType == "insert", {ledger = ledger.insert(commitPos + 1, curUID)}); + + if(commitType == "replace", {ledger = ledger.put(commitPos, curUID)}); + + equalityLedger = ledger.collect({arg item; item.asSymbol}); + if(equalityLedger.includes(\tmp).postln, {ledger.removeAt(equalityLedger.indexOf(\tmp).postln)}); + + ledger.postln; + + saveLedger.value(ledger, ledgerPath); + + addr.sendMsg("/committed", curUID, ledgerPath); + //refUID = curUID; + +}, \commit); + +OSCdef(\transport, {arg msg, time, addr, port; + msg.postln; + if(msg[1] == 0, { + group.set(\release, 2); + group.set(\gate, 0); + player.stop; + }, { + // the cued sequence can now be read from file, so this can be cleaned up + var cSize, patterns, pSeq, cuedSeek, indexStart, indexEnd, tmpLedger; + if(msg[1] == 1, { + pSeq = []; + cuedSeek = (seq != nil); + indexStart = msg[2].asInteger; + indexEnd = ledger.size - if(cuedSeek, {2}, {1}); + //ledger.postln; + if(((indexStart == (ledger.size - 1)) && cuedSeek).not, { + ledger[indexStart..indexEnd].do({arg uid, index; + var path, file; + path = (resourceDir +/+ uid +/+ uid ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.postln.parseJSON["music_data"]), path, indexStart + index, uid]); + file.close; + }); + }); + if(cuedSeek, { + var path, file; + path = (resourceDir +/+ "tmp/tmp_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.parseJSON["music_data"]), path, ledger.size - 1, "tmp"]); + file.close; + }); + patterns = genPatterns.value(pSeq, addr); + }, { + pSeq = [loadModelJSON.value(msg[2].asString.parseJSON)["music_data"].postln]; + patterns = genPatterns.value(pSeq, addr, true); + }); + player = Pfset(pattern: patterns, cleanupFunc: { + addr.sendMsg("/transport", 0); + addr.sendMsg("/one_shot", 0); + }); + player = player.play + }); +}, \transport); + + +OSCdef(\transcribe_motif, {arg msg, time, addr, port; + var tSeq, refChord, refUID; + + msg.postln; + + tSeq = [loadModelJSON.value(msg[1].asString.parseJSON)["music_data"]]; + refUID = msg[2].asString.postln; + + if((refUID != "nil") && (refUID != "tmp"), { + var file; + file = File((resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath, "r"); + refChord = msgInterpret.value(file.readAllString.parseJSON["last_changes"]).last; + file.close; + }, { + refChord = [[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0]]; + }); + + ~transcribe.value(tSeq, refChord, (dir +/+ ".." +/+ "lilypond" +/+ "includes").standardizePath, addr, "/transcribe_motif"); +}, \transcribe_motif); + + +OSCdef(\transcribe_all, {arg msg, time, addr, port; + var cSize, patterns, cuedSeek, indexStart, indexEnd, tmpLedger; + if(true, { + cuedSeek = (seq != nil); + indexStart = msg[1].asInteger; + indexEnd = ledger.size - if(cuedSeek, {2}, {1}); + + //tmp for testing transcription + //indexEnd = (indexStart+5); + + //ledger.postln; + if(((indexStart == (ledger.size - 1)) && cuedSeek).not, { + var lilyPartLedgerFiles; + + lilyPartLedgerFiles = 4.collect({arg p; + File((dir +/+ ".." +/+ "lilypond" +/+ "includes" +/+ "part_" ++ ["IV", "III", "II", "I"][p] ++ ".ly").standardizePath, "w"); + }); + + ledger[indexStart..indexEnd].do({arg uid, index; + var path, file, fileString, tSeq, refUID, refChord; + path = (resourceDir +/+ uid +/+ uid ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + fileString = file.readAllString; + tSeq = msgInterpret.value(fileString.parseJSON["music_data"]); + refUID = msgInterpret.value(fileString.parseJSON["ref_uid"]); + file.close; + + //uid.postln; + //(refUID == "nil").postln; + + refChord = [[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0]]; + + if(refUID != "nil", { + path = (resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + refChord = msgInterpret.value(file.readAllString.parseJSON["last_changes"]).last; + file.close; + }); + + if(index != indexEnd, { + ~transcribe.value(tSeq, refChord, (resourceDir +/+ uid +/+ "lilypond").standardizePath); + }, { + ~transcribe.value(tSeq, refChord, (resourceDir +/+ uid +/+ "lilypond").standardizePath, addr, "/transcribe_all"); + }); + + lilyPartLedgerFiles.do({arg f, p; + f.write("\\include \"" ++ resourceDir +/+ uid +/+ "lilypond" +/+ "part_" ++ ["IV", "III", "II", "I"][p] ++ ".ly\"\n"); + }); + + }); + + lilyPartLedgerFiles.do({arg f; + f.close + }); + }); + /* + if(cuedSeek, { + var path, file; + path = (dir +/+ ".." +/+ "resources/tmp/tmp_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.parseJSON["music_data"]), path, ledger.size - 1, "tmp"]); + file.close; + }); + */ + }, { + + }); + +}, \transcribe_all); + +) + + diff --git a/resources/string_quartet_3_rise/65120e88/65120e88_mus_model.json b/resources/string_quartet_3_rise/65120e88/65120e88_mus_model.json new file mode 100644 index 0000000..7e0733c --- /dev/null +++ b/resources/string_quartet_3_rise/65120e88/65120e88_mus_model.json @@ -0,0 +1,65 @@ +{ +"music_data": +[ + [ + [ + [ [ [ -6, 4, 0, 2, -1, 2 ], [ -4, 4, 0, 2, -1, 2 ], [ -6, 4, 1, 3, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0 ], + [ [ [ -6, 4, 0, 2, -1, 2 ], [ -4, 4, 0, 2, -1, 2 ], [ -5, 4, 0, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -7, 5, 1, 2, -1, 2 ], [ -4, 4, 0, 2, -1, 2 ], [ -5, 4, 0, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0 ], + [ [ [ -7, 5, 1, 2, -1, 2 ], [ -4, 4, 0, 2, -1, 2 ], [ -6, 5, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -7, 5, 1, 2, -1, 2 ], [ -4, 4, 0, 2, -1, 2 ], [ -6, 4, 1, 2, 0, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0 ], + [ [ [ -7, 5, 1, 2, -1, 2 ], [ -5, 5, 1, 2, -1, 2 ], [ -6, 4, 1, 2, 0, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -7, 5, 1, 2, -1, 2 ], [ -5, 5, 1, 2, -1, 2 ], [ -6, 5, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ] + ], + [ + [ [ [ -7, 5, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 3 ], [ -6, 5, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0 ], + [ [ [ -7, 5, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 3 ], [ -6, 4, 1, 2, -1, 3 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -7, 5, 1, 2, -1, 2 ], [ -5, 4, 1, 3, -1, 2 ], [ -6, 4, 1, 2, -1, 3 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ] + ], + [ + [ [ [ -7, 5, 1, 2, -1, 2 ], [ -5, 4, 1, 3, -1, 2 ], [ -6, 4, 1, 3, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0 ], + [ [ [ -7, 5, 1, 2, -1, 2 ], [ -6, 4, 1, 2, -1, 2 ], [ -6, 4, 1, 3, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0 ], + [ [ [ -6, 4, 0, 2, -1, 2 ], [ -6, 4, 1, 2, -1, 2 ], [ -6, 4, 1, 3, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ] + ], + [ + [ [ [ -7, 4, 1, 3, -1, 2 ], [ -6, 4, 1, 2, -1, 2 ], [ -6, 4, 1, 3, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0 ], + [ [ [ -7, 4, 1, 3, -1, 2 ], [ -6, 4, 1, 3, -1, 1 ], [ -6, 4, 1, 3, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -7, 4, 1, 3, -1, 2 ], [ -6, 3, 1, 3, -1, 2 ], [ -6, 4, 1, 3, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0 ], + [ [ [ -7, 4, 0, 3, -1, 2 ], [ -6, 3, 1, 3, -1, 2 ], [ -6, 4, 1, 3, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ] + ] + ] +], +"last_changes": +[ + [ [ -6, 4, 0, 2, -1, 2 ], [ -6, 4, 1, 2, -1, 2 ], [ -6, 4, 1, 3, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], + [ [ -7, 4, 1, 3, -1, 2 ], [ -6, 4, 1, 2, -1, 2 ], [ -6, 4, 1, 3, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], + [ [ -7, 4, 1, 3, -1, 2 ], [ -6, 4, 1, 3, -1, 1 ], [ -6, 4, 1, 3, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], + [ [ -7, 4, 1, 3, -1, 2 ], [ -6, 3, 1, 3, -1, 2 ], [ -6, 4, 1, 3, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], + [ [ -7, 4, 0, 3, -1, 2 ], [ -6, 3, 1, 3, -1, 2 ], [ -6, 4, 1, 3, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ] +], +"cur_uid": "65120e88", +"ref_uid": "66b20499", +"order_seed": 837263, +"dur_seed": 378109, +"motifs_seed": 521249, +"entrances_probs_vals": [ 0.78, 0, 0, 0, 1.02, 0.0098039215686275, 0, 0.20915032679739, 0, 0.24183006535948, 0.82432432432432, 0.28758169934641, 0, 1, 0 ], +"passages_probs_vals": [ 0.78, 0, 0, 0, 1.02, 0.0098039215686275, 0, 0.20915032679739, 0, 0.24183006535948, 0.82432432432432, 0.28758169934641, 0, 1, 0 ], +"exits_probs_vals": [ 0.78, 0, 0, 0, 1.02, 0.0098039215686275, 0, 0.20915032679739, 0, 0.24183006535948, 0.82432432432432, 0.28758169934641, 0, 1, 0 ], +"ranges": [ [ -2400, 1200 ], [ -1200, 1453 ], [ -702, 1694 ], [ -702, 1973 ] ], +"step_probs_vals": [ -1200, 1200, 0, 0, 0.41358024691358, 0.0056818181818177, 0.46913580246914, 0.67045454545455, 0.52057613168724, 0, 0.56378600823045, 0.85795454545455, 0.60082304526749, 0, 1, 0 ], +"passages_weights": [ 1, 0.15, 0.43, 1, 1 ], +"hd_exp": 10, +"hd_invert": 0, +"order": +[ + [ [ 3 ], [ 1, 2, 0, 2, 2, 1, 2 ], [ ] ], + [ [ 0, 3 ], [ 1, 2, 1 ], [ ] ], + [ [ 3 ], [ 2, 1, 0 ], [ ] ], + [ [ 3, 2 ], [ 0, 1, 1, 0 ], [ ] ] +], +"sus_weights": [ 0.7, 0.48, 0.49 ], +"order_size": [ 14.132653061224, 14.132653061224 ], +"passages_size": [ 0, 5 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/65120e88/lilypond/part_I.ly b/resources/string_quartet_3_rise/65120e88/lilypond/part_I.ly new file mode 100644 index 0000000..bad9fe0 --- /dev/null +++ b/resources/string_quartet_3_rise/65120e88/lilypond/part_I.ly @@ -0,0 +1,4 @@ +{ + { f'1^\markup { \pad-markup #0.2 "-38"}} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/65120e88/lilypond/part_II.ly b/resources/string_quartet_3_rise/65120e88/lilypond/part_II.ly new file mode 100644 index 0000000..ff5c3ec --- /dev/null +++ b/resources/string_quartet_3_rise/65120e88/lilypond/part_II.ly @@ -0,0 +1,4 @@ +{ + { cis'8^\markup { \pad-markup #0.2 "-25"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }}[ c'8^\markup { \pad-markup #0.2 "-37"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }}] ais8^\markup { \pad-markup #0.2 "+13"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 11↑" }}[ c'8^\markup { \pad-markup #0.2 "-37"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }}] cis'4^\markup { \pad-markup #0.2 "+2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }} d'4^\markup { \pad-markup #0.2 "+30"}} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/65120e88/lilypond/part_III.ly b/resources/string_quartet_3_rise/65120e88/lilypond/part_III.ly new file mode 100644 index 0000000..74d763b --- /dev/null +++ b/resources/string_quartet_3_rise/65120e88/lilypond/part_III.ly @@ -0,0 +1,4 @@ +{ + { cis''4^\markup { \pad-markup #0.2 "-25"} c''4^\markup { \pad-markup #0.2 "-37"} cis''8^\markup { \pad-markup #0.2 "+2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 13↑" }}[ d''8^\markup { \pad-markup #0.2 "+30"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 7↑" }}] f8^\markup { \pad-markup #0.2 "-38"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 1↑" }}[ fis8^\markup { \pad-markup #0.2 "-10"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 13↓" }}]} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/65120e88/lilypond/part_IV.ly b/resources/string_quartet_3_rise/65120e88/lilypond/part_IV.ly new file mode 100644 index 0000000..4fb75f0 --- /dev/null +++ b/resources/string_quartet_3_rise/65120e88/lilypond/part_IV.ly @@ -0,0 +1,4 @@ +{ + { cis8^\markup { \pad-markup #0.2 "-25"}[ c8^\markup { \pad-markup #0.2 "-37"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 3↑" }}] ~ c2 cis8^\markup { \pad-markup #0.2 "-25"}[ d8^\markup { \pad-markup #0.2 "+30"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 1↑" }}]} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/66b20499/66b20499_code.scd b/resources/string_quartet_3_rise/66b20499/66b20499_code.scd new file mode 100644 index 0000000..e1a9d92 --- /dev/null +++ b/resources/string_quartet_3_rise/66b20499/66b20499_code.scd @@ -0,0 +1,992 @@ +( +// helper funcs +var hsArrayToCents, pDist, hdSum, hsChordalDistance, hsArrayToFreq; + +// score funcs +var isInRange, spacingScore, rangeScore, intervalScore, inclusionScore; + +// subroutines +var genTuples, initVoices, genOrders, genSubMotif, updateVoices, genDurFunc, genStepFunc; + +// primary routines +var genMotif, genSecondarySeq; + +// audition funcs +var genPatterns, genMidiPatterns; + +// resource management funcs +var seedFunc, genUID, writeResources, stringifyToDepth, setSeeds, sanityCheck, +msgInterpret, loadLedgerFile, loadLedgerJSON, loadModelFile, loadModelJSON, +setGlobalVars, globalVarsToDict, saveLedger; + +// model vars +//(model and global vars mostly set by OSC funcs +var seq, lastXChanges, +curUID, refUID, orderSeed, durSeed, motifSeed, +entrancesProbVals, passagesProbVals, exitsProbVals, +ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, +orders, susWeights, orderSize, passagesSize, +motifEdited, orderEdited; + +// model aux vars +var entrancesDurFunc, passagesDurFunc, exitsDurFunc, stepFunc; + +// other global vars +var popSize, exPath, dir, primes, dims, tuples, +group, player, resourceDir, ledgerPath, ledger, currentlyPlayingUID, +nameSpaces; + +// install JSON quark (not used) +/* +if(Quarks.isInstalled("JSONlib").not, { + Quarks.install("https://github.com/musikinformatik/JSONlib.git"); + thisProcess.recompile; + //HelpBrowser.openHelpFor("Classes/JSONlib"); +}); +*/ + + +//------helper funcs + +hsArrayToCents = { + arg hsArray; + hsArray.collect({arg dist, p; dist * 1200 * log2(primes[p][0]/primes[p][1])}).sum +}; + +pDist = { + arg array1, array2, signed = false; + var pDistance; + pDistance = hsArrayToCents.value(array2) - hsArrayToCents.value(array1); + if(signed, {pDistance}, {abs(pDistance)}) +}; + +hdSum = { + arg hsArrays; + var size, distances, mean; + size = hsArrays.size; + distances = (size - 1).collect({arg i; + ((i + 1)..(size - 1)).collect({arg j; + abs(hsArrays[i] - hsArrays[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsChordalDistance = { + arg hsArrays1, hsArrays2; + var size, distances, mean; + size = hsArrays1.size; + distances = hsArrays1.size.collect({arg i; + hsArrays2.size.collect({arg j; + abs(hsArrays1[i] - hsArrays2[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsArrayToFreq = { + arg array; + array.collect({arg dim, d; pow(primes[d][0]/primes[d][1], dim)}).product +}; + +//------score funcs + +/* +isInRange = { + arg hsArray, min, max; + var cents; + cents = hsArrayToCents.value(hsArray); + (cents >= min) && (cents <= max) +}; +*/ + +spacingScore = { + arg hsArrays, min; + var centsArray; + centsArray = hsArrays.collect({arg hsArray; hsArrayToCents.value(hsArray)}).sort({arg a, b; a < b}); + centsArray.differentiate.drop(1).collect({arg pDistance; if(pDistance >= min, {1}, {0.01})}).sum; +}; + +rangeScore = { + arg hsArray1, hsArray2, min, max, low, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + if((pDistance >= min) && (pDistance <= max), {1}, {low}); +}; + +/* +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + stepFunc.value(pDistance) +}; +*/ + +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = true; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + //if(pDistance >= 0, {stepFunc.value(abs(pDistance))}, {0.01}); + stepFunc.value(pDistance) +}; + +inclusionScore = { + arg array, test, min = 0.01; + if(array.collect({arg v; v.hash}).includes(test.hash), {min}, {1}); +}; + + +//------subroutines + +genTuples = { + var tuples; + tuples = dims.collect({[-1, 0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum <= 1) && (tuple[0] == 0)}); + //tuples = dims.collect({[0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum == 1) && (tuple[0] == 0)}); + tuples = tuples ++ tuples.collect({arg tuple; [-3, -2, -1, 1, 2, 3].collect({arg octTrans; tuple.deepCopy.put(0, octTrans)})}).flatten; +}; + +initVoices = { + var init, voicesInit; + voicesInit = popSize.collect({dims.collect({0})}); + /* + voicesInit = [dims.collect({0})]; + (popSize - 1).do({ + arg rep, new; + rep = dims.rand; + new = voicesInit.last.deepCopy; + new[rep] = new[rep] + [-1, 1].choose(); + voicesInit = voicesInit.add(new); + }); + */ + voicesInit.deepCopy; +}; + +genDurFunc = {arg chordProb, minPad, maxPad, minDur, maxDur, envData, seed; + var env, pTable, durFunc; + env = Env.pairs([[0, 0]] ++ envData.clump(2) ++ [[1, 0]]).asSignal(256).asList.asArray; + pTable = env.asRandomTable; + //[chordProb, minPad, maxPad, minDur, maxDur, envData].postln; + durFunc = {arg allowChord, pad = false; + var res; + res = if(allowChord.not, { + pTable.tableRand * (maxDur - minDur) + minDur + }, { + if(1.0.rand < chordProb, {0}, {pTable.tableRand * (maxDur - minDur) + minDur}); + }).round(0.125); + if(pad, {res = res + rrand(minPad.asFloat, maxPad.asFloat).round(0.125)}); + if(res.asInteger == res, {res = res.asInteger}); + res + }; + seedFunc.value(durFunc, seed); +}; + +genStepFunc = {arg minStep, maxStep, envData, seed; + var envDataNorm, env, pTable, stepFunc; + //[minStep, maxStep, envData].postln; + envDataNorm = ([[0, 0]] ++ envData.clump(2) ++ [[1, 0]]).flop; + envDataNorm = [envDataNorm[0].normalize(minStep, maxStep), envDataNorm[1]].flop; + env = Env.pairs(envDataNorm); + stepFunc = {arg pDist; + env.at(pDist).clip(0.001, 1); + }; + seedFunc.value(stepFunc, seed); +}; + +genOrders = {arg minMotifLength = 1, maxMotifLength = 5, minProgLength = 0, maxProgLength = 5; + ((maxMotifLength.asInteger - minMotifLength.asInteger).rand + minMotifLength.asInteger).collect({ + var noProgIns, noSusIns, noSilentIns, prog, sus, silent, order; + noSusIns = [1, 2, 3].wchoose(susWeights.normalizeSum); + + //noProgIns = (popSize - noSusIns).rand + 1; + noProgIns = (popSize - noSusIns); + noSilentIns = popSize - noSusIns - noProgIns; + + /* + noSilentIns = (popSize - noSusIns).rand.clip(0, 1); + noProgIns = popSize - noSusIns - noSilentIns; + */ + + # prog, sus, silent = (0..(popSize-1)).scramble.clumps([noProgIns, noSusIns, noSilentIns]); + + prog = (prog.scramble ++ ((maxProgLength.asInteger - minProgLength.asInteger).rand + minProgLength.asInteger).collect({prog.choose}).scramble); + //prog = ((maxProgLength.asInteger - minProgLength.asInteger).rand + minProgLength.asInteger).collect({prog.choose}); + + if(silent == nil, {silent = []}); + [sus.scramble, prog, silent.scramble] + }); +}; + +updateVoices = {arg ins, sus; + var voices, candidates, nWeights, nProbs, sel; + + voices = lastXChanges.deepCopy.last; + + candidates = sus.collect({arg v; tuples.collect({arg t; voices[v] + t})}).flatten; + candidates = difference(candidates.asSet, voices.asSet).asList; + //candidates.select({arg item; (item ++ voices).asSet.size >= 4}); + nProbs = candidates.collect({arg candidate; + var stepScore, recentlySoundedScore, isInRangeScore, regScore, hdScore; + + //stepScore = intervalScore.value(voices[ins], candidate, 30, 400, 0.1); + stepScore = intervalScore.value(voices[ins], candidate, 100, 100); + recentlySoundedScore = inclusionScore.value(lastXChanges.flop[ins], candidate, 0.01); + //recentlySoundedScore = inclusionScore.value(lastXChanges.flatten.collect({arg item; item.drop(1)}), candidate.drop(1), 0.01); + + /* + if(rangeScore.value(candidate.collect({0}), voices[ins], ranges[ins][1] - 500, ranges[ins][1], 0, true) == 1, { + isInRangeScore = 1 - rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0] + 500, ranges[ins][1] - 500, 0, true); + isInRangeScore = isInRangeScore * rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true) + }, { + isInRangeScore = rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true); + }); + */ + + + isInRangeScore = rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true); + //old way - worked but actually by accident (I think) + //isInRangeScore = rangeScore.value(candidate, candidate.collect({0}), ranges[ins][0], ranges[ins][1], 0, true); + + + regScore = spacingScore.value(voices.deepCopy.put(ins, candidate), 300); + hdScore = pow(hdSum.value(voices.deepCopy.put(ins, candidate)), hdExp); + if(hdInvert == 0, {hdScore = 1/hdScore}); + //maybe what you want here is a vector to another root and then favoring movement towards it. + //distScore = pow(hsChordalDistance.value(voices, voices.put(ins, candidate)), 2); + + [stepScore, recentlySoundedScore, isInRangeScore, regScore, hdScore] + }); + + nWeights = passagesWeights; + + //this handles nWeights of 0; mainly for testing + nProbs = nProbs.flop.select({arg scores, s; nWeights[s] != 0}).flop; + nWeights = nWeights.select({arg weight; weight != 0}); + nProbs = nProbs.flop.collect({arg scores, s; + if(scores.sum == 0, {scores}, {scores.normalizeSum * nWeights[s]}).postln; + }); + nProbs = nProbs.flop.collect({arg scores, s; scores.product}).normalizeSum; + + nProbs.round(0.001).postln; + [candidates, nProbs.round(0.001)].flop.postln; + + sel = candidates.wchoose(nProbs); + + voices[ins] = sel; + lastXChanges = lastXChanges.add(voices).keep(-5); +}; + +genSubMotif = {arg order, orderIndex, lastState, repeatLast = false, startFromLast = false, isLastOrder = false; + var sus, prog, silent, flatOrder, res, isInChord, allowChord, pad, lastXChangesHold, voices, adder; + # sus, prog, silent = order; + flatOrder = silent ++ sus ++ prog; + lastXChangesHold = lastXChanges.deepCopy; + voices = lastState.deepCopy; + isInChord = popSize.collect({false}); + allowChord = false; + pad = false; + res = []; + "------generating motif".postln; + //need to figure out here if voices move between motifs + flatOrder.do({arg ins, i; + + if(prog.includes(ins) && repeatLast.not, {updateVoices.value(ins, sus)}); + adder = if(silent.includes(ins), {["Rest"]}, {lastXChanges.last.deepCopy[ins]}); + + if(voices[ins] != adder, { + var dur; + + if((sus ++ silent).includes(ins), { + allowChord = (ins != sus.last); + pad = (ins == sus.last); + }, { + if(i < (flatOrder.size - 1), { + allowChord = (isInChord[flatOrder[i + 1]] || (ins == flatOrder[i + 1])).not; + pad = false; + }, { + allowChord = false; + pad = true + }); + }); + if((orderIndex == 0) && sus.includes(ins), { + dur = entrancesDurFunc.value(allowChord, pad); + }, { + dur = passagesDurFunc.value(allowChord, pad); + }); + if(dur == 0, {isInChord[ins] = true}, {isInChord = popSize.collect({false})}); + + voices[ins] = adder; + res = res.add([voices.deepCopy.postln, dur]); + }); + }); + + // pad ending + /* + if(orderIndex == (orders.size - 1), { + (0..(popSize-1)).scramble.do({arg ins; + if(res.last.first[ins] != ["Rest"], { + var dur; + voices[ins] = ["Rest"]; + allowChord = (voices != popSize.collect({["Rest"]})); + pad = allowChord.not; + dur = exitsDurFunc.value(allowChord, pad); + res = res.add([voices.deepCopy.postln, dur]); + }); + }); + }); + */ + + //format and return + if(startFromLast, {lastXChanges = lastXChangesHold.deepCopy}); + res; +}; + + +//------primary routines + +genMotif = { + var repeats, fSeq, fDur, durAdd; + + repeats = 1; + fSeq = []; + + repeats.do({arg index; + var motif; + + motif = []; + + orders.do({arg order, o; + var lastState, subMotif; + //lastState = if(o == 0, {popSize.collect({["Rest"]})}, {motif.last.last.first}); + lastState = if(o == 0, {lastXChanges.last.deepCopy}, {motif.last.last.first}); + subMotif = genSubMotif.value(order, o, lastState, isLastOrder: o == (orders.size - 1)); + motif = motif.add(subMotif); + + }); + + sanityCheck.value(motif, index); + + fSeq = fSeq.add(motif); + }); + + //round last duration to measure + /* + fDur = fSeq.flatten.flatten.slice(nil, 1).sum; + durAdd = fDur.round(4) - fDur; + if(durAdd < 0, {durAdd = 4 - durAdd}); + fSeq[0][orders.size - 1][fSeq[0][orders.size - 1].size - 1][1] = fSeq[0][orders.size - 1][fSeq[0][orders.size - 1].size - 1][1] + durAdd; + */ + + fSeq +}; + +genSecondarySeq = {arg seq; + var curdles, fSeq; + curdles = []; + while({curdles.sum < seq.size}, {curdles = curdles ++ [3.rand + 1]}); + + fSeq = seq.clumps(curdles).collect({arg clump, m; + var repeats, paddedSeq; + + //add rest + paddedSeq = clump.add([[[popSize.collect({["Rest"]}), 0.5.rand]]]); + + //implement repeats + repeats = [0.rand + 1, 1].wchoose([1, 0].normalizeSum); + repeats.collect({paddedSeq}); + }); + fSeq +}; + + +//------audition funcs + +/* +Event.addEventType(\osc, { + if (~addr.postln.notNil) { + ~addr.sendMsg(~indexPath, ~indexMsg); + ~addr.sendMsg(~seqPath, stringifyToDepth.value(~seqMsg, 3)); + //~addr.sendMsg("/STATE/OPEN", (dir.replace("supercollider", "resources") +/+ ~idMsg +/+ ~idMsg ++ "_gui_state" ++ ".state").standardizePath.postln); + }; +}); +*/ + +Event.addEventType(\osc, { + if (~addr.notNil) { + ~msg; + ~addr.sendMsg(~path, *~msg); + }; +}); + +genPatterns = {arg inSeq, addr, oneShot = false; + var voices, durs, pbinds, res, indices, sectionDurs, msg, ids, seq; + seq = inSeq.collect({arg mSeq; mSeq[0]}); + //# voices, durs = seq.flatten2(seq.maxDepth - 5).flop; + # voices, durs = seq.flatten2(if(oneShot, {2}, {3})).flop; + pbinds = voices.flop.collect({arg voice, v; + var clumps, hdScores, freqs, fDurs, attacks, rels, amps; + clumps = voice.separate({arg a, b; a != b }); + freqs = clumps.collect({arg clump; if(clump[0] != ["Rest"], {(62.midicps * hsArrayToFreq.value(clump[0]))}, {Rest(0)})}); + fDurs = durs.clumps(clumps.collect({arg clump; clump.size})).collect({arg clump; clump.sum}); + //attacks = 2.collect({rrand(1, 3)}) ++ freqs.drop(2).collect({rrand(0.3, 0.5)}); + attacks = fDurs.collect({arg dur; dur * rrand(0.2, 0.4)}); + //rels = freqs.drop(2).collect({rrand(0.3, 0.5)}) ++ 2.collect({rrand(1, 3)}); + rels = (clumps.size - 1).collect({arg c; + if(clumps[c + 1][0] == ["Rest"], {rrand(1.0, 3.0)}, {rrand(0.3, 0.5)}); + }); + rels = rels.add(rrand(1.0, 3.0)); + amps = freqs.collect({rrand(0.6, 0.99)}); + + [ + Pbind( + \instrument, \string_model, + \group, group, + \freq, Pseq(freqs, 1), + \dur, Pseq(fDurs, 1), + \attack, Pseq(attacks, 1), + \sustain, Pseq(fDurs, 1), + \release, Pseq(rels, 1), + //\amp, Pseq(amps, 1), + \amp, Pbrown(0.5, 1, 0.5), + \busIndex, v + ), + Pbind( + \instrument, \sine, + \group, group, + \freq, Pseq(freqs, 1), + \dur, Pseq(fDurs, 1), + \sustain, Pseq(fDurs, 1), + \busIndex, v + ) + ] + }).flatten; + if(oneShot.not, { + msg = inSeq.collect({arg mSeq, m; mSeq[1..]}); + //ids = inSeq.collect({arg mSeq, m; mSeq[2]}); + sectionDurs = seq.collect({arg mSeq; mSeq.flatten2(2).flop[1].sum}); + pbinds = pbinds ++ + [ + Pbind( + \type, \osc, + \addr, addr, + \path, "/playing", + \msg, Pseq(msg, 1), + \dur, Pseq(sectionDurs, 1) + ); + ] + }); + res = Ppar(pbinds); + res +}; + +/* +genMidiPatterns = {arg seq; + var voices, durs, patterns, res, mOut, pbRange; + pbRange = 1; //semitones - change this as needed for your situation + mOut = MIDIOut.newByName("TiMidity", "TiMidity port 0").latency_(Server.default.latency); + # voices, durs = seq.flatten2(seq.maxDepth - 5).flop; + res = Ppar( + voices.flop.collect({arg voice, v; + var clumps, hdScores, freqs, fDurs; + + mOut.program(v, 70); + + clumps = voice.separate({arg a, b; a != b }); + freqs = clumps.collect({arg clump; if(clump[0] != ["Rest"], {(60.midicps * hsArrayToFreq.value(clump[0]))}, {Rest(0)})}); + fDurs = durs.clumps(clumps.collect({arg clump; clump.size})).collect({arg clump; clump.sum}); + + Pbind( + \type, \midi, + \chan, v, + \noteval, Pseq(freqs.cpsmidi - 24, 1), + \note, Pfunc({ | event | event[\noteval].floor }), + \dur, Pseq(fDurs, 1), + \midiout, mOut, + \amp, 1, + \bend, Pfunc({ + | event | + if (event[\note].isRest.not) { + var pitchbendvalue = event[\noteval].frac.linlin(0, pbRange, 8192, 8192*2).asInteger; + m.bend(v, pitchbendvalue); + }; + 0; // return something other than nil to avoid stopping the pattern + }), + ); + }); + ); + res +}; +*/ + + +//------resource management funcs + +genUID = {Date.seed.asHexString.toLower}; + +seedFunc = {arg func, seed; + var funcArgs, next; + next = Routine({loop{func.valueArray(funcArgs).yield }}); + next.randSeed_(seed); + {arg ...args; funcArgs = args; next.value} +}; + +stringifyToDepth = {arg data, maxDepth = 1; + var prettyString = "", rCount = 0, writeArray, indent; + + if(maxDepth == 0, { + data.asCompileString + }, { + indent = {arg size; size.collect({" "}).join("")}; + writeArray = {arg array; + prettyString = prettyString ++ indent.value(rCount) ++ "[\n"; + rCount = rCount + 1; + if(rCount < maxDepth, { + array.do({arg subArray; writeArray.value(subArray)}); + }, { + prettyString = prettyString ++ array.collect({arg subArray; + indent.value(rCount + 1) ++ subArray.asCompileString + }).join(",\n"); + }); + rCount = rCount - 1; + prettyString = prettyString ++ "\n" ++ indent.value(rCount) ++ "],\n"; + }; + + writeArray.value(data); + prettyString.replace(",\n\n", "\n").drop(-2); + }) +}; + +sanityCheck = {arg motif, index; + //print functions = very helpful + ("----------" + index + "------------").postln; + + motif.flatten.do({arg val, v; + if(v > 0, { + if(motif.flatten[v-1][0].hammingDistance(val[0]) > 1, {"problem 1".postln}); + if(motif.flatten[v-1][0].hammingDistance(val[0]) == 0, {"problem 2".postln}); + }); + val.postln + }); + "***********".postln; +}; + +msgInterpret = {arg in, escapeDoubleQuotes = true, escapeSingleQuotes = true; + var res; + + res = in; + if(res.isNil.not, { + if((res.isArray && res.isString.not), { + res = res.asCompileString; + res = res.replace(" ", "").replace("\n", "").replace("\t", ""); + if(escapeSingleQuotes, {res = res.replace("\'", "")}); + if(escapeDoubleQuotes, {res = res.replace("\"", "")}); + res = res.replace("Rest", "\"Rest\""); + res = res.interpret; + }, { + var tmpRes; + if(res.every({arg char; char.isDecDigit}), {tmpRes = res.asInteger}); + if(res.contains("."), {tmpRes = res.asFloat}); + if(tmpRes != nil, {res = tmpRes}); + }); + }); + res +}; + +writeResources = {arg path, dict; + var file, modelItems, resString; + file = File(path,"w"); + + modelItems = [ + seq, lastXChanges, + curUID, refUID, orderSeed, durSeed, motifSeed, + entrancesProbVals, passagesProbVals, exitsProbVals, + ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, + orders, susWeights, orderSize, passagesSize, + motifEdited, orderEdited + ]; + + resString = nameSpaces.collect({arg nameSpace; + var depth = 0, insert = " "; + if(nameSpace == "music_data", {depth = 3; insert = "\n"}); + if(nameSpace == "last_changes", {depth = 1; insert = "\n"}); + if(nameSpace == "order", {depth = 1; insert = "\n"}); + if((nameSpace == "ref_uid") && (dict[nameSpace] == nil), {dict[nameSpace] = "nil"}); + "\"" ++ nameSpace ++ "\":" ++ insert ++ stringifyToDepth.value(dict[nameSpace], depth) + }).join(",\n"); + + resString = "{\n" ++ resString ++ "\n}"; + + file.write(resString); + file.close; + resString +}; + +loadModelFile = {arg path; loadModelJSON.value(File(path, "r").readAllString.parseJSON)}; + +loadModelJSON = {arg jsonObject; + var dict; + dict = Dictionary.with(*nameSpaces.collect({arg nS; nS->msgInterpret.value(jsonObject[nS])})); + dict +}; + +setGlobalVars = {arg dict, skipLastXChanges = false; + var tmpLastXChanges; + tmpLastXChanges = lastXChanges.deepCopy; + // order really matters!!!! + # seq, lastXChanges, curUID, refUID, orderSeed, durSeed, motifSeed, + entrancesProbVals, passagesProbVals, exitsProbVals, + ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, + orders, susWeights, orderSize, passagesSize, + motifEdited, orderEdited = nameSpaces.collect({arg nS; dict[nS]}); + if(skipLastXChanges, {lastXChanges = tmpLastXChanges}); + dict +}; + +globalVarsToDict = { + var modelItems, dict; + // order really matters!!!! + modelItems = [ + seq, lastXChanges, + curUID, refUID, orderSeed, durSeed, motifSeed, + entrancesProbVals, passagesProbVals, exitsProbVals, + ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, + orders, susWeights, orderSize, passagesSize, + motifEdited, orderEdited + ]; + dict = Dictionary.with(*nameSpaces.collect({arg nS, n; nS->modelItems[n]})); +}; + +loadLedgerFile = {arg path; + ledgerPath = path; + resourceDir = path.splitext(".").drop(-1).join; + loadLedgerJSON.value(File(ledgerPath, "r").readAllString.parseJSON) +}; + +loadLedgerJSON = {arg jsonObject; ledger = jsonObject["ledger"]}; + +saveLedger = {arg ledger, path; + var file, curResourceDir; + file = File(path, "w"); + curResourceDir = resourceDir; + resourceDir = path.splitext(".").drop(-1).join; + if(curResourceDir != resourceDir, { + File.mkdir(resourceDir); + ledger.do({arg id; + File.copy(curResourceDir +/+ id, resourceDir +/+ id); + }); + }); + file.write("{\n\"ledger\":\n" ++ stringifyToDepth.value(ledger, 1) ++ "\n}"); + file.close; +}; + +//------global vars + +primes = [[2, 1], [3, 2], [5, 4], [7, 4], [11, 8], [13, 8]]; +//ranges = [[-2400, 0], [-1200, 1200], [0, 2400], [0, 2400]]; +exPath = thisProcess.nowExecutingPath; +dir = exPath.dirname; +//popSize = 4; +dims = primes.size; +tuples = genTuples.value(); +//refUID = nil; +group = Group.new; +~group = group; +loadLedgerFile.value(dir +/+ ".." +/+ "resources" +/+ "piece_ledger.json"); +resourceDir = (dir +/+ ".." +/+ "resources" +/+ "piece_ledger"); +//passagesWeights = [1, 1, 1, 1, 1]; +//susWeights = [1, 1, 1]; +// order really matters!!!! +nameSpaces = [ + "music_data", "last_changes", "cur_uid", "ref_uid", "order_seed", "dur_seed", "motifs_seed", + "entrances_probs_vals","passages_probs_vals", "exits_probs_vals", + "ranges", "step_probs_vals", "passages_weights", "hd_exp", "hd_invert", + "order", "sus_weights", "order_size", "passages_size", + "motif_edited", "order_edited" +]; + + +//------OSC funcs + +OSCdef(\load_ledger, {arg msg, time, addr, port; + loadLedgerFile.value(msg[1].asString); +}, \load_ledger); + +OSCdef(\load_model, {arg msg, time, addr, port; + var dict; + dict = loadModelFile.value(msg[1].asString); + setGlobalVars.value(dict); +}, \load_model); + +OSCdef(\save_ledger, {arg msg, time, addr, port; + msg.postln; + ledger = msgInterpret.value(msg[1].asString.parseJSON["ledger"], false).postln; + //loadLedgerJSON.value(msg[0]) + saveLedger.value(ledger, msg[2].asString); + //loadLedgerFile.value(msg[1].asString); +}, \save_ledger); + +OSCdef(\generate, {arg msg, time, addr, port; + var path, dict, durSeeds, musPath, modelString; + msg.postln; + + path = msg[1].asString; + + dict = loadModelFile.value(path); + setGlobalVars.value(dict, true); + + popSize = ranges.size; + + //refUID.postln; + + loadLedgerFile.value(ledgerPath); + if(ledger == nil, {ledger = ["tmp"]}); + if(ledger.last != "tmp", {ledger = ledger.add("tmp")}); + + if(refUID == nil, {lastXChanges = [initVoices.value().deepCopy]}); + if((refUID != nil) && (refUID != "tmp"), { + var file; + file = File((resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath, "r"); + lastXChanges = msgInterpret.value(file.readAllString.parseJSON["last_changes"]); + }); + + refUID.postln; + lastXChanges.collect({arg item; item.postln}); + + durSeeds = seedFunc.value({3.collect({rrand(100000, 999999)})}, durSeed).value.postln; + entrancesDurFunc = genDurFunc.valueArray(entrancesProbVals[..4] ++ [entrancesProbVals[5..]] ++ [durSeeds[0]]); + passagesDurFunc = genDurFunc.valueArray(passagesProbVals[..4] ++ [passagesProbVals[5..]] ++ [durSeeds[1]]); + exitsDurFunc = genDurFunc.valueArray(exitsProbVals[..4] ++ [exitsProbVals[5..]] ++ [durSeeds[2]]); + + if(orders == nil, { + orders = seedFunc.value(genOrders, orderSeed).valueArray(orderSize ++ passagesSize); + //addr.sendMsg("/order", stringifyToDepth.value(orders, 1)); + }); + + stepFunc = genStepFunc.valueArray(stepProbsVals[..1] ++ [stepProbsVals[2..]] ++ [motifSeed]); + seq = seedFunc.value(genMotif, motifSeed).value; + + lastXChanges.collect({arg item; item.postln}); + + dict = globalVarsToDict.value; + modelString = writeResources.value(path, dict); + + //addr.sendMsg("/generated", musPath, stringifyToDepth.value(seq, 3)); + //~seq = seq; + + addr.sendMsg("/generated", path, modelString, ledgerPath); +}, \generate); + + +OSCdef(\commit, {arg msg, time, addr, port; + var musicData, musicChanged, dict, newLedger, modelPath, musString, musFile, test1, test2, lastCurUID, commitType, commitPos, equalityLedger; + //msg.postln; + + /* + test1 = msg[1].asString.parseJSON; + test2 = (dir +/+ ".." +/+ "resources/tmp/tmp_music" ++ ".json").standardizePath.parseJSONFile; + msgInterpret.value(test1["music"])[0][0][0][1].class.postln; + msgInterpret.value(test2["music_data"])[0][0][0][1].class.postln; + (test1["music"] == test2["music_data"]).postln; + */ + + musicData = loadModelJSON.value(msg[1].asString.parseJSON)["music_data"].postln; + musicChanged = (musicData != seq).postln; + commitType = msg[2].asString; + commitPos = msg[3].postln.asInteger; + + lastCurUID = curUID.deepCopy; + curUID = genUID.value; + + File.mkdir((resourceDir +/+ curUID).standardizePath); + File.copy(exPath, (resourceDir +/+ curUID +/+ curUID ++ "_code" ++ ".scd").standardizePath); + + modelPath = (resourceDir +/+ curUID +/+ curUID ++ "_mus_model" ++ ".json").standardizePath; + dict = globalVarsToDict.value; + if(musicChanged, { + seq = musicData; + dict["music_data"] = seq; + dict["motif_edited"] = "true" + }); + dict["cur_uid"] = curUID; + + writeResources.value(modelPath, dict); + + File.delete(ledgerPath ++ "_bak"); + File.copy(ledgerPath, ledgerPath ++ "_bak"); + File.delete(ledgerPath); + + /* + if(commitType == "add", { + if(lastCurUID == "tmp", { + ledger = ledger.drop(-1).add(curUID); + }, { + ledger = ledger.add(curUID); + }) + }); + */ + + ledger.postln; + + if(commitType == "add", {ledger = ledger.add(curUID)}); + + if(commitType == "insert", {ledger = ledger.insert(commitPos + 1, curUID)}); + + if(commitType == "replace", {ledger = ledger.put(commitPos, curUID)}); + + equalityLedger = ledger.collect({arg item; item.asSymbol}); + if(equalityLedger.includes(\tmp).postln, {ledger.removeAt(equalityLedger.indexOf(\tmp).postln)}); + + ledger.postln; + + saveLedger.value(ledger, ledgerPath); + + addr.sendMsg("/committed", curUID, ledgerPath); + //refUID = curUID; + +}, \commit); + +OSCdef(\transport, {arg msg, time, addr, port; + msg.postln; + if(msg[1] == 0, { + group.set(\release, 2); + group.set(\gate, 0); + player.stop; + }, { + // the cued sequence can now be read from file, so this can be cleaned up + var cSize, patterns, pSeq, cuedSeek, indexStart, indexEnd, tmpLedger; + if(msg[1] == 1, { + pSeq = []; + cuedSeek = (seq != nil); + indexStart = msg[2].asInteger; + indexEnd = ledger.size - if(cuedSeek, {2}, {1}); + //ledger.postln; + if(((indexStart == (ledger.size - 1)) && cuedSeek).not, { + ledger[indexStart..indexEnd].do({arg uid, index; + var path, file; + path = (resourceDir +/+ uid +/+ uid ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.postln.parseJSON["music_data"]), path, indexStart + index, uid]); + file.close; + }); + }); + if(cuedSeek, { + var path, file; + path = (resourceDir +/+ "tmp/tmp_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.parseJSON["music_data"]), path, ledger.size - 1, "tmp"]); + file.close; + }); + patterns = genPatterns.value(pSeq, addr); + }, { + pSeq = [loadModelJSON.value(msg[2].asString.parseJSON)["music_data"].postln]; + patterns = genPatterns.value(pSeq, addr, true); + }); + player = Pfset(pattern: patterns, cleanupFunc: { + addr.sendMsg("/transport", 0); + addr.sendMsg("/one_shot", 0); + }); + player = player.play + }); +}, \transport); + + +OSCdef(\transcribe_motif, {arg msg, time, addr, port; + var tSeq, refChord, refUID; + + msg.postln; + + tSeq = [loadModelJSON.value(msg[1].asString.parseJSON)["music_data"]]; + refUID = msg[2].asString.postln; + + if((refUID != "nil") && (refUID != "tmp"), { + var file; + file = File((resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath, "r"); + refChord = msgInterpret.value(file.readAllString.parseJSON["last_changes"]).last; + file.close; + }, { + refChord = [[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0]]; + }); + + ~transcribe.value(tSeq, refChord, (dir +/+ ".." +/+ "lilypond" +/+ "includes").standardizePath, addr, "/transcribe_motif"); +}, \transcribe_motif); + + +OSCdef(\transcribe_all, {arg msg, time, addr, port; + var cSize, patterns, cuedSeek, indexStart, indexEnd, tmpLedger; + if(true, { + cuedSeek = (seq != nil); + indexStart = msg[1].asInteger; + indexEnd = ledger.size - if(cuedSeek, {2}, {1}); + + //tmp for testing transcription + //indexEnd = (indexStart+5); + + //ledger.postln; + if(((indexStart == (ledger.size - 1)) && cuedSeek).not, { + var lilyPartLedgerFiles; + + lilyPartLedgerFiles = 4.collect({arg p; + File((dir +/+ ".." +/+ "lilypond" +/+ "includes" +/+ "part_" ++ ["IV", "III", "II", "I"][p] ++ ".ly").standardizePath, "w"); + }); + + ledger[indexStart..indexEnd].do({arg uid, index; + var path, file, fileString, tSeq, refUID, refChord; + path = (resourceDir +/+ uid +/+ uid ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + fileString = file.readAllString; + tSeq = msgInterpret.value(fileString.parseJSON["music_data"]); + refUID = msgInterpret.value(fileString.parseJSON["ref_uid"]); + file.close; + + //uid.postln; + //(refUID == "nil").postln; + + refChord = [[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0]]; + + if(refUID != "nil", { + path = (resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + refChord = msgInterpret.value(file.readAllString.parseJSON["last_changes"]).last; + file.close; + }); + + if(index != indexEnd, { + ~transcribe.value(tSeq, refChord, (resourceDir +/+ uid +/+ "lilypond").standardizePath); + }, { + ~transcribe.value(tSeq, refChord, (resourceDir +/+ uid +/+ "lilypond").standardizePath, addr, "/transcribe_all"); + }); + + lilyPartLedgerFiles.do({arg f, p; + f.write("\\include \"" ++ resourceDir +/+ uid +/+ "lilypond" +/+ "part_" ++ ["IV", "III", "II", "I"][p] ++ ".ly\"\n"); + }); + + }); + + lilyPartLedgerFiles.do({arg f; + f.close + }); + }); + /* + if(cuedSeek, { + var path, file; + path = (dir +/+ ".." +/+ "resources/tmp/tmp_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.parseJSON["music_data"]), path, ledger.size - 1, "tmp"]); + file.close; + }); + */ + }, { + + }); + +}, \transcribe_all); + +) + + diff --git a/resources/string_quartet_3_rise/66b20499/66b20499_mus_model.json b/resources/string_quartet_3_rise/66b20499/66b20499_mus_model.json new file mode 100644 index 0000000..2a8636b --- /dev/null +++ b/resources/string_quartet_3_rise/66b20499/66b20499_mus_model.json @@ -0,0 +1,96 @@ +{ +"music_data": +[ + [ + [ + [ [ [ -4, 4, -1, 2, -1, 2 ], [ -4, 4, -1, 2, -2, 3 ], [ -5, 5, -1, 2, -2, 2 ], [ -4, 4, -1, 2, -2, 2 ] ], 0.25 ], + [ [ [ -4, 4, -1, 2, -1, 2 ], [ -4, 5, -1, 2, -2, 2 ], [ -5, 5, -1, 2, -2, 2 ], [ -4, 4, -1, 2, -2, 2 ] ], 0.25 ], + [ [ [ -4, 4, -1, 2, -1, 2 ], [ -3, 4, -2, 2, -2, 2 ], [ -5, 5, -1, 2, -2, 2 ], [ -4, 4, -1, 2, -2, 2 ] ], 0.25 ], + [ [ [ -4, 4, -1, 2, -1, 2 ], [ -4, 4, 0, 2, -1, 2 ], [ -5, 5, -1, 2, -2, 2 ], [ -4, 4, -1, 2, -2, 2 ] ], 0.25 ] + ], + [ + [ [ [ -4, 4, -1, 2, -1, 2 ], [ -4, 4, 0, 2, -1, 2 ], [ -5, 5, -1, 2, -2, 2 ], [ -4, 4, 0, 1, -1, 2 ] ], 0 ], + [ [ [ -4, 4, 0, 2, -2, 2 ], [ -4, 4, 0, 2, -1, 2 ], [ -5, 5, -1, 2, -2, 2 ], [ -4, 4, 0, 1, -1, 2 ] ], 0.25 ], + [ [ [ -4, 4, 0, 2, -2, 2 ], [ -4, 4, 0, 2, -1, 2 ], [ -5, 4, 0, 2, -1, 2 ], [ -4, 4, 0, 1, -1, 2 ] ], 0.25 ], + [ [ [ -4, 4, 0, 2, -2, 2 ], [ -4, 4, 0, 2, -1, 2 ], [ -5, 4, -1, 2, -1, 2 ], [ -4, 4, 0, 1, -1, 2 ] ], 0.25 ], + [ [ [ -4, 4, 0, 2, -2, 2 ], [ -4, 4, 0, 2, -1, 2 ], [ -5, 4, 0, 2, -2, 2 ], [ -4, 4, 0, 1, -1, 2 ] ], 0.25 ], + [ [ [ -4, 4, 0, 2, -2, 2 ], [ -4, 4, 0, 2, -1, 2 ], [ -6, 5, 0, 2, -1, 2 ], [ -4, 4, 0, 1, -1, 2 ] ], 0.25 ] + ], + [ + [ [ [ -4, 4, 0, 2, -2, 2 ], [ -4, 4, 0, 2, -1, 2 ], [ -6, 5, 0, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0 ], + [ [ [ -4, 4, 0, 2, -2, 2 ], [ -4, 4, 0, 2, -1, 2 ], [ -4, 4, 0, 1, -2, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ] + ], + [ + [ [ [ -4, 4, 0, 2, -2, 2 ], [ -4, 4, 0, 2, -1, 2 ], [ -6, 5, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0 ], + [ [ [ -4, 4, 0, 2, -2, 2 ], [ -5, 5, 1, 2, -1, 2 ], [ -6, 5, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0 ], + [ [ [ -5, 4, 2, 2, -1, 2 ], [ -5, 5, 1, 2, -1, 2 ], [ -6, 5, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -6, 4, 1, 2, -1, 2 ], [ -5, 5, 1, 2, -1, 2 ], [ -6, 5, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0 ], + [ [ [ -6, 4, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 3 ], [ -6, 5, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -6, 4, 1, 2, -1, 2 ], [ -5, 5, 1, 2, -1, 2 ], [ -6, 5, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -6, 4, 1, 2, -1, 2 ], [ -4, 3, 1, 2, -1, 2 ], [ -6, 5, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ] + ], + [ + [ [ [ -6, 4, 1, 2, -1, 2 ], [ -5, 4, 2, 2, -1, 2 ], [ -6, 5, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0 ], + [ [ [ -6, 4, 1, 2, -1, 2 ], [ -5, 4, 2, 2, -1, 2 ], [ -5, 4, 0, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0 ], + [ [ [ -6, 3, 1, 2, -1, 2 ], [ -5, 4, 2, 2, -1, 2 ], [ -5, 4, 0, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -6, 3, 1, 2, -1, 2 ], [ -5, 4, 2, 2, -1, 2 ], [ -6, 4, 1, 3, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -6, 3, 1, 2, -1, 2 ], [ -5, 4, 2, 2, -1, 2 ], [ -5, 3, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0 ], + [ [ [ -6, 3, 1, 2, -1, 2 ], [ -4, 3, 1, 2, -1, 2 ], [ -5, 3, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -6, 3, 1, 2, -1, 2 ], [ -4, 3, 1, 2, -1, 2 ], [ -6, 5, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ] + ], + [ + [ [ [ -6, 3, 1, 2, -1, 2 ], [ -5, 5, 1, 2, -1, 2 ], [ -6, 5, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0 ], + [ [ [ -6, 3, 1, 2, -1, 2 ], [ -5, 5, 1, 2, -1, 2 ], [ -5, 4, 0, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -6, 3, 1, 2, -1, 2 ], [ -4, 4, 0, 2, -1, 2 ], [ -5, 4, 0, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ] + ], + [ + [ [ [ -6, 3, 1, 2, -1, 2 ], [ -4, 4, 0, 2, -1, 2 ], [ -6, 4, 1, 3, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0 ], + [ [ [ -6, 3, 1, 2, -1, 2 ], [ -5, 4, 1, 3, -1, 2 ], [ -6, 4, 1, 3, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0 ], + [ [ [ -6, 4, 1, 2, -2, 2 ], [ -5, 4, 1, 3, -1, 2 ], [ -6, 4, 1, 3, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ] + ], + [ + [ [ [ -7, 4, 1, 3, -1, 2 ], [ -5, 4, 1, 3, -1, 2 ], [ -6, 4, 1, 3, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -7, 4, 1, 3, -1, 2 ], [ -5, 4, 1, 2, -1, 3 ], [ -6, 4, 1, 3, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -7, 4, 1, 3, -1, 2 ], [ -5, 5, 1, 2, -1, 2 ], [ -6, 4, 1, 3, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -6, 4, 0, 2, -1, 2 ], [ -5, 5, 1, 2, -1, 2 ], [ -6, 4, 1, 3, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ] + ] + ] +], +"last_changes": +[ + [ [ -6, 4, 1, 2, -2, 2 ], [ -5, 4, 1, 3, -1, 2 ], [ -6, 4, 1, 3, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], + [ [ -7, 4, 1, 3, -1, 2 ], [ -5, 4, 1, 3, -1, 2 ], [ -6, 4, 1, 3, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], + [ [ -7, 4, 1, 3, -1, 2 ], [ -5, 4, 1, 2, -1, 3 ], [ -6, 4, 1, 3, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], + [ [ -7, 4, 1, 3, -1, 2 ], [ -5, 5, 1, 2, -1, 2 ], [ -6, 4, 1, 3, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], + [ [ -6, 4, 0, 2, -1, 2 ], [ -5, 5, 1, 2, -1, 2 ], [ -6, 4, 1, 3, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ] +], +"cur_uid": "66b20499", +"ref_uid": "50c2b0ad", +"order_seed": 837263, +"dur_seed": 582673, +"motifs_seed": 530509, +"entrances_probs_vals": [ 0.78, 0, 0, 0, 1.02, 0.0098039215686275, 0, 0.20915032679739, 0, 0.24183006535948, 0.82432432432432, 0.28758169934641, 0, 1, 0 ], +"passages_probs_vals": [ 0.78, 0, 0, 0, 1.02, 0.0098039215686275, 0, 0.20915032679739, 0, 0.24183006535948, 0.82432432432432, 0.28758169934641, 0, 1, 0 ], +"exits_probs_vals": [ 0.78, 0, 0, 0, 1.02, 0.0098039215686275, 0, 0.20915032679739, 0, 0.24183006535948, 0.82432432432432, 0.28758169934641, 0, 1, 0 ], +"ranges": [ [ -2400, 1200 ], [ -1200, 1453 ], [ -702, 1694 ], [ -702, 1973 ] ], +"step_probs_vals": [ -1200, 1200, 0, 0, 0.41358024691358, 0.0056818181818177, 0.46913580246914, 0.67045454545455, 0.52057613168724, 0, 0.56378600823045, 0.85795454545455, 0.60082304526749, 0, 1, 0 ], +"passages_weights": [ 1, 0.15, 0.43, 1, 1 ], +"hd_exp": 10, +"hd_invert": 0, +"order": +[ + [ [ 0, 2, 3 ], [ 1, 1, 1, 1 ], [ ] ], + [ [ 1 ], [ 3, 0, 2, 2, 2, 2 ], [ ] ], + [ [ 1, 0 ], [ 3, 2 ], [ ] ], + [ [ 3 ], [ 2, 1, 0, 0, 1, 1, 1 ], [ ] ], + [ [ 3 ], [ 1, 2, 0, 2, 2, 1, 2 ], [ ] ], + [ [ 0, 3 ], [ 1, 2, 1 ], [ ] ], + [ [ 3 ], [ 2, 1, 0 ], [ ] ], + [ [ 3, 2 ], [ 0, 1, 1, 0 ], [ ] ] +], +"sus_weights": [ 0.7, 0.48, 0.49 ], +"order_size": [ 14.132653061224, 14.132653061224 ], +"passages_size": [ 0, 5 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/66b20499/lilypond/part_I.ly b/resources/string_quartet_3_rise/66b20499/lilypond/part_I.ly new file mode 100644 index 0000000..fa15707 --- /dev/null +++ b/resources/string_quartet_3_rise/66b20499/lilypond/part_I.ly @@ -0,0 +1,8 @@ +{ + { dis'2^\markup { \pad-markup #0.2 "+38"} dis'2^\markup { \pad-markup #0.2 "+6"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 7↓" }} ~ } + \bar "|" + { dis'8[ f'8^\markup { \pad-markup #0.2 "-38"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 5↑" }}] ~ f'2. ~ } + \bar "|" + { f'1} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/66b20499/lilypond/part_II.ly b/resources/string_quartet_3_rise/66b20499/lilypond/part_II.ly new file mode 100644 index 0000000..7c92675 --- /dev/null +++ b/resources/string_quartet_3_rise/66b20499/lilypond/part_II.ly @@ -0,0 +1,8 @@ +{ + { ais2^\markup { \pad-markup #0.2 "+40"} ~ ais8[ cis'8^\markup { \pad-markup #0.2 "-25"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }}] a8^\markup { \pad-markup #0.2 "-11"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 5↓" }}[ g8^\markup { \pad-markup #0.2 "+24"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }}] } + \bar "|" + { gis8^\markup { \pad-markup #0.2 "-23"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 3↑" }}[ ais8^\markup { \pad-markup #0.2 "-45"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 7↓" }}] c'2^\markup { \pad-markup #0.2 "-37"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 3↑" }} cis'8^\markup { \pad-markup #0.2 "-25"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↓" }}[ d'8^\markup { \pad-markup #0.2 "+30"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 7↑" }}] } + \bar "|" + { ais8^\markup { \pad-markup #0.2 "-40"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }}[ c'8^\markup { \pad-markup #0.2 "-37"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 3↑" }}] cis'4^\markup { \pad-markup #0.2 "-25"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 5↓" }} d'2^\markup { \pad-markup #0.2 "+30"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 7↑" }}} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/66b20499/lilypond/part_III.ly b/resources/string_quartet_3_rise/66b20499/lilypond/part_III.ly new file mode 100644 index 0000000..e20fbf7 --- /dev/null +++ b/resources/string_quartet_3_rise/66b20499/lilypond/part_III.ly @@ -0,0 +1,8 @@ +{ + { c''8^\markup { \pad-markup #0.2 "-22"}[ ais'8^\markup { \pad-markup #0.2 "+40"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 1↑" }}] c''8^\markup { \pad-markup #0.2 "-49"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 5↓" }}[ cis''8^\markup { \pad-markup #0.2 "-25"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↑" }}] ~ cis''2 ~ } + \bar "|" + { cis''4 c''8^\markup { \pad-markup #0.2 "-37"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 1↑" }}[ cis''8^\markup { \pad-markup #0.2 "+2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 13↑" }}] c''8^\markup { \pad-markup #0.2 "-37"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 1↑" }}[ ais'8^\markup { \pad-markup #0.2 "-40"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↓" }}] gis'4^\markup { \pad-markup #0.2 "+48"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↑" }} } + \bar "|" + { ais'4^\markup { \pad-markup #0.2 "-40"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }} c''8^\markup { \pad-markup #0.2 "-37"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 1↑" }}[ cis''8^\markup { \pad-markup #0.2 "-25"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 1↑" }}] d''4^\markup { \pad-markup #0.2 "+30"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 1↑" }} cis''8^\markup { \pad-markup #0.2 "+2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 13↑" }}[ c''8^\markup { \pad-markup #0.2 "-37"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 3↑" }}]} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/66b20499/lilypond/part_IV.ly b/resources/string_quartet_3_rise/66b20499/lilypond/part_IV.ly new file mode 100644 index 0000000..b94d3c5 --- /dev/null +++ b/resources/string_quartet_3_rise/66b20499/lilypond/part_IV.ly @@ -0,0 +1,8 @@ +{ + { a'2^\markup { \pad-markup #0.2 "-11"} g'2^\markup { \pad-markup #0.2 "+24"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 11↓" }} ~ } + \bar "|" + { g'4 gis'8^\markup { \pad-markup #0.2 "+48"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 5↑" }}[ f8^\markup { \pad-markup #0.2 "-38"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 1↑" }}] ~ f4 ais,4^\markup { \pad-markup #0.2 "-40"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 3↓" }} ~ } + \bar "|" + { ais,2 b,8^\markup { \pad-markup #0.2 "+10"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 11↓" }}[ d8^\markup { \pad-markup #0.2 "+30"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }}] ~ d4} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/6c46f950/6c46f950_code.scd b/resources/string_quartet_3_rise/6c46f950/6c46f950_code.scd new file mode 100644 index 0000000..e1a9d92 --- /dev/null +++ b/resources/string_quartet_3_rise/6c46f950/6c46f950_code.scd @@ -0,0 +1,992 @@ +( +// helper funcs +var hsArrayToCents, pDist, hdSum, hsChordalDistance, hsArrayToFreq; + +// score funcs +var isInRange, spacingScore, rangeScore, intervalScore, inclusionScore; + +// subroutines +var genTuples, initVoices, genOrders, genSubMotif, updateVoices, genDurFunc, genStepFunc; + +// primary routines +var genMotif, genSecondarySeq; + +// audition funcs +var genPatterns, genMidiPatterns; + +// resource management funcs +var seedFunc, genUID, writeResources, stringifyToDepth, setSeeds, sanityCheck, +msgInterpret, loadLedgerFile, loadLedgerJSON, loadModelFile, loadModelJSON, +setGlobalVars, globalVarsToDict, saveLedger; + +// model vars +//(model and global vars mostly set by OSC funcs +var seq, lastXChanges, +curUID, refUID, orderSeed, durSeed, motifSeed, +entrancesProbVals, passagesProbVals, exitsProbVals, +ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, +orders, susWeights, orderSize, passagesSize, +motifEdited, orderEdited; + +// model aux vars +var entrancesDurFunc, passagesDurFunc, exitsDurFunc, stepFunc; + +// other global vars +var popSize, exPath, dir, primes, dims, tuples, +group, player, resourceDir, ledgerPath, ledger, currentlyPlayingUID, +nameSpaces; + +// install JSON quark (not used) +/* +if(Quarks.isInstalled("JSONlib").not, { + Quarks.install("https://github.com/musikinformatik/JSONlib.git"); + thisProcess.recompile; + //HelpBrowser.openHelpFor("Classes/JSONlib"); +}); +*/ + + +//------helper funcs + +hsArrayToCents = { + arg hsArray; + hsArray.collect({arg dist, p; dist * 1200 * log2(primes[p][0]/primes[p][1])}).sum +}; + +pDist = { + arg array1, array2, signed = false; + var pDistance; + pDistance = hsArrayToCents.value(array2) - hsArrayToCents.value(array1); + if(signed, {pDistance}, {abs(pDistance)}) +}; + +hdSum = { + arg hsArrays; + var size, distances, mean; + size = hsArrays.size; + distances = (size - 1).collect({arg i; + ((i + 1)..(size - 1)).collect({arg j; + abs(hsArrays[i] - hsArrays[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsChordalDistance = { + arg hsArrays1, hsArrays2; + var size, distances, mean; + size = hsArrays1.size; + distances = hsArrays1.size.collect({arg i; + hsArrays2.size.collect({arg j; + abs(hsArrays1[i] - hsArrays2[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsArrayToFreq = { + arg array; + array.collect({arg dim, d; pow(primes[d][0]/primes[d][1], dim)}).product +}; + +//------score funcs + +/* +isInRange = { + arg hsArray, min, max; + var cents; + cents = hsArrayToCents.value(hsArray); + (cents >= min) && (cents <= max) +}; +*/ + +spacingScore = { + arg hsArrays, min; + var centsArray; + centsArray = hsArrays.collect({arg hsArray; hsArrayToCents.value(hsArray)}).sort({arg a, b; a < b}); + centsArray.differentiate.drop(1).collect({arg pDistance; if(pDistance >= min, {1}, {0.01})}).sum; +}; + +rangeScore = { + arg hsArray1, hsArray2, min, max, low, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + if((pDistance >= min) && (pDistance <= max), {1}, {low}); +}; + +/* +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + stepFunc.value(pDistance) +}; +*/ + +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = true; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + //if(pDistance >= 0, {stepFunc.value(abs(pDistance))}, {0.01}); + stepFunc.value(pDistance) +}; + +inclusionScore = { + arg array, test, min = 0.01; + if(array.collect({arg v; v.hash}).includes(test.hash), {min}, {1}); +}; + + +//------subroutines + +genTuples = { + var tuples; + tuples = dims.collect({[-1, 0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum <= 1) && (tuple[0] == 0)}); + //tuples = dims.collect({[0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum == 1) && (tuple[0] == 0)}); + tuples = tuples ++ tuples.collect({arg tuple; [-3, -2, -1, 1, 2, 3].collect({arg octTrans; tuple.deepCopy.put(0, octTrans)})}).flatten; +}; + +initVoices = { + var init, voicesInit; + voicesInit = popSize.collect({dims.collect({0})}); + /* + voicesInit = [dims.collect({0})]; + (popSize - 1).do({ + arg rep, new; + rep = dims.rand; + new = voicesInit.last.deepCopy; + new[rep] = new[rep] + [-1, 1].choose(); + voicesInit = voicesInit.add(new); + }); + */ + voicesInit.deepCopy; +}; + +genDurFunc = {arg chordProb, minPad, maxPad, minDur, maxDur, envData, seed; + var env, pTable, durFunc; + env = Env.pairs([[0, 0]] ++ envData.clump(2) ++ [[1, 0]]).asSignal(256).asList.asArray; + pTable = env.asRandomTable; + //[chordProb, minPad, maxPad, minDur, maxDur, envData].postln; + durFunc = {arg allowChord, pad = false; + var res; + res = if(allowChord.not, { + pTable.tableRand * (maxDur - minDur) + minDur + }, { + if(1.0.rand < chordProb, {0}, {pTable.tableRand * (maxDur - minDur) + minDur}); + }).round(0.125); + if(pad, {res = res + rrand(minPad.asFloat, maxPad.asFloat).round(0.125)}); + if(res.asInteger == res, {res = res.asInteger}); + res + }; + seedFunc.value(durFunc, seed); +}; + +genStepFunc = {arg minStep, maxStep, envData, seed; + var envDataNorm, env, pTable, stepFunc; + //[minStep, maxStep, envData].postln; + envDataNorm = ([[0, 0]] ++ envData.clump(2) ++ [[1, 0]]).flop; + envDataNorm = [envDataNorm[0].normalize(minStep, maxStep), envDataNorm[1]].flop; + env = Env.pairs(envDataNorm); + stepFunc = {arg pDist; + env.at(pDist).clip(0.001, 1); + }; + seedFunc.value(stepFunc, seed); +}; + +genOrders = {arg minMotifLength = 1, maxMotifLength = 5, minProgLength = 0, maxProgLength = 5; + ((maxMotifLength.asInteger - minMotifLength.asInteger).rand + minMotifLength.asInteger).collect({ + var noProgIns, noSusIns, noSilentIns, prog, sus, silent, order; + noSusIns = [1, 2, 3].wchoose(susWeights.normalizeSum); + + //noProgIns = (popSize - noSusIns).rand + 1; + noProgIns = (popSize - noSusIns); + noSilentIns = popSize - noSusIns - noProgIns; + + /* + noSilentIns = (popSize - noSusIns).rand.clip(0, 1); + noProgIns = popSize - noSusIns - noSilentIns; + */ + + # prog, sus, silent = (0..(popSize-1)).scramble.clumps([noProgIns, noSusIns, noSilentIns]); + + prog = (prog.scramble ++ ((maxProgLength.asInteger - minProgLength.asInteger).rand + minProgLength.asInteger).collect({prog.choose}).scramble); + //prog = ((maxProgLength.asInteger - minProgLength.asInteger).rand + minProgLength.asInteger).collect({prog.choose}); + + if(silent == nil, {silent = []}); + [sus.scramble, prog, silent.scramble] + }); +}; + +updateVoices = {arg ins, sus; + var voices, candidates, nWeights, nProbs, sel; + + voices = lastXChanges.deepCopy.last; + + candidates = sus.collect({arg v; tuples.collect({arg t; voices[v] + t})}).flatten; + candidates = difference(candidates.asSet, voices.asSet).asList; + //candidates.select({arg item; (item ++ voices).asSet.size >= 4}); + nProbs = candidates.collect({arg candidate; + var stepScore, recentlySoundedScore, isInRangeScore, regScore, hdScore; + + //stepScore = intervalScore.value(voices[ins], candidate, 30, 400, 0.1); + stepScore = intervalScore.value(voices[ins], candidate, 100, 100); + recentlySoundedScore = inclusionScore.value(lastXChanges.flop[ins], candidate, 0.01); + //recentlySoundedScore = inclusionScore.value(lastXChanges.flatten.collect({arg item; item.drop(1)}), candidate.drop(1), 0.01); + + /* + if(rangeScore.value(candidate.collect({0}), voices[ins], ranges[ins][1] - 500, ranges[ins][1], 0, true) == 1, { + isInRangeScore = 1 - rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0] + 500, ranges[ins][1] - 500, 0, true); + isInRangeScore = isInRangeScore * rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true) + }, { + isInRangeScore = rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true); + }); + */ + + + isInRangeScore = rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true); + //old way - worked but actually by accident (I think) + //isInRangeScore = rangeScore.value(candidate, candidate.collect({0}), ranges[ins][0], ranges[ins][1], 0, true); + + + regScore = spacingScore.value(voices.deepCopy.put(ins, candidate), 300); + hdScore = pow(hdSum.value(voices.deepCopy.put(ins, candidate)), hdExp); + if(hdInvert == 0, {hdScore = 1/hdScore}); + //maybe what you want here is a vector to another root and then favoring movement towards it. + //distScore = pow(hsChordalDistance.value(voices, voices.put(ins, candidate)), 2); + + [stepScore, recentlySoundedScore, isInRangeScore, regScore, hdScore] + }); + + nWeights = passagesWeights; + + //this handles nWeights of 0; mainly for testing + nProbs = nProbs.flop.select({arg scores, s; nWeights[s] != 0}).flop; + nWeights = nWeights.select({arg weight; weight != 0}); + nProbs = nProbs.flop.collect({arg scores, s; + if(scores.sum == 0, {scores}, {scores.normalizeSum * nWeights[s]}).postln; + }); + nProbs = nProbs.flop.collect({arg scores, s; scores.product}).normalizeSum; + + nProbs.round(0.001).postln; + [candidates, nProbs.round(0.001)].flop.postln; + + sel = candidates.wchoose(nProbs); + + voices[ins] = sel; + lastXChanges = lastXChanges.add(voices).keep(-5); +}; + +genSubMotif = {arg order, orderIndex, lastState, repeatLast = false, startFromLast = false, isLastOrder = false; + var sus, prog, silent, flatOrder, res, isInChord, allowChord, pad, lastXChangesHold, voices, adder; + # sus, prog, silent = order; + flatOrder = silent ++ sus ++ prog; + lastXChangesHold = lastXChanges.deepCopy; + voices = lastState.deepCopy; + isInChord = popSize.collect({false}); + allowChord = false; + pad = false; + res = []; + "------generating motif".postln; + //need to figure out here if voices move between motifs + flatOrder.do({arg ins, i; + + if(prog.includes(ins) && repeatLast.not, {updateVoices.value(ins, sus)}); + adder = if(silent.includes(ins), {["Rest"]}, {lastXChanges.last.deepCopy[ins]}); + + if(voices[ins] != adder, { + var dur; + + if((sus ++ silent).includes(ins), { + allowChord = (ins != sus.last); + pad = (ins == sus.last); + }, { + if(i < (flatOrder.size - 1), { + allowChord = (isInChord[flatOrder[i + 1]] || (ins == flatOrder[i + 1])).not; + pad = false; + }, { + allowChord = false; + pad = true + }); + }); + if((orderIndex == 0) && sus.includes(ins), { + dur = entrancesDurFunc.value(allowChord, pad); + }, { + dur = passagesDurFunc.value(allowChord, pad); + }); + if(dur == 0, {isInChord[ins] = true}, {isInChord = popSize.collect({false})}); + + voices[ins] = adder; + res = res.add([voices.deepCopy.postln, dur]); + }); + }); + + // pad ending + /* + if(orderIndex == (orders.size - 1), { + (0..(popSize-1)).scramble.do({arg ins; + if(res.last.first[ins] != ["Rest"], { + var dur; + voices[ins] = ["Rest"]; + allowChord = (voices != popSize.collect({["Rest"]})); + pad = allowChord.not; + dur = exitsDurFunc.value(allowChord, pad); + res = res.add([voices.deepCopy.postln, dur]); + }); + }); + }); + */ + + //format and return + if(startFromLast, {lastXChanges = lastXChangesHold.deepCopy}); + res; +}; + + +//------primary routines + +genMotif = { + var repeats, fSeq, fDur, durAdd; + + repeats = 1; + fSeq = []; + + repeats.do({arg index; + var motif; + + motif = []; + + orders.do({arg order, o; + var lastState, subMotif; + //lastState = if(o == 0, {popSize.collect({["Rest"]})}, {motif.last.last.first}); + lastState = if(o == 0, {lastXChanges.last.deepCopy}, {motif.last.last.first}); + subMotif = genSubMotif.value(order, o, lastState, isLastOrder: o == (orders.size - 1)); + motif = motif.add(subMotif); + + }); + + sanityCheck.value(motif, index); + + fSeq = fSeq.add(motif); + }); + + //round last duration to measure + /* + fDur = fSeq.flatten.flatten.slice(nil, 1).sum; + durAdd = fDur.round(4) - fDur; + if(durAdd < 0, {durAdd = 4 - durAdd}); + fSeq[0][orders.size - 1][fSeq[0][orders.size - 1].size - 1][1] = fSeq[0][orders.size - 1][fSeq[0][orders.size - 1].size - 1][1] + durAdd; + */ + + fSeq +}; + +genSecondarySeq = {arg seq; + var curdles, fSeq; + curdles = []; + while({curdles.sum < seq.size}, {curdles = curdles ++ [3.rand + 1]}); + + fSeq = seq.clumps(curdles).collect({arg clump, m; + var repeats, paddedSeq; + + //add rest + paddedSeq = clump.add([[[popSize.collect({["Rest"]}), 0.5.rand]]]); + + //implement repeats + repeats = [0.rand + 1, 1].wchoose([1, 0].normalizeSum); + repeats.collect({paddedSeq}); + }); + fSeq +}; + + +//------audition funcs + +/* +Event.addEventType(\osc, { + if (~addr.postln.notNil) { + ~addr.sendMsg(~indexPath, ~indexMsg); + ~addr.sendMsg(~seqPath, stringifyToDepth.value(~seqMsg, 3)); + //~addr.sendMsg("/STATE/OPEN", (dir.replace("supercollider", "resources") +/+ ~idMsg +/+ ~idMsg ++ "_gui_state" ++ ".state").standardizePath.postln); + }; +}); +*/ + +Event.addEventType(\osc, { + if (~addr.notNil) { + ~msg; + ~addr.sendMsg(~path, *~msg); + }; +}); + +genPatterns = {arg inSeq, addr, oneShot = false; + var voices, durs, pbinds, res, indices, sectionDurs, msg, ids, seq; + seq = inSeq.collect({arg mSeq; mSeq[0]}); + //# voices, durs = seq.flatten2(seq.maxDepth - 5).flop; + # voices, durs = seq.flatten2(if(oneShot, {2}, {3})).flop; + pbinds = voices.flop.collect({arg voice, v; + var clumps, hdScores, freqs, fDurs, attacks, rels, amps; + clumps = voice.separate({arg a, b; a != b }); + freqs = clumps.collect({arg clump; if(clump[0] != ["Rest"], {(62.midicps * hsArrayToFreq.value(clump[0]))}, {Rest(0)})}); + fDurs = durs.clumps(clumps.collect({arg clump; clump.size})).collect({arg clump; clump.sum}); + //attacks = 2.collect({rrand(1, 3)}) ++ freqs.drop(2).collect({rrand(0.3, 0.5)}); + attacks = fDurs.collect({arg dur; dur * rrand(0.2, 0.4)}); + //rels = freqs.drop(2).collect({rrand(0.3, 0.5)}) ++ 2.collect({rrand(1, 3)}); + rels = (clumps.size - 1).collect({arg c; + if(clumps[c + 1][0] == ["Rest"], {rrand(1.0, 3.0)}, {rrand(0.3, 0.5)}); + }); + rels = rels.add(rrand(1.0, 3.0)); + amps = freqs.collect({rrand(0.6, 0.99)}); + + [ + Pbind( + \instrument, \string_model, + \group, group, + \freq, Pseq(freqs, 1), + \dur, Pseq(fDurs, 1), + \attack, Pseq(attacks, 1), + \sustain, Pseq(fDurs, 1), + \release, Pseq(rels, 1), + //\amp, Pseq(amps, 1), + \amp, Pbrown(0.5, 1, 0.5), + \busIndex, v + ), + Pbind( + \instrument, \sine, + \group, group, + \freq, Pseq(freqs, 1), + \dur, Pseq(fDurs, 1), + \sustain, Pseq(fDurs, 1), + \busIndex, v + ) + ] + }).flatten; + if(oneShot.not, { + msg = inSeq.collect({arg mSeq, m; mSeq[1..]}); + //ids = inSeq.collect({arg mSeq, m; mSeq[2]}); + sectionDurs = seq.collect({arg mSeq; mSeq.flatten2(2).flop[1].sum}); + pbinds = pbinds ++ + [ + Pbind( + \type, \osc, + \addr, addr, + \path, "/playing", + \msg, Pseq(msg, 1), + \dur, Pseq(sectionDurs, 1) + ); + ] + }); + res = Ppar(pbinds); + res +}; + +/* +genMidiPatterns = {arg seq; + var voices, durs, patterns, res, mOut, pbRange; + pbRange = 1; //semitones - change this as needed for your situation + mOut = MIDIOut.newByName("TiMidity", "TiMidity port 0").latency_(Server.default.latency); + # voices, durs = seq.flatten2(seq.maxDepth - 5).flop; + res = Ppar( + voices.flop.collect({arg voice, v; + var clumps, hdScores, freqs, fDurs; + + mOut.program(v, 70); + + clumps = voice.separate({arg a, b; a != b }); + freqs = clumps.collect({arg clump; if(clump[0] != ["Rest"], {(60.midicps * hsArrayToFreq.value(clump[0]))}, {Rest(0)})}); + fDurs = durs.clumps(clumps.collect({arg clump; clump.size})).collect({arg clump; clump.sum}); + + Pbind( + \type, \midi, + \chan, v, + \noteval, Pseq(freqs.cpsmidi - 24, 1), + \note, Pfunc({ | event | event[\noteval].floor }), + \dur, Pseq(fDurs, 1), + \midiout, mOut, + \amp, 1, + \bend, Pfunc({ + | event | + if (event[\note].isRest.not) { + var pitchbendvalue = event[\noteval].frac.linlin(0, pbRange, 8192, 8192*2).asInteger; + m.bend(v, pitchbendvalue); + }; + 0; // return something other than nil to avoid stopping the pattern + }), + ); + }); + ); + res +}; +*/ + + +//------resource management funcs + +genUID = {Date.seed.asHexString.toLower}; + +seedFunc = {arg func, seed; + var funcArgs, next; + next = Routine({loop{func.valueArray(funcArgs).yield }}); + next.randSeed_(seed); + {arg ...args; funcArgs = args; next.value} +}; + +stringifyToDepth = {arg data, maxDepth = 1; + var prettyString = "", rCount = 0, writeArray, indent; + + if(maxDepth == 0, { + data.asCompileString + }, { + indent = {arg size; size.collect({" "}).join("")}; + writeArray = {arg array; + prettyString = prettyString ++ indent.value(rCount) ++ "[\n"; + rCount = rCount + 1; + if(rCount < maxDepth, { + array.do({arg subArray; writeArray.value(subArray)}); + }, { + prettyString = prettyString ++ array.collect({arg subArray; + indent.value(rCount + 1) ++ subArray.asCompileString + }).join(",\n"); + }); + rCount = rCount - 1; + prettyString = prettyString ++ "\n" ++ indent.value(rCount) ++ "],\n"; + }; + + writeArray.value(data); + prettyString.replace(",\n\n", "\n").drop(-2); + }) +}; + +sanityCheck = {arg motif, index; + //print functions = very helpful + ("----------" + index + "------------").postln; + + motif.flatten.do({arg val, v; + if(v > 0, { + if(motif.flatten[v-1][0].hammingDistance(val[0]) > 1, {"problem 1".postln}); + if(motif.flatten[v-1][0].hammingDistance(val[0]) == 0, {"problem 2".postln}); + }); + val.postln + }); + "***********".postln; +}; + +msgInterpret = {arg in, escapeDoubleQuotes = true, escapeSingleQuotes = true; + var res; + + res = in; + if(res.isNil.not, { + if((res.isArray && res.isString.not), { + res = res.asCompileString; + res = res.replace(" ", "").replace("\n", "").replace("\t", ""); + if(escapeSingleQuotes, {res = res.replace("\'", "")}); + if(escapeDoubleQuotes, {res = res.replace("\"", "")}); + res = res.replace("Rest", "\"Rest\""); + res = res.interpret; + }, { + var tmpRes; + if(res.every({arg char; char.isDecDigit}), {tmpRes = res.asInteger}); + if(res.contains("."), {tmpRes = res.asFloat}); + if(tmpRes != nil, {res = tmpRes}); + }); + }); + res +}; + +writeResources = {arg path, dict; + var file, modelItems, resString; + file = File(path,"w"); + + modelItems = [ + seq, lastXChanges, + curUID, refUID, orderSeed, durSeed, motifSeed, + entrancesProbVals, passagesProbVals, exitsProbVals, + ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, + orders, susWeights, orderSize, passagesSize, + motifEdited, orderEdited + ]; + + resString = nameSpaces.collect({arg nameSpace; + var depth = 0, insert = " "; + if(nameSpace == "music_data", {depth = 3; insert = "\n"}); + if(nameSpace == "last_changes", {depth = 1; insert = "\n"}); + if(nameSpace == "order", {depth = 1; insert = "\n"}); + if((nameSpace == "ref_uid") && (dict[nameSpace] == nil), {dict[nameSpace] = "nil"}); + "\"" ++ nameSpace ++ "\":" ++ insert ++ stringifyToDepth.value(dict[nameSpace], depth) + }).join(",\n"); + + resString = "{\n" ++ resString ++ "\n}"; + + file.write(resString); + file.close; + resString +}; + +loadModelFile = {arg path; loadModelJSON.value(File(path, "r").readAllString.parseJSON)}; + +loadModelJSON = {arg jsonObject; + var dict; + dict = Dictionary.with(*nameSpaces.collect({arg nS; nS->msgInterpret.value(jsonObject[nS])})); + dict +}; + +setGlobalVars = {arg dict, skipLastXChanges = false; + var tmpLastXChanges; + tmpLastXChanges = lastXChanges.deepCopy; + // order really matters!!!! + # seq, lastXChanges, curUID, refUID, orderSeed, durSeed, motifSeed, + entrancesProbVals, passagesProbVals, exitsProbVals, + ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, + orders, susWeights, orderSize, passagesSize, + motifEdited, orderEdited = nameSpaces.collect({arg nS; dict[nS]}); + if(skipLastXChanges, {lastXChanges = tmpLastXChanges}); + dict +}; + +globalVarsToDict = { + var modelItems, dict; + // order really matters!!!! + modelItems = [ + seq, lastXChanges, + curUID, refUID, orderSeed, durSeed, motifSeed, + entrancesProbVals, passagesProbVals, exitsProbVals, + ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, + orders, susWeights, orderSize, passagesSize, + motifEdited, orderEdited + ]; + dict = Dictionary.with(*nameSpaces.collect({arg nS, n; nS->modelItems[n]})); +}; + +loadLedgerFile = {arg path; + ledgerPath = path; + resourceDir = path.splitext(".").drop(-1).join; + loadLedgerJSON.value(File(ledgerPath, "r").readAllString.parseJSON) +}; + +loadLedgerJSON = {arg jsonObject; ledger = jsonObject["ledger"]}; + +saveLedger = {arg ledger, path; + var file, curResourceDir; + file = File(path, "w"); + curResourceDir = resourceDir; + resourceDir = path.splitext(".").drop(-1).join; + if(curResourceDir != resourceDir, { + File.mkdir(resourceDir); + ledger.do({arg id; + File.copy(curResourceDir +/+ id, resourceDir +/+ id); + }); + }); + file.write("{\n\"ledger\":\n" ++ stringifyToDepth.value(ledger, 1) ++ "\n}"); + file.close; +}; + +//------global vars + +primes = [[2, 1], [3, 2], [5, 4], [7, 4], [11, 8], [13, 8]]; +//ranges = [[-2400, 0], [-1200, 1200], [0, 2400], [0, 2400]]; +exPath = thisProcess.nowExecutingPath; +dir = exPath.dirname; +//popSize = 4; +dims = primes.size; +tuples = genTuples.value(); +//refUID = nil; +group = Group.new; +~group = group; +loadLedgerFile.value(dir +/+ ".." +/+ "resources" +/+ "piece_ledger.json"); +resourceDir = (dir +/+ ".." +/+ "resources" +/+ "piece_ledger"); +//passagesWeights = [1, 1, 1, 1, 1]; +//susWeights = [1, 1, 1]; +// order really matters!!!! +nameSpaces = [ + "music_data", "last_changes", "cur_uid", "ref_uid", "order_seed", "dur_seed", "motifs_seed", + "entrances_probs_vals","passages_probs_vals", "exits_probs_vals", + "ranges", "step_probs_vals", "passages_weights", "hd_exp", "hd_invert", + "order", "sus_weights", "order_size", "passages_size", + "motif_edited", "order_edited" +]; + + +//------OSC funcs + +OSCdef(\load_ledger, {arg msg, time, addr, port; + loadLedgerFile.value(msg[1].asString); +}, \load_ledger); + +OSCdef(\load_model, {arg msg, time, addr, port; + var dict; + dict = loadModelFile.value(msg[1].asString); + setGlobalVars.value(dict); +}, \load_model); + +OSCdef(\save_ledger, {arg msg, time, addr, port; + msg.postln; + ledger = msgInterpret.value(msg[1].asString.parseJSON["ledger"], false).postln; + //loadLedgerJSON.value(msg[0]) + saveLedger.value(ledger, msg[2].asString); + //loadLedgerFile.value(msg[1].asString); +}, \save_ledger); + +OSCdef(\generate, {arg msg, time, addr, port; + var path, dict, durSeeds, musPath, modelString; + msg.postln; + + path = msg[1].asString; + + dict = loadModelFile.value(path); + setGlobalVars.value(dict, true); + + popSize = ranges.size; + + //refUID.postln; + + loadLedgerFile.value(ledgerPath); + if(ledger == nil, {ledger = ["tmp"]}); + if(ledger.last != "tmp", {ledger = ledger.add("tmp")}); + + if(refUID == nil, {lastXChanges = [initVoices.value().deepCopy]}); + if((refUID != nil) && (refUID != "tmp"), { + var file; + file = File((resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath, "r"); + lastXChanges = msgInterpret.value(file.readAllString.parseJSON["last_changes"]); + }); + + refUID.postln; + lastXChanges.collect({arg item; item.postln}); + + durSeeds = seedFunc.value({3.collect({rrand(100000, 999999)})}, durSeed).value.postln; + entrancesDurFunc = genDurFunc.valueArray(entrancesProbVals[..4] ++ [entrancesProbVals[5..]] ++ [durSeeds[0]]); + passagesDurFunc = genDurFunc.valueArray(passagesProbVals[..4] ++ [passagesProbVals[5..]] ++ [durSeeds[1]]); + exitsDurFunc = genDurFunc.valueArray(exitsProbVals[..4] ++ [exitsProbVals[5..]] ++ [durSeeds[2]]); + + if(orders == nil, { + orders = seedFunc.value(genOrders, orderSeed).valueArray(orderSize ++ passagesSize); + //addr.sendMsg("/order", stringifyToDepth.value(orders, 1)); + }); + + stepFunc = genStepFunc.valueArray(stepProbsVals[..1] ++ [stepProbsVals[2..]] ++ [motifSeed]); + seq = seedFunc.value(genMotif, motifSeed).value; + + lastXChanges.collect({arg item; item.postln}); + + dict = globalVarsToDict.value; + modelString = writeResources.value(path, dict); + + //addr.sendMsg("/generated", musPath, stringifyToDepth.value(seq, 3)); + //~seq = seq; + + addr.sendMsg("/generated", path, modelString, ledgerPath); +}, \generate); + + +OSCdef(\commit, {arg msg, time, addr, port; + var musicData, musicChanged, dict, newLedger, modelPath, musString, musFile, test1, test2, lastCurUID, commitType, commitPos, equalityLedger; + //msg.postln; + + /* + test1 = msg[1].asString.parseJSON; + test2 = (dir +/+ ".." +/+ "resources/tmp/tmp_music" ++ ".json").standardizePath.parseJSONFile; + msgInterpret.value(test1["music"])[0][0][0][1].class.postln; + msgInterpret.value(test2["music_data"])[0][0][0][1].class.postln; + (test1["music"] == test2["music_data"]).postln; + */ + + musicData = loadModelJSON.value(msg[1].asString.parseJSON)["music_data"].postln; + musicChanged = (musicData != seq).postln; + commitType = msg[2].asString; + commitPos = msg[3].postln.asInteger; + + lastCurUID = curUID.deepCopy; + curUID = genUID.value; + + File.mkdir((resourceDir +/+ curUID).standardizePath); + File.copy(exPath, (resourceDir +/+ curUID +/+ curUID ++ "_code" ++ ".scd").standardizePath); + + modelPath = (resourceDir +/+ curUID +/+ curUID ++ "_mus_model" ++ ".json").standardizePath; + dict = globalVarsToDict.value; + if(musicChanged, { + seq = musicData; + dict["music_data"] = seq; + dict["motif_edited"] = "true" + }); + dict["cur_uid"] = curUID; + + writeResources.value(modelPath, dict); + + File.delete(ledgerPath ++ "_bak"); + File.copy(ledgerPath, ledgerPath ++ "_bak"); + File.delete(ledgerPath); + + /* + if(commitType == "add", { + if(lastCurUID == "tmp", { + ledger = ledger.drop(-1).add(curUID); + }, { + ledger = ledger.add(curUID); + }) + }); + */ + + ledger.postln; + + if(commitType == "add", {ledger = ledger.add(curUID)}); + + if(commitType == "insert", {ledger = ledger.insert(commitPos + 1, curUID)}); + + if(commitType == "replace", {ledger = ledger.put(commitPos, curUID)}); + + equalityLedger = ledger.collect({arg item; item.asSymbol}); + if(equalityLedger.includes(\tmp).postln, {ledger.removeAt(equalityLedger.indexOf(\tmp).postln)}); + + ledger.postln; + + saveLedger.value(ledger, ledgerPath); + + addr.sendMsg("/committed", curUID, ledgerPath); + //refUID = curUID; + +}, \commit); + +OSCdef(\transport, {arg msg, time, addr, port; + msg.postln; + if(msg[1] == 0, { + group.set(\release, 2); + group.set(\gate, 0); + player.stop; + }, { + // the cued sequence can now be read from file, so this can be cleaned up + var cSize, patterns, pSeq, cuedSeek, indexStart, indexEnd, tmpLedger; + if(msg[1] == 1, { + pSeq = []; + cuedSeek = (seq != nil); + indexStart = msg[2].asInteger; + indexEnd = ledger.size - if(cuedSeek, {2}, {1}); + //ledger.postln; + if(((indexStart == (ledger.size - 1)) && cuedSeek).not, { + ledger[indexStart..indexEnd].do({arg uid, index; + var path, file; + path = (resourceDir +/+ uid +/+ uid ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.postln.parseJSON["music_data"]), path, indexStart + index, uid]); + file.close; + }); + }); + if(cuedSeek, { + var path, file; + path = (resourceDir +/+ "tmp/tmp_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.parseJSON["music_data"]), path, ledger.size - 1, "tmp"]); + file.close; + }); + patterns = genPatterns.value(pSeq, addr); + }, { + pSeq = [loadModelJSON.value(msg[2].asString.parseJSON)["music_data"].postln]; + patterns = genPatterns.value(pSeq, addr, true); + }); + player = Pfset(pattern: patterns, cleanupFunc: { + addr.sendMsg("/transport", 0); + addr.sendMsg("/one_shot", 0); + }); + player = player.play + }); +}, \transport); + + +OSCdef(\transcribe_motif, {arg msg, time, addr, port; + var tSeq, refChord, refUID; + + msg.postln; + + tSeq = [loadModelJSON.value(msg[1].asString.parseJSON)["music_data"]]; + refUID = msg[2].asString.postln; + + if((refUID != "nil") && (refUID != "tmp"), { + var file; + file = File((resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath, "r"); + refChord = msgInterpret.value(file.readAllString.parseJSON["last_changes"]).last; + file.close; + }, { + refChord = [[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0]]; + }); + + ~transcribe.value(tSeq, refChord, (dir +/+ ".." +/+ "lilypond" +/+ "includes").standardizePath, addr, "/transcribe_motif"); +}, \transcribe_motif); + + +OSCdef(\transcribe_all, {arg msg, time, addr, port; + var cSize, patterns, cuedSeek, indexStart, indexEnd, tmpLedger; + if(true, { + cuedSeek = (seq != nil); + indexStart = msg[1].asInteger; + indexEnd = ledger.size - if(cuedSeek, {2}, {1}); + + //tmp for testing transcription + //indexEnd = (indexStart+5); + + //ledger.postln; + if(((indexStart == (ledger.size - 1)) && cuedSeek).not, { + var lilyPartLedgerFiles; + + lilyPartLedgerFiles = 4.collect({arg p; + File((dir +/+ ".." +/+ "lilypond" +/+ "includes" +/+ "part_" ++ ["IV", "III", "II", "I"][p] ++ ".ly").standardizePath, "w"); + }); + + ledger[indexStart..indexEnd].do({arg uid, index; + var path, file, fileString, tSeq, refUID, refChord; + path = (resourceDir +/+ uid +/+ uid ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + fileString = file.readAllString; + tSeq = msgInterpret.value(fileString.parseJSON["music_data"]); + refUID = msgInterpret.value(fileString.parseJSON["ref_uid"]); + file.close; + + //uid.postln; + //(refUID == "nil").postln; + + refChord = [[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0]]; + + if(refUID != "nil", { + path = (resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + refChord = msgInterpret.value(file.readAllString.parseJSON["last_changes"]).last; + file.close; + }); + + if(index != indexEnd, { + ~transcribe.value(tSeq, refChord, (resourceDir +/+ uid +/+ "lilypond").standardizePath); + }, { + ~transcribe.value(tSeq, refChord, (resourceDir +/+ uid +/+ "lilypond").standardizePath, addr, "/transcribe_all"); + }); + + lilyPartLedgerFiles.do({arg f, p; + f.write("\\include \"" ++ resourceDir +/+ uid +/+ "lilypond" +/+ "part_" ++ ["IV", "III", "II", "I"][p] ++ ".ly\"\n"); + }); + + }); + + lilyPartLedgerFiles.do({arg f; + f.close + }); + }); + /* + if(cuedSeek, { + var path, file; + path = (dir +/+ ".." +/+ "resources/tmp/tmp_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.parseJSON["music_data"]), path, ledger.size - 1, "tmp"]); + file.close; + }); + */ + }, { + + }); + +}, \transcribe_all); + +) + + diff --git a/resources/string_quartet_3_rise/6c46f950/6c46f950_mus_model.json b/resources/string_quartet_3_rise/6c46f950/6c46f950_mus_model.json new file mode 100644 index 0000000..25db3d0 --- /dev/null +++ b/resources/string_quartet_3_rise/6c46f950/6c46f950_mus_model.json @@ -0,0 +1,96 @@ +{ +"music_data": +[ + [ + [ + [ [ [ -4, 4, -1, 2, -1, 2 ], [ -4, 4, -1, 2, -2, 3 ], [ -5, 5, -1, 2, -2, 2 ], [ -4, 4, -1, 2, -2, 2 ] ], 0.25 ], + [ [ [ -4, 4, -1, 2, -1, 2 ], [ -4, 5, -1, 2, -2, 2 ], [ -5, 5, -1, 2, -2, 2 ], [ -4, 4, -1, 2, -2, 2 ] ], 0.25 ], + [ [ [ -4, 4, -1, 2, -1, 2 ], [ -3, 4, -2, 2, -2, 2 ], [ -5, 5, -1, 2, -2, 2 ], [ -4, 4, -1, 2, -2, 2 ] ], 0.25 ], + [ [ [ -4, 4, -1, 2, -1, 2 ], [ -4, 4, 0, 2, -1, 2 ], [ -5, 5, -1, 2, -2, 2 ], [ -4, 4, -1, 2, -2, 2 ] ], 0.25 ] + ], + [ + [ [ [ -4, 4, -1, 2, -1, 2 ], [ -4, 4, 0, 2, -1, 2 ], [ -5, 5, -1, 2, -2, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0 ], + [ [ [ -5, 5, 0, 2, -1, 2 ], [ -4, 4, 0, 2, -1, 2 ], [ -5, 5, -1, 2, -2, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0 ], + [ [ [ -5, 5, 0, 2, -1, 2 ], [ -4, 4, 0, 2, -1, 2 ], [ -5, 4, 0, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -5, 5, 0, 2, -1, 2 ], [ -4, 4, 0, 2, -1, 2 ], [ -4, 3, 0, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -5, 5, 0, 2, -1, 2 ], [ -4, 4, 0, 2, -1, 2 ], [ -4, 4, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -5, 5, 0, 2, -1, 2 ], [ -4, 4, 0, 2, -1, 2 ], [ -3, 3, 0, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ] + ], + [ + [ [ [ -5, 5, 0, 2, -1, 2 ], [ -4, 4, 0, 2, -1, 2 ], [ -3, 3, 0, 2, -1, 2 ], [ -4, 3, 0, 2, -1, 2 ] ], 0 ], + [ [ [ -5, 5, 0, 2, -1, 2 ], [ -4, 4, 0, 2, -1, 2 ], [ -5, 5, 0, 3, -1, 2 ], [ -4, 3, 0, 2, -1, 2 ] ], 0.25 ] + ], + [ + [ [ [ -5, 5, 0, 2, -1, 2 ], [ -4, 4, 0, 2, -1, 2 ], [ -5, 4, 0, 2, -1, 2 ], [ -4, 3, 0, 2, -1, 2 ] ], 0 ], + [ [ [ -5, 5, 0, 2, -1, 2 ], [ -5, 3, 0, 2, -1, 2 ], [ -5, 4, 0, 2, -1, 2 ], [ -4, 3, 0, 2, -1, 2 ] ], 0 ], + [ [ [ -3, 3, 0, 2, -1, 1 ], [ -5, 3, 0, 2, -1, 2 ], [ -5, 4, 0, 2, -1, 2 ], [ -4, 3, 0, 2, -1, 2 ] ], 0.25 ], + [ [ [ -4, 3, 1, 2, -1, 2 ], [ -5, 3, 0, 2, -1, 2 ], [ -5, 4, 0, 2, -1, 2 ], [ -4, 3, 0, 2, -1, 2 ] ], 0 ], + [ [ [ -4, 3, 1, 2, -1, 2 ], [ -4, 3, 0, 1, -1, 2 ], [ -5, 4, 0, 2, -1, 2 ], [ -4, 3, 0, 2, -1, 2 ] ], 0.25 ], + [ [ [ -4, 3, 1, 2, -1, 2 ], [ -5, 3, 1, 2, -1, 2 ], [ -5, 4, 0, 2, -1, 2 ], [ -4, 3, 0, 2, -1, 2 ] ], 0.25 ], + [ [ [ -4, 3, 1, 2, -1, 2 ], [ -4, 2, 0, 2, -1, 2 ], [ -5, 4, 0, 2, -1, 2 ], [ -4, 3, 0, 2, -1, 2 ] ], 0.25 ] + ], + [ + [ [ [ -4, 3, 1, 2, -1, 2 ], [ -5, 3, 1, 2, -1, 2 ], [ -5, 4, 0, 2, -1, 2 ], [ -4, 3, 0, 2, -1, 2 ] ], 0.25 ], + [ [ [ -4, 3, 1, 2, -1, 2 ], [ -5, 3, 1, 2, -1, 2 ], [ -5, 3, 0, 2, 0, 2 ], [ -4, 3, 0, 2, -1, 2 ] ], 0 ], + [ [ [ -4, 3, 0, 2, 0, 2 ], [ -5, 3, 1, 2, -1, 2 ], [ -5, 3, 0, 2, 0, 2 ], [ -4, 3, 0, 2, -1, 2 ] ], 0.25 ], + [ [ [ -4, 3, 0, 2, 0, 2 ], [ -5, 3, 1, 2, -1, 2 ], [ -4, 2, 0, 2, -1, 2 ], [ -4, 3, 0, 2, -1, 2 ] ], 0.25 ], + [ [ [ -4, 3, 0, 2, 0, 2 ], [ -5, 3, 1, 2, -1, 2 ], [ -4, 3, 0, 2, -1, 1 ], [ -4, 3, 0, 2, -1, 2 ] ], 0 ], + [ [ [ -4, 3, 0, 2, 0, 2 ], [ -4, 2, 0, 2, -1, 2 ], [ -4, 3, 0, 2, -1, 1 ], [ -4, 3, 0, 2, -1, 2 ] ], 0.25 ], + [ [ [ -4, 3, 0, 2, 0, 2 ], [ -4, 2, 0, 2, -1, 2 ], [ -4, 3, 0, 1, -1, 2 ], [ -4, 3, 0, 2, -1, 2 ] ], 0.25 ] + ], + [ + [ [ [ -4, 3, 0, 2, 0, 2 ], [ -5, 2, 0, 2, 0, 2 ], [ -4, 3, 0, 1, -1, 2 ], [ -4, 3, 0, 2, -1, 2 ] ], 0 ], + [ [ [ -4, 3, 0, 2, 0, 2 ], [ -5, 2, 0, 2, 0, 2 ], [ -6, 3, 0, 3, 0, 2 ], [ -4, 3, 0, 2, -1, 2 ] ], 0.25 ], + [ [ [ -4, 3, 0, 2, 0, 2 ], [ -5, 3, 0, 2, -1, 2 ], [ -6, 3, 0, 3, 0, 2 ], [ -4, 3, 0, 2, -1, 2 ] ], 0.25 ] + ], + [ + [ [ [ -4, 3, 0, 2, 0, 2 ], [ -5, 3, 0, 2, -1, 2 ], [ -4, 2, 0, 2, -1, 2 ], [ -4, 3, 0, 2, -1, 2 ] ], 0 ], + [ [ [ -4, 3, 0, 2, 0, 2 ], [ -3, 2, 0, 2, -1, 2 ], [ -4, 2, 0, 2, -1, 2 ], [ -4, 3, 0, 2, -1, 2 ] ], 0 ], + [ [ [ -4, 4, 0, 2, -1, 2 ], [ -3, 2, 0, 2, -1, 2 ], [ -4, 2, 0, 2, -1, 2 ], [ -4, 3, 0, 2, -1, 2 ] ], 0.25 ] + ], + [ + [ [ [ -3, 3, -1, 2, -1, 2 ], [ -3, 2, 0, 2, -1, 2 ], [ -4, 2, 0, 2, -1, 2 ], [ -4, 3, 0, 2, -1, 2 ] ], 0 ], + [ [ [ -3, 3, -1, 2, -1, 2 ], [ -4, 4, 0, 2, -1, 2 ], [ -4, 2, 0, 2, -1, 2 ], [ -4, 3, 0, 2, -1, 2 ] ], 0.25 ], + [ [ [ -3, 3, -1, 2, -1, 2 ], [ -3, 3, 0, 2, -2, 2 ], [ -4, 2, 0, 2, -1, 2 ], [ -4, 3, 0, 2, -1, 2 ] ], 0 ], + [ [ [ -3, 2, -1, 2, -1, 2 ], [ -3, 3, 0, 2, -2, 2 ], [ -4, 2, 0, 2, -1, 2 ], [ -4, 3, 0, 2, -1, 2 ] ], 0.25 ] + ] + ] +], +"last_changes": +[ + [ [ -4, 4, 0, 2, -1, 2 ], [ -3, 2, 0, 2, -1, 2 ], [ -4, 2, 0, 2, -1, 2 ], [ -4, 3, 0, 2, -1, 2 ] ], + [ [ -3, 3, -1, 2, -1, 2 ], [ -3, 2, 0, 2, -1, 2 ], [ -4, 2, 0, 2, -1, 2 ], [ -4, 3, 0, 2, -1, 2 ] ], + [ [ -3, 3, -1, 2, -1, 2 ], [ -4, 4, 0, 2, -1, 2 ], [ -4, 2, 0, 2, -1, 2 ], [ -4, 3, 0, 2, -1, 2 ] ], + [ [ -3, 3, -1, 2, -1, 2 ], [ -3, 3, 0, 2, -2, 2 ], [ -4, 2, 0, 2, -1, 2 ], [ -4, 3, 0, 2, -1, 2 ] ], + [ [ -3, 2, -1, 2, -1, 2 ], [ -3, 3, 0, 2, -2, 2 ], [ -4, 2, 0, 2, -1, 2 ], [ -4, 3, 0, 2, -1, 2 ] ] +], +"cur_uid": "6c46f950", +"ref_uid": "50c2b0ad", +"order_seed": 837263, +"dur_seed": 454936, +"motifs_seed": 918136, +"entrances_probs_vals": [ 0.78, 0, 0, 0, 1.02, 0.0098039215686275, 0, 0.20915032679739, 0, 0.24183006535948, 0.82432432432432, 0.28758169934641, 0, 1, 0 ], +"passages_probs_vals": [ 0.78, 0, 0, 0, 1.02, 0.0098039215686275, 0, 0.20915032679739, 0, 0.24183006535948, 0.82432432432432, 0.28758169934641, 0, 1, 0 ], +"exits_probs_vals": [ 0.78, 0, 0, 0, 1.02, 0.0098039215686275, 0, 0.20915032679739, 0, 0.24183006535948, 0.82432432432432, 0.28758169934641, 0, 1, 0 ], +"ranges": [ [ -2400, 1200 ], [ -1200, 1453 ], [ -702, 1694 ], [ -702, 1973 ] ], +"step_probs_vals": [ -1200, 1200, 0, 0, 0.41358024691358, 0.0056818181818177, 0.46913580246914, 0.67045454545455, 0.52057613168724, 0, 0.56378600823045, 0.85795454545455, 0.60082304526749, 0, 1, 0 ], +"passages_weights": [ 1, 0.15, 0.43, 1, 1 ], +"hd_exp": 10, +"hd_invert": 0, +"order": +[ + [ [ 0, 2, 3 ], [ 1, 1, 1, 1 ], [ ] ], + [ [ 1 ], [ 3, 0, 2, 2, 2, 2 ], [ ] ], + [ [ 1, 0 ], [ 3, 2 ], [ ] ], + [ [ 3 ], [ 2, 1, 0, 0, 1, 1, 1 ], [ ] ], + [ [ 3 ], [ 1, 2, 0, 2, 2, 1, 2 ], [ ] ], + [ [ 0, 3 ], [ 1, 2, 1 ], [ ] ], + [ [ 3 ], [ 2, 1, 0 ], [ ] ], + [ [ 3, 2 ], [ 0, 1, 1, 0 ], [ ] ] +], +"sus_weights": [ 0.7, 0.48, 0.49 ], +"order_size": [ 14.132653061224, 14.132653061224 ], +"passages_size": [ 0, 5 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/736745da/736745da_code.scd b/resources/string_quartet_3_rise/736745da/736745da_code.scd new file mode 100644 index 0000000..e1a9d92 --- /dev/null +++ b/resources/string_quartet_3_rise/736745da/736745da_code.scd @@ -0,0 +1,992 @@ +( +// helper funcs +var hsArrayToCents, pDist, hdSum, hsChordalDistance, hsArrayToFreq; + +// score funcs +var isInRange, spacingScore, rangeScore, intervalScore, inclusionScore; + +// subroutines +var genTuples, initVoices, genOrders, genSubMotif, updateVoices, genDurFunc, genStepFunc; + +// primary routines +var genMotif, genSecondarySeq; + +// audition funcs +var genPatterns, genMidiPatterns; + +// resource management funcs +var seedFunc, genUID, writeResources, stringifyToDepth, setSeeds, sanityCheck, +msgInterpret, loadLedgerFile, loadLedgerJSON, loadModelFile, loadModelJSON, +setGlobalVars, globalVarsToDict, saveLedger; + +// model vars +//(model and global vars mostly set by OSC funcs +var seq, lastXChanges, +curUID, refUID, orderSeed, durSeed, motifSeed, +entrancesProbVals, passagesProbVals, exitsProbVals, +ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, +orders, susWeights, orderSize, passagesSize, +motifEdited, orderEdited; + +// model aux vars +var entrancesDurFunc, passagesDurFunc, exitsDurFunc, stepFunc; + +// other global vars +var popSize, exPath, dir, primes, dims, tuples, +group, player, resourceDir, ledgerPath, ledger, currentlyPlayingUID, +nameSpaces; + +// install JSON quark (not used) +/* +if(Quarks.isInstalled("JSONlib").not, { + Quarks.install("https://github.com/musikinformatik/JSONlib.git"); + thisProcess.recompile; + //HelpBrowser.openHelpFor("Classes/JSONlib"); +}); +*/ + + +//------helper funcs + +hsArrayToCents = { + arg hsArray; + hsArray.collect({arg dist, p; dist * 1200 * log2(primes[p][0]/primes[p][1])}).sum +}; + +pDist = { + arg array1, array2, signed = false; + var pDistance; + pDistance = hsArrayToCents.value(array2) - hsArrayToCents.value(array1); + if(signed, {pDistance}, {abs(pDistance)}) +}; + +hdSum = { + arg hsArrays; + var size, distances, mean; + size = hsArrays.size; + distances = (size - 1).collect({arg i; + ((i + 1)..(size - 1)).collect({arg j; + abs(hsArrays[i] - hsArrays[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsChordalDistance = { + arg hsArrays1, hsArrays2; + var size, distances, mean; + size = hsArrays1.size; + distances = hsArrays1.size.collect({arg i; + hsArrays2.size.collect({arg j; + abs(hsArrays1[i] - hsArrays2[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsArrayToFreq = { + arg array; + array.collect({arg dim, d; pow(primes[d][0]/primes[d][1], dim)}).product +}; + +//------score funcs + +/* +isInRange = { + arg hsArray, min, max; + var cents; + cents = hsArrayToCents.value(hsArray); + (cents >= min) && (cents <= max) +}; +*/ + +spacingScore = { + arg hsArrays, min; + var centsArray; + centsArray = hsArrays.collect({arg hsArray; hsArrayToCents.value(hsArray)}).sort({arg a, b; a < b}); + centsArray.differentiate.drop(1).collect({arg pDistance; if(pDistance >= min, {1}, {0.01})}).sum; +}; + +rangeScore = { + arg hsArray1, hsArray2, min, max, low, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + if((pDistance >= min) && (pDistance <= max), {1}, {low}); +}; + +/* +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + stepFunc.value(pDistance) +}; +*/ + +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = true; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + //if(pDistance >= 0, {stepFunc.value(abs(pDistance))}, {0.01}); + stepFunc.value(pDistance) +}; + +inclusionScore = { + arg array, test, min = 0.01; + if(array.collect({arg v; v.hash}).includes(test.hash), {min}, {1}); +}; + + +//------subroutines + +genTuples = { + var tuples; + tuples = dims.collect({[-1, 0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum <= 1) && (tuple[0] == 0)}); + //tuples = dims.collect({[0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum == 1) && (tuple[0] == 0)}); + tuples = tuples ++ tuples.collect({arg tuple; [-3, -2, -1, 1, 2, 3].collect({arg octTrans; tuple.deepCopy.put(0, octTrans)})}).flatten; +}; + +initVoices = { + var init, voicesInit; + voicesInit = popSize.collect({dims.collect({0})}); + /* + voicesInit = [dims.collect({0})]; + (popSize - 1).do({ + arg rep, new; + rep = dims.rand; + new = voicesInit.last.deepCopy; + new[rep] = new[rep] + [-1, 1].choose(); + voicesInit = voicesInit.add(new); + }); + */ + voicesInit.deepCopy; +}; + +genDurFunc = {arg chordProb, minPad, maxPad, minDur, maxDur, envData, seed; + var env, pTable, durFunc; + env = Env.pairs([[0, 0]] ++ envData.clump(2) ++ [[1, 0]]).asSignal(256).asList.asArray; + pTable = env.asRandomTable; + //[chordProb, minPad, maxPad, minDur, maxDur, envData].postln; + durFunc = {arg allowChord, pad = false; + var res; + res = if(allowChord.not, { + pTable.tableRand * (maxDur - minDur) + minDur + }, { + if(1.0.rand < chordProb, {0}, {pTable.tableRand * (maxDur - minDur) + minDur}); + }).round(0.125); + if(pad, {res = res + rrand(minPad.asFloat, maxPad.asFloat).round(0.125)}); + if(res.asInteger == res, {res = res.asInteger}); + res + }; + seedFunc.value(durFunc, seed); +}; + +genStepFunc = {arg minStep, maxStep, envData, seed; + var envDataNorm, env, pTable, stepFunc; + //[minStep, maxStep, envData].postln; + envDataNorm = ([[0, 0]] ++ envData.clump(2) ++ [[1, 0]]).flop; + envDataNorm = [envDataNorm[0].normalize(minStep, maxStep), envDataNorm[1]].flop; + env = Env.pairs(envDataNorm); + stepFunc = {arg pDist; + env.at(pDist).clip(0.001, 1); + }; + seedFunc.value(stepFunc, seed); +}; + +genOrders = {arg minMotifLength = 1, maxMotifLength = 5, minProgLength = 0, maxProgLength = 5; + ((maxMotifLength.asInteger - minMotifLength.asInteger).rand + minMotifLength.asInteger).collect({ + var noProgIns, noSusIns, noSilentIns, prog, sus, silent, order; + noSusIns = [1, 2, 3].wchoose(susWeights.normalizeSum); + + //noProgIns = (popSize - noSusIns).rand + 1; + noProgIns = (popSize - noSusIns); + noSilentIns = popSize - noSusIns - noProgIns; + + /* + noSilentIns = (popSize - noSusIns).rand.clip(0, 1); + noProgIns = popSize - noSusIns - noSilentIns; + */ + + # prog, sus, silent = (0..(popSize-1)).scramble.clumps([noProgIns, noSusIns, noSilentIns]); + + prog = (prog.scramble ++ ((maxProgLength.asInteger - minProgLength.asInteger).rand + minProgLength.asInteger).collect({prog.choose}).scramble); + //prog = ((maxProgLength.asInteger - minProgLength.asInteger).rand + minProgLength.asInteger).collect({prog.choose}); + + if(silent == nil, {silent = []}); + [sus.scramble, prog, silent.scramble] + }); +}; + +updateVoices = {arg ins, sus; + var voices, candidates, nWeights, nProbs, sel; + + voices = lastXChanges.deepCopy.last; + + candidates = sus.collect({arg v; tuples.collect({arg t; voices[v] + t})}).flatten; + candidates = difference(candidates.asSet, voices.asSet).asList; + //candidates.select({arg item; (item ++ voices).asSet.size >= 4}); + nProbs = candidates.collect({arg candidate; + var stepScore, recentlySoundedScore, isInRangeScore, regScore, hdScore; + + //stepScore = intervalScore.value(voices[ins], candidate, 30, 400, 0.1); + stepScore = intervalScore.value(voices[ins], candidate, 100, 100); + recentlySoundedScore = inclusionScore.value(lastXChanges.flop[ins], candidate, 0.01); + //recentlySoundedScore = inclusionScore.value(lastXChanges.flatten.collect({arg item; item.drop(1)}), candidate.drop(1), 0.01); + + /* + if(rangeScore.value(candidate.collect({0}), voices[ins], ranges[ins][1] - 500, ranges[ins][1], 0, true) == 1, { + isInRangeScore = 1 - rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0] + 500, ranges[ins][1] - 500, 0, true); + isInRangeScore = isInRangeScore * rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true) + }, { + isInRangeScore = rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true); + }); + */ + + + isInRangeScore = rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true); + //old way - worked but actually by accident (I think) + //isInRangeScore = rangeScore.value(candidate, candidate.collect({0}), ranges[ins][0], ranges[ins][1], 0, true); + + + regScore = spacingScore.value(voices.deepCopy.put(ins, candidate), 300); + hdScore = pow(hdSum.value(voices.deepCopy.put(ins, candidate)), hdExp); + if(hdInvert == 0, {hdScore = 1/hdScore}); + //maybe what you want here is a vector to another root and then favoring movement towards it. + //distScore = pow(hsChordalDistance.value(voices, voices.put(ins, candidate)), 2); + + [stepScore, recentlySoundedScore, isInRangeScore, regScore, hdScore] + }); + + nWeights = passagesWeights; + + //this handles nWeights of 0; mainly for testing + nProbs = nProbs.flop.select({arg scores, s; nWeights[s] != 0}).flop; + nWeights = nWeights.select({arg weight; weight != 0}); + nProbs = nProbs.flop.collect({arg scores, s; + if(scores.sum == 0, {scores}, {scores.normalizeSum * nWeights[s]}).postln; + }); + nProbs = nProbs.flop.collect({arg scores, s; scores.product}).normalizeSum; + + nProbs.round(0.001).postln; + [candidates, nProbs.round(0.001)].flop.postln; + + sel = candidates.wchoose(nProbs); + + voices[ins] = sel; + lastXChanges = lastXChanges.add(voices).keep(-5); +}; + +genSubMotif = {arg order, orderIndex, lastState, repeatLast = false, startFromLast = false, isLastOrder = false; + var sus, prog, silent, flatOrder, res, isInChord, allowChord, pad, lastXChangesHold, voices, adder; + # sus, prog, silent = order; + flatOrder = silent ++ sus ++ prog; + lastXChangesHold = lastXChanges.deepCopy; + voices = lastState.deepCopy; + isInChord = popSize.collect({false}); + allowChord = false; + pad = false; + res = []; + "------generating motif".postln; + //need to figure out here if voices move between motifs + flatOrder.do({arg ins, i; + + if(prog.includes(ins) && repeatLast.not, {updateVoices.value(ins, sus)}); + adder = if(silent.includes(ins), {["Rest"]}, {lastXChanges.last.deepCopy[ins]}); + + if(voices[ins] != adder, { + var dur; + + if((sus ++ silent).includes(ins), { + allowChord = (ins != sus.last); + pad = (ins == sus.last); + }, { + if(i < (flatOrder.size - 1), { + allowChord = (isInChord[flatOrder[i + 1]] || (ins == flatOrder[i + 1])).not; + pad = false; + }, { + allowChord = false; + pad = true + }); + }); + if((orderIndex == 0) && sus.includes(ins), { + dur = entrancesDurFunc.value(allowChord, pad); + }, { + dur = passagesDurFunc.value(allowChord, pad); + }); + if(dur == 0, {isInChord[ins] = true}, {isInChord = popSize.collect({false})}); + + voices[ins] = adder; + res = res.add([voices.deepCopy.postln, dur]); + }); + }); + + // pad ending + /* + if(orderIndex == (orders.size - 1), { + (0..(popSize-1)).scramble.do({arg ins; + if(res.last.first[ins] != ["Rest"], { + var dur; + voices[ins] = ["Rest"]; + allowChord = (voices != popSize.collect({["Rest"]})); + pad = allowChord.not; + dur = exitsDurFunc.value(allowChord, pad); + res = res.add([voices.deepCopy.postln, dur]); + }); + }); + }); + */ + + //format and return + if(startFromLast, {lastXChanges = lastXChangesHold.deepCopy}); + res; +}; + + +//------primary routines + +genMotif = { + var repeats, fSeq, fDur, durAdd; + + repeats = 1; + fSeq = []; + + repeats.do({arg index; + var motif; + + motif = []; + + orders.do({arg order, o; + var lastState, subMotif; + //lastState = if(o == 0, {popSize.collect({["Rest"]})}, {motif.last.last.first}); + lastState = if(o == 0, {lastXChanges.last.deepCopy}, {motif.last.last.first}); + subMotif = genSubMotif.value(order, o, lastState, isLastOrder: o == (orders.size - 1)); + motif = motif.add(subMotif); + + }); + + sanityCheck.value(motif, index); + + fSeq = fSeq.add(motif); + }); + + //round last duration to measure + /* + fDur = fSeq.flatten.flatten.slice(nil, 1).sum; + durAdd = fDur.round(4) - fDur; + if(durAdd < 0, {durAdd = 4 - durAdd}); + fSeq[0][orders.size - 1][fSeq[0][orders.size - 1].size - 1][1] = fSeq[0][orders.size - 1][fSeq[0][orders.size - 1].size - 1][1] + durAdd; + */ + + fSeq +}; + +genSecondarySeq = {arg seq; + var curdles, fSeq; + curdles = []; + while({curdles.sum < seq.size}, {curdles = curdles ++ [3.rand + 1]}); + + fSeq = seq.clumps(curdles).collect({arg clump, m; + var repeats, paddedSeq; + + //add rest + paddedSeq = clump.add([[[popSize.collect({["Rest"]}), 0.5.rand]]]); + + //implement repeats + repeats = [0.rand + 1, 1].wchoose([1, 0].normalizeSum); + repeats.collect({paddedSeq}); + }); + fSeq +}; + + +//------audition funcs + +/* +Event.addEventType(\osc, { + if (~addr.postln.notNil) { + ~addr.sendMsg(~indexPath, ~indexMsg); + ~addr.sendMsg(~seqPath, stringifyToDepth.value(~seqMsg, 3)); + //~addr.sendMsg("/STATE/OPEN", (dir.replace("supercollider", "resources") +/+ ~idMsg +/+ ~idMsg ++ "_gui_state" ++ ".state").standardizePath.postln); + }; +}); +*/ + +Event.addEventType(\osc, { + if (~addr.notNil) { + ~msg; + ~addr.sendMsg(~path, *~msg); + }; +}); + +genPatterns = {arg inSeq, addr, oneShot = false; + var voices, durs, pbinds, res, indices, sectionDurs, msg, ids, seq; + seq = inSeq.collect({arg mSeq; mSeq[0]}); + //# voices, durs = seq.flatten2(seq.maxDepth - 5).flop; + # voices, durs = seq.flatten2(if(oneShot, {2}, {3})).flop; + pbinds = voices.flop.collect({arg voice, v; + var clumps, hdScores, freqs, fDurs, attacks, rels, amps; + clumps = voice.separate({arg a, b; a != b }); + freqs = clumps.collect({arg clump; if(clump[0] != ["Rest"], {(62.midicps * hsArrayToFreq.value(clump[0]))}, {Rest(0)})}); + fDurs = durs.clumps(clumps.collect({arg clump; clump.size})).collect({arg clump; clump.sum}); + //attacks = 2.collect({rrand(1, 3)}) ++ freqs.drop(2).collect({rrand(0.3, 0.5)}); + attacks = fDurs.collect({arg dur; dur * rrand(0.2, 0.4)}); + //rels = freqs.drop(2).collect({rrand(0.3, 0.5)}) ++ 2.collect({rrand(1, 3)}); + rels = (clumps.size - 1).collect({arg c; + if(clumps[c + 1][0] == ["Rest"], {rrand(1.0, 3.0)}, {rrand(0.3, 0.5)}); + }); + rels = rels.add(rrand(1.0, 3.0)); + amps = freqs.collect({rrand(0.6, 0.99)}); + + [ + Pbind( + \instrument, \string_model, + \group, group, + \freq, Pseq(freqs, 1), + \dur, Pseq(fDurs, 1), + \attack, Pseq(attacks, 1), + \sustain, Pseq(fDurs, 1), + \release, Pseq(rels, 1), + //\amp, Pseq(amps, 1), + \amp, Pbrown(0.5, 1, 0.5), + \busIndex, v + ), + Pbind( + \instrument, \sine, + \group, group, + \freq, Pseq(freqs, 1), + \dur, Pseq(fDurs, 1), + \sustain, Pseq(fDurs, 1), + \busIndex, v + ) + ] + }).flatten; + if(oneShot.not, { + msg = inSeq.collect({arg mSeq, m; mSeq[1..]}); + //ids = inSeq.collect({arg mSeq, m; mSeq[2]}); + sectionDurs = seq.collect({arg mSeq; mSeq.flatten2(2).flop[1].sum}); + pbinds = pbinds ++ + [ + Pbind( + \type, \osc, + \addr, addr, + \path, "/playing", + \msg, Pseq(msg, 1), + \dur, Pseq(sectionDurs, 1) + ); + ] + }); + res = Ppar(pbinds); + res +}; + +/* +genMidiPatterns = {arg seq; + var voices, durs, patterns, res, mOut, pbRange; + pbRange = 1; //semitones - change this as needed for your situation + mOut = MIDIOut.newByName("TiMidity", "TiMidity port 0").latency_(Server.default.latency); + # voices, durs = seq.flatten2(seq.maxDepth - 5).flop; + res = Ppar( + voices.flop.collect({arg voice, v; + var clumps, hdScores, freqs, fDurs; + + mOut.program(v, 70); + + clumps = voice.separate({arg a, b; a != b }); + freqs = clumps.collect({arg clump; if(clump[0] != ["Rest"], {(60.midicps * hsArrayToFreq.value(clump[0]))}, {Rest(0)})}); + fDurs = durs.clumps(clumps.collect({arg clump; clump.size})).collect({arg clump; clump.sum}); + + Pbind( + \type, \midi, + \chan, v, + \noteval, Pseq(freqs.cpsmidi - 24, 1), + \note, Pfunc({ | event | event[\noteval].floor }), + \dur, Pseq(fDurs, 1), + \midiout, mOut, + \amp, 1, + \bend, Pfunc({ + | event | + if (event[\note].isRest.not) { + var pitchbendvalue = event[\noteval].frac.linlin(0, pbRange, 8192, 8192*2).asInteger; + m.bend(v, pitchbendvalue); + }; + 0; // return something other than nil to avoid stopping the pattern + }), + ); + }); + ); + res +}; +*/ + + +//------resource management funcs + +genUID = {Date.seed.asHexString.toLower}; + +seedFunc = {arg func, seed; + var funcArgs, next; + next = Routine({loop{func.valueArray(funcArgs).yield }}); + next.randSeed_(seed); + {arg ...args; funcArgs = args; next.value} +}; + +stringifyToDepth = {arg data, maxDepth = 1; + var prettyString = "", rCount = 0, writeArray, indent; + + if(maxDepth == 0, { + data.asCompileString + }, { + indent = {arg size; size.collect({" "}).join("")}; + writeArray = {arg array; + prettyString = prettyString ++ indent.value(rCount) ++ "[\n"; + rCount = rCount + 1; + if(rCount < maxDepth, { + array.do({arg subArray; writeArray.value(subArray)}); + }, { + prettyString = prettyString ++ array.collect({arg subArray; + indent.value(rCount + 1) ++ subArray.asCompileString + }).join(",\n"); + }); + rCount = rCount - 1; + prettyString = prettyString ++ "\n" ++ indent.value(rCount) ++ "],\n"; + }; + + writeArray.value(data); + prettyString.replace(",\n\n", "\n").drop(-2); + }) +}; + +sanityCheck = {arg motif, index; + //print functions = very helpful + ("----------" + index + "------------").postln; + + motif.flatten.do({arg val, v; + if(v > 0, { + if(motif.flatten[v-1][0].hammingDistance(val[0]) > 1, {"problem 1".postln}); + if(motif.flatten[v-1][0].hammingDistance(val[0]) == 0, {"problem 2".postln}); + }); + val.postln + }); + "***********".postln; +}; + +msgInterpret = {arg in, escapeDoubleQuotes = true, escapeSingleQuotes = true; + var res; + + res = in; + if(res.isNil.not, { + if((res.isArray && res.isString.not), { + res = res.asCompileString; + res = res.replace(" ", "").replace("\n", "").replace("\t", ""); + if(escapeSingleQuotes, {res = res.replace("\'", "")}); + if(escapeDoubleQuotes, {res = res.replace("\"", "")}); + res = res.replace("Rest", "\"Rest\""); + res = res.interpret; + }, { + var tmpRes; + if(res.every({arg char; char.isDecDigit}), {tmpRes = res.asInteger}); + if(res.contains("."), {tmpRes = res.asFloat}); + if(tmpRes != nil, {res = tmpRes}); + }); + }); + res +}; + +writeResources = {arg path, dict; + var file, modelItems, resString; + file = File(path,"w"); + + modelItems = [ + seq, lastXChanges, + curUID, refUID, orderSeed, durSeed, motifSeed, + entrancesProbVals, passagesProbVals, exitsProbVals, + ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, + orders, susWeights, orderSize, passagesSize, + motifEdited, orderEdited + ]; + + resString = nameSpaces.collect({arg nameSpace; + var depth = 0, insert = " "; + if(nameSpace == "music_data", {depth = 3; insert = "\n"}); + if(nameSpace == "last_changes", {depth = 1; insert = "\n"}); + if(nameSpace == "order", {depth = 1; insert = "\n"}); + if((nameSpace == "ref_uid") && (dict[nameSpace] == nil), {dict[nameSpace] = "nil"}); + "\"" ++ nameSpace ++ "\":" ++ insert ++ stringifyToDepth.value(dict[nameSpace], depth) + }).join(",\n"); + + resString = "{\n" ++ resString ++ "\n}"; + + file.write(resString); + file.close; + resString +}; + +loadModelFile = {arg path; loadModelJSON.value(File(path, "r").readAllString.parseJSON)}; + +loadModelJSON = {arg jsonObject; + var dict; + dict = Dictionary.with(*nameSpaces.collect({arg nS; nS->msgInterpret.value(jsonObject[nS])})); + dict +}; + +setGlobalVars = {arg dict, skipLastXChanges = false; + var tmpLastXChanges; + tmpLastXChanges = lastXChanges.deepCopy; + // order really matters!!!! + # seq, lastXChanges, curUID, refUID, orderSeed, durSeed, motifSeed, + entrancesProbVals, passagesProbVals, exitsProbVals, + ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, + orders, susWeights, orderSize, passagesSize, + motifEdited, orderEdited = nameSpaces.collect({arg nS; dict[nS]}); + if(skipLastXChanges, {lastXChanges = tmpLastXChanges}); + dict +}; + +globalVarsToDict = { + var modelItems, dict; + // order really matters!!!! + modelItems = [ + seq, lastXChanges, + curUID, refUID, orderSeed, durSeed, motifSeed, + entrancesProbVals, passagesProbVals, exitsProbVals, + ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, + orders, susWeights, orderSize, passagesSize, + motifEdited, orderEdited + ]; + dict = Dictionary.with(*nameSpaces.collect({arg nS, n; nS->modelItems[n]})); +}; + +loadLedgerFile = {arg path; + ledgerPath = path; + resourceDir = path.splitext(".").drop(-1).join; + loadLedgerJSON.value(File(ledgerPath, "r").readAllString.parseJSON) +}; + +loadLedgerJSON = {arg jsonObject; ledger = jsonObject["ledger"]}; + +saveLedger = {arg ledger, path; + var file, curResourceDir; + file = File(path, "w"); + curResourceDir = resourceDir; + resourceDir = path.splitext(".").drop(-1).join; + if(curResourceDir != resourceDir, { + File.mkdir(resourceDir); + ledger.do({arg id; + File.copy(curResourceDir +/+ id, resourceDir +/+ id); + }); + }); + file.write("{\n\"ledger\":\n" ++ stringifyToDepth.value(ledger, 1) ++ "\n}"); + file.close; +}; + +//------global vars + +primes = [[2, 1], [3, 2], [5, 4], [7, 4], [11, 8], [13, 8]]; +//ranges = [[-2400, 0], [-1200, 1200], [0, 2400], [0, 2400]]; +exPath = thisProcess.nowExecutingPath; +dir = exPath.dirname; +//popSize = 4; +dims = primes.size; +tuples = genTuples.value(); +//refUID = nil; +group = Group.new; +~group = group; +loadLedgerFile.value(dir +/+ ".." +/+ "resources" +/+ "piece_ledger.json"); +resourceDir = (dir +/+ ".." +/+ "resources" +/+ "piece_ledger"); +//passagesWeights = [1, 1, 1, 1, 1]; +//susWeights = [1, 1, 1]; +// order really matters!!!! +nameSpaces = [ + "music_data", "last_changes", "cur_uid", "ref_uid", "order_seed", "dur_seed", "motifs_seed", + "entrances_probs_vals","passages_probs_vals", "exits_probs_vals", + "ranges", "step_probs_vals", "passages_weights", "hd_exp", "hd_invert", + "order", "sus_weights", "order_size", "passages_size", + "motif_edited", "order_edited" +]; + + +//------OSC funcs + +OSCdef(\load_ledger, {arg msg, time, addr, port; + loadLedgerFile.value(msg[1].asString); +}, \load_ledger); + +OSCdef(\load_model, {arg msg, time, addr, port; + var dict; + dict = loadModelFile.value(msg[1].asString); + setGlobalVars.value(dict); +}, \load_model); + +OSCdef(\save_ledger, {arg msg, time, addr, port; + msg.postln; + ledger = msgInterpret.value(msg[1].asString.parseJSON["ledger"], false).postln; + //loadLedgerJSON.value(msg[0]) + saveLedger.value(ledger, msg[2].asString); + //loadLedgerFile.value(msg[1].asString); +}, \save_ledger); + +OSCdef(\generate, {arg msg, time, addr, port; + var path, dict, durSeeds, musPath, modelString; + msg.postln; + + path = msg[1].asString; + + dict = loadModelFile.value(path); + setGlobalVars.value(dict, true); + + popSize = ranges.size; + + //refUID.postln; + + loadLedgerFile.value(ledgerPath); + if(ledger == nil, {ledger = ["tmp"]}); + if(ledger.last != "tmp", {ledger = ledger.add("tmp")}); + + if(refUID == nil, {lastXChanges = [initVoices.value().deepCopy]}); + if((refUID != nil) && (refUID != "tmp"), { + var file; + file = File((resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath, "r"); + lastXChanges = msgInterpret.value(file.readAllString.parseJSON["last_changes"]); + }); + + refUID.postln; + lastXChanges.collect({arg item; item.postln}); + + durSeeds = seedFunc.value({3.collect({rrand(100000, 999999)})}, durSeed).value.postln; + entrancesDurFunc = genDurFunc.valueArray(entrancesProbVals[..4] ++ [entrancesProbVals[5..]] ++ [durSeeds[0]]); + passagesDurFunc = genDurFunc.valueArray(passagesProbVals[..4] ++ [passagesProbVals[5..]] ++ [durSeeds[1]]); + exitsDurFunc = genDurFunc.valueArray(exitsProbVals[..4] ++ [exitsProbVals[5..]] ++ [durSeeds[2]]); + + if(orders == nil, { + orders = seedFunc.value(genOrders, orderSeed).valueArray(orderSize ++ passagesSize); + //addr.sendMsg("/order", stringifyToDepth.value(orders, 1)); + }); + + stepFunc = genStepFunc.valueArray(stepProbsVals[..1] ++ [stepProbsVals[2..]] ++ [motifSeed]); + seq = seedFunc.value(genMotif, motifSeed).value; + + lastXChanges.collect({arg item; item.postln}); + + dict = globalVarsToDict.value; + modelString = writeResources.value(path, dict); + + //addr.sendMsg("/generated", musPath, stringifyToDepth.value(seq, 3)); + //~seq = seq; + + addr.sendMsg("/generated", path, modelString, ledgerPath); +}, \generate); + + +OSCdef(\commit, {arg msg, time, addr, port; + var musicData, musicChanged, dict, newLedger, modelPath, musString, musFile, test1, test2, lastCurUID, commitType, commitPos, equalityLedger; + //msg.postln; + + /* + test1 = msg[1].asString.parseJSON; + test2 = (dir +/+ ".." +/+ "resources/tmp/tmp_music" ++ ".json").standardizePath.parseJSONFile; + msgInterpret.value(test1["music"])[0][0][0][1].class.postln; + msgInterpret.value(test2["music_data"])[0][0][0][1].class.postln; + (test1["music"] == test2["music_data"]).postln; + */ + + musicData = loadModelJSON.value(msg[1].asString.parseJSON)["music_data"].postln; + musicChanged = (musicData != seq).postln; + commitType = msg[2].asString; + commitPos = msg[3].postln.asInteger; + + lastCurUID = curUID.deepCopy; + curUID = genUID.value; + + File.mkdir((resourceDir +/+ curUID).standardizePath); + File.copy(exPath, (resourceDir +/+ curUID +/+ curUID ++ "_code" ++ ".scd").standardizePath); + + modelPath = (resourceDir +/+ curUID +/+ curUID ++ "_mus_model" ++ ".json").standardizePath; + dict = globalVarsToDict.value; + if(musicChanged, { + seq = musicData; + dict["music_data"] = seq; + dict["motif_edited"] = "true" + }); + dict["cur_uid"] = curUID; + + writeResources.value(modelPath, dict); + + File.delete(ledgerPath ++ "_bak"); + File.copy(ledgerPath, ledgerPath ++ "_bak"); + File.delete(ledgerPath); + + /* + if(commitType == "add", { + if(lastCurUID == "tmp", { + ledger = ledger.drop(-1).add(curUID); + }, { + ledger = ledger.add(curUID); + }) + }); + */ + + ledger.postln; + + if(commitType == "add", {ledger = ledger.add(curUID)}); + + if(commitType == "insert", {ledger = ledger.insert(commitPos + 1, curUID)}); + + if(commitType == "replace", {ledger = ledger.put(commitPos, curUID)}); + + equalityLedger = ledger.collect({arg item; item.asSymbol}); + if(equalityLedger.includes(\tmp).postln, {ledger.removeAt(equalityLedger.indexOf(\tmp).postln)}); + + ledger.postln; + + saveLedger.value(ledger, ledgerPath); + + addr.sendMsg("/committed", curUID, ledgerPath); + //refUID = curUID; + +}, \commit); + +OSCdef(\transport, {arg msg, time, addr, port; + msg.postln; + if(msg[1] == 0, { + group.set(\release, 2); + group.set(\gate, 0); + player.stop; + }, { + // the cued sequence can now be read from file, so this can be cleaned up + var cSize, patterns, pSeq, cuedSeek, indexStart, indexEnd, tmpLedger; + if(msg[1] == 1, { + pSeq = []; + cuedSeek = (seq != nil); + indexStart = msg[2].asInteger; + indexEnd = ledger.size - if(cuedSeek, {2}, {1}); + //ledger.postln; + if(((indexStart == (ledger.size - 1)) && cuedSeek).not, { + ledger[indexStart..indexEnd].do({arg uid, index; + var path, file; + path = (resourceDir +/+ uid +/+ uid ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.postln.parseJSON["music_data"]), path, indexStart + index, uid]); + file.close; + }); + }); + if(cuedSeek, { + var path, file; + path = (resourceDir +/+ "tmp/tmp_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.parseJSON["music_data"]), path, ledger.size - 1, "tmp"]); + file.close; + }); + patterns = genPatterns.value(pSeq, addr); + }, { + pSeq = [loadModelJSON.value(msg[2].asString.parseJSON)["music_data"].postln]; + patterns = genPatterns.value(pSeq, addr, true); + }); + player = Pfset(pattern: patterns, cleanupFunc: { + addr.sendMsg("/transport", 0); + addr.sendMsg("/one_shot", 0); + }); + player = player.play + }); +}, \transport); + + +OSCdef(\transcribe_motif, {arg msg, time, addr, port; + var tSeq, refChord, refUID; + + msg.postln; + + tSeq = [loadModelJSON.value(msg[1].asString.parseJSON)["music_data"]]; + refUID = msg[2].asString.postln; + + if((refUID != "nil") && (refUID != "tmp"), { + var file; + file = File((resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath, "r"); + refChord = msgInterpret.value(file.readAllString.parseJSON["last_changes"]).last; + file.close; + }, { + refChord = [[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0]]; + }); + + ~transcribe.value(tSeq, refChord, (dir +/+ ".." +/+ "lilypond" +/+ "includes").standardizePath, addr, "/transcribe_motif"); +}, \transcribe_motif); + + +OSCdef(\transcribe_all, {arg msg, time, addr, port; + var cSize, patterns, cuedSeek, indexStart, indexEnd, tmpLedger; + if(true, { + cuedSeek = (seq != nil); + indexStart = msg[1].asInteger; + indexEnd = ledger.size - if(cuedSeek, {2}, {1}); + + //tmp for testing transcription + //indexEnd = (indexStart+5); + + //ledger.postln; + if(((indexStart == (ledger.size - 1)) && cuedSeek).not, { + var lilyPartLedgerFiles; + + lilyPartLedgerFiles = 4.collect({arg p; + File((dir +/+ ".." +/+ "lilypond" +/+ "includes" +/+ "part_" ++ ["IV", "III", "II", "I"][p] ++ ".ly").standardizePath, "w"); + }); + + ledger[indexStart..indexEnd].do({arg uid, index; + var path, file, fileString, tSeq, refUID, refChord; + path = (resourceDir +/+ uid +/+ uid ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + fileString = file.readAllString; + tSeq = msgInterpret.value(fileString.parseJSON["music_data"]); + refUID = msgInterpret.value(fileString.parseJSON["ref_uid"]); + file.close; + + //uid.postln; + //(refUID == "nil").postln; + + refChord = [[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0]]; + + if(refUID != "nil", { + path = (resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + refChord = msgInterpret.value(file.readAllString.parseJSON["last_changes"]).last; + file.close; + }); + + if(index != indexEnd, { + ~transcribe.value(tSeq, refChord, (resourceDir +/+ uid +/+ "lilypond").standardizePath); + }, { + ~transcribe.value(tSeq, refChord, (resourceDir +/+ uid +/+ "lilypond").standardizePath, addr, "/transcribe_all"); + }); + + lilyPartLedgerFiles.do({arg f, p; + f.write("\\include \"" ++ resourceDir +/+ uid +/+ "lilypond" +/+ "part_" ++ ["IV", "III", "II", "I"][p] ++ ".ly\"\n"); + }); + + }); + + lilyPartLedgerFiles.do({arg f; + f.close + }); + }); + /* + if(cuedSeek, { + var path, file; + path = (dir +/+ ".." +/+ "resources/tmp/tmp_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.parseJSON["music_data"]), path, ledger.size - 1, "tmp"]); + file.close; + }); + */ + }, { + + }); + +}, \transcribe_all); + +) + + diff --git a/resources/string_quartet_3_rise/736745da/736745da_mus_model.json b/resources/string_quartet_3_rise/736745da/736745da_mus_model.json new file mode 100644 index 0000000..859dd7a --- /dev/null +++ b/resources/string_quartet_3_rise/736745da/736745da_mus_model.json @@ -0,0 +1,65 @@ +{ +"music_data": +[ + [ + [ + [ [ [ -6, 5, 1, 1, -1, 2 ], [ -5, 3, 1, 2, -1, 2 ], [ -6, 5, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0 ], + [ [ [ -6, 5, 1, 1, -1, 2 ], [ -5, 3, 1, 2, -1, 2 ], [ -5, 4, 0, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0 ], + [ [ [ -7, 4, 1, 2, 0, 2 ], [ -5, 3, 1, 2, -1, 2 ], [ -5, 4, 0, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -7, 4, 1, 2, 0, 2 ], [ -5, 3, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -2, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -7, 4, 1, 2, 0, 2 ], [ -5, 3, 1, 2, -1, 2 ], [ -6, 4, 1, 2, 0, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0 ], + [ [ [ -7, 4, 1, 2, 0, 2 ], [ -6, 4, 2, 2, -1, 2 ], [ -6, 4, 1, 2, 0, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -7, 4, 1, 2, 0, 2 ], [ -6, 4, 2, 2, -1, 2 ], [ -6, 5, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ] + ], + [ + [ [ [ -7, 4, 1, 2, 0, 2 ], [ -6, 4, 1, 2, 0, 2 ], [ -6, 5, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0 ], + [ [ [ -7, 4, 1, 2, 0, 2 ], [ -6, 4, 1, 2, 0, 2 ], [ -5, 4, 1, 2, 0, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -7, 4, 1, 2, 0, 2 ], [ -6, 5, 1, 2, -1, 2 ], [ -5, 4, 1, 2, 0, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ] + ], + [ + [ [ [ -7, 4, 1, 2, 0, 2 ], [ -6, 5, 1, 2, -1, 2 ], [ -5, 5, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0 ], + [ [ [ -7, 4, 1, 2, 0, 2 ], [ -5, 4, 0, 2, -1, 2 ], [ -5, 5, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0 ], + [ [ [ -7, 5, 1, 2, -1, 2 ], [ -5, 4, 0, 2, -1, 2 ], [ -5, 5, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ] + ], + [ + [ [ [ -6, 3, 1, 2, -1, 2 ], [ -5, 4, 0, 2, -1, 2 ], [ -5, 5, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0 ], + [ [ [ -6, 3, 1, 2, -1, 2 ], [ -6, 5, 1, 2, -1, 2 ], [ -5, 5, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -6, 3, 1, 2, -1, 2 ], [ -6, 4, 1, 2, -1, 2 ], [ -5, 5, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -7, 4, 1, 2, -1, 2 ], [ -6, 4, 1, 2, -1, 2 ], [ -5, 5, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ] + ] + ] +], +"last_changes": +[ + [ [ -7, 5, 1, 2, -1, 2 ], [ -5, 4, 0, 2, -1, 2 ], [ -5, 5, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], + [ [ -6, 3, 1, 2, -1, 2 ], [ -5, 4, 0, 2, -1, 2 ], [ -5, 5, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], + [ [ -6, 3, 1, 2, -1, 2 ], [ -6, 5, 1, 2, -1, 2 ], [ -5, 5, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], + [ [ -6, 3, 1, 2, -1, 2 ], [ -6, 4, 1, 2, -1, 2 ], [ -5, 5, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], + [ [ -7, 4, 1, 2, -1, 2 ], [ -6, 4, 1, 2, -1, 2 ], [ -5, 5, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ] +], +"cur_uid": "736745da", +"ref_uid": "567a9375", +"order_seed": 837263, +"dur_seed": 409865, +"motifs_seed": 622222, +"entrances_probs_vals": [ 0.78, 0, 0, 0, 1.02, 0.0098039215686275, 0, 0.20915032679739, 0, 0.24183006535948, 0.82432432432432, 0.28758169934641, 0, 1, 0 ], +"passages_probs_vals": [ 0.78, 0, 0, 0, 1.02, 0.0098039215686275, 0, 0.20915032679739, 0, 0.24183006535948, 0.82432432432432, 0.28758169934641, 0, 1, 0 ], +"exits_probs_vals": [ 0.78, 0, 0, 0, 1.02, 0.0098039215686275, 0, 0.20915032679739, 0, 0.24183006535948, 0.82432432432432, 0.28758169934641, 0, 1, 0 ], +"ranges": [ [ -2400, 1200 ], [ -1200, 1453 ], [ -702, 1694 ], [ -702, 1973 ] ], +"step_probs_vals": [ -1200, 1200, 0, 0, 0.41358024691358, 0.0056818181818177, 0.45884773662551, 0.5, 0.52057613168724, 0, 0.56378600823045, 0.85795454545455, 0.60082304526749, 0, 1, 0 ], +"passages_weights": [ 1, 0.15, 0.43, 1, 1 ], +"hd_exp": 10, +"hd_invert": 0, +"order": +[ + [ [ 3 ], [ 1, 2, 0, 2, 2, 1, 2 ], [ ] ], + [ [ 0, 3 ], [ 1, 2, 1 ], [ ] ], + [ [ 3 ], [ 2, 1, 0 ], [ ] ], + [ [ 3, 2 ], [ 0, 1, 1, 0 ], [ ] ] +], +"sus_weights": [ 0.7, 0.48, 0.49 ], +"order_size": [ 14.132653061224, 14.132653061224 ], +"passages_size": [ 0, 5 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/736745da/lilypond/part_I.ly b/resources/string_quartet_3_rise/736745da/lilypond/part_I.ly new file mode 100644 index 0000000..bad9fe0 --- /dev/null +++ b/resources/string_quartet_3_rise/736745da/lilypond/part_I.ly @@ -0,0 +1,4 @@ +{ + { f'1^\markup { \pad-markup #0.2 "-38"}} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/736745da/lilypond/part_II.ly b/resources/string_quartet_3_rise/736745da/lilypond/part_II.ly new file mode 100644 index 0000000..c31cc8d --- /dev/null +++ b/resources/string_quartet_3_rise/736745da/lilypond/part_II.ly @@ -0,0 +1,4 @@ +{ + { cis'8^\markup { \pad-markup #0.2 "-25"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 5↓" }}[ b8^\markup { \pad-markup #0.2 "+10"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 11↓" }}] ais8^\markup { \pad-markup #0.2 "+13"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }}[ c'8^\markup { \pad-markup #0.2 "-37"}] ais'4^\markup { \pad-markup #0.2 "+13"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }} c''4^\markup { \pad-markup #0.2 "-37"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }}} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/736745da/lilypond/part_III.ly b/resources/string_quartet_3_rise/736745da/lilypond/part_III.ly new file mode 100644 index 0000000..81984f4 --- /dev/null +++ b/resources/string_quartet_3_rise/736745da/lilypond/part_III.ly @@ -0,0 +1,4 @@ +{ + { ais4^\markup { \pad-markup #0.2 "-40"} gis4^\markup { \pad-markup #0.2 "+48"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 5↑" }} ais8^\markup { \pad-markup #0.2 "+13"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }}[ c'8^\markup { \pad-markup #0.2 "-37"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 3↑" }}] cis'8^\markup { \pad-markup #0.2 "-25"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 5↓" }}[ c'8^\markup { \pad-markup #0.2 "-37"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 1↑" }}]} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/736745da/lilypond/part_IV.ly b/resources/string_quartet_3_rise/736745da/lilypond/part_IV.ly new file mode 100644 index 0000000..b57cf57 --- /dev/null +++ b/resources/string_quartet_3_rise/736745da/lilypond/part_IV.ly @@ -0,0 +1,4 @@ +{ + { ais,2.^\markup { \pad-markup #0.2 "+13"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 11↑" }} c8^\markup { \pad-markup #0.2 "-37"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 1↑" }}[ ais,8^\markup { \pad-markup #0.2 "-40"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 3↓" }}]} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/767e70f0/767e70f0_code.scd b/resources/string_quartet_3_rise/767e70f0/767e70f0_code.scd new file mode 100644 index 0000000..e1a9d92 --- /dev/null +++ b/resources/string_quartet_3_rise/767e70f0/767e70f0_code.scd @@ -0,0 +1,992 @@ +( +// helper funcs +var hsArrayToCents, pDist, hdSum, hsChordalDistance, hsArrayToFreq; + +// score funcs +var isInRange, spacingScore, rangeScore, intervalScore, inclusionScore; + +// subroutines +var genTuples, initVoices, genOrders, genSubMotif, updateVoices, genDurFunc, genStepFunc; + +// primary routines +var genMotif, genSecondarySeq; + +// audition funcs +var genPatterns, genMidiPatterns; + +// resource management funcs +var seedFunc, genUID, writeResources, stringifyToDepth, setSeeds, sanityCheck, +msgInterpret, loadLedgerFile, loadLedgerJSON, loadModelFile, loadModelJSON, +setGlobalVars, globalVarsToDict, saveLedger; + +// model vars +//(model and global vars mostly set by OSC funcs +var seq, lastXChanges, +curUID, refUID, orderSeed, durSeed, motifSeed, +entrancesProbVals, passagesProbVals, exitsProbVals, +ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, +orders, susWeights, orderSize, passagesSize, +motifEdited, orderEdited; + +// model aux vars +var entrancesDurFunc, passagesDurFunc, exitsDurFunc, stepFunc; + +// other global vars +var popSize, exPath, dir, primes, dims, tuples, +group, player, resourceDir, ledgerPath, ledger, currentlyPlayingUID, +nameSpaces; + +// install JSON quark (not used) +/* +if(Quarks.isInstalled("JSONlib").not, { + Quarks.install("https://github.com/musikinformatik/JSONlib.git"); + thisProcess.recompile; + //HelpBrowser.openHelpFor("Classes/JSONlib"); +}); +*/ + + +//------helper funcs + +hsArrayToCents = { + arg hsArray; + hsArray.collect({arg dist, p; dist * 1200 * log2(primes[p][0]/primes[p][1])}).sum +}; + +pDist = { + arg array1, array2, signed = false; + var pDistance; + pDistance = hsArrayToCents.value(array2) - hsArrayToCents.value(array1); + if(signed, {pDistance}, {abs(pDistance)}) +}; + +hdSum = { + arg hsArrays; + var size, distances, mean; + size = hsArrays.size; + distances = (size - 1).collect({arg i; + ((i + 1)..(size - 1)).collect({arg j; + abs(hsArrays[i] - hsArrays[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsChordalDistance = { + arg hsArrays1, hsArrays2; + var size, distances, mean; + size = hsArrays1.size; + distances = hsArrays1.size.collect({arg i; + hsArrays2.size.collect({arg j; + abs(hsArrays1[i] - hsArrays2[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsArrayToFreq = { + arg array; + array.collect({arg dim, d; pow(primes[d][0]/primes[d][1], dim)}).product +}; + +//------score funcs + +/* +isInRange = { + arg hsArray, min, max; + var cents; + cents = hsArrayToCents.value(hsArray); + (cents >= min) && (cents <= max) +}; +*/ + +spacingScore = { + arg hsArrays, min; + var centsArray; + centsArray = hsArrays.collect({arg hsArray; hsArrayToCents.value(hsArray)}).sort({arg a, b; a < b}); + centsArray.differentiate.drop(1).collect({arg pDistance; if(pDistance >= min, {1}, {0.01})}).sum; +}; + +rangeScore = { + arg hsArray1, hsArray2, min, max, low, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + if((pDistance >= min) && (pDistance <= max), {1}, {low}); +}; + +/* +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + stepFunc.value(pDistance) +}; +*/ + +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = true; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + //if(pDistance >= 0, {stepFunc.value(abs(pDistance))}, {0.01}); + stepFunc.value(pDistance) +}; + +inclusionScore = { + arg array, test, min = 0.01; + if(array.collect({arg v; v.hash}).includes(test.hash), {min}, {1}); +}; + + +//------subroutines + +genTuples = { + var tuples; + tuples = dims.collect({[-1, 0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum <= 1) && (tuple[0] == 0)}); + //tuples = dims.collect({[0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum == 1) && (tuple[0] == 0)}); + tuples = tuples ++ tuples.collect({arg tuple; [-3, -2, -1, 1, 2, 3].collect({arg octTrans; tuple.deepCopy.put(0, octTrans)})}).flatten; +}; + +initVoices = { + var init, voicesInit; + voicesInit = popSize.collect({dims.collect({0})}); + /* + voicesInit = [dims.collect({0})]; + (popSize - 1).do({ + arg rep, new; + rep = dims.rand; + new = voicesInit.last.deepCopy; + new[rep] = new[rep] + [-1, 1].choose(); + voicesInit = voicesInit.add(new); + }); + */ + voicesInit.deepCopy; +}; + +genDurFunc = {arg chordProb, minPad, maxPad, minDur, maxDur, envData, seed; + var env, pTable, durFunc; + env = Env.pairs([[0, 0]] ++ envData.clump(2) ++ [[1, 0]]).asSignal(256).asList.asArray; + pTable = env.asRandomTable; + //[chordProb, minPad, maxPad, minDur, maxDur, envData].postln; + durFunc = {arg allowChord, pad = false; + var res; + res = if(allowChord.not, { + pTable.tableRand * (maxDur - minDur) + minDur + }, { + if(1.0.rand < chordProb, {0}, {pTable.tableRand * (maxDur - minDur) + minDur}); + }).round(0.125); + if(pad, {res = res + rrand(minPad.asFloat, maxPad.asFloat).round(0.125)}); + if(res.asInteger == res, {res = res.asInteger}); + res + }; + seedFunc.value(durFunc, seed); +}; + +genStepFunc = {arg minStep, maxStep, envData, seed; + var envDataNorm, env, pTable, stepFunc; + //[minStep, maxStep, envData].postln; + envDataNorm = ([[0, 0]] ++ envData.clump(2) ++ [[1, 0]]).flop; + envDataNorm = [envDataNorm[0].normalize(minStep, maxStep), envDataNorm[1]].flop; + env = Env.pairs(envDataNorm); + stepFunc = {arg pDist; + env.at(pDist).clip(0.001, 1); + }; + seedFunc.value(stepFunc, seed); +}; + +genOrders = {arg minMotifLength = 1, maxMotifLength = 5, minProgLength = 0, maxProgLength = 5; + ((maxMotifLength.asInteger - minMotifLength.asInteger).rand + minMotifLength.asInteger).collect({ + var noProgIns, noSusIns, noSilentIns, prog, sus, silent, order; + noSusIns = [1, 2, 3].wchoose(susWeights.normalizeSum); + + //noProgIns = (popSize - noSusIns).rand + 1; + noProgIns = (popSize - noSusIns); + noSilentIns = popSize - noSusIns - noProgIns; + + /* + noSilentIns = (popSize - noSusIns).rand.clip(0, 1); + noProgIns = popSize - noSusIns - noSilentIns; + */ + + # prog, sus, silent = (0..(popSize-1)).scramble.clumps([noProgIns, noSusIns, noSilentIns]); + + prog = (prog.scramble ++ ((maxProgLength.asInteger - minProgLength.asInteger).rand + minProgLength.asInteger).collect({prog.choose}).scramble); + //prog = ((maxProgLength.asInteger - minProgLength.asInteger).rand + minProgLength.asInteger).collect({prog.choose}); + + if(silent == nil, {silent = []}); + [sus.scramble, prog, silent.scramble] + }); +}; + +updateVoices = {arg ins, sus; + var voices, candidates, nWeights, nProbs, sel; + + voices = lastXChanges.deepCopy.last; + + candidates = sus.collect({arg v; tuples.collect({arg t; voices[v] + t})}).flatten; + candidates = difference(candidates.asSet, voices.asSet).asList; + //candidates.select({arg item; (item ++ voices).asSet.size >= 4}); + nProbs = candidates.collect({arg candidate; + var stepScore, recentlySoundedScore, isInRangeScore, regScore, hdScore; + + //stepScore = intervalScore.value(voices[ins], candidate, 30, 400, 0.1); + stepScore = intervalScore.value(voices[ins], candidate, 100, 100); + recentlySoundedScore = inclusionScore.value(lastXChanges.flop[ins], candidate, 0.01); + //recentlySoundedScore = inclusionScore.value(lastXChanges.flatten.collect({arg item; item.drop(1)}), candidate.drop(1), 0.01); + + /* + if(rangeScore.value(candidate.collect({0}), voices[ins], ranges[ins][1] - 500, ranges[ins][1], 0, true) == 1, { + isInRangeScore = 1 - rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0] + 500, ranges[ins][1] - 500, 0, true); + isInRangeScore = isInRangeScore * rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true) + }, { + isInRangeScore = rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true); + }); + */ + + + isInRangeScore = rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true); + //old way - worked but actually by accident (I think) + //isInRangeScore = rangeScore.value(candidate, candidate.collect({0}), ranges[ins][0], ranges[ins][1], 0, true); + + + regScore = spacingScore.value(voices.deepCopy.put(ins, candidate), 300); + hdScore = pow(hdSum.value(voices.deepCopy.put(ins, candidate)), hdExp); + if(hdInvert == 0, {hdScore = 1/hdScore}); + //maybe what you want here is a vector to another root and then favoring movement towards it. + //distScore = pow(hsChordalDistance.value(voices, voices.put(ins, candidate)), 2); + + [stepScore, recentlySoundedScore, isInRangeScore, regScore, hdScore] + }); + + nWeights = passagesWeights; + + //this handles nWeights of 0; mainly for testing + nProbs = nProbs.flop.select({arg scores, s; nWeights[s] != 0}).flop; + nWeights = nWeights.select({arg weight; weight != 0}); + nProbs = nProbs.flop.collect({arg scores, s; + if(scores.sum == 0, {scores}, {scores.normalizeSum * nWeights[s]}).postln; + }); + nProbs = nProbs.flop.collect({arg scores, s; scores.product}).normalizeSum; + + nProbs.round(0.001).postln; + [candidates, nProbs.round(0.001)].flop.postln; + + sel = candidates.wchoose(nProbs); + + voices[ins] = sel; + lastXChanges = lastXChanges.add(voices).keep(-5); +}; + +genSubMotif = {arg order, orderIndex, lastState, repeatLast = false, startFromLast = false, isLastOrder = false; + var sus, prog, silent, flatOrder, res, isInChord, allowChord, pad, lastXChangesHold, voices, adder; + # sus, prog, silent = order; + flatOrder = silent ++ sus ++ prog; + lastXChangesHold = lastXChanges.deepCopy; + voices = lastState.deepCopy; + isInChord = popSize.collect({false}); + allowChord = false; + pad = false; + res = []; + "------generating motif".postln; + //need to figure out here if voices move between motifs + flatOrder.do({arg ins, i; + + if(prog.includes(ins) && repeatLast.not, {updateVoices.value(ins, sus)}); + adder = if(silent.includes(ins), {["Rest"]}, {lastXChanges.last.deepCopy[ins]}); + + if(voices[ins] != adder, { + var dur; + + if((sus ++ silent).includes(ins), { + allowChord = (ins != sus.last); + pad = (ins == sus.last); + }, { + if(i < (flatOrder.size - 1), { + allowChord = (isInChord[flatOrder[i + 1]] || (ins == flatOrder[i + 1])).not; + pad = false; + }, { + allowChord = false; + pad = true + }); + }); + if((orderIndex == 0) && sus.includes(ins), { + dur = entrancesDurFunc.value(allowChord, pad); + }, { + dur = passagesDurFunc.value(allowChord, pad); + }); + if(dur == 0, {isInChord[ins] = true}, {isInChord = popSize.collect({false})}); + + voices[ins] = adder; + res = res.add([voices.deepCopy.postln, dur]); + }); + }); + + // pad ending + /* + if(orderIndex == (orders.size - 1), { + (0..(popSize-1)).scramble.do({arg ins; + if(res.last.first[ins] != ["Rest"], { + var dur; + voices[ins] = ["Rest"]; + allowChord = (voices != popSize.collect({["Rest"]})); + pad = allowChord.not; + dur = exitsDurFunc.value(allowChord, pad); + res = res.add([voices.deepCopy.postln, dur]); + }); + }); + }); + */ + + //format and return + if(startFromLast, {lastXChanges = lastXChangesHold.deepCopy}); + res; +}; + + +//------primary routines + +genMotif = { + var repeats, fSeq, fDur, durAdd; + + repeats = 1; + fSeq = []; + + repeats.do({arg index; + var motif; + + motif = []; + + orders.do({arg order, o; + var lastState, subMotif; + //lastState = if(o == 0, {popSize.collect({["Rest"]})}, {motif.last.last.first}); + lastState = if(o == 0, {lastXChanges.last.deepCopy}, {motif.last.last.first}); + subMotif = genSubMotif.value(order, o, lastState, isLastOrder: o == (orders.size - 1)); + motif = motif.add(subMotif); + + }); + + sanityCheck.value(motif, index); + + fSeq = fSeq.add(motif); + }); + + //round last duration to measure + /* + fDur = fSeq.flatten.flatten.slice(nil, 1).sum; + durAdd = fDur.round(4) - fDur; + if(durAdd < 0, {durAdd = 4 - durAdd}); + fSeq[0][orders.size - 1][fSeq[0][orders.size - 1].size - 1][1] = fSeq[0][orders.size - 1][fSeq[0][orders.size - 1].size - 1][1] + durAdd; + */ + + fSeq +}; + +genSecondarySeq = {arg seq; + var curdles, fSeq; + curdles = []; + while({curdles.sum < seq.size}, {curdles = curdles ++ [3.rand + 1]}); + + fSeq = seq.clumps(curdles).collect({arg clump, m; + var repeats, paddedSeq; + + //add rest + paddedSeq = clump.add([[[popSize.collect({["Rest"]}), 0.5.rand]]]); + + //implement repeats + repeats = [0.rand + 1, 1].wchoose([1, 0].normalizeSum); + repeats.collect({paddedSeq}); + }); + fSeq +}; + + +//------audition funcs + +/* +Event.addEventType(\osc, { + if (~addr.postln.notNil) { + ~addr.sendMsg(~indexPath, ~indexMsg); + ~addr.sendMsg(~seqPath, stringifyToDepth.value(~seqMsg, 3)); + //~addr.sendMsg("/STATE/OPEN", (dir.replace("supercollider", "resources") +/+ ~idMsg +/+ ~idMsg ++ "_gui_state" ++ ".state").standardizePath.postln); + }; +}); +*/ + +Event.addEventType(\osc, { + if (~addr.notNil) { + ~msg; + ~addr.sendMsg(~path, *~msg); + }; +}); + +genPatterns = {arg inSeq, addr, oneShot = false; + var voices, durs, pbinds, res, indices, sectionDurs, msg, ids, seq; + seq = inSeq.collect({arg mSeq; mSeq[0]}); + //# voices, durs = seq.flatten2(seq.maxDepth - 5).flop; + # voices, durs = seq.flatten2(if(oneShot, {2}, {3})).flop; + pbinds = voices.flop.collect({arg voice, v; + var clumps, hdScores, freqs, fDurs, attacks, rels, amps; + clumps = voice.separate({arg a, b; a != b }); + freqs = clumps.collect({arg clump; if(clump[0] != ["Rest"], {(62.midicps * hsArrayToFreq.value(clump[0]))}, {Rest(0)})}); + fDurs = durs.clumps(clumps.collect({arg clump; clump.size})).collect({arg clump; clump.sum}); + //attacks = 2.collect({rrand(1, 3)}) ++ freqs.drop(2).collect({rrand(0.3, 0.5)}); + attacks = fDurs.collect({arg dur; dur * rrand(0.2, 0.4)}); + //rels = freqs.drop(2).collect({rrand(0.3, 0.5)}) ++ 2.collect({rrand(1, 3)}); + rels = (clumps.size - 1).collect({arg c; + if(clumps[c + 1][0] == ["Rest"], {rrand(1.0, 3.0)}, {rrand(0.3, 0.5)}); + }); + rels = rels.add(rrand(1.0, 3.0)); + amps = freqs.collect({rrand(0.6, 0.99)}); + + [ + Pbind( + \instrument, \string_model, + \group, group, + \freq, Pseq(freqs, 1), + \dur, Pseq(fDurs, 1), + \attack, Pseq(attacks, 1), + \sustain, Pseq(fDurs, 1), + \release, Pseq(rels, 1), + //\amp, Pseq(amps, 1), + \amp, Pbrown(0.5, 1, 0.5), + \busIndex, v + ), + Pbind( + \instrument, \sine, + \group, group, + \freq, Pseq(freqs, 1), + \dur, Pseq(fDurs, 1), + \sustain, Pseq(fDurs, 1), + \busIndex, v + ) + ] + }).flatten; + if(oneShot.not, { + msg = inSeq.collect({arg mSeq, m; mSeq[1..]}); + //ids = inSeq.collect({arg mSeq, m; mSeq[2]}); + sectionDurs = seq.collect({arg mSeq; mSeq.flatten2(2).flop[1].sum}); + pbinds = pbinds ++ + [ + Pbind( + \type, \osc, + \addr, addr, + \path, "/playing", + \msg, Pseq(msg, 1), + \dur, Pseq(sectionDurs, 1) + ); + ] + }); + res = Ppar(pbinds); + res +}; + +/* +genMidiPatterns = {arg seq; + var voices, durs, patterns, res, mOut, pbRange; + pbRange = 1; //semitones - change this as needed for your situation + mOut = MIDIOut.newByName("TiMidity", "TiMidity port 0").latency_(Server.default.latency); + # voices, durs = seq.flatten2(seq.maxDepth - 5).flop; + res = Ppar( + voices.flop.collect({arg voice, v; + var clumps, hdScores, freqs, fDurs; + + mOut.program(v, 70); + + clumps = voice.separate({arg a, b; a != b }); + freqs = clumps.collect({arg clump; if(clump[0] != ["Rest"], {(60.midicps * hsArrayToFreq.value(clump[0]))}, {Rest(0)})}); + fDurs = durs.clumps(clumps.collect({arg clump; clump.size})).collect({arg clump; clump.sum}); + + Pbind( + \type, \midi, + \chan, v, + \noteval, Pseq(freqs.cpsmidi - 24, 1), + \note, Pfunc({ | event | event[\noteval].floor }), + \dur, Pseq(fDurs, 1), + \midiout, mOut, + \amp, 1, + \bend, Pfunc({ + | event | + if (event[\note].isRest.not) { + var pitchbendvalue = event[\noteval].frac.linlin(0, pbRange, 8192, 8192*2).asInteger; + m.bend(v, pitchbendvalue); + }; + 0; // return something other than nil to avoid stopping the pattern + }), + ); + }); + ); + res +}; +*/ + + +//------resource management funcs + +genUID = {Date.seed.asHexString.toLower}; + +seedFunc = {arg func, seed; + var funcArgs, next; + next = Routine({loop{func.valueArray(funcArgs).yield }}); + next.randSeed_(seed); + {arg ...args; funcArgs = args; next.value} +}; + +stringifyToDepth = {arg data, maxDepth = 1; + var prettyString = "", rCount = 0, writeArray, indent; + + if(maxDepth == 0, { + data.asCompileString + }, { + indent = {arg size; size.collect({" "}).join("")}; + writeArray = {arg array; + prettyString = prettyString ++ indent.value(rCount) ++ "[\n"; + rCount = rCount + 1; + if(rCount < maxDepth, { + array.do({arg subArray; writeArray.value(subArray)}); + }, { + prettyString = prettyString ++ array.collect({arg subArray; + indent.value(rCount + 1) ++ subArray.asCompileString + }).join(",\n"); + }); + rCount = rCount - 1; + prettyString = prettyString ++ "\n" ++ indent.value(rCount) ++ "],\n"; + }; + + writeArray.value(data); + prettyString.replace(",\n\n", "\n").drop(-2); + }) +}; + +sanityCheck = {arg motif, index; + //print functions = very helpful + ("----------" + index + "------------").postln; + + motif.flatten.do({arg val, v; + if(v > 0, { + if(motif.flatten[v-1][0].hammingDistance(val[0]) > 1, {"problem 1".postln}); + if(motif.flatten[v-1][0].hammingDistance(val[0]) == 0, {"problem 2".postln}); + }); + val.postln + }); + "***********".postln; +}; + +msgInterpret = {arg in, escapeDoubleQuotes = true, escapeSingleQuotes = true; + var res; + + res = in; + if(res.isNil.not, { + if((res.isArray && res.isString.not), { + res = res.asCompileString; + res = res.replace(" ", "").replace("\n", "").replace("\t", ""); + if(escapeSingleQuotes, {res = res.replace("\'", "")}); + if(escapeDoubleQuotes, {res = res.replace("\"", "")}); + res = res.replace("Rest", "\"Rest\""); + res = res.interpret; + }, { + var tmpRes; + if(res.every({arg char; char.isDecDigit}), {tmpRes = res.asInteger}); + if(res.contains("."), {tmpRes = res.asFloat}); + if(tmpRes != nil, {res = tmpRes}); + }); + }); + res +}; + +writeResources = {arg path, dict; + var file, modelItems, resString; + file = File(path,"w"); + + modelItems = [ + seq, lastXChanges, + curUID, refUID, orderSeed, durSeed, motifSeed, + entrancesProbVals, passagesProbVals, exitsProbVals, + ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, + orders, susWeights, orderSize, passagesSize, + motifEdited, orderEdited + ]; + + resString = nameSpaces.collect({arg nameSpace; + var depth = 0, insert = " "; + if(nameSpace == "music_data", {depth = 3; insert = "\n"}); + if(nameSpace == "last_changes", {depth = 1; insert = "\n"}); + if(nameSpace == "order", {depth = 1; insert = "\n"}); + if((nameSpace == "ref_uid") && (dict[nameSpace] == nil), {dict[nameSpace] = "nil"}); + "\"" ++ nameSpace ++ "\":" ++ insert ++ stringifyToDepth.value(dict[nameSpace], depth) + }).join(",\n"); + + resString = "{\n" ++ resString ++ "\n}"; + + file.write(resString); + file.close; + resString +}; + +loadModelFile = {arg path; loadModelJSON.value(File(path, "r").readAllString.parseJSON)}; + +loadModelJSON = {arg jsonObject; + var dict; + dict = Dictionary.with(*nameSpaces.collect({arg nS; nS->msgInterpret.value(jsonObject[nS])})); + dict +}; + +setGlobalVars = {arg dict, skipLastXChanges = false; + var tmpLastXChanges; + tmpLastXChanges = lastXChanges.deepCopy; + // order really matters!!!! + # seq, lastXChanges, curUID, refUID, orderSeed, durSeed, motifSeed, + entrancesProbVals, passagesProbVals, exitsProbVals, + ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, + orders, susWeights, orderSize, passagesSize, + motifEdited, orderEdited = nameSpaces.collect({arg nS; dict[nS]}); + if(skipLastXChanges, {lastXChanges = tmpLastXChanges}); + dict +}; + +globalVarsToDict = { + var modelItems, dict; + // order really matters!!!! + modelItems = [ + seq, lastXChanges, + curUID, refUID, orderSeed, durSeed, motifSeed, + entrancesProbVals, passagesProbVals, exitsProbVals, + ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, + orders, susWeights, orderSize, passagesSize, + motifEdited, orderEdited + ]; + dict = Dictionary.with(*nameSpaces.collect({arg nS, n; nS->modelItems[n]})); +}; + +loadLedgerFile = {arg path; + ledgerPath = path; + resourceDir = path.splitext(".").drop(-1).join; + loadLedgerJSON.value(File(ledgerPath, "r").readAllString.parseJSON) +}; + +loadLedgerJSON = {arg jsonObject; ledger = jsonObject["ledger"]}; + +saveLedger = {arg ledger, path; + var file, curResourceDir; + file = File(path, "w"); + curResourceDir = resourceDir; + resourceDir = path.splitext(".").drop(-1).join; + if(curResourceDir != resourceDir, { + File.mkdir(resourceDir); + ledger.do({arg id; + File.copy(curResourceDir +/+ id, resourceDir +/+ id); + }); + }); + file.write("{\n\"ledger\":\n" ++ stringifyToDepth.value(ledger, 1) ++ "\n}"); + file.close; +}; + +//------global vars + +primes = [[2, 1], [3, 2], [5, 4], [7, 4], [11, 8], [13, 8]]; +//ranges = [[-2400, 0], [-1200, 1200], [0, 2400], [0, 2400]]; +exPath = thisProcess.nowExecutingPath; +dir = exPath.dirname; +//popSize = 4; +dims = primes.size; +tuples = genTuples.value(); +//refUID = nil; +group = Group.new; +~group = group; +loadLedgerFile.value(dir +/+ ".." +/+ "resources" +/+ "piece_ledger.json"); +resourceDir = (dir +/+ ".." +/+ "resources" +/+ "piece_ledger"); +//passagesWeights = [1, 1, 1, 1, 1]; +//susWeights = [1, 1, 1]; +// order really matters!!!! +nameSpaces = [ + "music_data", "last_changes", "cur_uid", "ref_uid", "order_seed", "dur_seed", "motifs_seed", + "entrances_probs_vals","passages_probs_vals", "exits_probs_vals", + "ranges", "step_probs_vals", "passages_weights", "hd_exp", "hd_invert", + "order", "sus_weights", "order_size", "passages_size", + "motif_edited", "order_edited" +]; + + +//------OSC funcs + +OSCdef(\load_ledger, {arg msg, time, addr, port; + loadLedgerFile.value(msg[1].asString); +}, \load_ledger); + +OSCdef(\load_model, {arg msg, time, addr, port; + var dict; + dict = loadModelFile.value(msg[1].asString); + setGlobalVars.value(dict); +}, \load_model); + +OSCdef(\save_ledger, {arg msg, time, addr, port; + msg.postln; + ledger = msgInterpret.value(msg[1].asString.parseJSON["ledger"], false).postln; + //loadLedgerJSON.value(msg[0]) + saveLedger.value(ledger, msg[2].asString); + //loadLedgerFile.value(msg[1].asString); +}, \save_ledger); + +OSCdef(\generate, {arg msg, time, addr, port; + var path, dict, durSeeds, musPath, modelString; + msg.postln; + + path = msg[1].asString; + + dict = loadModelFile.value(path); + setGlobalVars.value(dict, true); + + popSize = ranges.size; + + //refUID.postln; + + loadLedgerFile.value(ledgerPath); + if(ledger == nil, {ledger = ["tmp"]}); + if(ledger.last != "tmp", {ledger = ledger.add("tmp")}); + + if(refUID == nil, {lastXChanges = [initVoices.value().deepCopy]}); + if((refUID != nil) && (refUID != "tmp"), { + var file; + file = File((resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath, "r"); + lastXChanges = msgInterpret.value(file.readAllString.parseJSON["last_changes"]); + }); + + refUID.postln; + lastXChanges.collect({arg item; item.postln}); + + durSeeds = seedFunc.value({3.collect({rrand(100000, 999999)})}, durSeed).value.postln; + entrancesDurFunc = genDurFunc.valueArray(entrancesProbVals[..4] ++ [entrancesProbVals[5..]] ++ [durSeeds[0]]); + passagesDurFunc = genDurFunc.valueArray(passagesProbVals[..4] ++ [passagesProbVals[5..]] ++ [durSeeds[1]]); + exitsDurFunc = genDurFunc.valueArray(exitsProbVals[..4] ++ [exitsProbVals[5..]] ++ [durSeeds[2]]); + + if(orders == nil, { + orders = seedFunc.value(genOrders, orderSeed).valueArray(orderSize ++ passagesSize); + //addr.sendMsg("/order", stringifyToDepth.value(orders, 1)); + }); + + stepFunc = genStepFunc.valueArray(stepProbsVals[..1] ++ [stepProbsVals[2..]] ++ [motifSeed]); + seq = seedFunc.value(genMotif, motifSeed).value; + + lastXChanges.collect({arg item; item.postln}); + + dict = globalVarsToDict.value; + modelString = writeResources.value(path, dict); + + //addr.sendMsg("/generated", musPath, stringifyToDepth.value(seq, 3)); + //~seq = seq; + + addr.sendMsg("/generated", path, modelString, ledgerPath); +}, \generate); + + +OSCdef(\commit, {arg msg, time, addr, port; + var musicData, musicChanged, dict, newLedger, modelPath, musString, musFile, test1, test2, lastCurUID, commitType, commitPos, equalityLedger; + //msg.postln; + + /* + test1 = msg[1].asString.parseJSON; + test2 = (dir +/+ ".." +/+ "resources/tmp/tmp_music" ++ ".json").standardizePath.parseJSONFile; + msgInterpret.value(test1["music"])[0][0][0][1].class.postln; + msgInterpret.value(test2["music_data"])[0][0][0][1].class.postln; + (test1["music"] == test2["music_data"]).postln; + */ + + musicData = loadModelJSON.value(msg[1].asString.parseJSON)["music_data"].postln; + musicChanged = (musicData != seq).postln; + commitType = msg[2].asString; + commitPos = msg[3].postln.asInteger; + + lastCurUID = curUID.deepCopy; + curUID = genUID.value; + + File.mkdir((resourceDir +/+ curUID).standardizePath); + File.copy(exPath, (resourceDir +/+ curUID +/+ curUID ++ "_code" ++ ".scd").standardizePath); + + modelPath = (resourceDir +/+ curUID +/+ curUID ++ "_mus_model" ++ ".json").standardizePath; + dict = globalVarsToDict.value; + if(musicChanged, { + seq = musicData; + dict["music_data"] = seq; + dict["motif_edited"] = "true" + }); + dict["cur_uid"] = curUID; + + writeResources.value(modelPath, dict); + + File.delete(ledgerPath ++ "_bak"); + File.copy(ledgerPath, ledgerPath ++ "_bak"); + File.delete(ledgerPath); + + /* + if(commitType == "add", { + if(lastCurUID == "tmp", { + ledger = ledger.drop(-1).add(curUID); + }, { + ledger = ledger.add(curUID); + }) + }); + */ + + ledger.postln; + + if(commitType == "add", {ledger = ledger.add(curUID)}); + + if(commitType == "insert", {ledger = ledger.insert(commitPos + 1, curUID)}); + + if(commitType == "replace", {ledger = ledger.put(commitPos, curUID)}); + + equalityLedger = ledger.collect({arg item; item.asSymbol}); + if(equalityLedger.includes(\tmp).postln, {ledger.removeAt(equalityLedger.indexOf(\tmp).postln)}); + + ledger.postln; + + saveLedger.value(ledger, ledgerPath); + + addr.sendMsg("/committed", curUID, ledgerPath); + //refUID = curUID; + +}, \commit); + +OSCdef(\transport, {arg msg, time, addr, port; + msg.postln; + if(msg[1] == 0, { + group.set(\release, 2); + group.set(\gate, 0); + player.stop; + }, { + // the cued sequence can now be read from file, so this can be cleaned up + var cSize, patterns, pSeq, cuedSeek, indexStart, indexEnd, tmpLedger; + if(msg[1] == 1, { + pSeq = []; + cuedSeek = (seq != nil); + indexStart = msg[2].asInteger; + indexEnd = ledger.size - if(cuedSeek, {2}, {1}); + //ledger.postln; + if(((indexStart == (ledger.size - 1)) && cuedSeek).not, { + ledger[indexStart..indexEnd].do({arg uid, index; + var path, file; + path = (resourceDir +/+ uid +/+ uid ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.postln.parseJSON["music_data"]), path, indexStart + index, uid]); + file.close; + }); + }); + if(cuedSeek, { + var path, file; + path = (resourceDir +/+ "tmp/tmp_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.parseJSON["music_data"]), path, ledger.size - 1, "tmp"]); + file.close; + }); + patterns = genPatterns.value(pSeq, addr); + }, { + pSeq = [loadModelJSON.value(msg[2].asString.parseJSON)["music_data"].postln]; + patterns = genPatterns.value(pSeq, addr, true); + }); + player = Pfset(pattern: patterns, cleanupFunc: { + addr.sendMsg("/transport", 0); + addr.sendMsg("/one_shot", 0); + }); + player = player.play + }); +}, \transport); + + +OSCdef(\transcribe_motif, {arg msg, time, addr, port; + var tSeq, refChord, refUID; + + msg.postln; + + tSeq = [loadModelJSON.value(msg[1].asString.parseJSON)["music_data"]]; + refUID = msg[2].asString.postln; + + if((refUID != "nil") && (refUID != "tmp"), { + var file; + file = File((resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath, "r"); + refChord = msgInterpret.value(file.readAllString.parseJSON["last_changes"]).last; + file.close; + }, { + refChord = [[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0]]; + }); + + ~transcribe.value(tSeq, refChord, (dir +/+ ".." +/+ "lilypond" +/+ "includes").standardizePath, addr, "/transcribe_motif"); +}, \transcribe_motif); + + +OSCdef(\transcribe_all, {arg msg, time, addr, port; + var cSize, patterns, cuedSeek, indexStart, indexEnd, tmpLedger; + if(true, { + cuedSeek = (seq != nil); + indexStart = msg[1].asInteger; + indexEnd = ledger.size - if(cuedSeek, {2}, {1}); + + //tmp for testing transcription + //indexEnd = (indexStart+5); + + //ledger.postln; + if(((indexStart == (ledger.size - 1)) && cuedSeek).not, { + var lilyPartLedgerFiles; + + lilyPartLedgerFiles = 4.collect({arg p; + File((dir +/+ ".." +/+ "lilypond" +/+ "includes" +/+ "part_" ++ ["IV", "III", "II", "I"][p] ++ ".ly").standardizePath, "w"); + }); + + ledger[indexStart..indexEnd].do({arg uid, index; + var path, file, fileString, tSeq, refUID, refChord; + path = (resourceDir +/+ uid +/+ uid ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + fileString = file.readAllString; + tSeq = msgInterpret.value(fileString.parseJSON["music_data"]); + refUID = msgInterpret.value(fileString.parseJSON["ref_uid"]); + file.close; + + //uid.postln; + //(refUID == "nil").postln; + + refChord = [[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0]]; + + if(refUID != "nil", { + path = (resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + refChord = msgInterpret.value(file.readAllString.parseJSON["last_changes"]).last; + file.close; + }); + + if(index != indexEnd, { + ~transcribe.value(tSeq, refChord, (resourceDir +/+ uid +/+ "lilypond").standardizePath); + }, { + ~transcribe.value(tSeq, refChord, (resourceDir +/+ uid +/+ "lilypond").standardizePath, addr, "/transcribe_all"); + }); + + lilyPartLedgerFiles.do({arg f, p; + f.write("\\include \"" ++ resourceDir +/+ uid +/+ "lilypond" +/+ "part_" ++ ["IV", "III", "II", "I"][p] ++ ".ly\"\n"); + }); + + }); + + lilyPartLedgerFiles.do({arg f; + f.close + }); + }); + /* + if(cuedSeek, { + var path, file; + path = (dir +/+ ".." +/+ "resources/tmp/tmp_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.parseJSON["music_data"]), path, ledger.size - 1, "tmp"]); + file.close; + }); + */ + }, { + + }); + +}, \transcribe_all); + +) + + diff --git a/resources/string_quartet_3_rise/767e70f0/767e70f0_mus_model.json b/resources/string_quartet_3_rise/767e70f0/767e70f0_mus_model.json new file mode 100644 index 0000000..8421367 --- /dev/null +++ b/resources/string_quartet_3_rise/767e70f0/767e70f0_mus_model.json @@ -0,0 +1,65 @@ +{ +"music_data": +[ + [ + [ + [ [ [ -4, 3, 1, 2, -1, 2 ], [ -6, 5, 1, 2, -1, 2 ], [ -5, 4, 0, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0 ], + [ [ [ -4, 3, 1, 2, -1, 2 ], [ -6, 5, 1, 2, -1, 2 ], [ -5, 3, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0 ], + [ [ [ -5, 5, 1, 2, -1, 2 ], [ -6, 5, 1, 2, -1, 2 ], [ -5, 3, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -5, 5, 1, 2, -1, 2 ], [ -6, 5, 1, 2, -1, 2 ], [ -6, 4, 2, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -5, 5, 1, 2, -1, 2 ], [ -6, 5, 1, 2, -1, 2 ], [ -6, 4, 1, 2, 0, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0 ], + [ [ [ -5, 5, 1, 2, -1, 2 ], [ -5, 4, 0, 2, -1, 2 ], [ -6, 4, 1, 2, 0, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -5, 5, 1, 2, -1, 2 ], [ -5, 4, 0, 2, -1, 2 ], [ -6, 5, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ] + ], + [ + [ [ [ -5, 5, 1, 2, -1, 2 ], [ -6, 4, 1, 2, -1, 2 ], [ -6, 5, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0 ], + [ [ [ -5, 5, 1, 2, -1, 2 ], [ -6, 4, 1, 2, -1, 2 ], [ -5, 4, 0, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -5, 5, 1, 2, -1, 2 ], [ -7, 6, 1, 2, -1, 2 ], [ -5, 4, 0, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ] + ], + [ + [ [ [ -5, 5, 1, 2, -1, 2 ], [ -7, 6, 1, 2, -1, 2 ], [ -6, 4, 1, 3, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0 ], + [ [ [ -5, 5, 1, 2, -1, 2 ], [ -6, 4, 2, 2, -1, 2 ], [ -6, 4, 1, 3, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -4, 4, 0, 2, -1, 2 ], [ -6, 4, 2, 2, -1, 2 ], [ -6, 4, 1, 3, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ] + ], + [ + [ [ [ -6, 4, 1, 4, -1, 2 ], [ -6, 4, 2, 2, -1, 2 ], [ -6, 4, 1, 3, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0 ], + [ [ [ -6, 4, 1, 4, -1, 2 ], [ -5, 3, 1, 2, -1, 2 ], [ -6, 4, 1, 3, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -6, 4, 1, 4, -1, 2 ], [ -5, 4, 1, 2, -1, 1 ], [ -6, 4, 1, 3, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0 ], + [ [ [ -6, 4, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 1 ], [ -6, 4, 1, 3, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], 0.25 ] + ] + ] +], +"last_changes": +[ + [ [ -4, 4, 0, 2, -1, 2 ], [ -6, 4, 2, 2, -1, 2 ], [ -6, 4, 1, 3, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], + [ [ -6, 4, 1, 4, -1, 2 ], [ -6, 4, 2, 2, -1, 2 ], [ -6, 4, 1, 3, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], + [ [ -6, 4, 1, 4, -1, 2 ], [ -5, 3, 1, 2, -1, 2 ], [ -6, 4, 1, 3, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], + [ [ -6, 4, 1, 4, -1, 2 ], [ -5, 4, 1, 2, -1, 1 ], [ -6, 4, 1, 3, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ], + [ [ -6, 4, 1, 2, -1, 2 ], [ -5, 4, 1, 2, -1, 1 ], [ -6, 4, 1, 3, -1, 2 ], [ -5, 4, 1, 2, -1, 2 ] ] +], +"cur_uid": "767e70f0", +"ref_uid": "4dd2a130", +"order_seed": 837263, +"dur_seed": 452719, +"motifs_seed": 173645, +"entrances_probs_vals": [ 0.78, 0, 0, 0, 1.02, 0.0098039215686275, 0, 0.20915032679739, 0, 0.24183006535948, 0.82432432432432, 0.28758169934641, 0, 1, 0 ], +"passages_probs_vals": [ 0.78, 0, 0, 0, 1.02, 0.0098039215686275, 0, 0.20915032679739, 0, 0.24183006535948, 0.82432432432432, 0.28758169934641, 0, 1, 0 ], +"exits_probs_vals": [ 0.78, 0, 0, 0, 1.02, 0.0098039215686275, 0, 0.20915032679739, 0, 0.24183006535948, 0.82432432432432, 0.28758169934641, 0, 1, 0 ], +"ranges": [ [ -2400, 1200 ], [ -1200, 1453 ], [ -702, 1694 ], [ -702, 1973 ] ], +"step_probs_vals": [ -1200, 1200, 0, 0, 0.41358024691358, 0.0056818181818177, 0.46296296296296, 0.39204545454545, 0.52057613168724, 0, 0.56378600823045, 0.85795454545455, 0.60082304526749, 0, 1, 0 ], +"passages_weights": [ 1, 0.15, 0.43, 1, 1 ], +"hd_exp": 10, +"hd_invert": 0, +"order": +[ + [ [ 3 ], [ 1, 2, 0, 2, 2, 1, 2 ], [ ] ], + [ [ 0, 3 ], [ 1, 2, 1 ], [ ] ], + [ [ 3 ], [ 2, 1, 0 ], [ ] ], + [ [ 3, 2 ], [ 0, 1, 1, 0 ], [ ] ] +], +"sus_weights": [ 0.7, 0.48, 0.49 ], +"order_size": [ 14.132653061224, 14.132653061224 ], +"passages_size": [ 0, 5 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/767e70f0/lilypond/part_I.ly b/resources/string_quartet_3_rise/767e70f0/lilypond/part_I.ly new file mode 100644 index 0000000..bad9fe0 --- /dev/null +++ b/resources/string_quartet_3_rise/767e70f0/lilypond/part_I.ly @@ -0,0 +1,4 @@ +{ + { f'1^\markup { \pad-markup #0.2 "-38"}} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/767e70f0/lilypond/part_II.ly b/resources/string_quartet_3_rise/767e70f0/lilypond/part_II.ly new file mode 100644 index 0000000..df080a2 --- /dev/null +++ b/resources/string_quartet_3_rise/767e70f0/lilypond/part_II.ly @@ -0,0 +1,4 @@ +{ + { ais8^\markup { \pad-markup #0.2 "-40"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }}[ gis8^\markup { \pad-markup #0.2 "+48"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 5↑" }}] ais8^\markup { \pad-markup #0.2 "+13"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 11↑" }}[ c'8^\markup { \pad-markup #0.2 "-37"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }}] cis'4^\markup { \pad-markup #0.2 "-25"} d'4^\markup { \pad-markup #0.2 "+30"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 7↑" }}} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/767e70f0/lilypond/part_III.ly b/resources/string_quartet_3_rise/767e70f0/lilypond/part_III.ly new file mode 100644 index 0000000..1c9ff64 --- /dev/null +++ b/resources/string_quartet_3_rise/767e70f0/lilypond/part_III.ly @@ -0,0 +1,4 @@ +{ + { c'4^\markup { \pad-markup #0.2 "-37"} cis'4^\markup { \pad-markup #0.2 "-25"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 5↓" }} f8^\markup { \pad-markup #0.2 "-38"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 1↑" }}[ g8^\markup { \pad-markup #0.2 "-35"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↑" }}] gis4^\markup { \pad-markup #0.2 "+48"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 5↑" }}} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/767e70f0/lilypond/part_IV.ly b/resources/string_quartet_3_rise/767e70f0/lilypond/part_IV.ly new file mode 100644 index 0000000..7b06638 --- /dev/null +++ b/resources/string_quartet_3_rise/767e70f0/lilypond/part_IV.ly @@ -0,0 +1,4 @@ +{ + { c''2.^\markup { \pad-markup #0.2 "-37"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }} ~ c''8[ cis''8^\markup { \pad-markup #0.2 "-25"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 5↓" }}]} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/tmp/tmp_mus_model.json b/resources/string_quartet_3_rise/tmp/tmp_mus_model.json index 8d3e9ab..ac64281 100644 --- a/resources/string_quartet_3_rise/tmp/tmp_mus_model.json +++ b/resources/string_quartet_3_rise/tmp/tmp_mus_model.json @@ -3,223 +3,93 @@ [ [ [ - [ [ [ -4, 6, -1, 1, -3, 2 ], [ -3, 5, -1, 1, -2, 2 ], [ -4, 6, 0, 1, -3, 2 ], [ -4, 6, -1, 1, -2, 2 ] ], 0 ], - [ [ [ -4, 6, -1, 1, -3, 2 ], [ -3, 5, -1, 1, -2, 2 ], [ -4, 6, 0, 1, -3, 2 ], [ -4, 7, -1, 1, -3, 2 ] ], 0.25 ], - [ [ [ -4, 6, -1, 1, -3, 2 ], [ -3, 5, -1, 1, -2, 2 ], [ -4, 6, 0, 1, -3, 2 ], [ -3, 5, -1, 1, -3, 2 ] ], 0.25 ] + [ [ [ -5, 3, 1, 2, -2, 2 ], [ -4, 2, 1, 2, -1, 2 ], [ -3, 2, 1, 2, -1, 2 ], [ -4, 3, 1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -5, 3, 1, 2, -2, 2 ], [ -5, 3, 2, 2, -1, 2 ], [ -3, 2, 1, 2, -1, 2 ], [ -4, 3, 1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -5, 3, 1, 2, -2, 2 ], [ -5, 2, 1, 3, -1, 2 ], [ -3, 2, 1, 2, -1, 2 ], [ -4, 3, 1, 2, -1, 2 ] ], 0.25 ], + [ [ [ -5, 3, 1, 2, -2, 2 ], [ -4, 3, 1, 1, -1, 2 ], [ -3, 2, 1, 2, -1, 2 ], [ -4, 3, 1, 2, -1, 2 ] ], 0.25 ] ], [ - [ [ [ -4, 6, 0, 1, -4, 2 ], [ -3, 5, -1, 1, -2, 2 ], [ -4, 6, 0, 1, -3, 2 ], [ -3, 5, -1, 1, -3, 2 ] ], 0.25 ], - [ [ [ -4, 6, 0, 1, -4, 2 ], [ -3, 5, -1, 1, -2, 2 ], [ -4, 6, 0, 1, -3, 2 ], [ -3, 5, -2, 1, -2, 2 ] ], 0 ], - [ [ [ -4, 5, -1, 1, -2, 2 ], [ -3, 5, -1, 1, -2, 2 ], [ -4, 6, 0, 1, -3, 2 ], [ -3, 5, -2, 1, -2, 2 ] ], 0.25 ], - [ [ [ -4, 5, -1, 1, -2, 2 ], [ -3, 5, -1, 1, -2, 2 ], [ -4, 6, 0, 1, -3, 2 ], [ -4, 6, -1, 1, -2, 2 ] ], 0 ], - [ [ [ -5, 6, 0, 1, -2, 2 ], [ -3, 5, -1, 1, -2, 2 ], [ -4, 6, 0, 1, -3, 2 ], [ -4, 6, -1, 1, -2, 2 ] ], 0.25 ], - [ [ [ -4, 5, 0, 1, -3, 2 ], [ -3, 5, -1, 1, -2, 2 ], [ -4, 6, 0, 1, -3, 2 ], [ -4, 6, -1, 1, -2, 2 ] ], 0.25 ] + [ [ [ -5, 3, 1, 2, -2, 2 ], [ -4, 3, 1, 1, -1, 2 ], [ -3, 2, 1, 2, -1, 2 ], [ -3, 3, 1, 1, -1, 2 ] ], 0 ], + [ [ [ -4, 2, 1, 1, -1, 2 ], [ -4, 3, 1, 1, -1, 2 ], [ -3, 2, 1, 2, -1, 2 ], [ -3, 3, 1, 1, -1, 2 ] ], 0 ], + [ [ [ -4, 2, 1, 1, -1, 2 ], [ -4, 3, 1, 1, -1, 2 ], [ -2, 2, 1, 1, -1, 2 ], [ -3, 3, 1, 1, -1, 2 ] ], 0.25 ], + [ [ [ -4, 2, 1, 1, -1, 2 ], [ -4, 3, 1, 1, -1, 2 ], [ -3, 4, 1, 1, -1, 2 ], [ -3, 3, 1, 1, -1, 2 ] ], 0.25 ], + [ [ [ -4, 2, 1, 1, -1, 2 ], [ -4, 3, 1, 1, -1, 2 ], [ -3, 2, 1, 1, -1, 2 ], [ -3, 3, 1, 1, -1, 2 ] ], 0.25 ], + [ [ [ -4, 2, 1, 1, -1, 2 ], [ -4, 3, 1, 1, -1, 2 ], [ -4, 4, 1, 1, -1, 2 ], [ -3, 3, 1, 1, -1, 2 ] ], 0.25 ] ], [ - [ [ [ -4, 5, 0, 1, -3, 2 ], [ -3, 5, 0, 1, -3, 2 ], [ -4, 6, 0, 1, -3, 2 ], [ -4, 6, -1, 1, -2, 2 ] ], 0.25 ] + [ [ [ -4, 2, 1, 1, -1, 2 ], [ -4, 3, 1, 1, -1, 2 ], [ -4, 4, 1, 1, -1, 2 ], [ -2, 2, 0, 1, -1, 2 ] ], 0.25 ], + [ [ [ -4, 2, 1, 1, -1, 2 ], [ -4, 3, 1, 1, -1, 2 ], [ -3, 2, 2, 1, -1, 2 ], [ -2, 2, 0, 1, -1, 2 ] ], 0.25 ] ], [ - [ [ [ -4, 5, 0, 1, -3, 2 ], [ -3, 5, 0, 1, -3, 2 ], [ -4, 6, 0, 1, -3, 2 ], [ -4, 6, 1, 1, -3, 2 ] ], 0 ], - [ [ [ -4, 5, 0, 1, -3, 2 ], [ -4, 7, 0, 1, -3, 2 ], [ -4, 6, 0, 1, -3, 2 ], [ -4, 6, 1, 1, -3, 2 ] ], 0.25 ], - [ [ [ -4, 5, 0, 1, -3, 2 ], [ -4, 7, 0, 1, -3, 2 ], [ -4, 6, 0, 1, -3, 2 ], [ -3, 5, 0, 1, -3, 2 ] ], 0.25 ], - [ [ [ -4, 5, 0, 1, -3, 2 ], [ -4, 7, 0, 1, -3, 2 ], [ -4, 6, 0, 1, -3, 2 ], [ -3, 6, 0, 1, -4, 2 ] ], 0.25 ] + [ [ [ -4, 2, 1, 1, -1, 2 ], [ -4, 3, 1, 1, -1, 2 ], [ -3, 3, 0, 1, -1, 2 ], [ -2, 2, 0, 1, -1, 2 ] ], 0 ], + [ [ [ -4, 2, 1, 1, -1, 2 ], [ -3, 2, 0, 1, -1, 2 ], [ -3, 3, 0, 1, -1, 2 ], [ -2, 2, 0, 1, -1, 2 ] ], 0 ], + [ [ [ -3, 1, 0, 1, -1, 2 ], [ -3, 2, 0, 1, -1, 2 ], [ -3, 3, 0, 1, -1, 2 ], [ -2, 2, 0, 1, -1, 2 ] ], 0.25 ], + [ [ [ -4, 3, 0, 1, -1, 2 ], [ -3, 2, 0, 1, -1, 2 ], [ -3, 3, 0, 1, -1, 2 ], [ -2, 2, 0, 1, -1, 2 ] ], 0.25 ], + [ [ [ -4, 3, 0, 1, -1, 2 ], [ -2, 2, 0, 0, -1, 2 ], [ -3, 3, 0, 1, -1, 2 ], [ -2, 2, 0, 1, -1, 2 ] ], 0.25 ], + [ [ [ -4, 3, 0, 1, -1, 2 ], [ -2, 2, 0, 1, -1, 1 ], [ -3, 3, 0, 1, -1, 2 ], [ -2, 2, 0, 1, -1, 2 ] ], 0.25 ], + [ [ [ -4, 3, 0, 1, -1, 2 ], [ -2, 1, 0, 1, -1, 2 ], [ -3, 3, 0, 1, -1, 2 ], [ -2, 2, 0, 1, -1, 2 ] ], 0.25 ] ], [ - [ [ [ -4, 5, 0, 1, -3, 2 ], [ -4, 7, 0, 1, -3, 2 ], [ -4, 6, 0, 1, -3, 2 ], [ -3, 6, -1, 1, -3, 2 ] ], 0 ], - [ [ [ -4, 5, 0, 1, -3, 2 ], [ -3, 6, 0, 1, -4, 2 ], [ -4, 6, 0, 1, -3, 2 ], [ -3, 6, -1, 1, -3, 2 ] ], 0 ], - [ [ [ -4, 6, 0, 1, -4, 2 ], [ -3, 6, 0, 1, -4, 2 ], [ -4, 6, 0, 1, -3, 2 ], [ -3, 6, -1, 1, -3, 2 ] ], 0.25 ] + [ [ [ -4, 3, 0, 1, -1, 2 ], [ -3, 2, 1, 1, -1, 2 ], [ -3, 3, 0, 1, -1, 2 ], [ -2, 2, 0, 1, -1, 2 ] ], 0 ], + [ [ [ -4, 3, 0, 1, -1, 2 ], [ -3, 2, 1, 1, -1, 2 ], [ -3, 2, 0, 1, -1, 2 ], [ -2, 2, 0, 1, -1, 2 ] ], 0 ], + [ [ [ -3, 2, 0, 1, -2, 2 ], [ -3, 2, 1, 1, -1, 2 ], [ -3, 2, 0, 1, -1, 2 ], [ -2, 2, 0, 1, -1, 2 ] ], 0.25 ], + [ [ [ -3, 2, 0, 1, -2, 2 ], [ -3, 2, 1, 1, -1, 2 ], [ -4, 3, 0, 1, -1, 2 ], [ -2, 2, 0, 1, -1, 2 ] ], 0.25 ], + [ [ [ -3, 2, 0, 1, -2, 2 ], [ -3, 2, 1, 1, -1, 2 ], [ -3, 2, -1, 1, -1, 2 ], [ -2, 2, 0, 1, -1, 2 ] ], 0 ], + [ [ [ -3, 2, 0, 1, -2, 2 ], [ -2, 1, 0, 1, -1, 2 ], [ -3, 2, -1, 1, -1, 2 ], [ -2, 2, 0, 1, -1, 2 ] ], 0.25 ], + [ [ [ -3, 2, 0, 1, -2, 2 ], [ -2, 1, 0, 1, -1, 2 ], [ -4, 2, 0, 2, -1, 2 ], [ -2, 2, 0, 1, -1, 2 ] ], 0.25 ] ], [ - [ [ [ -4, 6, 0, 1, -4, 2 ], [ -3, 6, 0, 1, -4, 2 ], [ -4, 6, 0, 1, -3, 2 ], [ -4, 7, 0, 1, -3, 2 ] ], 0.25 ], - [ [ [ -4, 6, 0, 1, -4, 2 ], [ -3, 5, 0, 1, -3, 2 ], [ -4, 6, 0, 1, -3, 2 ], [ -4, 7, 0, 1, -3, 2 ] ], 0.25 ], - [ [ [ -4, 5, 0, 1, -3, 2 ], [ -3, 5, 0, 1, -3, 2 ], [ -4, 6, 0, 1, -3, 2 ], [ -4, 7, 0, 1, -3, 2 ] ], 0 ], - [ [ [ -4, 5, 0, 1, -3, 2 ], [ -4, 6, 1, 1, -3, 2 ], [ -4, 6, 0, 1, -3, 2 ], [ -4, 7, 0, 1, -3, 2 ] ], 0.25 ], - [ [ [ -5, 7, 0, 1, -3, 2 ], [ -4, 6, 1, 1, -3, 2 ], [ -4, 6, 0, 1, -3, 2 ], [ -4, 7, 0, 1, -3, 2 ] ], 0.25 ], - [ [ [ -5, 7, 0, 1, -3, 2 ], [ -4, 6, 1, 1, -3, 2 ], [ -4, 6, 0, 1, -3, 2 ], [ -3, 6, -1, 1, -3, 2 ] ], 0 ], - [ [ [ -5, 7, 0, 1, -3, 2 ], [ -3, 6, 0, 0, -3, 2 ], [ -4, 6, 0, 1, -3, 2 ], [ -3, 6, -1, 1, -3, 2 ] ], 0.25 ] + [ [ [ -3, 2, 0, 1, -2, 2 ], [ -2, 2, 0, 1, -2, 2 ], [ -4, 2, 0, 2, -1, 2 ], [ -2, 2, 0, 1, -1, 2 ] ], 0 ], + [ [ [ -3, 2, 0, 1, -2, 2 ], [ -2, 2, 0, 1, -2, 2 ], [ -2, 2, 0, 0, -2, 2 ], [ -2, 2, 0, 1, -1, 2 ] ], 0.25 ], + [ [ [ -3, 2, 0, 1, -2, 2 ], [ -2, 2, 0, 1, -3, 2 ], [ -2, 2, 0, 0, -2, 2 ], [ -2, 2, 0, 1, -1, 2 ] ], 0.25 ] ], [ - [ [ [ -5, 7, 0, 1, -3, 2 ], [ -5, 6, 0, 1, -3, 2 ], [ -4, 6, 0, 1, -3, 2 ], [ -3, 6, -1, 1, -3, 2 ] ], 0 ], - [ [ [ -4, 6, -1, 1, -3, 2 ], [ -5, 6, 0, 1, -3, 2 ], [ -4, 6, 0, 1, -3, 2 ], [ -3, 6, -1, 1, -3, 2 ] ], 0.25 ] + [ [ [ -3, 2, 0, 1, -2, 2 ], [ -2, 2, 0, 1, -3, 2 ], [ -4, 3, 0, 1, -1, 2 ], [ -2, 2, 0, 1, -1, 2 ] ], 0 ], + [ [ [ -3, 2, 0, 1, -2, 2 ], [ -3, 2, 0, 1, -1, 2 ], [ -4, 3, 0, 1, -1, 2 ], [ -2, 2, 0, 1, -1, 2 ] ], 0 ], + [ [ [ -3, 1, 0, 1, -1, 2 ], [ -3, 2, 0, 1, -1, 2 ], [ -4, 3, 0, 1, -1, 2 ], [ -2, 2, 0, 1, -1, 2 ] ], 0.25 ] ], [ - [ [ [ -4, 6, -1, 1, -3, 2 ], [ -5, 6, 0, 1, -3, 2 ], [ -4, 6, 0, 1, -3, 2 ], [ -4, 6, 0, 2, -3, 2 ] ], 0 ], - [ [ [ -5, 6, 0, 2, -3, 2 ], [ -5, 6, 0, 1, -3, 2 ], [ -4, 6, 0, 1, -3, 2 ], [ -4, 6, 0, 2, -3, 2 ] ], 0.25 ], - [ [ [ -7, 6, 0, 2, -3, 2 ], [ -5, 6, 0, 1, -3, 2 ], [ -4, 6, 0, 1, -3, 2 ], [ -4, 6, 0, 2, -3, 2 ] ], 0.25 ], - [ [ [ -6, 6, 0, 1, -3, 2 ], [ -5, 6, 0, 1, -3, 2 ], [ -4, 6, 0, 1, -3, 2 ], [ -4, 6, 0, 2, -3, 2 ] ], 0.25 ], - [ [ [ -6, 6, 0, 1, -3, 2 ], [ -5, 6, 0, 1, -3, 2 ], [ -4, 6, 0, 1, -3, 2 ], [ -3, 6, 0, 1, -3, 2 ] ], 0 ], - [ [ [ -5, 6, 0, 0, -3, 2 ], [ -5, 6, 0, 1, -3, 2 ], [ -4, 6, 0, 1, -3, 2 ], [ -3, 6, 0, 1, -3, 2 ] ], 0.25 ] - ], - [ - [ [ [ -5, 6, 0, 0, -3, 2 ], [ -4, 6, 0, 0, -3, 2 ], [ -4, 6, 0, 1, -3, 2 ], [ -3, 6, 0, 1, -3, 2 ] ], 0 ], - [ [ [ -3, 5, 0, 1, -3, 2 ], [ -4, 6, 0, 0, -3, 2 ], [ -4, 6, 0, 1, -3, 2 ], [ -3, 6, 0, 1, -3, 2 ] ], 0.25 ], - [ [ [ -3, 5, 0, 1, -3, 2 ], [ -4, 5, 0, 1, -3, 2 ], [ -4, 6, 0, 1, -3, 2 ], [ -3, 6, 0, 1, -3, 2 ] ], 0 ], - [ [ [ -4, 7, 0, 1, -3, 2 ], [ -4, 5, 0, 1, -3, 2 ], [ -4, 6, 0, 1, -3, 2 ], [ -3, 6, 0, 1, -3, 2 ] ], 0.25 ] - ], - [ - [ [ [ -4, 6, 0, 0, -3, 2 ], [ -4, 5, 0, 1, -3, 2 ], [ -4, 6, 0, 1, -3, 2 ], [ -3, 6, 0, 1, -3, 2 ] ], 0 ], - [ [ [ -4, 6, 0, 0, -3, 2 ], [ -4, 5, 0, 1, -3, 2 ], [ -4, 6, 0, 1, -3, 2 ], [ -4, 7, 0, 1, -3, 2 ] ], 0 ], - [ [ [ -4, 6, 0, 0, -3, 2 ], [ -5, 7, 0, 1, -3, 2 ], [ -4, 6, 0, 1, -3, 2 ], [ -4, 7, 0, 1, -3, 2 ] ], 0.25 ] - ], - [ - [ [ [ -4, 6, 0, 0, -3, 2 ], [ -5, 7, 0, 1, -3, 2 ], [ -4, 6, 0, 1, -3, 2 ], [ -2, 5, 0, 0, -3, 2 ] ], 0 ], - [ [ [ -4, 6, 0, 0, -3, 2 ], [ -5, 7, 0, 1, -3, 2 ], [ -5, 8, 0, 1, -3, 2 ], [ -2, 5, 0, 0, -3, 2 ] ], 0.25 ], - [ [ [ -4, 6, 0, 0, -3, 2 ], [ -5, 7, 0, 1, -3, 2 ], [ -3, 6, 0, 0, -3, 2 ], [ -2, 5, 0, 0, -3, 2 ] ], 0.25 ], - [ [ [ -4, 6, 0, 0, -3, 2 ], [ -5, 7, 0, 1, -3, 2 ], [ -4, 7, 0, 1, -4, 2 ], [ -2, 5, 0, 0, -3, 2 ] ], 0 ], - [ [ [ -4, 6, 0, 0, -3, 2 ], [ -5, 7, 0, 1, -3, 2 ], [ -4, 7, 0, 1, -4, 2 ], [ -2, 6, 0, 0, -4, 2 ] ], 0.25 ] - ], - [ - [ [ [ -4, 6, 0, 0, -3, 2 ], [ -5, 7, 0, 1, -3, 2 ], [ -4, 7, 0, 1, -4, 2 ], [ -4, 7, 0, 1, -3, 2 ] ], 0.25 ], - [ [ [ -4, 6, 0, 0, -3, 2 ], [ -5, 7, 0, 1, -3, 2 ], [ -4, 6, 0, 1, -3, 2 ], [ -4, 7, 0, 1, -3, 2 ] ], 0.25 ], - [ [ [ -4, 6, 0, 0, -3, 2 ], [ -5, 7, 0, 1, -3, 2 ], [ -5, 7, 1, 1, -3, 2 ], [ -4, 7, 0, 1, -3, 2 ] ], 0.25 ] - ], - [ - [ [ [ -5, 6, 1, 1, -3, 2 ], [ -5, 7, 0, 1, -3, 2 ], [ -5, 7, 1, 1, -3, 2 ], [ -4, 7, 0, 1, -3, 2 ] ], 0 ], - [ [ [ -5, 6, 1, 1, -3, 2 ], [ -5, 7, 0, 1, -3, 2 ], [ -5, 7, 1, 1, -3, 2 ], [ -5, 8, 1, 1, -3, 2 ] ], 0 ], - [ [ [ -5, 6, 1, 1, -3, 2 ], [ -6, 8, 1, 1, -3, 2 ], [ -5, 7, 1, 1, -3, 2 ], [ -5, 8, 1, 1, -3, 2 ] ], 0.25 ] - ], - [ - [ [ [ -5, 6, 1, 1, -3, 2 ], [ -6, 8, 1, 1, -3, 2 ], [ -6, 9, 1, 1, -3, 2 ], [ -5, 8, 1, 1, -3, 2 ] ], 0 ], - [ [ [ -5, 6, 1, 1, -3, 2 ], [ -6, 8, 1, 1, -3, 2 ], [ -6, 9, 1, 1, -3, 2 ], [ -4, 8, 1, 0, -3, 2 ] ], 0.25 ] - ], - [ - [ [ [ -5, 6, 1, 1, -3, 2 ], [ -6, 8, 1, 1, -3, 2 ], [ -5, 8, 0, 1, -3, 2 ], [ -4, 8, 1, 0, -3, 2 ] ], 0.25 ], - [ [ [ -5, 6, 1, 1, -3, 2 ], [ -6, 8, 1, 1, -3, 2 ], [ -4, 6, 1, 1, -3, 2 ], [ -4, 8, 1, 0, -3, 2 ] ], 0.25 ], - [ [ [ -5, 6, 1, 1, -3, 2 ], [ -6, 8, 1, 1, -3, 2 ], [ -5, 8, 1, 1, -3, 2 ], [ -4, 8, 1, 0, -3, 2 ] ], 0.25 ] - ], - [ - [ [ [ -7, 8, 1, 2, -3, 2 ], [ -6, 8, 1, 1, -3, 2 ], [ -5, 8, 1, 1, -3, 2 ], [ -4, 8, 1, 0, -3, 2 ] ], 0 ], - [ [ [ -7, 8, 1, 2, -3, 2 ], [ -6, 8, 1, 1, -3, 2 ], [ -5, 8, 1, 1, -3, 2 ], [ -5, 8, 2, 1, -3, 2 ] ], 0.25 ], - [ [ [ -7, 8, 1, 2, -3, 2 ], [ -6, 8, 1, 1, -3, 2 ], [ -6, 9, 1, 1, -3, 2 ], [ -5, 8, 2, 1, -3, 2 ] ], 0 ], - [ [ [ -7, 8, 1, 2, -3, 2 ], [ -6, 8, 1, 1, -3, 2 ], [ -6, 9, 1, 1, -3, 2 ], [ -4, 7, 1, 1, -3, 2 ] ], 0.25 ], - [ [ [ -7, 8, 1, 2, -3, 2 ], [ -6, 8, 1, 1, -3, 2 ], [ -6, 9, 1, 1, -3, 2 ], [ -5, 9, 1, 1, -3, 2 ] ], 0.25 ], - [ [ [ -7, 8, 1, 2, -3, 2 ], [ -6, 8, 1, 1, -3, 2 ], [ -6, 9, 1, 1, -3, 2 ], [ -4, 8, 0, 1, -3, 2 ] ], 0.25 ] - ], - [ - [ [ [ -7, 8, 1, 2, -3, 2 ], [ -5, 7, 0, 1, -3, 2 ], [ -6, 9, 1, 1, -3, 2 ], [ -4, 8, 0, 1, -3, 2 ] ], 0 ], - [ [ [ -7, 8, 1, 2, -3, 2 ], [ -5, 7, 0, 1, -3, 2 ], [ -5, 8, 0, 1, -3, 2 ], [ -4, 8, 0, 1, -3, 2 ] ], 0 ], - [ [ [ -4, 7, 0, 1, -3, 2 ], [ -5, 7, 0, 1, -3, 2 ], [ -5, 8, 0, 1, -3, 2 ], [ -4, 8, 0, 1, -3, 2 ] ], 0.25 ], - [ [ [ -4, 7, 0, 1, -3, 2 ], [ -6, 8, 0, 1, -3, 2 ], [ -5, 8, 0, 1, -3, 2 ], [ -4, 8, 0, 1, -3, 2 ] ], 0.25 ], - [ [ [ -4, 7, 0, 1, -3, 2 ], [ -5, 8, 0, 0, -3, 2 ], [ -5, 8, 0, 1, -3, 2 ], [ -4, 8, 0, 1, -3, 2 ] ], 0.25 ] - ], - [ - [ [ [ -5, 8, 1, 1, -3, 2 ], [ -5, 8, 0, 0, -3, 2 ], [ -5, 8, 0, 1, -3, 2 ], [ -4, 8, 0, 1, -3, 2 ] ], 0.25 ], - [ [ [ -5, 8, 1, 1, -3, 2 ], [ -5, 8, 0, 0, -3, 2 ], [ -5, 8, 0, 1, -3, 2 ], [ -6, 8, 1, 1, -3, 2 ] ], 0 ], - [ [ [ -4, 8, 0, 0, -3, 2 ], [ -5, 8, 0, 0, -3, 2 ], [ -5, 8, 0, 1, -3, 2 ], [ -6, 8, 1, 1, -3, 2 ] ], 0.25 ] - ], - [ - [ [ [ -4, 8, 0, 0, -3, 2 ], [ -5, 8, 0, 0, -3, 2 ], [ -6, 9, 1, 1, -3, 2 ], [ -6, 8, 1, 1, -3, 2 ] ], 0 ], - [ [ [ -4, 8, 0, 0, -3, 2 ], [ -7, 8, 1, 2, -3, 2 ], [ -6, 9, 1, 1, -3, 2 ], [ -6, 8, 1, 1, -3, 2 ] ], 0 ], - [ [ [ -6, 8, 1, 2, -3, 2 ], [ -7, 8, 1, 2, -3, 2 ], [ -6, 9, 1, 1, -3, 2 ], [ -6, 8, 1, 1, -3, 2 ] ], 0.25 ] - ], - [ - [ [ [ -6, 8, 1, 2, -3, 2 ], [ -7, 8, 1, 2, -3, 2 ], [ -6, 8, 0, 2, -3, 2 ], [ -6, 8, 1, 1, -3, 2 ] ], 0.25 ], - [ [ [ -6, 8, 1, 2, -3, 2 ], [ -7, 8, 1, 2, -3, 2 ], [ -7, 9, 1, 2, -3, 2 ], [ -6, 8, 1, 1, -3, 2 ] ], 0.25 ], - [ [ [ -6, 8, 1, 2, -3, 2 ], [ -7, 8, 1, 2, -3, 2 ], [ -7, 8, 1, 2, -3, 3 ], [ -6, 8, 1, 1, -3, 2 ] ], 0.25 ], - [ [ [ -6, 8, 1, 2, -3, 2 ], [ -7, 8, 1, 2, -3, 2 ], [ -5, 7, 1, 1, -3, 2 ], [ -6, 8, 1, 1, -3, 2 ] ], 0.25 ], - [ [ [ -6, 8, 1, 2, -3, 2 ], [ -7, 8, 1, 2, -3, 2 ], [ -6, 8, 1, 2, -4, 2 ], [ -6, 8, 1, 1, -3, 2 ] ], 0.25 ] - ], - [ - [ [ [ -6, 8, 1, 2, -3, 2 ], [ -7, 8, 1, 2, -3, 2 ], [ -6, 8, 1, 2, -4, 2 ], [ -7, 8, 2, 2, -3, 2 ] ], 0 ], - [ [ [ -6, 8, 1, 2, -3, 2 ], [ -7, 8, 2, 2, -4, 2 ], [ -6, 8, 1, 2, -4, 2 ], [ -7, 8, 2, 2, -3, 2 ] ], 0.25 ], - [ [ [ -6, 8, 1, 2, -3, 2 ], [ -7, 8, 2, 2, -4, 2 ], [ -6, 8, 1, 2, -4, 2 ], [ -6, 8, 0, 2, -4, 2 ] ], 0.25 ] - ], - [ - [ [ [ -6, 8, 1, 2, -3, 2 ], [ -7, 8, 2, 2, -4, 2 ], [ -7, 8, 2, 2, -4, 3 ], [ -6, 8, 0, 2, -4, 2 ] ], 0.25 ], - [ [ [ -6, 8, 1, 2, -3, 2 ], [ -7, 8, 2, 2, -4, 2 ], [ -5, 8, 0, 2, -4, 1 ], [ -6, 8, 0, 2, -4, 2 ] ], 0.25 ] - ], - [ - [ [ [ -6, 8, 1, 2, -3, 2 ], [ -7, 8, 2, 2, -4, 2 ], [ -5, 7, 0, 2, -4, 2 ], [ -6, 8, 0, 2, -4, 2 ] ], 0 ], - [ [ [ -6, 8, 2, 2, -4, 2 ], [ -7, 8, 2, 2, -4, 2 ], [ -5, 7, 0, 2, -4, 2 ], [ -6, 8, 0, 2, -4, 2 ] ], 0.25 ], - [ [ [ -6, 8, 2, 2, -4, 2 ], [ -7, 8, 2, 2, -4, 2 ], [ -6, 8, 1, 2, -4, 2 ], [ -6, 8, 0, 2, -4, 2 ] ], 0.25 ] - ], - [ - [ [ [ -6, 8, 2, 2, -4, 2 ], [ -7, 8, 2, 2, -4, 2 ], [ -6, 8, 1, 2, -4, 2 ], [ -6, 7, 2, 2, -4, 2 ] ], 0 ], - [ [ [ -6, 8, 2, 2, -4, 2 ], [ -7, 8, 2, 2, -4, 2 ], [ -7, 9, 2, 2, -4, 2 ], [ -6, 7, 2, 2, -4, 2 ] ], 0 ], - [ [ [ -6, 8, 2, 2, -4, 2 ], [ -6, 9, 2, 2, -4, 2 ], [ -7, 9, 2, 2, -4, 2 ], [ -6, 7, 2, 2, -4, 2 ] ], 0.25 ], - [ [ [ -6, 8, 2, 2, -4, 2 ], [ -6, 9, 2, 2, -4, 2 ], [ -7, 9, 2, 2, -4, 2 ], [ -7, 8, 2, 2, -4, 2 ] ], 0 ], - [ [ [ -6, 8, 2, 2, -4, 2 ], [ -5, 8, 1, 2, -4, 2 ], [ -7, 9, 2, 2, -4, 2 ], [ -7, 8, 2, 2, -4, 2 ] ], 0.25 ], - [ [ [ -6, 8, 2, 2, -4, 2 ], [ -6, 9, 2, 2, -4, 2 ], [ -7, 9, 2, 2, -4, 2 ], [ -7, 8, 2, 2, -4, 2 ] ], 0.25 ], - [ [ [ -6, 8, 2, 2, -4, 2 ], [ -6, 8, 2, 2, -4, 3 ], [ -7, 9, 2, 2, -4, 2 ], [ -7, 8, 2, 2, -4, 2 ] ], 0.25 ] - ], - [ - [ [ [ -6, 8, 2, 2, -4, 2 ], [ -6, 8, 2, 2, -4, 3 ], [ -7, 9, 2, 2, -4, 2 ], [ -7, 7, 2, 2, -4, 3 ] ], 0 ], - [ [ [ -6, 7, 2, 2, -4, 3 ], [ -6, 8, 2, 2, -4, 3 ], [ -7, 9, 2, 2, -4, 2 ], [ -7, 7, 2, 2, -4, 3 ] ], 0.25 ], - [ [ [ -6, 7, 2, 2, -4, 3 ], [ -6, 8, 2, 2, -4, 3 ], [ -7, 9, 2, 2, -4, 2 ], [ -8, 9, 2, 2, -4, 3 ] ], 0.25 ] - ], - [ - [ [ [ -7, 9, 2, 2, -4, 3 ], [ -6, 8, 2, 2, -4, 3 ], [ -7, 9, 2, 2, -4, 2 ], [ -8, 9, 2, 2, -4, 3 ] ], 0 ], - [ [ [ -7, 9, 2, 2, -4, 3 ], [ -6, 8, 2, 2, -4, 3 ], [ -7, 8, 2, 2, -4, 3 ], [ -8, 9, 2, 2, -4, 3 ] ], 0.25 ], - [ [ [ -7, 9, 2, 2, -4, 3 ], [ -6, 8, 2, 2, -4, 3 ], [ -8, 9, 3, 2, -4, 3 ], [ -8, 9, 2, 2, -4, 3 ] ], 0.25 ] - ], - [ - [ [ [ -7, 9, 2, 2, -4, 3 ], [ -6, 8, 2, 2, -4, 3 ], [ -7, 8, 2, 2, -4, 3 ], [ -8, 9, 2, 2, -4, 3 ] ], 0 ], - [ [ [ -7, 9, 2, 2, -4, 3 ], [ -7, 9, 3, 2, -4, 3 ], [ -7, 8, 2, 2, -4, 3 ], [ -8, 9, 2, 2, -4, 3 ] ], 0.25 ], - [ [ [ -7, 9, 2, 2, -4, 3 ], [ -6, 8, 2, 2, -4, 3 ], [ -7, 8, 2, 2, -4, 3 ], [ -8, 9, 2, 2, -4, 3 ] ], 0 ], - [ [ [ -7, 9, 2, 2, -4, 3 ], [ -6, 8, 2, 2, -4, 3 ], [ -8, 10, 2, 2, -4, 3 ], [ -8, 9, 2, 2, -4, 3 ] ], 0.25 ], - [ [ [ -7, 9, 2, 2, -4, 3 ], [ -6, 9, 2, 2, -4, 2 ], [ -8, 10, 2, 2, -4, 3 ], [ -8, 9, 2, 2, -4, 3 ] ], 0.25 ] - ], - [ - [ [ [ -7, 9, 2, 2, -4, 3 ], [ -8, 11, 2, 2, -4, 3 ], [ -8, 10, 2, 2, -4, 3 ], [ -8, 9, 2, 2, -4, 3 ] ], 0.25 ], - [ [ [ -8, 10, 3, 2, -4, 3 ], [ -8, 11, 2, 2, -4, 3 ], [ -8, 10, 2, 2, -4, 3 ], [ -8, 9, 2, 2, -4, 3 ] ], 0 ], - [ [ [ -8, 10, 3, 2, -4, 3 ], [ -8, 8, 2, 2, -4, 3 ], [ -8, 10, 2, 2, -4, 3 ], [ -8, 9, 2, 2, -4, 3 ] ], 0.25 ] + [ [ [ -4, 2, 0, 1, -1, 2 ], [ -3, 2, 0, 1, -1, 2 ], [ -4, 3, 0, 1, -1, 2 ], [ -2, 2, 0, 1, -1, 2 ] ], 0 ], + [ [ [ -4, 2, 0, 1, -1, 2 ], [ -4, 4, 0, 1, -1, 2 ], [ -4, 3, 0, 1, -1, 2 ], [ -2, 2, 0, 1, -1, 2 ] ], 0.25 ], + [ [ [ -4, 2, 0, 1, -1, 2 ], [ -3, 3, -1, 1, -1, 2 ], [ -4, 3, 0, 1, -1, 2 ], [ -2, 2, 0, 1, -1, 2 ] ], 0 ], + [ [ [ -5, 4, 0, 1, -1, 2 ], [ -3, 3, -1, 1, -1, 2 ], [ -4, 3, 0, 1, -1, 2 ], [ -2, 2, 0, 1, -1, 2 ] ], 0.25 ] ] ] ], "last_changes": [ - [ [ -7, 9, 2, 2, -4, 3 ], [ -6, 8, 2, 2, -4, 3 ], [ -8, 10, 2, 2, -4, 3 ], [ -8, 9, 2, 2, -4, 3 ] ], - [ [ -7, 9, 2, 2, -4, 3 ], [ -6, 9, 2, 2, -4, 2 ], [ -8, 10, 2, 2, -4, 3 ], [ -8, 9, 2, 2, -4, 3 ] ], - [ [ -7, 9, 2, 2, -4, 3 ], [ -8, 11, 2, 2, -4, 3 ], [ -8, 10, 2, 2, -4, 3 ], [ -8, 9, 2, 2, -4, 3 ] ], - [ [ -8, 10, 3, 2, -4, 3 ], [ -8, 11, 2, 2, -4, 3 ], [ -8, 10, 2, 2, -4, 3 ], [ -8, 9, 2, 2, -4, 3 ] ], - [ [ -8, 10, 3, 2, -4, 3 ], [ -8, 8, 2, 2, -4, 3 ], [ -8, 10, 2, 2, -4, 3 ], [ -8, 9, 2, 2, -4, 3 ] ] + [ [ -3, 1, 0, 1, -1, 2 ], [ -3, 2, 0, 1, -1, 2 ], [ -4, 3, 0, 1, -1, 2 ], [ -2, 2, 0, 1, -1, 2 ] ], + [ [ -4, 2, 0, 1, -1, 2 ], [ -3, 2, 0, 1, -1, 2 ], [ -4, 3, 0, 1, -1, 2 ], [ -2, 2, 0, 1, -1, 2 ] ], + [ [ -4, 2, 0, 1, -1, 2 ], [ -4, 4, 0, 1, -1, 2 ], [ -4, 3, 0, 1, -1, 2 ], [ -2, 2, 0, 1, -1, 2 ] ], + [ [ -4, 2, 0, 1, -1, 2 ], [ -3, 3, -1, 1, -1, 2 ], [ -4, 3, 0, 1, -1, 2 ], [ -2, 2, 0, 1, -1, 2 ] ], + [ [ -5, 4, 0, 1, -1, 2 ], [ -3, 3, -1, 1, -1, 2 ], [ -4, 3, 0, 1, -1, 2 ], [ -2, 2, 0, 1, -1, 2 ] ] ], "cur_uid": "tmp", -"ref_uid": "624f7439", -"order_seed": 297821, -"dur_seed": 692519, -"motifs_seed": 730459, +"ref_uid": "536cac90", +"order_seed": 837263, +"dur_seed": 899115, +"motifs_seed": 454954, "entrances_probs_vals": [ 0.78, 0, 0, 0, 1.02, 0.0098039215686275, 0, 0.20915032679739, 0, 0.24183006535948, 0.82432432432432, 0.28758169934641, 0, 1, 0 ], "passages_probs_vals": [ 0.78, 0, 0, 0, 1.02, 0.0098039215686275, 0, 0.20915032679739, 0, 0.24183006535948, 0.82432432432432, 0.28758169934641, 0, 1, 0 ], "exits_probs_vals": [ 0.78, 0, 0, 0, 1.02, 0.0098039215686275, 0, 0.20915032679739, 0, 0.24183006535948, 0.82432432432432, 0.28758169934641, 0, 1, 0 ], "ranges": [ [ -2400, 1200 ], [ -1200, 1453 ], [ -702, 1694 ], [ -702, 1973 ] ], "step_probs_vals": [ -1200, 1200, 0, 0, 0.41358024691358, 0.0056818181818177, 0.46913580246914, 0.67045454545455, 0.52057613168724, 0, 0.56378600823045, 0.85795454545455, 0.60082304526749, 0, 1, 0 ], -"passages_weights": [ 0.38, 0.15, 0.43, 1, 1 ], +"passages_weights": [ 1, 0.15, 0.43, 1, 1 ], "hd_exp": 10, "hd_invert": 0, "order": [ - [ [ 0, 1 ], [ 2, 3, 3 ], [ ] ], - [ [ 1, 2 ], [ 0, 3, 0, 3, 0, 0 ], [ ] ], - [ [ 3, 0, 2 ], [ 1 ], [ ] ], - [ [ 0, 2 ], [ 3, 1, 3, 3 ], [ ] ], - [ [ 2 ], [ 3, 1, 0 ], [ ] ], - [ [ 2 ], [ 3, 1, 0, 1, 0, 3, 1 ], [ ] ], - [ [ 3, 2 ], [ 1, 0 ], [ ] ], - [ [ 2, 1 ], [ 3, 0, 0, 0, 3, 0 ], [ ] ], - [ [ 3, 2 ], [ 1, 0, 1, 0 ], [ ] ], - [ [ 2 ], [ 0, 3, 1 ], [ ] ], - [ [ 0, 1 ], [ 3, 2, 2, 2, 3 ], [ ] ], - [ [ 1, 0 ], [ 3, 2, 2 ], [ ] ], - [ [ 2 ], [ 0, 3, 1 ], [ ] ], - [ [ 1, 0 ], [ 2, 3 ], [ ] ], - [ [ 1, 0, 3 ], [ 2, 2, 2 ], [ ] ], - [ [ 1 ], [ 0, 3, 2, 3, 3, 3 ], [ ] ], - [ [ 3 ], [ 1, 2, 0, 1, 1 ], [ ] ], - [ [ 2, 1 ], [ 0, 3, 0 ], [ ] ], + [ [ 0, 2, 3 ], [ 1, 1, 1, 1 ], [ ] ], + [ [ 1 ], [ 3, 0, 2, 2, 2, 2 ], [ ] ], + [ [ 1, 0 ], [ 3, 2 ], [ ] ], + [ [ 3 ], [ 2, 1, 0, 0, 1, 1, 1 ], [ ] ], + [ [ 3 ], [ 1, 2, 0, 2, 2, 1, 2 ], [ ] ], + [ [ 0, 3 ], [ 1, 2, 1 ], [ ] ], [ [ 3 ], [ 2, 1, 0 ], [ ] ], - [ [ 0, 3, 1 ], [ 2, 2, 2, 2, 2 ], [ ] ], - [ [ 2, 0 ], [ 3, 1, 3 ], [ ] ], - [ [ 0, 3, 1 ], [ 2, 2 ], [ ] ], - [ [ 3, 1 ], [ 2, 0, 2 ], [ ] ], - [ [ 0 ], [ 3, 2, 1, 3, 1, 1, 1 ], [ ] ], - [ [ 2, 1 ], [ 3, 0, 3 ], [ ] ], - [ [ 3, 1 ], [ 0, 2, 2 ], [ ] ], - [ [ 3, 0 ], [ 2, 1, 1, 2, 1 ], [ ] ], - [ [ 2, 3 ], [ 1, 0, 1 ], [ ] ] + [ [ 3, 2 ], [ 0, 1, 1, 0 ], [ ] ] ], -"sus_weights": [ 0.37, 0.61, 0.22535211267606 ], -"order_size": [ 28.275510204082, 28.275510204082 ], +"sus_weights": [ 0.7, 0.48, 0.49 ], +"order_size": [ 14.132653061224, 14.132653061224 ], "passages_size": [ 0, 5 ], "motif_edited": "false", "order_edited": "false" diff --git a/supercollider/material_tweak.scd b/supercollider/material_tweak.scd index b4cf4ed..6493dec 100644 --- a/supercollider/material_tweak.scd +++ b/supercollider/material_tweak.scd @@ -57,35 +57,12 @@ swap = {arg data, swaplist; c = [ [ [ - [ [ [ -2, 3, -2, -1, 1, 0 ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 1 ], - [ [ [ -2, 3, -2, -1, 1, 0 ], [ "Rest" ], [ "Rest" ], [ -2, 4, -2, -1, 1, 0 ] ], 0 ], - [ [ [ -2, 3, -2, -1, 1, 0 ], [ "Rest" ], [ -1, 3, -2, -1, 2, 0 ], [ -2, 4, -2, -1, 1, 0 ] ], 0 ], - [ [ [ -2, 3, -2, -1, 1, 0 ], [ -2, 3, -1, -1, 1, 0 ], [ -1, 3, -2, -1, 2, 0 ], [ -2, 4, -2, -1, 1, 0 ] ], 7 ] - ], - [ - [ [ [ -2, 3, -2, -1, 1, 0 ], [ -2, 3, -1, -1, 1, 0 ], [ -1, 3, -1, -1, 1, 0 ], [ -2, 4, -2, -1, 1, 0 ] ], 0 ], - [ [ [ -2, 3, -2, -1, 1, 0 ], [ -1, 3, -2, -1, 1, -1 ], [ -1, 3, -1, -1, 1, 0 ], [ -2, 4, -2, -1, 1, 0 ] ], 0 ], - [ [ [ -2, 3, -2, -1, 1, 0 ], [ -1, 3, -2, -1, 1, -1 ], [ -1, 3, -1, -1, 1, 0 ], [ -1, 3, -2, -1, 0, 0 ] ], 5.5 ] - ], - [ - [ [ [ -2, 3, -2, -1, 1, 0 ], [ -1, 3, -2, -1, 1, -1 ], [ 0, 2, -2, -1, 1, 0 ], [ -1, 3, -2, -1, 0, 0 ] ], 0 ], - [ [ [ -2, 3, -2, -1, 1, 0 ], [ -1, 3, -2, -1, 1, -1 ], [ 0, 2, -2, -1, 1, 0 ], [ -1, 2, -2, -1, 1, 0 ] ], 0 ], - [ [ [ -2, 3, -2, -1, 1, 0 ], [ -1, 3, -3, -1, 1, 0 ], [ 0, 2, -2, -1, 1, 0 ], [ -1, 2, -2, -1, 1, 0 ] ], 4 ] - ], - [ - [ [ [ -2, 3, -2, -1, 1, 0 ], [ -1, 3, -3, -1, 1, 0 ], [ 0, 3, -2, -2, 1, 0 ], [ -1, 2, -2, -1, 1, 0 ] ], 0 ], - [ [ [ -2, 3, -2, -1, 1, 0 ], [ -1, 3, -3, -1, 1, 0 ], [ 0, 3, -2, -2, 1, 0 ], [ -2, 3, -1, -1, 1, 0 ] ], 0 ], - [ [ [ -2, 3, -2, -1, 1, 0 ], [ -2, 3, -2, -1, 1, 1 ], [ 0, 3, -2, -2, 1, 0 ], [ -2, 3, -1, -1, 1, 0 ] ], 5.625 ] - ], - [ - [ [ [ -2, 3, -2, -1, 1, 0 ], [ -2, 3, -2, -1, 1, 1 ], [ -1, 3, -2, -1, 1, 0 ], [ -2, 3, -1, -1, 1, 0 ] ], 0 ], - [ [ [ -2, 3, -2, -1, 1, 0 ], [ -2, 3, -2, -1, 1, 1 ], [ -1, 3, -2, -1, 1, 0 ], [ -1, 3, -2, -1, 1, -1 ] ], 0 ], - [ [ [ -2, 3, -2, -1, 1, 0 ], [ -2, 3, -2, 0, 1, 0 ], [ -1, 3, -2, -1, 1, 0 ], [ -1, 3, -2, -1, 1, -1 ] ], 6.5 ], - [ [ [ -2, 3, -2, -1, 1, 0 ], [ -2, 3, -2, 0, 1, 0 ], [ "Rest" ], [ -1, 3, -2, -1, 1, -1 ] ], 0 ], - [ [ [ "Rest" ], [ -2, 3, -2, 0, 1, 0 ], [ "Rest" ], [ -1, 3, -2, -1, 1, -1 ] ], 0 ], - [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ -1, 3, -2, -1, 1, -1 ] ], 0 ], - [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 6.375 ] - ] + [ [ -3, 5, -2, 1, -2, 2 ], [ -3, 4, -1, 1, -2, 2 ], [ -4, 5, -1, 1, -2, 2 ], [ -5, 5, -1, 2, -2, 2 ] ], + [ [ -3, 5, -2, 1, -2, 2 ], [ -3, 4, -1, 1, -2, 2 ], [ -4, 5, -1, 1, -2, 2 ], [ -4, 5, -2, 1, -2, 2 ] ], + [ [ -3, 5, -2, 1, -2, 2 ], [ -3, 4, -1, 1, -2, 2 ], [ -4, 5, -1, 1, -2, 2 ], [ -5, 6, -1, 1, -2, 2 ] ], + [ [ -3, 5, -2, 1, -2, 2 ], [ -4, 6, -1, 1, -2, 2 ], [ -4, 5, -1, 1, -2, 2 ], [ -5, 6, -1, 1, -2, 2 ] ], + [ [ -6, 6, -1, 1, -2, 2 ], [ -4, 6, -1, 1, -2, 2 ], [ -4, 5, -1, 1, -2, 2 ], [ -5, 6, -1, 1, -2, 2 ] ] +] ] ];