From 12488699e9f0f38a5f3d139f28ade3d4b8e5dc15 Mon Sep 17 00:00:00 2001 From: mwinter Date: Tue, 4 Jul 2023 21:14:34 +0200 Subject: [PATCH] bug squashing and composing --- lilypond/includes/part_I.ly | 12 + lilypond/includes/part_II.ly | 12 + lilypond/includes/part_III.ly | 12 + lilypond/includes/part_IV.ly | 12 + lilypond/score_template.pdf | Bin 782343 -> 1343702 bytes open_stage_control/seeds_and_ledgers_gui.json | 4 +- .../piece_ledger_sq1_candidates_stitch.json | 13 +- ...iece_ledger_sq1_candidates_stitch.json_bak | 14 +- .../4200a90d/4200a90d_code.scd | 945 ++++++++++++++++++ .../4200a90d/4200a90d_mus_model.json | 59 ++ .../4200a90d/lilypond/part_I.ly | 24 + .../4200a90d/lilypond/part_II.ly | 24 + .../4200a90d/lilypond/part_III.ly | 24 + .../4200a90d/lilypond/part_IV.ly | 24 + .../43b009ff/43b009ff_code.scd | 945 ++++++++++++++++++ .../43b009ff/43b009ff_mus_model.json | 48 + .../43b009ff/lilypond/part_I.ly | 26 + .../43b009ff/lilypond/part_II.ly | 26 + .../43b009ff/lilypond/part_III.ly | 26 + .../43b009ff/lilypond/part_IV.ly | 26 + .../443ec222/443ec222_code.scd | 945 ++++++++++++++++++ .../443ec222/443ec222_mus_model.json | 54 + .../443ec222/lilypond/part_I.ly | 20 + .../443ec222/lilypond/part_II.ly | 20 + .../443ec222/lilypond/part_III.ly | 20 + .../443ec222/lilypond/part_IV.ly | 20 + .../4526b76b/4526b76b_code.scd | 943 +++++++++++++++++ .../4526b76b/4526b76b_mus_model.json | 44 + .../4b7745df/4b7745df_code.scd | 945 ++++++++++++++++++ .../4b7745df/4b7745df_mus_model.json | 48 + .../4b7745df/lilypond/part_I.ly | 24 + .../4b7745df/lilypond/part_II.ly | 24 + .../4b7745df/lilypond/part_III.ly | 24 + .../4b7745df/lilypond/part_IV.ly | 24 + .../4e7d35e5/4e7d35e5_code.scd | 945 ++++++++++++++++++ .../4e7d35e5/4e7d35e5_mus_model.json | 52 + .../4e7d35e5/lilypond/part_I.ly | 16 + .../4e7d35e5/lilypond/part_II.ly | 16 + .../4e7d35e5/lilypond/part_III.ly | 16 + .../4e7d35e5/lilypond/part_IV.ly | 16 + .../52c9a980/52c9a980_code.scd | 945 ++++++++++++++++++ .../52c9a980/52c9a980_mus_model.json | 55 + .../52c9a980/lilypond/part_I.ly | 20 + .../52c9a980/lilypond/part_II.ly | 20 + .../52c9a980/lilypond/part_III.ly | 20 + .../52c9a980/lilypond/part_IV.ly | 20 + .../62820081/62820081_code.scd | 945 ++++++++++++++++++ .../62820081/62820081_mus_model.json | 48 + .../631e2af1/631e2af1_code.scd | 943 +++++++++++++++++ .../631e2af1/631e2af1_mus_model.json | 48 + .../6d635e88/6d635e88_code.scd | 945 ++++++++++++++++++ .../6d635e88/6d635e88_mus_model.json | 95 ++ .../6d635e88/lilypond/part_I.ly | 72 ++ .../6d635e88/lilypond/part_II.ly | 72 ++ .../6d635e88/lilypond/part_III.ly | 72 ++ .../6d635e88/lilypond/part_IV.ly | 72 ++ .../6ed95c4c/6ed95c4c_code.scd | 945 ++++++++++++++++++ .../6ed95c4c/6ed95c4c_mus_model.json | 67 ++ .../6ed95c4c/lilypond/part_I.ly | 52 + .../6ed95c4c/lilypond/part_II.ly | 52 + .../6ed95c4c/lilypond/part_III.ly | 52 + .../6ed95c4c/lilypond/part_IV.ly | 52 + .../784130cc/784130cc_code.scd | 945 ++++++++++++++++++ .../784130cc/784130cc_mus_model.json | 53 + .../784130cc/lilypond/part_I.ly | 16 + .../784130cc/lilypond/part_II.ly | 16 + .../784130cc/lilypond/part_III.ly | 16 + .../784130cc/lilypond/part_IV.ly | 16 + .../79e0a4a7/79e0a4a7_code.scd | 943 +++++++++++++++++ .../79e0a4a7/79e0a4a7_mus_model.json | 47 + .../79e0a4a7/lilypond/part_I.ly | 24 + .../79e0a4a7/lilypond/part_II.ly | 24 + .../79e0a4a7/lilypond/part_III.ly | 24 + .../79e0a4a7/lilypond/part_IV.ly | 24 + .../7d3c9a80/7d3c9a80_code.scd | 945 ++++++++++++++++++ .../7d3c9a80/7d3c9a80_mus_model.json | 48 + .../7d3c9a80/lilypond/part_I.ly | 24 + .../7d3c9a80/lilypond/part_II.ly | 24 + .../7d3c9a80/lilypond/part_III.ly | 24 + .../7d3c9a80/lilypond/part_IV.ly | 24 + .../7edbdceb/7edbdceb_code.scd | 945 ++++++++++++++++++ .../7edbdceb/7edbdceb_mus_model.json | 53 + .../7edbdceb/lilypond/part_I.ly | 16 + .../7edbdceb/lilypond/part_II.ly | 16 + .../7edbdceb/lilypond/part_III.ly | 16 + .../7edbdceb/lilypond/part_IV.ly | 16 + .../tmp/tmp_mus_model.json | 118 +-- supercollider/seeds_and_ledgers_backend.scd | 10 +- 88 files changed, 16442 insertions(+), 89 deletions(-) create mode 100644 resources/piece_ledger_sq1_candidates_stitch/4200a90d/4200a90d_code.scd create mode 100644 resources/piece_ledger_sq1_candidates_stitch/4200a90d/4200a90d_mus_model.json create mode 100644 resources/piece_ledger_sq1_candidates_stitch/4200a90d/lilypond/part_I.ly create mode 100644 resources/piece_ledger_sq1_candidates_stitch/4200a90d/lilypond/part_II.ly create mode 100644 resources/piece_ledger_sq1_candidates_stitch/4200a90d/lilypond/part_III.ly create mode 100644 resources/piece_ledger_sq1_candidates_stitch/4200a90d/lilypond/part_IV.ly create mode 100644 resources/piece_ledger_sq1_candidates_stitch/43b009ff/43b009ff_code.scd create mode 100644 resources/piece_ledger_sq1_candidates_stitch/43b009ff/43b009ff_mus_model.json create mode 100644 resources/piece_ledger_sq1_candidates_stitch/43b009ff/lilypond/part_I.ly create mode 100644 resources/piece_ledger_sq1_candidates_stitch/43b009ff/lilypond/part_II.ly create mode 100644 resources/piece_ledger_sq1_candidates_stitch/43b009ff/lilypond/part_III.ly create mode 100644 resources/piece_ledger_sq1_candidates_stitch/43b009ff/lilypond/part_IV.ly create mode 100644 resources/piece_ledger_sq1_candidates_stitch/443ec222/443ec222_code.scd create mode 100644 resources/piece_ledger_sq1_candidates_stitch/443ec222/443ec222_mus_model.json create mode 100644 resources/piece_ledger_sq1_candidates_stitch/443ec222/lilypond/part_I.ly create mode 100644 resources/piece_ledger_sq1_candidates_stitch/443ec222/lilypond/part_II.ly create mode 100644 resources/piece_ledger_sq1_candidates_stitch/443ec222/lilypond/part_III.ly create mode 100644 resources/piece_ledger_sq1_candidates_stitch/443ec222/lilypond/part_IV.ly create mode 100644 resources/piece_ledger_sq1_candidates_stitch/4526b76b/4526b76b_code.scd create mode 100644 resources/piece_ledger_sq1_candidates_stitch/4526b76b/4526b76b_mus_model.json create mode 100644 resources/piece_ledger_sq1_candidates_stitch/4b7745df/4b7745df_code.scd create mode 100644 resources/piece_ledger_sq1_candidates_stitch/4b7745df/4b7745df_mus_model.json create mode 100644 resources/piece_ledger_sq1_candidates_stitch/4b7745df/lilypond/part_I.ly create mode 100644 resources/piece_ledger_sq1_candidates_stitch/4b7745df/lilypond/part_II.ly create mode 100644 resources/piece_ledger_sq1_candidates_stitch/4b7745df/lilypond/part_III.ly create mode 100644 resources/piece_ledger_sq1_candidates_stitch/4b7745df/lilypond/part_IV.ly create mode 100644 resources/piece_ledger_sq1_candidates_stitch/4e7d35e5/4e7d35e5_code.scd create mode 100644 resources/piece_ledger_sq1_candidates_stitch/4e7d35e5/4e7d35e5_mus_model.json create mode 100644 resources/piece_ledger_sq1_candidates_stitch/4e7d35e5/lilypond/part_I.ly create mode 100644 resources/piece_ledger_sq1_candidates_stitch/4e7d35e5/lilypond/part_II.ly create mode 100644 resources/piece_ledger_sq1_candidates_stitch/4e7d35e5/lilypond/part_III.ly create mode 100644 resources/piece_ledger_sq1_candidates_stitch/4e7d35e5/lilypond/part_IV.ly create mode 100644 resources/piece_ledger_sq1_candidates_stitch/52c9a980/52c9a980_code.scd create mode 100644 resources/piece_ledger_sq1_candidates_stitch/52c9a980/52c9a980_mus_model.json create mode 100644 resources/piece_ledger_sq1_candidates_stitch/52c9a980/lilypond/part_I.ly create mode 100644 resources/piece_ledger_sq1_candidates_stitch/52c9a980/lilypond/part_II.ly create mode 100644 resources/piece_ledger_sq1_candidates_stitch/52c9a980/lilypond/part_III.ly create mode 100644 resources/piece_ledger_sq1_candidates_stitch/52c9a980/lilypond/part_IV.ly create mode 100644 resources/piece_ledger_sq1_candidates_stitch/62820081/62820081_code.scd create mode 100644 resources/piece_ledger_sq1_candidates_stitch/62820081/62820081_mus_model.json create mode 100644 resources/piece_ledger_sq1_candidates_stitch/631e2af1/631e2af1_code.scd create mode 100644 resources/piece_ledger_sq1_candidates_stitch/631e2af1/631e2af1_mus_model.json create mode 100644 resources/piece_ledger_sq1_candidates_stitch/6d635e88/6d635e88_code.scd create mode 100644 resources/piece_ledger_sq1_candidates_stitch/6d635e88/6d635e88_mus_model.json create mode 100644 resources/piece_ledger_sq1_candidates_stitch/6d635e88/lilypond/part_I.ly create mode 100644 resources/piece_ledger_sq1_candidates_stitch/6d635e88/lilypond/part_II.ly create mode 100644 resources/piece_ledger_sq1_candidates_stitch/6d635e88/lilypond/part_III.ly create mode 100644 resources/piece_ledger_sq1_candidates_stitch/6d635e88/lilypond/part_IV.ly create mode 100644 resources/piece_ledger_sq1_candidates_stitch/6ed95c4c/6ed95c4c_code.scd create mode 100644 resources/piece_ledger_sq1_candidates_stitch/6ed95c4c/6ed95c4c_mus_model.json create mode 100644 resources/piece_ledger_sq1_candidates_stitch/6ed95c4c/lilypond/part_I.ly create mode 100644 resources/piece_ledger_sq1_candidates_stitch/6ed95c4c/lilypond/part_II.ly create mode 100644 resources/piece_ledger_sq1_candidates_stitch/6ed95c4c/lilypond/part_III.ly create mode 100644 resources/piece_ledger_sq1_candidates_stitch/6ed95c4c/lilypond/part_IV.ly create mode 100644 resources/piece_ledger_sq1_candidates_stitch/784130cc/784130cc_code.scd create mode 100644 resources/piece_ledger_sq1_candidates_stitch/784130cc/784130cc_mus_model.json create mode 100644 resources/piece_ledger_sq1_candidates_stitch/784130cc/lilypond/part_I.ly create mode 100644 resources/piece_ledger_sq1_candidates_stitch/784130cc/lilypond/part_II.ly create mode 100644 resources/piece_ledger_sq1_candidates_stitch/784130cc/lilypond/part_III.ly create mode 100644 resources/piece_ledger_sq1_candidates_stitch/784130cc/lilypond/part_IV.ly create mode 100644 resources/piece_ledger_sq1_candidates_stitch/79e0a4a7/79e0a4a7_code.scd create mode 100644 resources/piece_ledger_sq1_candidates_stitch/79e0a4a7/79e0a4a7_mus_model.json create mode 100644 resources/piece_ledger_sq1_candidates_stitch/79e0a4a7/lilypond/part_I.ly create mode 100644 resources/piece_ledger_sq1_candidates_stitch/79e0a4a7/lilypond/part_II.ly create mode 100644 resources/piece_ledger_sq1_candidates_stitch/79e0a4a7/lilypond/part_III.ly create mode 100644 resources/piece_ledger_sq1_candidates_stitch/79e0a4a7/lilypond/part_IV.ly create mode 100644 resources/piece_ledger_sq1_candidates_stitch/7d3c9a80/7d3c9a80_code.scd create mode 100644 resources/piece_ledger_sq1_candidates_stitch/7d3c9a80/7d3c9a80_mus_model.json create mode 100644 resources/piece_ledger_sq1_candidates_stitch/7d3c9a80/lilypond/part_I.ly create mode 100644 resources/piece_ledger_sq1_candidates_stitch/7d3c9a80/lilypond/part_II.ly create mode 100644 resources/piece_ledger_sq1_candidates_stitch/7d3c9a80/lilypond/part_III.ly create mode 100644 resources/piece_ledger_sq1_candidates_stitch/7d3c9a80/lilypond/part_IV.ly create mode 100644 resources/piece_ledger_sq1_candidates_stitch/7edbdceb/7edbdceb_code.scd create mode 100644 resources/piece_ledger_sq1_candidates_stitch/7edbdceb/7edbdceb_mus_model.json create mode 100644 resources/piece_ledger_sq1_candidates_stitch/7edbdceb/lilypond/part_I.ly create mode 100644 resources/piece_ledger_sq1_candidates_stitch/7edbdceb/lilypond/part_II.ly create mode 100644 resources/piece_ledger_sq1_candidates_stitch/7edbdceb/lilypond/part_III.ly create mode 100644 resources/piece_ledger_sq1_candidates_stitch/7edbdceb/lilypond/part_IV.ly diff --git a/lilypond/includes/part_I.ly b/lilypond/includes/part_I.ly index a3f5519..682a22a 100644 --- a/lilypond/includes/part_I.ly +++ b/lilypond/includes/part_I.ly @@ -4,3 +4,15 @@ \include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/piece_ledger_sq1_candidates_stitch/46985d14/lilypond/part_I.ly" \include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/piece_ledger_sq1_candidates_stitch/761e4585/lilypond/part_I.ly" \include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/piece_ledger_sq1_candidates_stitch/6fb60ab6/lilypond/part_I.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/piece_ledger_sq1_candidates_stitch/79e0a4a7/lilypond/part_I.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/piece_ledger_sq1_candidates_stitch/43b009ff/lilypond/part_I.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/piece_ledger_sq1_candidates_stitch/7d3c9a80/lilypond/part_I.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/piece_ledger_sq1_candidates_stitch/4b7745df/lilypond/part_I.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/piece_ledger_sq1_candidates_stitch/6ed95c4c/lilypond/part_I.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/piece_ledger_sq1_candidates_stitch/6d635e88/lilypond/part_I.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/piece_ledger_sq1_candidates_stitch/4e7d35e5/lilypond/part_I.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/piece_ledger_sq1_candidates_stitch/7edbdceb/lilypond/part_I.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/piece_ledger_sq1_candidates_stitch/784130cc/lilypond/part_I.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/piece_ledger_sq1_candidates_stitch/443ec222/lilypond/part_I.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/piece_ledger_sq1_candidates_stitch/52c9a980/lilypond/part_I.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/piece_ledger_sq1_candidates_stitch/4200a90d/lilypond/part_I.ly" diff --git a/lilypond/includes/part_II.ly b/lilypond/includes/part_II.ly index ae04fd2..8cba3f6 100644 --- a/lilypond/includes/part_II.ly +++ b/lilypond/includes/part_II.ly @@ -4,3 +4,15 @@ \include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/piece_ledger_sq1_candidates_stitch/46985d14/lilypond/part_II.ly" \include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/piece_ledger_sq1_candidates_stitch/761e4585/lilypond/part_II.ly" \include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/piece_ledger_sq1_candidates_stitch/6fb60ab6/lilypond/part_II.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/piece_ledger_sq1_candidates_stitch/79e0a4a7/lilypond/part_II.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/piece_ledger_sq1_candidates_stitch/43b009ff/lilypond/part_II.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/piece_ledger_sq1_candidates_stitch/7d3c9a80/lilypond/part_II.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/piece_ledger_sq1_candidates_stitch/4b7745df/lilypond/part_II.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/piece_ledger_sq1_candidates_stitch/6ed95c4c/lilypond/part_II.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/piece_ledger_sq1_candidates_stitch/6d635e88/lilypond/part_II.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/piece_ledger_sq1_candidates_stitch/4e7d35e5/lilypond/part_II.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/piece_ledger_sq1_candidates_stitch/7edbdceb/lilypond/part_II.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/piece_ledger_sq1_candidates_stitch/784130cc/lilypond/part_II.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/piece_ledger_sq1_candidates_stitch/443ec222/lilypond/part_II.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/piece_ledger_sq1_candidates_stitch/52c9a980/lilypond/part_II.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/piece_ledger_sq1_candidates_stitch/4200a90d/lilypond/part_II.ly" diff --git a/lilypond/includes/part_III.ly b/lilypond/includes/part_III.ly index a82e0dc..4e39361 100644 --- a/lilypond/includes/part_III.ly +++ b/lilypond/includes/part_III.ly @@ -4,3 +4,15 @@ \include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/piece_ledger_sq1_candidates_stitch/46985d14/lilypond/part_III.ly" \include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/piece_ledger_sq1_candidates_stitch/761e4585/lilypond/part_III.ly" \include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/piece_ledger_sq1_candidates_stitch/6fb60ab6/lilypond/part_III.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/piece_ledger_sq1_candidates_stitch/79e0a4a7/lilypond/part_III.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/piece_ledger_sq1_candidates_stitch/43b009ff/lilypond/part_III.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/piece_ledger_sq1_candidates_stitch/7d3c9a80/lilypond/part_III.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/piece_ledger_sq1_candidates_stitch/4b7745df/lilypond/part_III.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/piece_ledger_sq1_candidates_stitch/6ed95c4c/lilypond/part_III.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/piece_ledger_sq1_candidates_stitch/6d635e88/lilypond/part_III.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/piece_ledger_sq1_candidates_stitch/4e7d35e5/lilypond/part_III.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/piece_ledger_sq1_candidates_stitch/7edbdceb/lilypond/part_III.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/piece_ledger_sq1_candidates_stitch/784130cc/lilypond/part_III.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/piece_ledger_sq1_candidates_stitch/443ec222/lilypond/part_III.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/piece_ledger_sq1_candidates_stitch/52c9a980/lilypond/part_III.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/piece_ledger_sq1_candidates_stitch/4200a90d/lilypond/part_III.ly" diff --git a/lilypond/includes/part_IV.ly b/lilypond/includes/part_IV.ly index 6c5f83e..beb2a80 100644 --- a/lilypond/includes/part_IV.ly +++ b/lilypond/includes/part_IV.ly @@ -4,3 +4,15 @@ \include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/piece_ledger_sq1_candidates_stitch/46985d14/lilypond/part_IV.ly" \include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/piece_ledger_sq1_candidates_stitch/761e4585/lilypond/part_IV.ly" \include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/piece_ledger_sq1_candidates_stitch/6fb60ab6/lilypond/part_IV.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/piece_ledger_sq1_candidates_stitch/79e0a4a7/lilypond/part_IV.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/piece_ledger_sq1_candidates_stitch/43b009ff/lilypond/part_IV.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/piece_ledger_sq1_candidates_stitch/7d3c9a80/lilypond/part_IV.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/piece_ledger_sq1_candidates_stitch/4b7745df/lilypond/part_IV.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/piece_ledger_sq1_candidates_stitch/6ed95c4c/lilypond/part_IV.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/piece_ledger_sq1_candidates_stitch/6d635e88/lilypond/part_IV.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/piece_ledger_sq1_candidates_stitch/4e7d35e5/lilypond/part_IV.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/piece_ledger_sq1_candidates_stitch/7edbdceb/lilypond/part_IV.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/piece_ledger_sq1_candidates_stitch/784130cc/lilypond/part_IV.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/piece_ledger_sq1_candidates_stitch/443ec222/lilypond/part_IV.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/piece_ledger_sq1_candidates_stitch/52c9a980/lilypond/part_IV.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/piece_ledger_sq1_candidates_stitch/4200a90d/lilypond/part_IV.ly" diff --git a/lilypond/score_template.pdf b/lilypond/score_template.pdf index b20ef3940e7af0b4816115f3b9e87bb01b9b0924..ca91b5def4f5e9116ecbabaa7f2071eeeb841a2b 100644 GIT binary patch delta 267515 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
  • m)Ls9Z663s1^N?IlB5kBBohdP{ zZ>!DwG_$xyJ@(Df|CAUPqnQ%pr0OX#&NCMJDfBBbj;Mz@3@8Tb!SI0&T|)q zEn7gcO@~MRFabK0vv^fsawm51)0ktQXWx! zVV*XtDl_>esiXWZ^MSarCGB_(T>R#=`i-lB4Bh%sMEI*D;^ z(rKH7Osxen+T<0)s8D^&cwEy@?e@v z9H@Wp-*iW*uozgW+F;D2^G+sim#D_OJ#CreWLU&vjC;Dj#61SA>%>8vNst!pi0cRM z6guU^Q!-RAd39IVrvLG!Wum-EJdhek!s}3u>G3QbnaZ3on8etem>=kTbe^{55lG%Y z0yV>DV7e;i5Ap-mB>X_N(FjFFZ?A8?)_3;^6FpOyKcmL{a@0IMuVlV zfn|fGg;tAk;oil#LO=pl0y3F-g#~rYsZbIxeIxEWqOn%nt%Q5+(?nD*4oj-7vlD ze}I{Dj}8c!1GO&b7s)PFY7E?@BkrssR)OrZ6+QYN&uB?Nk?c~X$c^H9a#hpuIQ?07 z0dutlz$(rF_N4z`oE@_~)oO)o5<_a7AV9%%olTgyOkr@I^1P1Q^*`!gs@3yoLq@we zhB6@;1_c@l{oJax7*Fw?iIx80W@nlNSax}saXfapJ8|d20u%5&iEqcjkl&82p8DUE zMb!_gh_9WDEtWqhjUy+X*OYT&s_kZ&Z2F&L&rAmjLpDtr)c_)5V(LPAH;rrEz#*Nz z-|y%j^O|z1(26V4mt)+uW7Cnt&1=d@TxhkVBJ-e*7)PhPjQlyUOiE64bs5Dc6Osp3 zQB$yT*L9wOS~)%&6&Oa&hu{(#+C>N?WU>H2sJ8#0!PI z%Sl|)%1RBB>DQ>%HmL|su}9Gi!x|PA{BIIa=4LvKByF{DQtMauom;Osrn13OWI4YN z9P_6CA&*iNsgvn!qR1)=3+g8)OTG&-M*$N(c5-#{+Y7y^esDC99H_=5&sbt!#&xBK zp2{oGwu!3){J^d`frI}I{W7b2cLEK`kf`oOA5&vl8-do^vBKyBZ05I`>f}4HkD$Na zlQmADp%y4(BRS?MR|R_-{*$QI0lrYJcp;gTKzm8W2k!J*49|37hL3mQ0rtP6U!b8T z7HFuCIq_=I8h+$BLFPIpzUtF4nV%ud-O(Rw&lH#uEA3`HorzOD5Bxg(bn zi_@m07Nbi~Ee2OkMx!FRR5MC?aY799rkWk}KPAa^#*(88{o~3>LX!Vpl+GSKQu;#Z zTo`9Hee^%w=X!;xD``P}a-Xp+Z^GoJ>mk+f2be>!H)&>S97_LVGZZJpdQxud7SgJj zSVmpNnYU^ z5z03xkHx&ETnns*D`O&6J|TxnzKulx<7FvB6E9m0gpE52GWPaU5l4XGcLXIzU${k0g>Uq?*;m?Iwi@90PUwL;Y2A6HP4 z+SAO(sM4hPJCEFP=AR_?G7RU{i*iO15oHk(L!}2WBT7*bV=jN<%eza}>bY}OtH&c- z`G4c3Ew8p9b74^8ePNJXyBe?=a7ui=cOh`twF$@+bF>`Ny~1 z%!g@F6fDRb-3)xofJ|bY7ElFS+6@h}J#6^l!=?B)$3}Upx%!HN1(_TDRH-E3X=9yC zYHwqG(Emn_VT50GOuKc=qLBzo7? zqB+QF(F$Md+j{l*Vm0?zO-_7%_IQ{Sw+3L1tGF)vTOMAFn;r#mBNWj|$N$k7I4@My zB+~F|$2_A!A0sF4US9v$-+TL|&r6kidi{6*-rMi`$*;Zp^oQ?$nAhmt4}N<8gJ1ih zr>7tO+VB5VuIr~i^n<_u<^9*b`g{L>M|}OwSHJq^U;R@*^|OEK&;0Dq{Lweu>0(iL PZ+_SB`k_Dc!*BjSgECxT delta 60236 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 diff --git a/open_stage_control/seeds_and_ledgers_gui.json b/open_stage_control/seeds_and_ledgers_gui.json index 0daddc3..ca9b562 100644 --- a/open_stage_control/seeds_and_ledgers_gui.json +++ b/open_stage_control/seeds_and_ledgers_gui.json @@ -470,7 +470,7 @@ "align": "center", "hidePath": true, "mode": "open", - "directory": "Sketches/seeds_and_ledgers/source/resources", + "directory": "resources", "extension": "*", "allowDir": false }, @@ -1026,7 +1026,7 @@ "align": "center", "hidePath": true, "mode": "save", - "directory": "Sketches/seeds_and_ledgers/source/resources", + "directory": "resources", "extension": "*", "allowDir": false }, diff --git a/resources/piece_ledger_sq1_candidates_stitch.json b/resources/piece_ledger_sq1_candidates_stitch.json index 5261501..43e9593 100644 --- a/resources/piece_ledger_sq1_candidates_stitch.json +++ b/resources/piece_ledger_sq1_candidates_stitch.json @@ -7,6 +7,17 @@ "46985d14", "761e4585", "6fb60ab6", - "5e54c468" + "79e0a4a7", + "43b009ff", + "7d3c9a80", + "4b7745df", + "6ed95c4c", + "6d635e88", + "4e7d35e5", + "7edbdceb", + "784130cc", + "443ec222", + "52c9a980", + "4200a90d" ] } \ No newline at end of file diff --git a/resources/piece_ledger_sq1_candidates_stitch.json_bak b/resources/piece_ledger_sq1_candidates_stitch.json_bak index d231aa7..a8fca11 100644 --- a/resources/piece_ledger_sq1_candidates_stitch.json_bak +++ b/resources/piece_ledger_sq1_candidates_stitch.json_bak @@ -6,6 +6,18 @@ "490b1e6e", "46985d14", "761e4585", - "6fb60ab6" + "6fb60ab6", + "79e0a4a7", + "62820081", + "43b009ff", + "7d3c9a80", + "4b7745df", + "6ed95c4c", + "6d635e88", + "4e7d35e5", + "7edbdceb", + "784130cc", + "443ec222", + "52c9a980" ] } \ No newline at end of file diff --git a/resources/piece_ledger_sq1_candidates_stitch/4200a90d/4200a90d_code.scd b/resources/piece_ledger_sq1_candidates_stitch/4200a90d/4200a90d_code.scd new file mode 100644 index 0000000..a98b916 --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/4200a90d/4200a90d_code.scd @@ -0,0 +1,945 @@ +( +// 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(array1) - hsArrayToCents.value(array2); + 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); +}; + +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 = 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 - minMotifLength).rand + minMotifLength).collect({ + var noProgIns, noSusIns, noSilentIns, prog, sus, silent, order; + noSusIns = [1, 2, 3].wchoose(susWeights.normalizeSum); + noProgIns = (popSize - noSusIns).rand + 1; + noSilentIns = popSize - noSusIns - noProgIns; + + # prog, sus, silent = (0..(popSize-1)).scramble.clumps([noProgIns, noSusIns, noSilentIns]); + + prog = (prog.scramble ++ ((maxProgLength - minProgLength).rand + minProgLength).collect({prog.choose}).scramble); + 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; + 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); + 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]}) + }); + nProbs = nProbs.flop.collect({arg scores, s; scores.product}).normalizeSum; + + 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}); + 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; + 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"], {(60.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(mSeq.maxDepth - 5).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/piece_ledger_sq1_candidates_stitch/4200a90d/4200a90d_mus_model.json b/resources/piece_ledger_sq1_candidates_stitch/4200a90d/4200a90d_mus_model.json new file mode 100644 index 0000000..ace6b67 --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/4200a90d/4200a90d_mus_model.json @@ -0,0 +1,59 @@ +{ +"music_data": +[ + [ + [ + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ 1, 0, 1, -2, 1, 1 ] ], 2 ], + [ [ [ "Rest" ], [ "Rest" ], [ 1, 0, 0, -2, 1, 1 ], [ 1, 0, 1, -2, 1, 1 ] ], 1.5 ] + ], + [ + [ [ [ "Rest" ], [ 1, -1, 1, -2, 2, 0 ], [ 1, 0, 0, -2, 1, 1 ], [ 1, 0, 1, -2, 1, 1 ] ], 2 ], + [ [ [ "Rest" ], [ 1, -1, 1, -2, 2, 0 ], [ 2, -2, 1, -2, 2, 0 ], [ 1, 0, 1, -2, 1, 1 ] ], 0 ], + [ [ [ 0, -1, 1, -2, 2, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ 2, -2, 1, -2, 2, 0 ], [ 1, 0, 1, -2, 1, 1 ] ], 0.625 ], + [ [ [ 0, -1, 1, -2, 2, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ 1, 0, 1, -2, 2, 0 ], [ 1, 0, 1, -2, 1, 1 ] ], 0 ], + [ [ [ -1, -1, 1, -1, 2, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ 1, 0, 1, -2, 2, 0 ], [ 1, 0, 1, -2, 1, 1 ] ], 1.25 ], + [ [ [ -1, -1, 1, -2, 2, 1 ], [ 1, -1, 1, -2, 2, 0 ], [ 1, 0, 1, -2, 2, 0 ], [ 1, 0, 1, -2, 1, 1 ] ], 0 ], + [ [ [ -1, -1, 1, -2, 2, 1 ], [ 1, -1, 1, -2, 2, 0 ], [ 1, -1, 1, -2, 2, 1 ], [ 1, 0, 1, -2, 1, 1 ] ], 0 ], + [ [ [ -1, -1, 1, -2, 2, 1 ], [ 1, -1, 1, -2, 2, 0 ], [ 1, -1, 1, -2, 2, 1 ], [ 2, -1, 0, -2, 2, 0 ] ], 1.25 ], + [ [ [ 0, -1, 1, -2, 1, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ 1, -1, 1, -2, 2, 1 ], [ 2, -1, 0, -2, 2, 0 ] ], 1.25 ], + [ [ [ 0, -1, 1, -2, 1, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ 1, -1, 1, -1, 2, 0 ], [ 2, -1, 0, -2, 2, 0 ] ], 1.125 ], + [ [ [ 0, -1, 1, -2, 1, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ 1, -1, 1, -1, 2, 0 ], [ 1, -1, 1, -2, 3, 0 ] ], 2.25 ], + [ [ [ 0, -1, 1, -2, 1, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ "Rest" ], [ 1, -1, 1, -2, 3, 0 ] ], 1.5 ], + [ [ [ "Rest" ], [ 1, -1, 1, -2, 2, 0 ], [ "Rest" ], [ 1, -1, 1, -2, 3, 0 ] ], 0.875 ], + [ [ [ "Rest" ], [ 1, -1, 1, -2, 2, 0 ], [ "Rest" ], [ "Rest" ] ], 1.125 ], + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 3.25 ] + ] + ] +], +"last_changes": +[ + [ [ -1, -1, 1, -2, 2, 1 ], [ 1, -1, 1, -2, 2, 0 ], [ 1, -1, 1, -2, 2, 1 ], [ 1, 0, 1, -2, 1, 1 ] ], + [ [ -1, -1, 1, -2, 2, 1 ], [ 1, -1, 1, -2, 2, 0 ], [ 1, -1, 1, -2, 2, 1 ], [ 2, -1, 0, -2, 2, 0 ] ], + [ [ 0, -1, 1, -2, 1, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ 1, -1, 1, -2, 2, 1 ], [ 2, -1, 0, -2, 2, 0 ] ], + [ [ 0, -1, 1, -2, 1, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ 1, -1, 1, -1, 2, 0 ], [ 2, -1, 0, -2, 2, 0 ] ], + [ [ 0, -1, 1, -2, 1, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ 1, -1, 1, -1, 2, 0 ], [ 1, -1, 1, -2, 3, 0 ] ] +], +"cur_uid": "4200a90d", +"ref_uid": "6d635e88", +"order_seed": 516056, +"dur_seed": 358555, +"motifs_seed": 168145, +"entrances_probs_vals": [ 0.18, 0.28, 1.4285714285714, 0.82, 2.0054945054945, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 0.29, 0, 1.1111111111111, 0.65934065934066, 1.37, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 0.18, 0.28, 1.4285714285714, 0.82, 2.0054945054945, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"ranges": [ [ -2411.1455108359, -850.77399380805 ], [ -1872, 450 ], [ -479, 1304.0247678019 ], [ -368, 1173.9938080495 ] ], +"step_probs_vals": [ 0, 1200, 0, 0, 0.082304526748971, 0.99431818181818, 0.33950617283951, 0, 0.72839506172839, 0, 1, 0 ], +"passages_weights": [ 0.35, 0.42, 0.75, 0.9, 0.93 ], +"hd_exp": 2, +"hd_invert": 0, +"order": +[ + [ [ 3 ], [ 2 ], [ 0, 1 ] ], + [ [ 1 ], [ 2, 0, 2, 0, 0, 2, 3, 0, 2, 3 ], [ ] ] +], +"sus_weights": [ 0.41, 0, 0 ], +"order_size": [ 2, 6 ], +"passages_size": [ 0, 5 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/piece_ledger_sq1_candidates_stitch/4200a90d/lilypond/part_I.ly b/resources/piece_ledger_sq1_candidates_stitch/4200a90d/lilypond/part_I.ly new file mode 100644 index 0000000..fe7f79d --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/4200a90d/lilypond/part_I.ly @@ -0,0 +1,24 @@ +{ + { ais'1^\markup { \pad-markup #0.2 "+41"} ~ } + \bar "|" + { ais'1 ~ } + \bar "|" + { ais'1 ~ } + \bar "|" + { ais'2 ~ ais'8.[ a'16^\markup { \pad-markup #0.2 "-37"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 5↓" }}] ~ a'4 ~ } + \bar "|" + { a'1 ~ } + \bar "|" + { a'2 fis'2^\markup { \pad-markup #0.2 "+1"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 11↑" }} ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'2. ~ fis'16[ r8.] } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/piece_ledger_sq1_candidates_stitch/4200a90d/lilypond/part_II.ly b/resources/piece_ledger_sq1_candidates_stitch/4200a90d/lilypond/part_II.ly new file mode 100644 index 0000000..d3e409e --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/4200a90d/lilypond/part_II.ly @@ -0,0 +1,24 @@ +{ + { r1 } + \bar "|" + { g'1^\markup { \pad-markup #0.2 "-46"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 5↓" }} ~ } + \bar "|" + { g'2. f'4^\markup { \pad-markup #0.2 "+47"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 3↓" }} ~ } + \bar "|" + { f'16[ gis'8.^\markup { \pad-markup #0.2 "-49"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↑" }}] ~ gis'4 ~ gis'8.[ a'16^\markup { \pad-markup #0.2 "-10"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }}] ~ a'4 ~ } + \bar "|" + { a'2. ~ a'8.[ ais'16^\markup { \pad-markup #0.2 "+18"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 7↑" }}] ~ } + \bar "|" + { ais'1 ~ } + \bar "|" + { ais'2 ~ ais'8[ r8] r4 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/piece_ledger_sq1_candidates_stitch/4200a90d/lilypond/part_III.ly b/resources/piece_ledger_sq1_candidates_stitch/4200a90d/lilypond/part_III.ly new file mode 100644 index 0000000..78f4f29 --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/4200a90d/lilypond/part_III.ly @@ -0,0 +1,24 @@ +{ + { r1 } + \bar "|" + { r2. c'4^\markup { \pad-markup #0.2 "+49"} ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'4 ~ c'8[ r8] r2 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/piece_ledger_sq1_candidates_stitch/4200a90d/lilypond/part_IV.ly b/resources/piece_ledger_sq1_candidates_stitch/4200a90d/lilypond/part_IV.ly new file mode 100644 index 0000000..81da498 --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/4200a90d/lilypond/part_IV.ly @@ -0,0 +1,24 @@ +{ + { r1 } + \bar "|" + { r1 } + \bar "|" + { r2. c4^\markup { \pad-markup #0.2 "+49"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }} ~ } + \bar "|" + { c16[ ais,8.^\markup { \pad-markup #0.2 "+18"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 7↑" }}] ~ ais,4 ~ ais,8.[ a,16^\markup { \pad-markup #0.2 "-10"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 13↑" }}] ~ a,4 ~ } + \bar "|" + { a,4 ~ a,16[ g,8.^\markup { \pad-markup #0.2 "-2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 11↓" }}] ~ g,2 ~ } + \bar "|" + { g,1 ~ } + \bar "|" + { g,1 ~ } + \bar "|" + { g,4 ~ g,8[ r8] r2 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/piece_ledger_sq1_candidates_stitch/43b009ff/43b009ff_code.scd b/resources/piece_ledger_sq1_candidates_stitch/43b009ff/43b009ff_code.scd new file mode 100644 index 0000000..a98b916 --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/43b009ff/43b009ff_code.scd @@ -0,0 +1,945 @@ +( +// 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(array1) - hsArrayToCents.value(array2); + 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); +}; + +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 = 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 - minMotifLength).rand + minMotifLength).collect({ + var noProgIns, noSusIns, noSilentIns, prog, sus, silent, order; + noSusIns = [1, 2, 3].wchoose(susWeights.normalizeSum); + noProgIns = (popSize - noSusIns).rand + 1; + noSilentIns = popSize - noSusIns - noProgIns; + + # prog, sus, silent = (0..(popSize-1)).scramble.clumps([noProgIns, noSusIns, noSilentIns]); + + prog = (prog.scramble ++ ((maxProgLength - minProgLength).rand + minProgLength).collect({prog.choose}).scramble); + 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; + 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); + 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]}) + }); + nProbs = nProbs.flop.collect({arg scores, s; scores.product}).normalizeSum; + + 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}); + 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; + 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"], {(60.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(mSeq.maxDepth - 5).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/piece_ledger_sq1_candidates_stitch/43b009ff/43b009ff_mus_model.json b/resources/piece_ledger_sq1_candidates_stitch/43b009ff/43b009ff_mus_model.json new file mode 100644 index 0000000..4b558fd --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/43b009ff/43b009ff_mus_model.json @@ -0,0 +1,48 @@ +{ +"music_data": +[ + [ + [ + [ [ [ "Rest" ], [ 2, -2, 0, -1, 1, 0 ], [ "Rest" ], [ "Rest" ] ], 1.75 ], + [ [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ "Rest" ], [ "Rest" ] ], 2.75 ], + [ [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ "Rest" ], [ 3, -3, 0, -1, 1, 0 ] ], 1.75 ], + [ [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ 3, -2, 0, -2, 1, 0 ], [ 3, -3, 0, -1, 1, 0 ] ], 1.5 ], + [ [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ 3, -2, 0, -2, 1, 0 ], [ 2, -2, 0, -1, 2, 0 ] ], 4.125 ], + [ [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ "Rest" ], [ 2, -2, 0, -1, 2, 0 ] ], 0.875 ], + [ [ [ 1, -1, 0, -1, 1, 0 ], [ "Rest" ], [ "Rest" ], [ 2, -2, 0, -1, 2, 0 ] ], 1 ], + [ [ [ 1, -1, 0, -1, 1, 0 ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 0.875 ], + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 7.875 ] + ] + ] +], +"last_changes": +[ + [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ 2, -1, -1, -1, 1, 0 ], [ 1, -1, 0, 0, 1, 0 ] ], + [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ 2, -1, -1, -1, 1, 0 ], [ 2, -2, 1, -1, 1, 0 ] ], + [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ 2, -1, -1, -1, 1, 0 ], [ 3, -3, 0, -1, 1, 0 ] ], + [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ 3, -2, 0, -2, 1, 0 ], [ 3, -3, 0, -1, 1, 0 ] ], + [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ 3, -2, 0, -2, 1, 0 ], [ 2, -2, 0, -1, 2, 0 ] ] +], +"cur_uid": "43b009ff", +"ref_uid": 62820081, +"order_seed": 216475, +"dur_seed": 323751, +"motifs_seed": 466146, +"entrances_probs_vals": [ 0, 1.1904761904762, 3.3333333333333, 0.71, 1.92, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 0, 1.1904761904762, 3.3333333333333, 0.71, 1.92, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 0, 1.1904761904762, 3.3333333333333, 0.71, 1.92, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"ranges": [ [ -3600, -312 ], [ -1872, 1378 ], [ -145, 1583 ], [ -182, 1527 ] ], +"step_probs_vals": [ 0, 1200, 0, 0, 0.082304526748971, 0.99431818181818, 0.14197530864198, 0, 1, 0 ], +"passages_weights": [ 0.75, 0.75, 0.75, 0.75, 0.75 ], +"hd_exp": 2, +"hd_invert": 0, +"order": +[ + [ [ 1, 0 ], [ 3, 2, 3 ], [ ] ] +], +"sus_weights": [ 0, 0.65, 0 ], +"order_size": [ 1, 1 ], +"passages_size": [ 1, 6 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/piece_ledger_sq1_candidates_stitch/43b009ff/lilypond/part_I.ly b/resources/piece_ledger_sq1_candidates_stitch/43b009ff/lilypond/part_I.ly new file mode 100644 index 0000000..ba593fd --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/43b009ff/lilypond/part_I.ly @@ -0,0 +1,26 @@ +{ + { r1 } + \bar "|" + { r1 } + \bar "|" + { r4 b'2.^\markup { \pad-markup #0.2 "-23"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 3↓" }} ~ } + \bar "|" + { b'2. ~ b'8[ b'8^\markup { \pad-markup #0.2 "+30"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 11↑" }}] ~ } + \bar "|" + { b'1 ~ } + \bar "|" + { b'1 ~ } + \bar "|" + { b'2. ~ b'8[ r8] } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/piece_ledger_sq1_candidates_stitch/43b009ff/lilypond/part_II.ly b/resources/piece_ledger_sq1_candidates_stitch/43b009ff/lilypond/part_II.ly new file mode 100644 index 0000000..c804e99 --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/43b009ff/lilypond/part_II.ly @@ -0,0 +1,26 @@ +{ + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r8[ gis'8^\markup { \pad-markup #0.2 "+10"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 7↓" }}] ~ gis'2. ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'2. ~ gis'8.[ r16] } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/piece_ledger_sq1_candidates_stitch/43b009ff/lilypond/part_III.ly b/resources/piece_ledger_sq1_candidates_stitch/43b009ff/lilypond/part_III.ly new file mode 100644 index 0000000..508719b --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/43b009ff/lilypond/part_III.ly @@ -0,0 +1,26 @@ +{ + { fis'1^\markup { \pad-markup #0.2 "-21"} ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'4 ~ fis'8[ r8] r2 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/piece_ledger_sq1_candidates_stitch/43b009ff/lilypond/part_IV.ly b/resources/piece_ledger_sq1_candidates_stitch/43b009ff/lilypond/part_IV.ly new file mode 100644 index 0000000..3388bcf --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/43b009ff/lilypond/part_IV.ly @@ -0,0 +1,26 @@ +{ + { r2. r8[ cis'8^\markup { \pad-markup #0.2 "-19"}] ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'4 ~ cis'16[ r8.] r2 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/piece_ledger_sq1_candidates_stitch/443ec222/443ec222_code.scd b/resources/piece_ledger_sq1_candidates_stitch/443ec222/443ec222_code.scd new file mode 100644 index 0000000..a98b916 --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/443ec222/443ec222_code.scd @@ -0,0 +1,945 @@ +( +// 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(array1) - hsArrayToCents.value(array2); + 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); +}; + +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 = 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 - minMotifLength).rand + minMotifLength).collect({ + var noProgIns, noSusIns, noSilentIns, prog, sus, silent, order; + noSusIns = [1, 2, 3].wchoose(susWeights.normalizeSum); + noProgIns = (popSize - noSusIns).rand + 1; + noSilentIns = popSize - noSusIns - noProgIns; + + # prog, sus, silent = (0..(popSize-1)).scramble.clumps([noProgIns, noSusIns, noSilentIns]); + + prog = (prog.scramble ++ ((maxProgLength - minProgLength).rand + minProgLength).collect({prog.choose}).scramble); + 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; + 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); + 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]}) + }); + nProbs = nProbs.flop.collect({arg scores, s; scores.product}).normalizeSum; + + 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}); + 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; + 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"], {(60.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(mSeq.maxDepth - 5).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/piece_ledger_sq1_candidates_stitch/443ec222/443ec222_mus_model.json b/resources/piece_ledger_sq1_candidates_stitch/443ec222/443ec222_mus_model.json new file mode 100644 index 0000000..91c560a --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/443ec222/443ec222_mus_model.json @@ -0,0 +1,54 @@ +{ +"music_data": +[ + [ + [ + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ 1, 0, 1, -2, 1, 1 ] ], 1.75 ], + [ [ [ "Rest" ], [ "Rest" ], [ 1, 0, 1, -2, 1, 0 ], [ 1, 0, 1, -2, 1, 1 ] ], 1.5 ] + ], + [ + [ [ [ "Rest" ], [ "Rest" ], [ 1, 0, 1, -2, 1, 0 ], [ "Rest" ] ], 0 ], + [ [ [ "Rest" ], [ 1, -1, 1, -2, 2, 0 ], [ 1, 0, 1, -2, 1, 0 ], [ "Rest" ] ], 1.5 ], + [ [ [ "Rest" ], [ 1, -1, 1, -2, 2, 0 ], [ 1, -1, 2, -2, 2, 0 ], [ "Rest" ] ], 0 ], + [ [ [ 0, -1, 1, -2, 2, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ 1, -1, 2, -2, 2, 0 ], [ "Rest" ] ], 0.75 ], + [ [ [ 0, -1, 1, -2, 2, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ 2, -2, 1, -2, 2, 0 ], [ "Rest" ] ], 0.75 ], + [ [ [ -1, -1, 1, -1, 2, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ 2, -2, 1, -2, 2, 0 ], [ "Rest" ] ], 0.625 ], + [ [ [ 0, -1, 0, -2, 2, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ 2, -2, 1, -2, 2, 0 ], [ "Rest" ] ], 2.125 ], + [ [ [ "Rest" ], [ 1, -1, 1, -2, 2, 0 ], [ 2, -2, 1, -2, 2, 0 ], [ "Rest" ] ], 1.125 ], + [ [ [ "Rest" ], [ 1, -1, 1, -2, 2, 0 ], [ "Rest" ], [ "Rest" ] ], 0.625 ], + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 6.5 ] + ] + ] +], +"last_changes": +[ + [ [ 1, -1, 1, -3, 2, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ 1, -1, 2, -2, 2, 0 ], [ 1, 0, 1, -2, 1, 1 ] ], + [ [ 0, -1, 1, -2, 2, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ 1, -1, 2, -2, 2, 0 ], [ 1, 0, 1, -2, 1, 1 ] ], + [ [ 0, -1, 1, -2, 2, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ 2, -2, 1, -2, 2, 0 ], [ 1, 0, 1, -2, 1, 1 ] ], + [ [ -1, -1, 1, -1, 2, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ 2, -2, 1, -2, 2, 0 ], [ 1, 0, 1, -2, 1, 1 ] ], + [ [ 0, -1, 0, -2, 2, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ 2, -2, 1, -2, 2, 0 ], [ 1, 0, 1, -2, 1, 1 ] ] +], +"cur_uid": "443ec222", +"ref_uid": "6d635e88", +"order_seed": 516056, +"dur_seed": 358555, +"motifs_seed": 962315, +"entrances_probs_vals": [ 0.18, 0.28, 1.4285714285714, 0.47, 1.62, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 0.29, 0, 1.1111111111111, 0.65934065934066, 1.37, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 0.18, 0.28, 1.4285714285714, 0.47, 1.62, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"ranges": [ [ -2411.1455108359, -850.77399380805 ], [ -1872, 450 ], [ -479, 1304.0247678019 ], [ -368, 1173.9938080495 ] ], +"step_probs_vals": [ 0, 1200, 0, 0, 0.082304526748971, 0.99431818181818, 0.33950617283951, 0, 0.72839506172839, 0, 1, 0 ], +"passages_weights": [ 0.35, 0.42, 0.75, 0.9, 0.93 ], +"hd_exp": 2, +"hd_invert": 0, +"order": +[ + [ [ 3 ], [ 2 ], [ 0, 1 ] ], + [ [ 1 ], [ 2, 0, 2, 0, 0 ], [ 3 ] ] +], +"sus_weights": [ 0.41, 0, 0 ], +"order_size": [ 2, 6 ], +"passages_size": [ 0, 5 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/piece_ledger_sq1_candidates_stitch/443ec222/lilypond/part_I.ly b/resources/piece_ledger_sq1_candidates_stitch/443ec222/lilypond/part_I.ly new file mode 100644 index 0000000..f5a381e --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/443ec222/lilypond/part_I.ly @@ -0,0 +1,20 @@ +{ + { ais'1^\markup { \pad-markup #0.2 "+41"} ~ } + \bar "|" + { ais'2 ~ ais'8[ r8] r4 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/piece_ledger_sq1_candidates_stitch/443ec222/lilypond/part_II.ly b/resources/piece_ledger_sq1_candidates_stitch/443ec222/lilypond/part_II.ly new file mode 100644 index 0000000..b5322b9 --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/443ec222/lilypond/part_II.ly @@ -0,0 +1,20 @@ +{ + { r2. r8[ d'8^\markup { \pad-markup #0.2 "+0"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 13↓" }}] ~ } + \bar "|" + { d'1 ~ } + \bar "|" + { d'4 ~ d'8[ e'8^\markup { \pad-markup #0.2 "+36"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 5↑" }}] ~ e'4 f'4^\markup { \pad-markup #0.2 "+47"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↓" }} ~ } + \bar "|" + { f'1 ~ } + \bar "|" + { f'1 ~ } + \bar "|" + { f'16[ r8.] r2. } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/piece_ledger_sq1_candidates_stitch/443ec222/lilypond/part_III.ly b/resources/piece_ledger_sq1_candidates_stitch/443ec222/lilypond/part_III.ly new file mode 100644 index 0000000..aefa415 --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/443ec222/lilypond/part_III.ly @@ -0,0 +1,20 @@ +{ + { r1 } + \bar "|" + { r2 r8[ c'8^\markup { \pad-markup #0.2 "+49"}] ~ c'4 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'4 ~ c'8[ r8] r2 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/piece_ledger_sq1_candidates_stitch/443ec222/lilypond/part_IV.ly b/resources/piece_ledger_sq1_candidates_stitch/443ec222/lilypond/part_IV.ly new file mode 100644 index 0000000..98f6657 --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/443ec222/lilypond/part_IV.ly @@ -0,0 +1,20 @@ +{ + { r1 } + \bar "|" + { r1 } + \bar "|" + { r4 r8[ c8^\markup { \pad-markup #0.2 "+49"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }}] ~ c2 ~ } + \bar "|" + { c8[ ais,8^\markup { \pad-markup #0.2 "+18"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 7↑" }}] ~ ais,8.[ a,16^\markup { \pad-markup #0.2 "-37"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 5↓" }}] ~ a,2 ~ } + \bar "|" + { a,2 r2 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/piece_ledger_sq1_candidates_stitch/4526b76b/4526b76b_code.scd b/resources/piece_ledger_sq1_candidates_stitch/4526b76b/4526b76b_code.scd new file mode 100644 index 0000000..8e9fe42 --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/4526b76b/4526b76b_code.scd @@ -0,0 +1,943 @@ +( +// 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(array1) - hsArrayToCents.value(array2); + 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); +}; + +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 = 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 - minMotifLength).rand + minMotifLength).collect({ + var noProgIns, noSusIns, noSilentIns, prog, sus, silent, order; + noSusIns = [1, 2, 3].wchoose(susWeights.normalizeSum); + noProgIns = (popSize - noSusIns).rand + 1; + noSilentIns = popSize - noSusIns - noProgIns; + + # prog, sus, silent = (0..(popSize-1)).scramble.clumps([noProgIns, noSusIns, noSilentIns]); + + prog = (prog.scramble ++ ((maxProgLength - minProgLength).rand + minProgLength).collect({prog.choose}).scramble); + 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; + 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); + 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]}) + }); + nProbs = nProbs.flop.collect({arg scores, s; scores.product}).normalizeSum; + + 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}); + 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; + 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"], {(60.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(mSeq.maxDepth - 5).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; + 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/piece_ledger_sq1_candidates_stitch/4526b76b/4526b76b_mus_model.json b/resources/piece_ledger_sq1_candidates_stitch/4526b76b/4526b76b_mus_model.json new file mode 100644 index 0000000..7489563 --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/4526b76b/4526b76b_mus_model.json @@ -0,0 +1,44 @@ +{ +"music_data": +[ + [ + [ + [ [ [ "Rest" ], [ "Rest" ], [ 1, -1, 0, -1, 1, -1 ], [ "Rest" ] ], 0.625 ], + [ [ [ "Rest" ], [ 2, -2, 0, -1, 1, 0 ], [ 1, -1, 0, -1, 1, -1 ], [ "Rest" ] ], 1.625 ], + [ [ [ "Rest" ], [ 2, -2, 0, -1, 1, 0 ], [ 1, -1, 0, -1, 1, -1 ], [ 3, -1, 0, -2, 1, -1 ] ], 1.625 ], + [ [ [ "Rest" ], [ 2, -2, 0, -1, 1, 0 ], [ 1, -1, 0, -1, 1, -1 ], [ 3, -1, 0, -1, 1, -2 ] ], 11 ], + [ [ [ "Rest" ], [ 2, -2, 0, -1, 1, 0 ], [ "Rest" ], [ 3, -1, 0, -1, 1, -2 ] ], 1.375 ], + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ 3, -1, 0, -1, 1, -2 ] ], 1.5 ], + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 6.25 ] + ] + ] +], +"last_changes": +[ + [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ 1, -1, 0, -1, 1, -1 ], [ 1, 0, 0, -1, 1, 0 ] ], + [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ 1, -1, 0, -1, 1, -1 ], [ 3, -1, 0, -2, 1, -1 ] ], + [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ 1, -1, 0, -1, 1, -1 ], [ 3, -1, 0, -1, 1, -2 ] ] +], +"cur_uid": "4526b76b", +"ref_uid": "6fb60ab6", +"order_seed": 785868, +"dur_seed": 994115, +"motifs_seed": 805843, +"entrances_probs_vals": [ 0.34, 0, 10, 0.5, 2, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 0.34, 0, 10, 0.5, 2.12, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 0.34, 0, 10, 0.5, 2.12, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"ranges": [ [ -3600, -312 ], [ -1872, 1378 ], [ -145, 1583 ], [ -182, 1527 ] ], +"step_probs_vals": [ 0, 1200, 0, 0, 0.082304526748971, 0.99431818181818, 0.14197530864198, 0, 1, 0 ], +"passages_weights": [ 0.75, 0.75, 0.75, 0.75, 0.75 ], +"hd_exp": 2, +"hd_invert": 0, +"order": +[ + [ [ 2, 1 ], [ 3, 3 ], [ 0 ] ] +], +"sus_weights": [ 0.75, 0.25, 0.25 ], +"order_size": [ 1, 4 ], +"passages_size": [ 0, 3 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/piece_ledger_sq1_candidates_stitch/4b7745df/4b7745df_code.scd b/resources/piece_ledger_sq1_candidates_stitch/4b7745df/4b7745df_code.scd new file mode 100644 index 0000000..a98b916 --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/4b7745df/4b7745df_code.scd @@ -0,0 +1,945 @@ +( +// 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(array1) - hsArrayToCents.value(array2); + 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); +}; + +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 = 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 - minMotifLength).rand + minMotifLength).collect({ + var noProgIns, noSusIns, noSilentIns, prog, sus, silent, order; + noSusIns = [1, 2, 3].wchoose(susWeights.normalizeSum); + noProgIns = (popSize - noSusIns).rand + 1; + noSilentIns = popSize - noSusIns - noProgIns; + + # prog, sus, silent = (0..(popSize-1)).scramble.clumps([noProgIns, noSusIns, noSilentIns]); + + prog = (prog.scramble ++ ((maxProgLength - minProgLength).rand + minProgLength).collect({prog.choose}).scramble); + 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; + 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); + 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]}) + }); + nProbs = nProbs.flop.collect({arg scores, s; scores.product}).normalizeSum; + + 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}); + 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; + 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"], {(60.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(mSeq.maxDepth - 5).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/piece_ledger_sq1_candidates_stitch/4b7745df/4b7745df_mus_model.json b/resources/piece_ledger_sq1_candidates_stitch/4b7745df/4b7745df_mus_model.json new file mode 100644 index 0000000..10afc7f --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/4b7745df/4b7745df_mus_model.json @@ -0,0 +1,48 @@ +{ +"music_data": +[ + [ + [ + [ [ [ "Rest" ], [ 2, -2, 0, -1, 1, 0 ], [ "Rest" ], [ "Rest" ] ], 1.5 ], + [ [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ "Rest" ], [ "Rest" ] ], 3.625 ], + [ [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ "Rest" ], [ 3, -2, -1, -1, 1, 0 ] ], 1.875 ], + [ [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ 2, -1, -1, -1, 1, 0 ], [ 3, -2, -1, -1, 1, 0 ] ], 1.75 ], + [ [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ 2, -1, -1, -1, 1, 0 ], [ 3, -1, 0, -2, 1, 0 ] ], 3.375 ], + [ [ [ 1, -1, 0, -1, 1, 0 ], [ "Rest" ], [ 2, -1, -1, -1, 1, 0 ], [ 3, -1, 0, -2, 1, 0 ] ], 1.75 ], + [ [ [ "Rest" ], [ "Rest" ], [ 2, -1, -1, -1, 1, 0 ], [ 3, -1, 0, -2, 1, 0 ] ], 0.75 ], + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ 3, -1, 0, -2, 1, 0 ] ], 1.125 ], + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 4.25 ] + ] + ] +], +"last_changes": +[ + [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ 3, -2, 0, -1, 1, -1 ], [ 3, -2, 0, -1, 0, 0 ] ], + [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ 3, -2, 0, -1, 1, -1 ], [ 2, -1, 0, -1, 1, 0 ] ], + [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ 3, -2, 0, -1, 1, -1 ], [ 3, -2, -1, -1, 1, 0 ] ], + [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ 2, -1, -1, -1, 1, 0 ], [ 3, -2, -1, -1, 1, 0 ] ], + [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ 2, -1, -1, -1, 1, 0 ], [ 3, -1, 0, -2, 1, 0 ] ] +], +"cur_uid": "4b7745df", +"ref_uid": "7d3c9a80", +"order_seed": 216475, +"dur_seed": 241788, +"motifs_seed": 440693, +"entrances_probs_vals": [ 0, 1.1904761904762, 3.3333333333333, 0.71, 1.92, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 0, 1.1904761904762, 3.3333333333333, 0.71, 1.92, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 0, 1.1904761904762, 3.3333333333333, 0.71, 1.92, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"ranges": [ [ -3600, -312 ], [ -1872, 1378 ], [ -145, 1583 ], [ -182, 1527 ] ], +"step_probs_vals": [ 0, 1200, 0, 0, 0.082304526748971, 0.99431818181818, 0.14197530864198, 0, 1, 0 ], +"passages_weights": [ 0.75, 0.75, 0.75, 0.75, 0.75 ], +"hd_exp": 2, +"hd_invert": 0, +"order": +[ + [ [ 1, 0 ], [ 3, 2, 3 ], [ ] ] +], +"sus_weights": [ 0, 0.65, 0 ], +"order_size": [ 1, 2 ], +"passages_size": [ 1, 6 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/piece_ledger_sq1_candidates_stitch/4b7745df/lilypond/part_I.ly b/resources/piece_ledger_sq1_candidates_stitch/4b7745df/lilypond/part_I.ly new file mode 100644 index 0000000..8b32d05 --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/4b7745df/lilypond/part_I.ly @@ -0,0 +1,24 @@ +{ + { r1 } + \bar "|" + { r1 } + \bar "|" + { r2 r16[ d''8.^\markup { \pad-markup #0.2 "-8"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 5↓" }}] ~ d''4 ~ } + \bar "|" + { d''1 ~ } + \bar "|" + { d''4 ~ d''8[ dis''8^\markup { \pad-markup #0.2 "+12"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 7↓" }}] ~ dis''2 ~ } + \bar "|" + { dis''1 ~ } + \bar "|" + { dis''1 ~ } + \bar "|" + { dis''2. ~ dis''8[ r8] } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/piece_ledger_sq1_candidates_stitch/4b7745df/lilypond/part_II.ly b/resources/piece_ledger_sq1_candidates_stitch/4b7745df/lilypond/part_II.ly new file mode 100644 index 0000000..46336ad --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/4b7745df/lilypond/part_II.ly @@ -0,0 +1,24 @@ +{ + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r2 a'2^\markup { \pad-markup #0.2 "-6"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↓" }} ~ } + \bar "|" + { a'1 ~ } + \bar "|" + { a'1 ~ } + \bar "|" + { a'1 ~ } + \bar "|" + { a'4 ~ a'16[ r8.] r2 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/piece_ledger_sq1_candidates_stitch/4b7745df/lilypond/part_III.ly b/resources/piece_ledger_sq1_candidates_stitch/4b7745df/lilypond/part_III.ly new file mode 100644 index 0000000..4d977c4 --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/4b7745df/lilypond/part_III.ly @@ -0,0 +1,24 @@ +{ + { fis'1^\markup { \pad-markup #0.2 "-21"} ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'16[ r8.] r2. } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/piece_ledger_sq1_candidates_stitch/4b7745df/lilypond/part_IV.ly b/resources/piece_ledger_sq1_candidates_stitch/4b7745df/lilypond/part_IV.ly new file mode 100644 index 0000000..b9937fe --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/4b7745df/lilypond/part_IV.ly @@ -0,0 +1,24 @@ +{ + { r2. cis'4^\markup { \pad-markup #0.2 "-19"} ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'2. ~ cis'8.[ r16] } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/piece_ledger_sq1_candidates_stitch/4e7d35e5/4e7d35e5_code.scd b/resources/piece_ledger_sq1_candidates_stitch/4e7d35e5/4e7d35e5_code.scd new file mode 100644 index 0000000..a98b916 --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/4e7d35e5/4e7d35e5_code.scd @@ -0,0 +1,945 @@ +( +// 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(array1) - hsArrayToCents.value(array2); + 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); +}; + +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 = 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 - minMotifLength).rand + minMotifLength).collect({ + var noProgIns, noSusIns, noSilentIns, prog, sus, silent, order; + noSusIns = [1, 2, 3].wchoose(susWeights.normalizeSum); + noProgIns = (popSize - noSusIns).rand + 1; + noSilentIns = popSize - noSusIns - noProgIns; + + # prog, sus, silent = (0..(popSize-1)).scramble.clumps([noProgIns, noSusIns, noSilentIns]); + + prog = (prog.scramble ++ ((maxProgLength - minProgLength).rand + minProgLength).collect({prog.choose}).scramble); + 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; + 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); + 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]}) + }); + nProbs = nProbs.flop.collect({arg scores, s; scores.product}).normalizeSum; + + 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}); + 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; + 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"], {(60.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(mSeq.maxDepth - 5).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/piece_ledger_sq1_candidates_stitch/4e7d35e5/4e7d35e5_mus_model.json b/resources/piece_ledger_sq1_candidates_stitch/4e7d35e5/4e7d35e5_mus_model.json new file mode 100644 index 0000000..0eebea8 --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/4e7d35e5/4e7d35e5_mus_model.json @@ -0,0 +1,52 @@ +{ +"music_data": +[ + [ + [ + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ 1, 0, 1, -2, 1, 1 ] ], 1.75 ], + [ [ [ "Rest" ], [ "Rest" ], [ 1, 0, 0, -2, 1, 1 ], [ 1, 0, 1, -2, 1, 1 ] ], 1.5 ] + ], + [ + [ [ [ "Rest" ], [ "Rest" ], [ 1, 0, 0, -2, 1, 1 ], [ "Rest" ] ], 0 ], + [ [ [ "Rest" ], [ 1, -1, 1, -2, 2, 0 ], [ 1, 0, 0, -2, 1, 1 ], [ "Rest" ] ], 1.5 ], + [ [ [ "Rest" ], [ 1, -1, 1, -2, 2, 0 ], [ 1, 0, 1, -2, 2, 0 ], [ "Rest" ] ], 0 ], + [ [ [ 0, -1, 1, -2, 2, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ 1, 0, 1, -2, 2, 0 ], [ "Rest" ] ], 0.75 ], + [ [ [ 0, -1, 1, -2, 2, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ 1, -1, 1, -2, 3, 0 ], [ "Rest" ] ], 1.375 ], + [ [ [ 0, -1, 1, -2, 2, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ "Rest" ], [ "Rest" ] ], 1.125 ], + [ [ [ 0, -1, 1, -2, 2, 0 ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 0.625 ], + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 3.375 ] + ] + ] +], +"last_changes": +[ + [ [ 1, -1, 1, -3, 2, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ 0, 1, 1, -2, 1, 1 ], [ 1, 0, 1, -2, 1, 1 ] ], + [ [ 1, -1, 1, -3, 2, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ 1, 0, 0, -2, 1, 1 ], [ 1, 0, 1, -2, 1, 1 ] ], + [ [ 1, -1, 1, -3, 2, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ 1, 0, 1, -2, 2, 0 ], [ 1, 0, 1, -2, 1, 1 ] ], + [ [ 0, -1, 1, -2, 2, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ 1, 0, 1, -2, 2, 0 ], [ 1, 0, 1, -2, 1, 1 ] ], + [ [ 0, -1, 1, -2, 2, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ 1, -1, 1, -2, 3, 0 ], [ 1, 0, 1, -2, 1, 1 ] ] +], +"cur_uid": "4e7d35e5", +"ref_uid": "6d635e88", +"order_seed": 516056, +"dur_seed": 358555, +"motifs_seed": 595740, +"entrances_probs_vals": [ 0.18, 0.28, 1.4285714285714, 0.47, 1.62, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 0.29, 0, 1.1111111111111, 0.65934065934066, 1.37, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 0.18, 0.28, 1.4285714285714, 0.47, 1.62, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"ranges": [ [ -2411.1455108359, -850.77399380805 ], [ -1872, 450 ], [ -479, 1304.0247678019 ], [ -368, 1173.9938080495 ] ], +"step_probs_vals": [ 0, 1200, 0, 0, 0.082304526748971, 0.99431818181818, 0.33950617283951, 0, 0.72839506172839, 0, 1, 0 ], +"passages_weights": [ 0.35, 0.42, 0.75, 0.9, 0.93 ], +"hd_exp": 2, +"hd_invert": 0, +"order": +[ + [ [ 3 ], [ 2 ], [ 0, 1 ] ], + [ [ 1 ], [ 2, 0, 2 ], [ 3 ] ] +], +"sus_weights": [ 0.41, 0, 0 ], +"order_size": [ 2, 6 ], +"passages_size": [ 0, 5 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/piece_ledger_sq1_candidates_stitch/4e7d35e5/lilypond/part_I.ly b/resources/piece_ledger_sq1_candidates_stitch/4e7d35e5/lilypond/part_I.ly new file mode 100644 index 0000000..b7a37a6 --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/4e7d35e5/lilypond/part_I.ly @@ -0,0 +1,16 @@ +{ + { ais'1^\markup { \pad-markup #0.2 "+41"} ~ } + \bar "|" + { ais'2 ~ ais'8[ r8] r4 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/piece_ledger_sq1_candidates_stitch/4e7d35e5/lilypond/part_II.ly b/resources/piece_ledger_sq1_candidates_stitch/4e7d35e5/lilypond/part_II.ly new file mode 100644 index 0000000..6be914b --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/4e7d35e5/lilypond/part_II.ly @@ -0,0 +1,16 @@ +{ + { r2. r8[ g'8^\markup { \pad-markup #0.2 "-46"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 5↓" }}] ~ } + \bar "|" + { g'1 ~ } + \bar "|" + { g'4 ~ g'8[ gis'8^\markup { \pad-markup #0.2 "-49"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 3↑" }}] ~ gis'4 fis'4^\markup { \pad-markup #0.2 "+1"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 11↑" }} ~ } + \bar "|" + { fis'4 ~ fis'8.[ r16] r2 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/piece_ledger_sq1_candidates_stitch/4e7d35e5/lilypond/part_III.ly b/resources/piece_ledger_sq1_candidates_stitch/4e7d35e5/lilypond/part_III.ly new file mode 100644 index 0000000..c1924aa --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/4e7d35e5/lilypond/part_III.ly @@ -0,0 +1,16 @@ +{ + { r1 } + \bar "|" + { r2 r8[ c'8^\markup { \pad-markup #0.2 "+49"}] ~ c'4 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/piece_ledger_sq1_candidates_stitch/4e7d35e5/lilypond/part_IV.ly b/resources/piece_ledger_sq1_candidates_stitch/4e7d35e5/lilypond/part_IV.ly new file mode 100644 index 0000000..f6f37f5 --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/4e7d35e5/lilypond/part_IV.ly @@ -0,0 +1,16 @@ +{ + { r1 } + \bar "|" + { r1 } + \bar "|" + { r4 r8[ c8^\markup { \pad-markup #0.2 "+49"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }}] ~ c2 ~ } + \bar "|" + { c1 ~ } + \bar "|" + { c4 ~ c16[ r8.] r2 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/piece_ledger_sq1_candidates_stitch/52c9a980/52c9a980_code.scd b/resources/piece_ledger_sq1_candidates_stitch/52c9a980/52c9a980_code.scd new file mode 100644 index 0000000..a98b916 --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/52c9a980/52c9a980_code.scd @@ -0,0 +1,945 @@ +( +// 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(array1) - hsArrayToCents.value(array2); + 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); +}; + +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 = 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 - minMotifLength).rand + minMotifLength).collect({ + var noProgIns, noSusIns, noSilentIns, prog, sus, silent, order; + noSusIns = [1, 2, 3].wchoose(susWeights.normalizeSum); + noProgIns = (popSize - noSusIns).rand + 1; + noSilentIns = popSize - noSusIns - noProgIns; + + # prog, sus, silent = (0..(popSize-1)).scramble.clumps([noProgIns, noSusIns, noSilentIns]); + + prog = (prog.scramble ++ ((maxProgLength - minProgLength).rand + minProgLength).collect({prog.choose}).scramble); + 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; + 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); + 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]}) + }); + nProbs = nProbs.flop.collect({arg scores, s; scores.product}).normalizeSum; + + 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}); + 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; + 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"], {(60.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(mSeq.maxDepth - 5).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/piece_ledger_sq1_candidates_stitch/52c9a980/52c9a980_mus_model.json b/resources/piece_ledger_sq1_candidates_stitch/52c9a980/52c9a980_mus_model.json new file mode 100644 index 0000000..f3cde46 --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/52c9a980/52c9a980_mus_model.json @@ -0,0 +1,55 @@ +{ +"music_data": +[ + [ + [ + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ 1, 0, 1, -2, 1, 1 ] ], 2 ], + [ [ [ "Rest" ], [ "Rest" ], [ 0, 0, 1, -1, 1, 1 ], [ 1, 0, 1, -2, 1, 1 ] ], 1.5 ] + ], + [ + [ [ [ "Rest" ], [ "Rest" ], [ 0, 0, 1, -1, 1, 1 ], [ "Rest" ] ], 0 ], + [ [ [ "Rest" ], [ 1, -1, 1, -2, 2, 0 ], [ 0, 0, 1, -1, 1, 1 ], [ "Rest" ] ], 1.5 ], + [ [ [ "Rest" ], [ 1, -1, 1, -2, 2, 0 ], [ 2, -1, 1, -2, 1, 0 ], [ "Rest" ] ], 0 ], + [ [ [ 0, -1, 1, -2, 2, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ 2, -1, 1, -2, 1, 0 ], [ "Rest" ] ], 0.75 ], + [ [ [ 0, -1, 1, -2, 2, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ 2, -2, 1, -2, 2, 0 ], [ "Rest" ] ], 0.75 ], + [ [ [ -1, -1, 1, -1, 2, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ 2, -2, 1, -2, 2, 0 ], [ "Rest" ] ], 0.625 ], + [ [ [ 0, -1, 0, -2, 2, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ 2, -2, 1, -2, 2, 0 ], [ "Rest" ] ], 1.25 ], + [ [ [ 0, -1, 0, -2, 2, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ 2, -1, 1, -2, 2, -1 ], [ "Rest" ] ], 2.25 ], + [ [ [ 0, -1, 0, -2, 2, 0 ], [ "Rest" ], [ 2, -1, 1, -2, 2, -1 ], [ "Rest" ] ], 1.5 ], + [ [ [ 0, -1, 0, -2, 2, 0 ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 0.875 ], + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 3.0 ] + ] + ] +], +"last_changes": +[ + [ [ 0, -1, 1, -2, 2, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ 2, -1, 1, -2, 1, 0 ], [ 1, 0, 1, -2, 1, 1 ] ], + [ [ 0, -1, 1, -2, 2, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ 2, -2, 1, -2, 2, 0 ], [ 1, 0, 1, -2, 1, 1 ] ], + [ [ -1, -1, 1, -1, 2, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ 2, -2, 1, -2, 2, 0 ], [ 1, 0, 1, -2, 1, 1 ] ], + [ [ 0, -1, 0, -2, 2, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ 2, -2, 1, -2, 2, 0 ], [ 1, 0, 1, -2, 1, 1 ] ], + [ [ 0, -1, 0, -2, 2, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ 2, -1, 1, -2, 2, -1 ], [ 1, 0, 1, -2, 1, 1 ] ] +], +"cur_uid": "52c9a980", +"ref_uid": "6d635e88", +"order_seed": 516056, +"dur_seed": 358555, +"motifs_seed": 210544, +"entrances_probs_vals": [ 0.18, 0.28, 1.4285714285714, 0.82, 2.0054945054945, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 0.29, 0, 1.1111111111111, 0.65934065934066, 1.37, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 0.18, 0.28, 1.4285714285714, 0.82, 2.0054945054945, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"ranges": [ [ -2411.1455108359, -850.77399380805 ], [ -1872, 450 ], [ -479, 1304.0247678019 ], [ -368, 1173.9938080495 ] ], +"step_probs_vals": [ 0, 1200, 0, 0, 0.082304526748971, 0.99431818181818, 0.33950617283951, 0, 0.72839506172839, 0, 1, 0 ], +"passages_weights": [ 0.35, 0.42, 0.75, 0.9, 0.93 ], +"hd_exp": 2, +"hd_invert": 0, +"order": +[ + [ [ 3 ], [ 2 ], [ 0, 1 ] ], + [ [ 1 ], [ 2, 0, 2, 0, 0, 2 ], [ 3 ] ] +], +"sus_weights": [ 0.41, 0, 0 ], +"order_size": [ 2, 6 ], +"passages_size": [ 0, 5 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/piece_ledger_sq1_candidates_stitch/52c9a980/lilypond/part_I.ly b/resources/piece_ledger_sq1_candidates_stitch/52c9a980/lilypond/part_I.ly new file mode 100644 index 0000000..6a62992 --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/52c9a980/lilypond/part_I.ly @@ -0,0 +1,20 @@ +{ + { ais'1^\markup { \pad-markup #0.2 "+41"} ~ } + \bar "|" + { ais'2. r4 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/piece_ledger_sq1_candidates_stitch/52c9a980/lilypond/part_II.ly b/resources/piece_ledger_sq1_candidates_stitch/52c9a980/lilypond/part_II.ly new file mode 100644 index 0000000..af4f360 --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/52c9a980/lilypond/part_II.ly @@ -0,0 +1,20 @@ +{ + { r1 } + \bar "|" + { gis'1^\markup { \pad-markup #0.2 "+9"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 7↑" }} ~ } + \bar "|" + { gis'2 g'4^\markup { \pad-markup #0.2 "-2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 11↓" }} ~ g'8[ f'8^\markup { \pad-markup #0.2 "+47"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↓" }}] ~ } + \bar "|" + { f'1 ~ } + \bar "|" + { f'8.[ e'16^\markup { \pad-markup #0.2 "+9"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 13↓" }}] ~ e'2. ~ } + \bar "|" + { e'1 ~ } + \bar "|" + { e'16[ r8.] r2. } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/piece_ledger_sq1_candidates_stitch/52c9a980/lilypond/part_III.ly b/resources/piece_ledger_sq1_candidates_stitch/52c9a980/lilypond/part_III.ly new file mode 100644 index 0000000..12194b7 --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/52c9a980/lilypond/part_III.ly @@ -0,0 +1,20 @@ +{ + { r1 } + \bar "|" + { r2. c'4^\markup { \pad-markup #0.2 "+49"} ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'4 ~ c'16[ r8.] r2 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/piece_ledger_sq1_candidates_stitch/52c9a980/lilypond/part_IV.ly b/resources/piece_ledger_sq1_candidates_stitch/52c9a980/lilypond/part_IV.ly new file mode 100644 index 0000000..9cbcd88 --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/52c9a980/lilypond/part_IV.ly @@ -0,0 +1,20 @@ +{ + { r1 } + \bar "|" + { r1 } + \bar "|" + { r2 c2^\markup { \pad-markup #0.2 "+49"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }} ~ } + \bar "|" + { c4 ais,4^\markup { \pad-markup #0.2 "+18"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 7↑" }} ~ ais,16[ a,8.^\markup { \pad-markup #0.2 "-37"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 5↓" }}] ~ a,4 ~ } + \bar "|" + { a,1 ~ } + \bar "|" + { a,1 ~ } + \bar "|" + { a,2 r2 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/piece_ledger_sq1_candidates_stitch/62820081/62820081_code.scd b/resources/piece_ledger_sq1_candidates_stitch/62820081/62820081_code.scd new file mode 100644 index 0000000..a98b916 --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/62820081/62820081_code.scd @@ -0,0 +1,945 @@ +( +// 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(array1) - hsArrayToCents.value(array2); + 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); +}; + +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 = 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 - minMotifLength).rand + minMotifLength).collect({ + var noProgIns, noSusIns, noSilentIns, prog, sus, silent, order; + noSusIns = [1, 2, 3].wchoose(susWeights.normalizeSum); + noProgIns = (popSize - noSusIns).rand + 1; + noSilentIns = popSize - noSusIns - noProgIns; + + # prog, sus, silent = (0..(popSize-1)).scramble.clumps([noProgIns, noSusIns, noSilentIns]); + + prog = (prog.scramble ++ ((maxProgLength - minProgLength).rand + minProgLength).collect({prog.choose}).scramble); + 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; + 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); + 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]}) + }); + nProbs = nProbs.flop.collect({arg scores, s; scores.product}).normalizeSum; + + 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}); + 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; + 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"], {(60.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(mSeq.maxDepth - 5).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/piece_ledger_sq1_candidates_stitch/62820081/62820081_mus_model.json b/resources/piece_ledger_sq1_candidates_stitch/62820081/62820081_mus_model.json new file mode 100644 index 0000000..ab5d8c3 --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/62820081/62820081_mus_model.json @@ -0,0 +1,48 @@ +{ +"music_data": +[ + [ + [ + [ [ [ "Rest" ], [ 2, -2, 0, -1, 1, 0 ], [ "Rest" ], [ "Rest" ] ], 1.5 ], + [ [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ "Rest" ], [ "Rest" ] ], 4.375 ], + [ [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ "Rest" ], [ 1, -1, 0, 0, 1, 0 ] ], 1.625 ], + [ [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ 2, -1, -1, -1, 1, 0 ], [ 1, -1, 0, 0, 1, 0 ] ], 1.25 ], + [ [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ 2, -1, -1, -1, 1, 0 ], [ 2, -2, 1, -1, 1, 0 ] ], 3.375 ], + [ [ [ "Rest" ], [ 2, -2, 0, -1, 1, 0 ], [ 2, -1, -1, -1, 1, 0 ], [ 2, -2, 1, -1, 1, 0 ] ], 1.625 ], + [ [ [ "Rest" ], [ "Rest" ], [ 2, -1, -1, -1, 1, 0 ], [ 2, -2, 1, -1, 1, 0 ] ], 1.625 ], + [ [ [ "Rest" ], [ "Rest" ], [ 2, -1, -1, -1, 1, 0 ], [ "Rest" ] ], 0.875 ], + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 10.0 ] + ] + ] +], +"last_changes": +[ + [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ 1, 0, 0, -1, 1, 0 ], [ 2, -1, -1, -1, 1, 0 ] ], + [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ 1, 0, 0, -1, 1, 0 ], [ 3, -2, 0, -1, 1, -1 ] ], + [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ 1, 0, 0, -1, 1, 0 ], [ 1, -1, 0, 0, 1, 0 ] ], + [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ 2, -1, -1, -1, 1, 0 ], [ 1, -1, 0, 0, 1, 0 ] ], + [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ 2, -1, -1, -1, 1, 0 ], [ 2, -2, 1, -1, 1, 0 ] ] +], +"cur_uid": "62820081", +"ref_uid": "79e0a4a7", +"order_seed": 216475, +"dur_seed": 914627, +"motifs_seed": 252655, +"entrances_probs_vals": [ 0, 1.1904761904762, 3.3333333333333, 0.71, 1.92, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 0, 1.1904761904762, 3.3333333333333, 0.71, 1.92, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 0, 1.1904761904762, 3.3333333333333, 0.71, 1.92, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"ranges": [ [ -3600, -312 ], [ -1872, 1378 ], [ -145, 1583 ], [ -182, 1527 ] ], +"step_probs_vals": [ 0, 1200, 0, 0, 0.082304526748971, 0.99431818181818, 0.14197530864198, 0, 1, 0 ], +"passages_weights": [ 0.75, 0.75, 0.75, 0.75, 0.75 ], +"hd_exp": 2, +"hd_invert": 0, +"order": +[ + [ [ 1, 0 ], [ 3, 2, 3 ], [ ] ] +], +"sus_weights": [ 0, 0.65, 0 ], +"order_size": [ 1, 1 ], +"passages_size": [ 1, 6 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/piece_ledger_sq1_candidates_stitch/631e2af1/631e2af1_code.scd b/resources/piece_ledger_sq1_candidates_stitch/631e2af1/631e2af1_code.scd new file mode 100644 index 0000000..8e9fe42 --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/631e2af1/631e2af1_code.scd @@ -0,0 +1,943 @@ +( +// 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(array1) - hsArrayToCents.value(array2); + 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); +}; + +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 = 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 - minMotifLength).rand + minMotifLength).collect({ + var noProgIns, noSusIns, noSilentIns, prog, sus, silent, order; + noSusIns = [1, 2, 3].wchoose(susWeights.normalizeSum); + noProgIns = (popSize - noSusIns).rand + 1; + noSilentIns = popSize - noSusIns - noProgIns; + + # prog, sus, silent = (0..(popSize-1)).scramble.clumps([noProgIns, noSusIns, noSilentIns]); + + prog = (prog.scramble ++ ((maxProgLength - minProgLength).rand + minProgLength).collect({prog.choose}).scramble); + 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; + 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); + 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]}) + }); + nProbs = nProbs.flop.collect({arg scores, s; scores.product}).normalizeSum; + + 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}); + 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; + 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"], {(60.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(mSeq.maxDepth - 5).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; + 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/piece_ledger_sq1_candidates_stitch/631e2af1/631e2af1_mus_model.json b/resources/piece_ledger_sq1_candidates_stitch/631e2af1/631e2af1_mus_model.json new file mode 100644 index 0000000..78ae751 --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/631e2af1/631e2af1_mus_model.json @@ -0,0 +1,48 @@ +{ +"music_data": +[ + [ + [ + [ [ [ "Rest" ], [ 2, -2, 0, -1, 1, 0 ], [ "Rest" ], [ "Rest" ] ], 1.125 ], + [ [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ "Rest" ], [ "Rest" ] ], 4.375 ], + [ [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ "Rest" ], [ 1, -1, 0, 0, 1, 0 ] ], 1.375 ], + [ [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ 1, -1, 0, -1, 2, 0 ], [ 1, -1, 0, 0, 1, 0 ] ], 1.25 ], + [ [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ 1, -1, 0, -1, 2, 0 ], [ 2, -2, 0, -1, 2, 0 ] ], 2.375 ], + [ [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ 1, -1, 0, -1, 2, 0 ], [ "Rest" ] ], 1.75 ], + [ [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ "Rest" ], [ "Rest" ] ], 1.125 ], + [ [ [ "Rest" ], [ 2, -2, 0, -1, 1, 0 ], [ "Rest" ], [ "Rest" ] ], 1.625 ], + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 5.0 ] + ] + ] +], +"last_changes": +[ + [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ 1, 0, 0, -1, 1, 0 ], [ 2, -1, -1, -1, 1, 0 ] ], + [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ 1, 0, 0, -1, 1, 0 ], [ 3, -2, 0, -1, 1, -1 ] ], + [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ 1, 0, 0, -1, 1, 0 ], [ 1, -1, 0, 0, 1, 0 ] ], + [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ 1, -1, 0, -1, 2, 0 ], [ 1, -1, 0, 0, 1, 0 ] ], + [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ 1, -1, 0, -1, 2, 0 ], [ 2, -2, 0, -1, 2, 0 ] ] +], +"cur_uid": "631e2af1", +"ref_uid": "79e0a4a7", +"order_seed": 216475, +"dur_seed": 698877, +"motifs_seed": 336611, +"entrances_probs_vals": [ 0, 1.1904761904762, 3.3333333333333, 0.71, 1.92, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 0, 1.1904761904762, 3.3333333333333, 0.71, 1.92, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 0, 1.1904761904762, 3.3333333333333, 0.71, 1.92, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"ranges": [ [ -3600, -312 ], [ -1872, 1378 ], [ -145, 1583 ], [ -182, 1527 ] ], +"step_probs_vals": [ 0, 1200, 0, 0, 0.082304526748971, 0.99431818181818, 0.14197530864198, 0, 1, 0 ], +"passages_weights": [ 0.75, 0.75, 0.75, 0.75, 0.75 ], +"hd_exp": 2, +"hd_invert": 0, +"order": +[ + [ [ 1, 0 ], [ 3, 2, 3 ], [ ] ] +], +"sus_weights": [ 0, 0.65, 0 ], +"order_size": [ 1, 1 ], +"passages_size": [ 1, 6 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/piece_ledger_sq1_candidates_stitch/6d635e88/6d635e88_code.scd b/resources/piece_ledger_sq1_candidates_stitch/6d635e88/6d635e88_code.scd new file mode 100644 index 0000000..a98b916 --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/6d635e88/6d635e88_code.scd @@ -0,0 +1,945 @@ +( +// 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(array1) - hsArrayToCents.value(array2); + 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); +}; + +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 = 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 - minMotifLength).rand + minMotifLength).collect({ + var noProgIns, noSusIns, noSilentIns, prog, sus, silent, order; + noSusIns = [1, 2, 3].wchoose(susWeights.normalizeSum); + noProgIns = (popSize - noSusIns).rand + 1; + noSilentIns = popSize - noSusIns - noProgIns; + + # prog, sus, silent = (0..(popSize-1)).scramble.clumps([noProgIns, noSusIns, noSilentIns]); + + prog = (prog.scramble ++ ((maxProgLength - minProgLength).rand + minProgLength).collect({prog.choose}).scramble); + 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; + 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); + 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]}) + }); + nProbs = nProbs.flop.collect({arg scores, s; scores.product}).normalizeSum; + + 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}); + 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; + 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"], {(60.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(mSeq.maxDepth - 5).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/piece_ledger_sq1_candidates_stitch/6d635e88/6d635e88_mus_model.json b/resources/piece_ledger_sq1_candidates_stitch/6d635e88/6d635e88_mus_model.json new file mode 100644 index 0000000..228456c --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/6d635e88/6d635e88_mus_model.json @@ -0,0 +1,95 @@ +{ +"music_data": +[ + [ + [ + [ [ [ 1, -2, 1, -2, 1, 0 ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 1.625 ], + [ [ [ 1, -2, 1, -2, 1, 0 ], [ "Rest" ], [ 2, -1, 1, -2, 1, 0 ], [ "Rest" ] ], 3.125 ], + [ [ [ 1, -2, 1, -2, 1, 0 ], [ 3, -2, 1, -2, 0, 0 ], [ 2, -1, 1, -2, 1, 0 ], [ "Rest" ] ], 1.125 ], + [ [ [ 1, -2, 1, -2, 1, 0 ], [ 3, -2, 0, -2, 1, 0 ], [ 2, -1, 1, -2, 1, 0 ], [ "Rest" ] ], 3.5 ] + ], + [ + [ [ [ 1, -2, 1, -2, 1, 0 ], [ 3, -2, 0, -2, 1, 0 ], [ 2, -1, 1, -2, 1, 0 ], [ 2, -2, 1, -1, 1, 0 ] ], 3.25 ], + [ [ [ 1, -2, 1, -2, 1, 0 ], [ 1, -2, 1, -1, 1, 1 ], [ 2, -1, 1, -2, 1, 0 ], [ 2, -2, 1, -1, 1, 0 ] ], 1.375 ], + [ [ [ 1, -2, 1, -2, 1, 0 ], [ 1, -1, 1, -1, 1, 0 ], [ 2, -1, 1, -2, 1, 0 ], [ 2, -2, 1, -1, 1, 0 ] ], 2.75 ] + ], + [ + [ [ [ 1, -2, 1, -2, 1, 0 ], [ 3, -2, 1, -2, 1, -1 ], [ 2, -1, 1, -2, 1, 0 ], [ 2, -2, 1, -1, 1, 0 ] ], 1.875 ], + [ [ [ 1, -2, 1, -2, 1, 0 ], [ 1, 0, 1, -2, 1, 0 ], [ 2, -1, 1, -2, 1, 0 ], [ 2, -2, 1, -1, 1, 0 ] ], 2.875 ] + ], + [ + [ [ [ "Rest" ], [ 1, 0, 1, -2, 1, 0 ], [ 2, -1, 1, -2, 1, 0 ], [ 2, -2, 1, -1, 1, 0 ] ], 2 ], + [ [ [ "Rest" ], [ 1, -2, 1, -1, 2, 0 ], [ 2, -1, 1, -2, 1, 0 ], [ 2, -2, 1, -1, 1, 0 ] ], 3.125 ] + ], + [ + [ [ [ "Rest" ], [ "Rest" ], [ 2, -1, 1, -2, 1, 0 ], [ 2, -2, 1, -1, 1, 0 ] ], 1.25 ], + [ [ [ 0, 0, 1, -2, 1, 0 ], [ "Rest" ], [ 2, -1, 1, -2, 1, 0 ], [ 2, -2, 1, -1, 1, 0 ] ], 3.875 ] + ], + [ + [ [ [ 0, 0, 1, -2, 1, 0 ], [ 1, -2, 1, -1, 2, 0 ], [ 2, -1, 1, -2, 1, 0 ], [ 2, -2, 1, -1, 1, 0 ] ], 1.875 ], + [ [ [ 0, 0, 1, -2, 1, 0 ], [ 1, -2, 1, -1, 2, 0 ], [ 1, 0, 1, -2, 2, 0 ], [ 2, -2, 1, -1, 1, 0 ] ], 2 ], + [ [ [ 0, 0, 1, -2, 1, 0 ], [ 1, -2, 1, -1, 2, 0 ], [ 2, -2, 1, -2, 2, 0 ], [ 2, -2, 1, -1, 1, 0 ] ], 2.75 ] + ], + [ + [ [ [ 0, 0, 1, -2, 1, 0 ], [ 1, -2, 1, -1, 2, 0 ], [ 2, -2, 1, -2, 2, 0 ], [ 1, 0, 1, -1, 1, 0 ] ], 1.875 ], + [ [ [ 0, 0, 1, -2, 1, 0 ], [ 1, -2, 1, -1, 2, 0 ], [ 2, -2, 1, -2, 2, 0 ], [ 1, 0, 1, -2, 1, 1 ] ], 2.875 ] + ], + [ + [ [ [ 0, 0, 1, -2, 1, 0 ], [ 2, -2, 0, -2, 2, 0 ], [ 2, -2, 1, -2, 2, 0 ], [ 1, 0, 1, -2, 1, 1 ] ], 1.25 ], + [ [ [ 0, 0, 1, -2, 1, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ 2, -2, 1, -2, 2, 0 ], [ 1, 0, 1, -2, 1, 1 ] ], 3.25 ] + ], + [ + [ [ [ 0, 0, 1, -2, 1, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ 0, 1, 1, -2, 1, 1 ], [ 1, 0, 1, -2, 1, 1 ] ], 1.125 ], + [ [ [ 0, -1, 1, -2, 1, 1 ], [ 1, -1, 1, -2, 2, 0 ], [ 0, 1, 1, -2, 1, 1 ], [ 1, 0, 1, -2, 1, 1 ] ], 2 ], + [ [ [ -1, 1, 1, -2, 1, 1 ], [ 1, -1, 1, -2, 2, 0 ], [ 0, 1, 1, -2, 1, 1 ], [ 1, 0, 1, -2, 1, 1 ] ], 4 ] + ], + [ + [ [ [ -1, 0, 1, -2, 2, 1 ], [ 1, -1, 1, -2, 2, 0 ], [ 0, 1, 1, -2, 1, 1 ], [ 1, 0, 1, -2, 1, 1 ] ], 1.875 ], + [ [ [ 1, -1, 1, -3, 2, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ 0, 1, 1, -2, 1, 1 ], [ 1, 0, 1, -2, 1, 1 ] ], 2.25 ], + [ [ [ 1, -1, 1, -3, 2, 0 ], [ "Rest" ], [ 0, 1, 1, -2, 1, 1 ], [ 1, 0, 1, -2, 1, 1 ] ], 1.75 ], + [ [ [ 1, -1, 1, -3, 2, 0 ], [ "Rest" ], [ "Rest" ], [ 1, 0, 1, -2, 1, 1 ] ], 1.75 ], + [ [ [ 1, -1, 1, -3, 2, 0 ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 1.625 ], + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 4.0 ] + ] + ] +], +"last_changes": +[ + [ [ 0, 0, 1, -2, 1, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ 0, 1, 1, -2, 1, 1 ], [ 1, 0, 1, -2, 1, 1 ] ], + [ [ 0, -1, 1, -2, 1, 1 ], [ 1, -1, 1, -2, 2, 0 ], [ 0, 1, 1, -2, 1, 1 ], [ 1, 0, 1, -2, 1, 1 ] ], + [ [ -1, 1, 1, -2, 1, 1 ], [ 1, -1, 1, -2, 2, 0 ], [ 0, 1, 1, -2, 1, 1 ], [ 1, 0, 1, -2, 1, 1 ] ], + [ [ -1, 0, 1, -2, 2, 1 ], [ 1, -1, 1, -2, 2, 0 ], [ 0, 1, 1, -2, 1, 1 ], [ 1, 0, 1, -2, 1, 1 ] ], + [ [ 1, -1, 1, -3, 2, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ 0, 1, 1, -2, 1, 1 ], [ 1, 0, 1, -2, 1, 1 ] ] +], +"cur_uid": "6d635e88", +"ref_uid": "6ed95c4c", +"order_seed": 526896, +"dur_seed": 815251, +"motifs_seed": 342685, +"entrances_probs_vals": [ 0, 0.91269841269841, 2.8571428571429, 0.71, 1.92, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 0, 0.63, 2.1031746031746, 0.98901098901099, 2.23, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 0, 0.91269841269841, 2.8571428571429, 0.71, 1.92, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"ranges": [ [ -2894.1176470588, -312 ], [ -1872, 1378 ], [ -145, 1583 ], [ -182, 1527 ] ], +"step_probs_vals": [ 0, 1200, 0, 0, 0.082304526748971, 0.99431818181818, 0.23045267489712, 1.1102230246252e-16, 0.61522633744856, 0, 1, 0 ], +"passages_weights": [ 0.75, 0.75, 0.75, 0.75, 0.8 ], +"hd_exp": 2, +"hd_invert": 0, +"order": +[ + [ [ 0, 2 ], [ 1, 1 ], [ 3 ] ], + [ [ 0, 2, 3 ], [ 1, 1 ], [ ] ], + [ [ 2, 0, 3 ], [ 1, 1 ], [ ] ], + [ [ 2, 3 ], [ 1 ], [ 0 ] ], + [ [ 3, 2 ], [ 0 ], [ 1 ] ], + [ [ 1, 3, 0 ], [ 2, 2 ], [ ] ], + [ [ 0, 2, 1 ], [ 3, 3 ], [ ] ], + [ [ 2, 0, 3 ], [ 1, 1 ], [ ] ], + [ [ 3, 1 ], [ 2, 0, 0 ], [ ] ], + [ [ 2, 3, 1 ], [ 0, 0 ], [ ] ] +], +"sus_weights": [ 0, 0.31, 0.55 ], +"order_size": [ 10, 10 ], +"passages_size": [ 0, 2 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/piece_ledger_sq1_candidates_stitch/6d635e88/lilypond/part_I.ly b/resources/piece_ledger_sq1_candidates_stitch/6d635e88/lilypond/part_I.ly new file mode 100644 index 0000000..02f9fcb --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/6d635e88/lilypond/part_I.ly @@ -0,0 +1,72 @@ +{ + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r2 r8.[ ais'16^\markup { \pad-markup #0.2 "-35"}] ~ ais'4 ~ } + \bar "|" + { ais'1 ~ } + \bar "|" + { ais'1 ~ } + \bar "|" + { ais'1 ~ } + \bar "|" + { ais'1 ~ } + \bar "|" + { ais'1 ~ } + \bar "|" + { ais'1 ~ } + \bar "|" + { ais'1 ~ } + \bar "|" + { ais'1 ~ } + \bar "|" + { ais'1 ~ } + \bar "|" + { ais'1 ~ } + \bar "|" + { ais'1 ~ } + \bar "|" + { ais'1 ~ } + \bar "|" + { ais'1 ~ } + \bar "|" + { ais'1 ~ } + \bar "|" + { ais'8.[ c''16^\markup { \pad-markup #0.2 "-31"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 7↑" }}] ~ c''2. ~ } + \bar "|" + { c''8[ ais'8^\markup { \pad-markup #0.2 "+41"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 13↑" }}] ~ ais'2. ~ } + \bar "|" + { ais'1 ~ } + \bar "|" + { ais'1 ~ } + \bar "|" + { ais'1 ~ } + \bar "|" + { ais'1 ~ } + \bar "|" + { ais'1 ~ } + \bar "|" + { ais'1 ~ } + \bar "|" + { ais'1 ~ } + \bar "|" + { ais'1 ~ } + \bar "|" + { ais'1 ~ } + \bar "|" + { ais'1 ~ } + \bar "|" + { ais'8.[ r16] r2. } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/piece_ledger_sq1_candidates_stitch/6d635e88/lilypond/part_II.ly b/resources/piece_ledger_sq1_candidates_stitch/6d635e88/lilypond/part_II.ly new file mode 100644 index 0000000..7f12f94 --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/6d635e88/lilypond/part_II.ly @@ -0,0 +1,72 @@ +{ + { r2. r16[ g'8.^\markup { \pad-markup #0.2 "-2"}] ~ } + \bar "|" + { g'1 ~ } + \bar "|" + { g'1 ~ } + \bar "|" + { g'1 ~ } + \bar "|" + { g'1 ~ } + \bar "|" + { g'1 ~ } + \bar "|" + { g'1 ~ } + \bar "|" + { g'1 ~ } + \bar "|" + { g'1 ~ } + \bar "|" + { g'1 ~ } + \bar "|" + { g'1 ~ } + \bar "|" + { g'1 ~ } + \bar "|" + { g'1 ~ } + \bar "|" + { g'1 ~ } + \bar "|" + { g'1 ~ } + \bar "|" + { g'1 ~ } + \bar "|" + { g'2. ~ g'16[ gis'8.^\markup { \pad-markup #0.2 "-49"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 11↑" }}] ~ } + \bar "|" + { gis'2. ~ gis'16[ f'8.^\markup { \pad-markup #0.2 "+47"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 7↓" }}] ~ } + \bar "|" + { f'1 ~ } + \bar "|" + { f'1 ~ } + \bar "|" + { f'1 ~ } + \bar "|" + { f'1 ~ } + \bar "|" + { f'1 ~ } + \bar "|" + { f'2. ~ f'16[ f'8.^\markup { \pad-markup #0.2 "+42"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 3↑" }}] ~ } + \bar "|" + { f'1 ~ } + \bar "|" + { f'1 ~ } + \bar "|" + { f'1 ~ } + \bar "|" + { f'1 ~ } + \bar "|" + { f'1 ~ } + \bar "|" + { f'1 ~ } + \bar "|" + { f'4 ~ f'16[ r8.] r2 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/piece_ledger_sq1_candidates_stitch/6d635e88/lilypond/part_III.ly b/resources/piece_ledger_sq1_candidates_stitch/6d635e88/lilypond/part_III.ly new file mode 100644 index 0000000..b953351 --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/6d635e88/lilypond/part_III.ly @@ -0,0 +1,72 @@ +{ + { r1 } + \bar "|" + { r1 } + \bar "|" + { r4 r8[ fis'8^\markup { \pad-markup #0.2 "+45"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 11↓" }}] ~ fis'4 ~ fis'8.[ gis'16^\markup { \pad-markup #0.2 "+10"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↓" }}] ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'4 ~ gis'16[ fis'8.^\markup { \pad-markup #0.2 "+5"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 13↑" }}] ~ fis'2 } + \bar "|" + { f'1^\markup { \pad-markup #0.2 "-33"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 7↑" }} ~ } + \bar "|" + { f'4 ~ f'8[ e'8^\markup { \pad-markup #0.2 "-44"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 13↓" }}] ~ e'2 ~ } + \bar "|" + { e'4 ~ e'16[ d'8.^\markup { \pad-markup #0.2 "+0"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 3↑" }}] ~ d'2 ~ } + \bar "|" + { d'1 ~ } + \bar "|" + { d'2. dis'4^\markup { \pad-markup #0.2 "+16"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 11↑" }} ~ } + \bar "|" + { dis'1 ~ } + \bar "|" + { dis'4 ~ dis'16[ r8.] r2 } + \bar "|" + { r1 } + \bar "|" + { r2. r8[ dis'8^\markup { \pad-markup #0.2 "+16"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 11↑" }}] ~ } + \bar "|" + { dis'1 ~ } + \bar "|" + { dis'1 ~ } + \bar "|" + { dis'1 ~ } + \bar "|" + { dis'1 ~ } + \bar "|" + { dis'1 ~ } + \bar "|" + { dis'2 ~ dis'16[ d'8.^\markup { \pad-markup #0.2 "-39"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 5↓" }}] ~ d'4 ~ } + \bar "|" + { d'8.[ c'16^\markup { \pad-markup #0.2 "+49"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 3↑" }}] ~ c'2. ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'4 ~ c'8.[ r16] r2 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/piece_ledger_sq1_candidates_stitch/6d635e88/lilypond/part_IV.ly b/resources/piece_ledger_sq1_candidates_stitch/6d635e88/lilypond/part_IV.ly new file mode 100644 index 0000000..48eb158 --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/6d635e88/lilypond/part_IV.ly @@ -0,0 +1,72 @@ +{ + { c1^\markup { \pad-markup #0.2 "-4"} ~ } + \bar "|" + { c1 ~ } + \bar "|" + { c1 ~ } + \bar "|" + { c1 ~ } + \bar "|" + { c1 ~ } + \bar "|" + { c1 ~ } + \bar "|" + { c1 ~ } + \bar "|" + { c1 ~ } + \bar "|" + { c1 ~ } + \bar "|" + { c1 ~ } + \bar "|" + { c2. r4 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r2. r8.[ d16^\markup { \pad-markup #0.2 "+0"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 3↑" }}] ~ } + \bar "|" + { d1 ~ } + \bar "|" + { d1 ~ } + \bar "|" + { d1 ~ } + \bar "|" + { d1 ~ } + \bar "|" + { d1 ~ } + \bar "|" + { d1 ~ } + \bar "|" + { d1 ~ } + \bar "|" + { d1 ~ } + \bar "|" + { d1 ~ } + \bar "|" + { d1 ~ } + \bar "|" + { d4 ~ d8[ dis8^\markup { \pad-markup #0.2 "+39"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 3↓" }}] ~ dis2 ~ } + \bar "|" + { dis4 ~ dis8[ f8^\markup { \pad-markup #0.2 "+42"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 1↑" }}] ~ f2 ~ } + \bar "|" + { f1 ~ } + \bar "|" + { f4 ~ f8[ e8^\markup { \pad-markup #0.2 "-8"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 11↑" }}] ~ e2 ~ } + \bar "|" + { e4 ~ e16[ dis8.^\markup { \pad-markup #0.2 "-19"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 7↓" }}] ~ dis2 ~ } + \bar "|" + { dis1 ~ } + \bar "|" + { dis1 ~ } + \bar "|" + { dis1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/piece_ledger_sq1_candidates_stitch/6ed95c4c/6ed95c4c_code.scd b/resources/piece_ledger_sq1_candidates_stitch/6ed95c4c/6ed95c4c_code.scd new file mode 100644 index 0000000..a98b916 --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/6ed95c4c/6ed95c4c_code.scd @@ -0,0 +1,945 @@ +( +// 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(array1) - hsArrayToCents.value(array2); + 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); +}; + +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 = 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 - minMotifLength).rand + minMotifLength).collect({ + var noProgIns, noSusIns, noSilentIns, prog, sus, silent, order; + noSusIns = [1, 2, 3].wchoose(susWeights.normalizeSum); + noProgIns = (popSize - noSusIns).rand + 1; + noSilentIns = popSize - noSusIns - noProgIns; + + # prog, sus, silent = (0..(popSize-1)).scramble.clumps([noProgIns, noSusIns, noSilentIns]); + + prog = (prog.scramble ++ ((maxProgLength - minProgLength).rand + minProgLength).collect({prog.choose}).scramble); + 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; + 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); + 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]}) + }); + nProbs = nProbs.flop.collect({arg scores, s; scores.product}).normalizeSum; + + 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}); + 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; + 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"], {(60.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(mSeq.maxDepth - 5).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/piece_ledger_sq1_candidates_stitch/6ed95c4c/6ed95c4c_mus_model.json b/resources/piece_ledger_sq1_candidates_stitch/6ed95c4c/6ed95c4c_mus_model.json new file mode 100644 index 0000000..25c21a6 --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/6ed95c4c/6ed95c4c_mus_model.json @@ -0,0 +1,67 @@ +{ +"music_data": +[ + [ + [ + [ [ [ "Rest" ], [ 2, -2, 0, -1, 1, 0 ], [ "Rest" ], [ "Rest" ] ], 4.125 ], + [ [ [ "Rest" ], [ 2, -2, 0, -1, 1, 0 ], [ "Rest" ], [ 2, -2, 0, -1, 1, 1 ] ], 1.375 ], + [ [ [ "Rest" ], [ 2, -2, 0, -1, 1, 0 ], [ 3, -2, 0, -2, 1, 0 ], [ 2, -2, 0, -1, 1, 1 ] ], 1.25 ], + [ [ [ "Rest" ], [ 2, -2, 0, -1, 1, 0 ], [ 3, -2, 0, -2, 1, 0 ], [ 2, -1, 0, -1, 1, 0 ] ], 1.125 ], + [ [ [ "Rest" ], [ 2, -2, 0, -1, 1, 0 ], [ 2, -2, 1, -1, 1, 0 ], [ 2, -1, 0, -1, 1, 0 ] ], 3.5 ] + ], + [ + [ [ [ "Rest" ], [ 2, -2, 0, -1, 1, 0 ], [ 2, -2, 1, -1, 1, 0 ], [ "Rest" ] ], 1.5 ], + [ [ [ 1, -3, 1, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ 2, -2, 1, -1, 1, 0 ], [ "Rest" ] ], 1.625 ], + [ [ [ 1, -3, 1, -1, 1, 0 ], [ 1, -2, 1, -1, 1, 1 ], [ 2, -2, 1, -1, 1, 0 ], [ "Rest" ] ], 2.25 ], + [ [ [ 1, -3, 1, -1, 1, 0 ], [ 1, -1, 1, -1, 1, 0 ], [ 2, -2, 1, -1, 1, 0 ], [ "Rest" ] ], 1.5 ], + [ [ [ 0, -2, 2, -1, 1, 0 ], [ 1, -1, 1, -1, 1, 0 ], [ 2, -2, 1, -1, 1, 0 ], [ "Rest" ] ], 2.125 ], + [ [ [ 1, -2, 1, -2, 1, 0 ], [ 1, -1, 1, -1, 1, 0 ], [ 2, -2, 1, -1, 1, 0 ], [ "Rest" ] ], 4.25 ] + ], + [ + [ [ [ 1, -2, 1, -2, 1, 0 ], [ 1, -1, 1, -1, 1, 0 ], [ 3, -2, 0, -2, 1, 0 ], [ "Rest" ] ], 2 ], + [ [ [ 1, -2, 1, -2, 1, 0 ], [ 3, -2, 1, -2, 1, -1 ], [ 3, -2, 0, -2, 1, 0 ], [ "Rest" ] ], 1.75 ], + [ [ [ 1, -2, 1, -2, 1, 0 ], [ 3, -2, 1, -2, 1, -1 ], [ 3, -2, 0, -2, 1, 0 ], [ 3, -2, 1, -2, 1, 0 ] ], 1 ], + [ [ [ 1, -2, 1, -2, 1, 0 ], [ 3, -2, 1, -2, 1, -1 ], [ 3, -2, 0, -2, 1, 0 ], [ 2, -2, 1, -1, 1, 0 ] ], 1.125 ], + [ [ [ 1, -2, 1, -2, 1, 0 ], [ 2, -2, 2, -2, 1, 0 ], [ 3, -2, 0, -2, 1, 0 ], [ 2, -2, 1, -1, 1, 0 ] ], 1.75 ], + [ [ [ 1, -2, 1, -2, 1, 0 ], [ 3, -3, 1, -2, 1, 0 ], [ 3, -2, 0, -2, 1, 0 ], [ 2, -2, 1, -1, 1, 0 ] ], 2 ], + [ [ [ 1, -2, 1, -2, 1, 0 ], [ 3, -3, 1, -2, 1, 0 ], [ 2, -1, 1, -2, 1, 0 ], [ 2, -2, 1, -1, 1, 0 ] ], 3.875 ], + [ [ [ 1, -2, 1, -2, 1, 0 ], [ 3, -3, 1, -2, 1, 0 ], [ "Rest" ], [ 2, -2, 1, -1, 1, 0 ] ], 1 ], + [ [ [ 1, -2, 1, -2, 1, 0 ], [ 3, -3, 1, -2, 1, 0 ], [ "Rest" ], [ "Rest" ] ], 0.875 ], + [ [ [ "Rest" ], [ 3, -3, 1, -2, 1, 0 ], [ "Rest" ], [ "Rest" ] ], 1.625 ], + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 7.875 ] + ] + ] +], +"last_changes": +[ + [ [ 1, -2, 1, -2, 1, 0 ], [ 3, -2, 1, -2, 1, -1 ], [ 3, -2, 0, -2, 1, 0 ], [ 3, -2, 1, -2, 1, 0 ] ], + [ [ 1, -2, 1, -2, 1, 0 ], [ 3, -2, 1, -2, 1, -1 ], [ 3, -2, 0, -2, 1, 0 ], [ 2, -2, 1, -1, 1, 0 ] ], + [ [ 1, -2, 1, -2, 1, 0 ], [ 2, -2, 2, -2, 1, 0 ], [ 3, -2, 0, -2, 1, 0 ], [ 2, -2, 1, -1, 1, 0 ] ], + [ [ 1, -2, 1, -2, 1, 0 ], [ 3, -3, 1, -2, 1, 0 ], [ 3, -2, 0, -2, 1, 0 ], [ 2, -2, 1, -1, 1, 0 ] ], + [ [ 1, -2, 1, -2, 1, 0 ], [ 3, -3, 1, -2, 1, 0 ], [ 2, -1, 1, -2, 1, 0 ], [ 2, -2, 1, -1, 1, 0 ] ] +], +"cur_uid": "6ed95c4c", +"ref_uid": "4b7745df", +"order_seed": 602538, +"dur_seed": 495773, +"motifs_seed": 128841, +"entrances_probs_vals": [ 0, 1.1904761904762, 3.3333333333333, 0.71, 1.92, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 0, 1.55, 3.452380952381, 0.98901098901099, 2.23, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 0, 1.1904761904762, 3.3333333333333, 0.71, 1.92, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"ranges": [ [ -2894.1176470588, -312 ], [ -1872, 1378 ], [ -145, 1583 ], [ -182, 1527 ] ], +"step_probs_vals": [ 0, 1200, 0, 0, 0.082304526748971, 0.99431818181818, 0.14197530864198, 0, 1, 0 ], +"passages_weights": [ 0.75, 0.75, 0.75, 0.75, 0.8 ], +"hd_exp": 2, +"hd_invert": 0, +"order": +[ + [ [ 1 ], [ 3, 2, 3, 2 ], [ 0 ] ], + [ [ 2 ], [ 0, 1, 1, 0, 0 ], [ 3 ] ], + [ [ 0 ], [ 2, 1, 3, 3, 1, 1, 2 ], [ ] ] +], +"sus_weights": [ 0.66, 0, 0 ], +"order_size": [ 1, 4 ], +"passages_size": [ 2, 5 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/piece_ledger_sq1_candidates_stitch/6ed95c4c/lilypond/part_I.ly b/resources/piece_ledger_sq1_candidates_stitch/6ed95c4c/lilypond/part_I.ly new file mode 100644 index 0000000..fb4ac79 --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/6ed95c4c/lilypond/part_I.ly @@ -0,0 +1,52 @@ +{ + { r1 } + \bar "|" + { r1 } + \bar "|" + { r16[ d''8.^\markup { \pad-markup #0.2 "+19"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 13↑" }}] ~ d''2. ~ } + \bar "|" + { d''4 ~ d''8[ cis''8^\markup { \pad-markup #0.2 "-19"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 3↑" }}] ~ cis''2 ~ } + \bar "|" + { cis''1 ~ } + \bar "|" + { cis''2 ~ cis''8.[ r16] r4 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r8.[ c''16^\markup { \pad-markup #0.2 "-4"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }}] ~ c''4 ~ c''8.[ ais'16^\markup { \pad-markup #0.2 "-35"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 7↑" }}] ~ ais'4 ~ } + \bar "|" + { ais'1 ~ } + \bar "|" + { ais'1 ~ } + \bar "|" + { ais'1 ~ } + \bar "|" + { ais'1 ~ } + \bar "|" + { ais'2 ~ ais'16[ r8.] r4 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/piece_ledger_sq1_candidates_stitch/6ed95c4c/lilypond/part_II.ly b/resources/piece_ledger_sq1_candidates_stitch/6ed95c4c/lilypond/part_II.ly new file mode 100644 index 0000000..607cf33 --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/6ed95c4c/lilypond/part_II.ly @@ -0,0 +1,52 @@ +{ + { r1 } + \bar "|" + { r1 } + \bar "|" + { r2. gis'4^\markup { \pad-markup #0.2 "+10"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 7↓" }} ~ } + \bar "|" + { gis'2. ~ gis'8.[ ais'16^\markup { \pad-markup #0.2 "-35"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 5↑" }}] ~ } + \bar "|" + { ais'1 ~ } + \bar "|" + { ais'1 ~ } + \bar "|" + { ais'1 ~ } + \bar "|" + { ais'1 ~ } + \bar "|" + { ais'1 ~ } + \bar "|" + { ais'1 ~ } + \bar "|" + { ais'1 ~ } + \bar "|" + { ais'1 ~ } + \bar "|" + { ais'4 ~ ais'16[ gis'8.^\markup { \pad-markup #0.2 "+10"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↓" }}] ~ gis'2 ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'8[ g'8^\markup { \pad-markup #0.2 "-2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↑" }}] ~ g'2. ~ } + \bar "|" + { g'1 ~ } + \bar "|" + { g'16[ r8.] r2. } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/piece_ledger_sq1_candidates_stitch/6ed95c4c/lilypond/part_III.ly b/resources/piece_ledger_sq1_candidates_stitch/6ed95c4c/lilypond/part_III.ly new file mode 100644 index 0000000..9c904dc --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/6ed95c4c/lilypond/part_III.ly @@ -0,0 +1,52 @@ +{ + { fis'1^\markup { \pad-markup #0.2 "-21"} ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'4 fis'2.^\markup { \pad-markup #0.2 "+5"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 13↑" }} ~ } + \bar "|" + { fis'4 ~ fis'8[ f'8^\markup { \pad-markup #0.2 "-33"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 3↑" }}] ~ f'2 ~ } + \bar "|" + { f'1 ~ } + \bar "|" + { f'1 ~ } + \bar "|" + { f'1 ~ } + \bar "|" + { f'1 ~ } + \bar "|" + { f'4 ~ f'16[ e'8.^\markup { \pad-markup #0.2 "-44"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 13↓" }}] ~ e'2 ~ } + \bar "|" + { e'1 ~ } + \bar "|" + { e'4 e'2.^\markup { \pad-markup #0.2 "-18"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↑" }} ~ } + \bar "|" + { e'8[ f'8^\markup { \pad-markup #0.2 "-6"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↓" }}] ~ f'2. ~ } + \bar "|" + { f'1 ~ } + \bar "|" + { f'1 ~ } + \bar "|" + { f'1 ~ } + \bar "|" + { f'2. ~ f'16[ r8.] } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/piece_ledger_sq1_candidates_stitch/6ed95c4c/lilypond/part_IV.ly b/resources/piece_ledger_sq1_candidates_stitch/6ed95c4c/lilypond/part_IV.ly new file mode 100644 index 0000000..451ee24 --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/6ed95c4c/lilypond/part_IV.ly @@ -0,0 +1,52 @@ +{ + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r4 r8.[ dis16^\markup { \pad-markup #0.2 "-37"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 3↓" }}] ~ dis2 ~ } + \bar "|" + { dis1 ~ } + \bar "|" + { dis1 ~ } + \bar "|" + { dis8[ d8^\markup { \pad-markup #0.2 "-49"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 5↑" }}] ~ d2. ~ } + \bar "|" + { d8.[ c16^\markup { \pad-markup #0.2 "-4"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 7↓" }}] ~ c2. ~ } + \bar "|" + { c1 ~ } + \bar "|" + { c1 ~ } + \bar "|" + { c1 ~ } + \bar "|" + { c1 ~ } + \bar "|" + { c1 ~ } + \bar "|" + { c1 ~ } + \bar "|" + { c1 ~ } + \bar "|" + { c1 ~ } + \bar "|" + { c1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/piece_ledger_sq1_candidates_stitch/784130cc/784130cc_code.scd b/resources/piece_ledger_sq1_candidates_stitch/784130cc/784130cc_code.scd new file mode 100644 index 0000000..a98b916 --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/784130cc/784130cc_code.scd @@ -0,0 +1,945 @@ +( +// 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(array1) - hsArrayToCents.value(array2); + 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); +}; + +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 = 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 - minMotifLength).rand + minMotifLength).collect({ + var noProgIns, noSusIns, noSilentIns, prog, sus, silent, order; + noSusIns = [1, 2, 3].wchoose(susWeights.normalizeSum); + noProgIns = (popSize - noSusIns).rand + 1; + noSilentIns = popSize - noSusIns - noProgIns; + + # prog, sus, silent = (0..(popSize-1)).scramble.clumps([noProgIns, noSusIns, noSilentIns]); + + prog = (prog.scramble ++ ((maxProgLength - minProgLength).rand + minProgLength).collect({prog.choose}).scramble); + 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; + 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); + 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]}) + }); + nProbs = nProbs.flop.collect({arg scores, s; scores.product}).normalizeSum; + + 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}); + 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; + 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"], {(60.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(mSeq.maxDepth - 5).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/piece_ledger_sq1_candidates_stitch/784130cc/784130cc_mus_model.json b/resources/piece_ledger_sq1_candidates_stitch/784130cc/784130cc_mus_model.json new file mode 100644 index 0000000..697284e --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/784130cc/784130cc_mus_model.json @@ -0,0 +1,53 @@ +{ +"music_data": +[ + [ + [ + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ 1, 0, 1, -2, 1, 1 ] ], 1.75 ], + [ [ [ "Rest" ], [ "Rest" ], [ 1, -1, 1, -2, 1, 1 ], [ 1, 0, 1, -2, 1, 1 ] ], 1.5 ] + ], + [ + [ [ [ "Rest" ], [ "Rest" ], [ 1, -1, 1, -2, 1, 1 ], [ "Rest" ] ], 0 ], + [ [ [ "Rest" ], [ 1, -1, 1, -2, 2, 0 ], [ 1, -1, 1, -2, 1, 1 ], [ "Rest" ] ], 1.5 ], + [ [ [ "Rest" ], [ 1, -1, 1, -2, 2, 0 ], [ 2, -1, 1, -2, 2, -1 ], [ "Rest" ] ], 0 ], + [ [ [ 0, -1, 1, -2, 2, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ 2, -1, 1, -2, 2, -1 ], [ "Rest" ] ], 0.75 ], + [ [ [ 0, -1, 1, -2, 2, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ 1, 0, 1, -2, 2, 0 ], [ "Rest" ] ], 0.75 ], + [ [ [ -1, -1, 1, -2, 2, 1 ], [ 1, -1, 1, -2, 2, 0 ], [ 1, 0, 1, -2, 2, 0 ], [ "Rest" ] ], 1.625 ], + [ [ [ -1, -1, 1, -2, 2, 1 ], [ 1, -1, 1, -2, 2, 0 ], [ "Rest" ], [ "Rest" ] ], 1.125 ], + [ [ [ -1, -1, 1, -2, 2, 1 ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 0.625 ], + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 2.375 ] + ] + ] +], +"last_changes": +[ + [ [ 1, -1, 1, -3, 2, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ 1, -1, 1, -2, 1, 1 ], [ 1, 0, 1, -2, 1, 1 ] ], + [ [ 1, -1, 1, -3, 2, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ 2, -1, 1, -2, 2, -1 ], [ 1, 0, 1, -2, 1, 1 ] ], + [ [ 0, -1, 1, -2, 2, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ 2, -1, 1, -2, 2, -1 ], [ 1, 0, 1, -2, 1, 1 ] ], + [ [ 0, -1, 1, -2, 2, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ 1, 0, 1, -2, 2, 0 ], [ 1, 0, 1, -2, 1, 1 ] ], + [ [ -1, -1, 1, -2, 2, 1 ], [ 1, -1, 1, -2, 2, 0 ], [ 1, 0, 1, -2, 2, 0 ], [ 1, 0, 1, -2, 1, 1 ] ] +], +"cur_uid": "784130cc", +"ref_uid": "6d635e88", +"order_seed": 516056, +"dur_seed": 358555, +"motifs_seed": 830376, +"entrances_probs_vals": [ 0.18, 0.28, 1.4285714285714, 0.47, 1.62, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 0.29, 0, 1.1111111111111, 0.65934065934066, 1.37, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 0.18, 0.28, 1.4285714285714, 0.47, 1.62, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"ranges": [ [ -2411.1455108359, -850.77399380805 ], [ -1872, 450 ], [ -479, 1304.0247678019 ], [ -368, 1173.9938080495 ] ], +"step_probs_vals": [ 0, 1200, 0, 0, 0.082304526748971, 0.99431818181818, 0.33950617283951, 0, 0.72839506172839, 0, 1, 0 ], +"passages_weights": [ 0.35, 0.42, 0.75, 0.9, 0.93 ], +"hd_exp": 2, +"hd_invert": 0, +"order": +[ + [ [ 3 ], [ 2 ], [ 0, 1 ] ], + [ [ 1 ], [ 2, 0, 2, 0 ], [ 3 ] ] +], +"sus_weights": [ 0.41, 0, 0 ], +"order_size": [ 2, 6 ], +"passages_size": [ 0, 5 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/piece_ledger_sq1_candidates_stitch/784130cc/lilypond/part_I.ly b/resources/piece_ledger_sq1_candidates_stitch/784130cc/lilypond/part_I.ly new file mode 100644 index 0000000..b7a37a6 --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/784130cc/lilypond/part_I.ly @@ -0,0 +1,16 @@ +{ + { ais'1^\markup { \pad-markup #0.2 "+41"} ~ } + \bar "|" + { ais'2 ~ ais'8[ r8] r4 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/piece_ledger_sq1_candidates_stitch/784130cc/lilypond/part_II.ly b/resources/piece_ledger_sq1_candidates_stitch/784130cc/lilypond/part_II.ly new file mode 100644 index 0000000..24db7e3 --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/784130cc/lilypond/part_II.ly @@ -0,0 +1,16 @@ +{ + { r2. r8[ dis'8^\markup { \pad-markup #0.2 "+39"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 3↓" }}] ~ } + \bar "|" + { dis'1 ~ } + \bar "|" + { dis'4 ~ dis'8[ e'8^\markup { \pad-markup #0.2 "+9"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 13↓" }}] ~ e'4 gis'4^\markup { \pad-markup #0.2 "-49"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↑" }} ~ } + \bar "|" + { gis'2. ~ gis'8.[ r16] } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/piece_ledger_sq1_candidates_stitch/784130cc/lilypond/part_III.ly b/resources/piece_ledger_sq1_candidates_stitch/784130cc/lilypond/part_III.ly new file mode 100644 index 0000000..8386d38 --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/784130cc/lilypond/part_III.ly @@ -0,0 +1,16 @@ +{ + { r1 } + \bar "|" + { r2 r8[ c'8^\markup { \pad-markup #0.2 "+49"}] ~ c'4 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'2 r2 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/piece_ledger_sq1_candidates_stitch/784130cc/lilypond/part_IV.ly b/resources/piece_ledger_sq1_candidates_stitch/784130cc/lilypond/part_IV.ly new file mode 100644 index 0000000..8411728 --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/784130cc/lilypond/part_IV.ly @@ -0,0 +1,16 @@ +{ + { r1 } + \bar "|" + { r1 } + \bar "|" + { r4 r8[ c8^\markup { \pad-markup #0.2 "+49"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }}] ~ c2 ~ } + \bar "|" + { c8[ a,8^\markup { \pad-markup #0.2 "-10"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 13↑" }}] ~ a,2. ~ } + \bar "|" + { a,2. ~ a,16[ r8.] } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/piece_ledger_sq1_candidates_stitch/79e0a4a7/79e0a4a7_code.scd b/resources/piece_ledger_sq1_candidates_stitch/79e0a4a7/79e0a4a7_code.scd new file mode 100644 index 0000000..8e9fe42 --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/79e0a4a7/79e0a4a7_code.scd @@ -0,0 +1,943 @@ +( +// 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(array1) - hsArrayToCents.value(array2); + 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); +}; + +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 = 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 - minMotifLength).rand + minMotifLength).collect({ + var noProgIns, noSusIns, noSilentIns, prog, sus, silent, order; + noSusIns = [1, 2, 3].wchoose(susWeights.normalizeSum); + noProgIns = (popSize - noSusIns).rand + 1; + noSilentIns = popSize - noSusIns - noProgIns; + + # prog, sus, silent = (0..(popSize-1)).scramble.clumps([noProgIns, noSusIns, noSilentIns]); + + prog = (prog.scramble ++ ((maxProgLength - minProgLength).rand + minProgLength).collect({prog.choose}).scramble); + 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; + 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); + 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]}) + }); + nProbs = nProbs.flop.collect({arg scores, s; scores.product}).normalizeSum; + + 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}); + 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; + 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"], {(60.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(mSeq.maxDepth - 5).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; + 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/piece_ledger_sq1_candidates_stitch/79e0a4a7/79e0a4a7_mus_model.json b/resources/piece_ledger_sq1_candidates_stitch/79e0a4a7/79e0a4a7_mus_model.json new file mode 100644 index 0000000..50ab1f3 --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/79e0a4a7/79e0a4a7_mus_model.json @@ -0,0 +1,47 @@ +{ +"music_data": +[ + [ + [ + [ [ [ "Rest" ], [ 2, -2, 0, -1, 1, 0 ], [ "Rest" ], [ "Rest" ] ], 1.75 ], + [ [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ "Rest" ], [ "Rest" ] ], 4.25 ], + [ [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ "Rest" ], [ 2, -1, -1, -1, 1, 0 ] ], 1.75 ], + [ [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ 1, 0, 0, -1, 1, 0 ], [ 2, -1, -1, -1, 1, 0 ] ], 1.5 ], + [ [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ 1, 0, 0, -1, 1, 0 ], [ 3, -2, 0, -1, 1, -1 ] ], 2.375 ], + [ [ [ 1, -1, 0, -1, 1, 0 ], [ "Rest" ], [ 1, 0, 0, -1, 1, 0 ], [ 3, -2, 0, -1, 1, -1 ] ], 1.125 ], + [ [ [ 1, -1, 0, -1, 1, 0 ], [ "Rest" ], [ "Rest" ], [ 3, -2, 0, -1, 1, -1 ] ], 1.5 ], + [ [ [ 1, -1, 0, -1, 1, 0 ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 0.75 ], + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 5.0 ] + ] + ] +], +"last_changes": +[ + [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ 1, -1, 0, -1, 1, -1 ], [ 1, 0, 0, -1, 1, 0 ] ], + [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ 1, -1, 0, -1, 1, -1 ], [ 2, -1, -1, -1, 1, 0 ] ], + [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ 1, 0, 0, -1, 1, 0 ], [ 2, -1, -1, -1, 1, 0 ] ], + [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ 1, 0, 0, -1, 1, 0 ], [ 3, -2, 0, -1, 1, -1 ] ] +], +"cur_uid": "79e0a4a7", +"ref_uid": "6fb60ab6", +"order_seed": 216475, +"dur_seed": 359011, +"motifs_seed": 501751, +"entrances_probs_vals": [ 0, 1.1904761904762, 3.3333333333333, 0.71, 1.9230769230769, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 0, 1.1904761904762, 3.3333333333333, 0.71, 1.9230769230769, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 0, 1.1904761904762, 3.3333333333333, 0.71, 1.9230769230769, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"ranges": [ [ -3600, -312 ], [ -1872, 1378 ], [ -145, 1583 ], [ -182, 1527 ] ], +"step_probs_vals": [ 0, 1200, 0, 0, 0.082304526748971, 0.99431818181818, 0.14197530864198, 0, 1, 0 ], +"passages_weights": [ 0.75, 0.75, 0.75, 0.75, 0.75 ], +"hd_exp": 2, +"hd_invert": 0, +"order": +[ + [ [ 1, 0 ], [ 3, 2, 3 ], [ ] ] +], +"sus_weights": [ 0, 0.65, 0 ], +"order_size": [ 1, 1 ], +"passages_size": [ 1, 6 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/piece_ledger_sq1_candidates_stitch/79e0a4a7/lilypond/part_I.ly b/resources/piece_ledger_sq1_candidates_stitch/79e0a4a7/lilypond/part_I.ly new file mode 100644 index 0000000..b793d84 --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/79e0a4a7/lilypond/part_I.ly @@ -0,0 +1,24 @@ +{ + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { a'1^\markup { \pad-markup #0.2 "-6"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↓" }} ~ } + \bar "|" + { a'2 ~ a'8[ a'8^\markup { \pad-markup #0.2 "+38"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 13↓" }}] ~ a'4 ~ } + \bar "|" + { a'1 ~ } + \bar "|" + { a'1 ~ } + \bar "|" + { a'8[ r8] r2. } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/piece_ledger_sq1_candidates_stitch/79e0a4a7/lilypond/part_II.ly b/resources/piece_ledger_sq1_candidates_stitch/79e0a4a7/lilypond/part_II.ly new file mode 100644 index 0000000..b8a6a8f --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/79e0a4a7/lilypond/part_II.ly @@ -0,0 +1,24 @@ +{ + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r2. r8[ gis'8^\markup { \pad-markup #0.2 "-18"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↑" }}] ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'4 ~ gis'8[ r8] r2 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/piece_ledger_sq1_candidates_stitch/79e0a4a7/lilypond/part_III.ly b/resources/piece_ledger_sq1_candidates_stitch/79e0a4a7/lilypond/part_III.ly new file mode 100644 index 0000000..fa8b756 --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/79e0a4a7/lilypond/part_III.ly @@ -0,0 +1,24 @@ +{ + { fis'1^\markup { \pad-markup #0.2 "-21"} ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'2. ~ fis'16[ r8.] } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/piece_ledger_sq1_candidates_stitch/79e0a4a7/lilypond/part_IV.ly b/resources/piece_ledger_sq1_candidates_stitch/79e0a4a7/lilypond/part_IV.ly new file mode 100644 index 0000000..9c943e2 --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/79e0a4a7/lilypond/part_IV.ly @@ -0,0 +1,24 @@ +{ + { r2. r8[ cis'8^\markup { \pad-markup #0.2 "-19"}] ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'2 r2 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/piece_ledger_sq1_candidates_stitch/7d3c9a80/7d3c9a80_code.scd b/resources/piece_ledger_sq1_candidates_stitch/7d3c9a80/7d3c9a80_code.scd new file mode 100644 index 0000000..a98b916 --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/7d3c9a80/7d3c9a80_code.scd @@ -0,0 +1,945 @@ +( +// 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(array1) - hsArrayToCents.value(array2); + 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); +}; + +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 = 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 - minMotifLength).rand + minMotifLength).collect({ + var noProgIns, noSusIns, noSilentIns, prog, sus, silent, order; + noSusIns = [1, 2, 3].wchoose(susWeights.normalizeSum); + noProgIns = (popSize - noSusIns).rand + 1; + noSilentIns = popSize - noSusIns - noProgIns; + + # prog, sus, silent = (0..(popSize-1)).scramble.clumps([noProgIns, noSusIns, noSilentIns]); + + prog = (prog.scramble ++ ((maxProgLength - minProgLength).rand + minProgLength).collect({prog.choose}).scramble); + 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; + 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); + 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]}) + }); + nProbs = nProbs.flop.collect({arg scores, s; scores.product}).normalizeSum; + + 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}); + 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; + 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"], {(60.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(mSeq.maxDepth - 5).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/piece_ledger_sq1_candidates_stitch/7d3c9a80/7d3c9a80_mus_model.json b/resources/piece_ledger_sq1_candidates_stitch/7d3c9a80/7d3c9a80_mus_model.json new file mode 100644 index 0000000..3228b39 --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/7d3c9a80/7d3c9a80_mus_model.json @@ -0,0 +1,48 @@ +{ +"music_data": +[ + [ + [ + [ [ [ "Rest" ], [ 2, -2, 0, -1, 1, 0 ], [ "Rest" ], [ "Rest" ] ], 1.25 ], + [ [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ "Rest" ], [ "Rest" ] ], 4.25 ], + [ [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ "Rest" ], [ 3, -2, 0, -1, 0, 0 ] ], 1.75 ], + [ [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ 3, -2, 0, -1, 1, -1 ], [ 3, -2, 0, -1, 0, 0 ] ], 0.75 ], + [ [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ 3, -2, 0, -1, 1, -1 ], [ 2, -1, 0, -1, 1, 0 ] ], 3.875 ], + [ [ [ 1, -1, 0, -1, 1, 0 ], [ "Rest" ], [ 3, -2, 0, -1, 1, -1 ], [ 2, -1, 0, -1, 1, 0 ] ], 1.75 ], + [ [ [ 1, -1, 0, -1, 1, 0 ], [ "Rest" ], [ "Rest" ], [ 2, -1, 0, -1, 1, 0 ] ], 0.75 ], + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ 2, -1, 0, -1, 1, 0 ] ], 1.25 ], + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 4.375 ] + ] + ] +], +"last_changes": +[ + [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ 3, -2, 0, -2, 1, 0 ], [ 3, -3, 0, -1, 1, 0 ] ], + [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ 3, -2, 0, -2, 1, 0 ], [ 2, -2, 0, -1, 2, 0 ] ], + [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ 3, -2, 0, -2, 1, 0 ], [ 3, -2, 0, -1, 0, 0 ] ], + [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ 3, -2, 0, -1, 1, -1 ], [ 3, -2, 0, -1, 0, 0 ] ], + [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ 3, -2, 0, -1, 1, -1 ], [ 2, -1, 0, -1, 1, 0 ] ] +], +"cur_uid": "7d3c9a80", +"ref_uid": "43b009ff", +"order_seed": 216475, +"dur_seed": 155918, +"motifs_seed": 903149, +"entrances_probs_vals": [ 0, 1.1904761904762, 3.3333333333333, 0.71, 1.92, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 0, 1.1904761904762, 3.3333333333333, 0.71, 1.92, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 0, 1.1904761904762, 3.3333333333333, 0.71, 1.92, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"ranges": [ [ -3600, -312 ], [ -1872, 1378 ], [ -145, 1583 ], [ -182, 1527 ] ], +"step_probs_vals": [ 0, 1200, 0, 0, 0.082304526748971, 0.99431818181818, 0.14197530864198, 0, 1, 0 ], +"passages_weights": [ 0.75, 0.75, 0.75, 0.75, 0.75 ], +"hd_exp": 2, +"hd_invert": 0, +"order": +[ + [ [ 1, 0 ], [ 3, 2, 3 ], [ ] ] +], +"sus_weights": [ 0, 0.65, 0 ], +"order_size": [ 1, 1 ], +"passages_size": [ 1, 6 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/piece_ledger_sq1_candidates_stitch/7d3c9a80/lilypond/part_I.ly b/resources/piece_ledger_sq1_candidates_stitch/7d3c9a80/lilypond/part_I.ly new file mode 100644 index 0000000..475ba90 --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/7d3c9a80/lilypond/part_I.ly @@ -0,0 +1,24 @@ +{ + { r1 } + \bar "|" + { r1 } + \bar "|" + { r2. c''4^\markup { \pad-markup #0.2 "+27"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 11↓" }} ~ } + \bar "|" + { c''1 } + \bar "|" + { cis''1^\markup { \pad-markup #0.2 "-19"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }} ~ } + \bar "|" + { cis''1 ~ } + \bar "|" + { cis''1 ~ } + \bar "|" + { cis''2. ~ cis''16[ r8.] } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/piece_ledger_sq1_candidates_stitch/7d3c9a80/lilypond/part_II.ly b/resources/piece_ledger_sq1_candidates_stitch/7d3c9a80/lilypond/part_II.ly new file mode 100644 index 0000000..3708f3e --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/7d3c9a80/lilypond/part_II.ly @@ -0,0 +1,24 @@ +{ + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r2 r8[ a'8^\markup { \pad-markup #0.2 "+38"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 13↓" }}] ~ a'4 ~ } + \bar "|" + { a'1 ~ } + \bar "|" + { a'1 ~ } + \bar "|" + { a'2. ~ a'16[ r8.] } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/piece_ledger_sq1_candidates_stitch/7d3c9a80/lilypond/part_III.ly b/resources/piece_ledger_sq1_candidates_stitch/7d3c9a80/lilypond/part_III.ly new file mode 100644 index 0000000..13f7ee6 --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/7d3c9a80/lilypond/part_III.ly @@ -0,0 +1,24 @@ +{ + { fis'1^\markup { \pad-markup #0.2 "-21"} ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'2. ~ fis'8.[ r16] } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/piece_ledger_sq1_candidates_stitch/7d3c9a80/lilypond/part_IV.ly b/resources/piece_ledger_sq1_candidates_stitch/7d3c9a80/lilypond/part_IV.ly new file mode 100644 index 0000000..6522c8c --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/7d3c9a80/lilypond/part_IV.ly @@ -0,0 +1,24 @@ +{ + { r2 r8[ cis'8^\markup { \pad-markup #0.2 "-19"}] ~ cis'4 ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'8.[ r16] r2. } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/piece_ledger_sq1_candidates_stitch/7edbdceb/7edbdceb_code.scd b/resources/piece_ledger_sq1_candidates_stitch/7edbdceb/7edbdceb_code.scd new file mode 100644 index 0000000..a98b916 --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/7edbdceb/7edbdceb_code.scd @@ -0,0 +1,945 @@ +( +// 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(array1) - hsArrayToCents.value(array2); + 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); +}; + +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 = 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 - minMotifLength).rand + minMotifLength).collect({ + var noProgIns, noSusIns, noSilentIns, prog, sus, silent, order; + noSusIns = [1, 2, 3].wchoose(susWeights.normalizeSum); + noProgIns = (popSize - noSusIns).rand + 1; + noSilentIns = popSize - noSusIns - noProgIns; + + # prog, sus, silent = (0..(popSize-1)).scramble.clumps([noProgIns, noSusIns, noSilentIns]); + + prog = (prog.scramble ++ ((maxProgLength - minProgLength).rand + minProgLength).collect({prog.choose}).scramble); + 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; + 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); + 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]}) + }); + nProbs = nProbs.flop.collect({arg scores, s; scores.product}).normalizeSum; + + 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}); + 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; + 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"], {(60.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(mSeq.maxDepth - 5).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/piece_ledger_sq1_candidates_stitch/7edbdceb/7edbdceb_mus_model.json b/resources/piece_ledger_sq1_candidates_stitch/7edbdceb/7edbdceb_mus_model.json new file mode 100644 index 0000000..fe59e9c --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/7edbdceb/7edbdceb_mus_model.json @@ -0,0 +1,53 @@ +{ +"music_data": +[ + [ + [ + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ 1, 0, 1, -2, 1, 1 ] ], 1.75 ], + [ [ [ "Rest" ], [ "Rest" ], [ 1, 0, 1, -2, 0, 1 ], [ 1, 0, 1, -2, 1, 1 ] ], 1.5 ] + ], + [ + [ [ [ "Rest" ], [ "Rest" ], [ 1, 0, 1, -2, 0, 1 ], [ "Rest" ] ], 0 ], + [ [ [ "Rest" ], [ 1, -1, 1, -2, 2, 0 ], [ 1, 0, 1, -2, 0, 1 ], [ "Rest" ] ], 1.5 ], + [ [ [ "Rest" ], [ 1, -1, 1, -2, 2, 0 ], [ 1, -1, 2, -2, 2, 0 ], [ "Rest" ] ], 0 ], + [ [ [ 0, -1, 1, -2, 2, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ 1, -1, 2, -2, 2, 0 ], [ "Rest" ] ], 0.75 ], + [ [ [ 0, -1, 1, -2, 2, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ 2, -2, 1, -2, 2, 0 ], [ "Rest" ] ], 0.75 ], + [ [ [ -1, -1, 1, -1, 2, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ 2, -2, 1, -2, 2, 0 ], [ "Rest" ] ], 1.625 ], + [ [ [ -1, -1, 1, -1, 2, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ "Rest" ], [ "Rest" ] ], 1.125 ], + [ [ [ -1, -1, 1, -1, 2, 0 ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 0.625 ], + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 2.375 ] + ] + ] +], +"last_changes": +[ + [ [ 1, -1, 1, -3, 2, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ 1, 0, 1, -2, 0, 1 ], [ 1, 0, 1, -2, 1, 1 ] ], + [ [ 1, -1, 1, -3, 2, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ 1, -1, 2, -2, 2, 0 ], [ 1, 0, 1, -2, 1, 1 ] ], + [ [ 0, -1, 1, -2, 2, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ 1, -1, 2, -2, 2, 0 ], [ 1, 0, 1, -2, 1, 1 ] ], + [ [ 0, -1, 1, -2, 2, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ 2, -2, 1, -2, 2, 0 ], [ 1, 0, 1, -2, 1, 1 ] ], + [ [ -1, -1, 1, -1, 2, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ 2, -2, 1, -2, 2, 0 ], [ 1, 0, 1, -2, 1, 1 ] ] +], +"cur_uid": "7edbdceb", +"ref_uid": "6d635e88", +"order_seed": 516056, +"dur_seed": 358555, +"motifs_seed": 481455, +"entrances_probs_vals": [ 0.18, 0.28, 1.4285714285714, 0.47, 1.62, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 0.29, 0, 1.1111111111111, 0.65934065934066, 1.37, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 0.18, 0.28, 1.4285714285714, 0.47, 1.62, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"ranges": [ [ -2411.1455108359, -850.77399380805 ], [ -1872, 450 ], [ -479, 1304.0247678019 ], [ -368, 1173.9938080495 ] ], +"step_probs_vals": [ 0, 1200, 0, 0, 0.082304526748971, 0.99431818181818, 0.33950617283951, 0, 0.72839506172839, 0, 1, 0 ], +"passages_weights": [ 0.35, 0.42, 0.75, 0.9, 0.93 ], +"hd_exp": 2, +"hd_invert": 0, +"order": +[ + [ [ 3 ], [ 2 ], [ 0, 1 ] ], + [ [ 1 ], [ 2, 0, 2, 0 ], [ 3 ] ] +], +"sus_weights": [ 0.41, 0, 0 ], +"order_size": [ 2, 6 ], +"passages_size": [ 0, 5 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/piece_ledger_sq1_candidates_stitch/7edbdceb/lilypond/part_I.ly b/resources/piece_ledger_sq1_candidates_stitch/7edbdceb/lilypond/part_I.ly new file mode 100644 index 0000000..b7a37a6 --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/7edbdceb/lilypond/part_I.ly @@ -0,0 +1,16 @@ +{ + { ais'1^\markup { \pad-markup #0.2 "+41"} ~ } + \bar "|" + { ais'2 ~ ais'8[ r8] r4 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/piece_ledger_sq1_candidates_stitch/7edbdceb/lilypond/part_II.ly b/resources/piece_ledger_sq1_candidates_stitch/7edbdceb/lilypond/part_II.ly new file mode 100644 index 0000000..9527068 --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/7edbdceb/lilypond/part_II.ly @@ -0,0 +1,16 @@ +{ + { r2. r8[ f'8^\markup { \pad-markup #0.2 "-11"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 11↓" }}] ~ } + \bar "|" + { f'1 ~ } + \bar "|" + { f'4 ~ f'8[ e'8^\markup { \pad-markup #0.2 "+36"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 5↑" }}] ~ e'4 f'4^\markup { \pad-markup #0.2 "+47"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↓" }} ~ } + \bar "|" + { f'2. ~ f'8.[ r16] } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/piece_ledger_sq1_candidates_stitch/7edbdceb/lilypond/part_III.ly b/resources/piece_ledger_sq1_candidates_stitch/7edbdceb/lilypond/part_III.ly new file mode 100644 index 0000000..8386d38 --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/7edbdceb/lilypond/part_III.ly @@ -0,0 +1,16 @@ +{ + { r1 } + \bar "|" + { r2 r8[ c'8^\markup { \pad-markup #0.2 "+49"}] ~ c'4 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'2 r2 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/piece_ledger_sq1_candidates_stitch/7edbdceb/lilypond/part_IV.ly b/resources/piece_ledger_sq1_candidates_stitch/7edbdceb/lilypond/part_IV.ly new file mode 100644 index 0000000..2417863 --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/7edbdceb/lilypond/part_IV.ly @@ -0,0 +1,16 @@ +{ + { r1 } + \bar "|" + { r1 } + \bar "|" + { r4 r8[ c8^\markup { \pad-markup #0.2 "+49"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }}] ~ c2 ~ } + \bar "|" + { c8[ ais,8^\markup { \pad-markup #0.2 "+18"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 7↑" }}] ~ ais,2. ~ } + \bar "|" + { ais,2. ~ ais,16[ r8.] } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/piece_ledger_sq1_candidates_stitch/tmp/tmp_mus_model.json b/resources/piece_ledger_sq1_candidates_stitch/tmp/tmp_mus_model.json index a11fbdb..ebb3746 100644 --- a/resources/piece_ledger_sq1_candidates_stitch/tmp/tmp_mus_model.json +++ b/resources/piece_ledger_sq1_candidates_stitch/tmp/tmp_mus_model.json @@ -3,101 +3,57 @@ [ [ [ - [ [ [ "Rest" ], [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ "Rest" ] ], 3.125 ], - [ [ [ "Rest" ], [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ 0, 0, -1, 0, 0, 0 ] ], 1.875 ], - [ [ [ "Rest" ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ], [ 0, 0, -1, 0, 0, 0 ] ], 1.875 ], - [ [ [ "Rest" ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ], [ 0, 0, 0, -1, 0, 0 ] ], 0 ], - [ [ [ "Rest" ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, -1, 0, 0, 0 ], [ 0, 0, 0, -1, 0, 0 ] ], 1 ], - [ [ [ "Rest" ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, -1, 0, 0, 0 ], [ -1, 0, 1, 0, 0, 0 ] ], 1.875 ], - [ [ [ "Rest" ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, -1, 0, 0, 0 ], [ -1, 0, 0, 0, 0, 0 ] ], 1.125 ], - [ [ [ "Rest" ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, -1 ], [ -1, 0, 0, 0, 0, 0 ] ], 0.875 ], - [ [ [ "Rest" ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, -1 ], [ 0, -1, 0, 0, 0, 0 ] ], 0.75 ], - [ [ [ "Rest" ], [ 0, 0, 0, 0, 0, 0 ], [ -1, 0, 0, 0, 0, 0 ], [ 0, -1, 0, 0, 0, 0 ] ], 0 ], - [ [ [ "Rest" ], [ 0, 0, 0, 0, 0, 0 ], [ -1, 0, 0, 0, 0, 0 ], [ 0, 0, -1, 0, 0, 0 ] ], 1.125 ], - [ [ [ "Rest" ], [ 0, 0, 0, 0, 0, 0 ], [ -1, 0, 0, 0, 0, 0 ], [ 0, 0, 0, -1, 0, 0 ] ], 6.375 ] + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ 1, 0, 1, -2, 1, 1 ] ], 2 ], + [ [ [ "Rest" ], [ "Rest" ], [ 1, 0, 0, -2, 1, 1 ], [ 1, 0, 1, -2, 1, 1 ] ], 1.5 ] ], [ - [ [ [ 1, -1, 0, -1, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ -1, 0, 0, 0, 0, 0 ], [ 0, 0, 0, -1, 0, 0 ] ], 0 ], - [ [ [ 1, -1, 0, -1, 0, 0 ], [ 1, 0, 0, -1, 1, 0 ], [ -1, 0, 0, 0, 0, 0 ], [ 0, 0, 0, -1, 0, 0 ] ], 0.625 ], - [ [ [ 1, -1, 0, -1, 0, 0 ], [ 1, 0, 0, -1, 1, 0 ], [ 0, 0, 0, -1, 1, 0 ], [ 0, 0, 0, -1, 0, 0 ] ], 1.375 ], - [ [ [ 1, 0, 0, -1, 0, 0 ], [ 1, 0, 0, -1, 1, 0 ], [ 0, 0, 0, -1, 1, 0 ], [ 0, 0, 0, -1, 0, 0 ] ], 0 ], - [ [ [ 1, 0, 0, -1, 0, 0 ], [ 2, 0, -1, -1, 0, 0 ], [ 0, 0, 0, -1, 1, 0 ], [ 0, 0, 0, -1, 0, 0 ] ], 1.125 ], - [ [ [ 1, 0, 0, -1, 0, 0 ], [ 2, 0, -1, -1, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, -1, 0, 0 ] ], 1.5 ], - [ [ [ 1, 0, 0, -1, 0, 0 ], [ 2, 0, -1, -1, 0, 0 ], [ 1, 1, 0, -1, 0, 0 ], [ 0, 0, 0, -1, 0, 0 ] ], 4.875 ] - ], - [ - [ [ [ 0, 0, 0, 0, 0, 0 ], [ 2, 0, -1, -1, 0, 0 ], [ 1, 1, 0, -1, 0, 0 ], [ 0, 0, 0, -1, 0, 0 ] ], 1.625 ], - [ [ [ 0, 0, -1, -1, 0, 1 ], [ 2, 0, -1, -1, 0, 0 ], [ 1, 1, 0, -1, 0, 0 ], [ 0, 0, 0, -1, 0, 0 ] ], 0.875 ], - [ [ [ 1, 0, -1, -1, 1, 0 ], [ 2, 0, -1, -1, 0, 0 ], [ 1, 1, 0, -1, 0, 0 ], [ 0, 0, 0, -1, 0, 0 ] ], 5.375 ] - ], - [ - [ [ [ 1, 0, -1, -1, 1, 0 ], [ 2, 0, -1, -1, 0, 0 ], [ 2, 0, 0, -1, 1, 0 ], [ 0, 0, 0, -1, 0, 0 ] ], 5.375 ] - ], - [ - [ [ [ 1, 0, -1, -1, 1, 0 ], [ 2, 0, -1, -1, 0, 0 ], [ 2, 0, 0, -1, 1, 0 ], [ 0, 0, -1, -1, 1, 0 ] ], 0 ], - [ [ [ 1, 0, -1, -1, 1, 0 ], [ 2, 0, -1, -1, 0, 0 ], [ 3, -1, -1, -1, 1, 0 ], [ 0, 0, -1, -1, 1, 0 ] ], 0.5 ], - [ [ [ 1, 0, -1, -1, 1, 0 ], [ 2, 0, -1, -1, 0, 0 ], [ 2, 0, 0, -1, 0, 0 ], [ 0, 0, -1, -1, 1, 0 ] ], 1.875 ], - [ [ [ 1, 0, -1, -1, 1, 0 ], [ 2, 0, -1, -1, 0, 0 ], [ 2, 0, 0, -1, 0, 0 ], [ 1, -1, -1, -1, 1, 0 ] ], 1.125 ], - [ [ [ 1, 0, -1, -1, 1, 0 ], [ 2, 0, -1, -1, 0, 0 ], [ 2, 0, 0, -1, 0, 0 ], [ 0, 0, -1, -1, 0, 1 ] ], 1.75 ], - [ [ [ 1, 0, -1, -1, 1, 0 ], [ 2, 0, -1, -1, 0, 0 ], [ 2, 0, -1, -1, 1, 0 ], [ 0, 0, -1, -1, 0, 1 ] ], 0.875 ], - [ [ [ 1, 0, -1, -1, 1, 0 ], [ 2, 0, -1, -1, 0, 0 ], [ 2, 0, -1, -1, 1, 0 ], [ 0, 1, -1, -1, 1, 0 ] ], 0.5 ], - [ [ [ 1, 0, -1, -1, 1, 0 ], [ 2, 0, -1, -1, 0, 0 ], [ 2, 0, -2, -1, 1, 0 ], [ 0, 1, -1, -1, 1, 0 ] ], 1.5 ], - [ [ [ 1, 0, -1, -1, 1, 0 ], [ 2, 0, -1, -1, 0, 0 ], [ 2, 0, -1, -1, 2, 0 ], [ 0, 1, -1, -1, 1, 0 ] ], 3.75 ] - ], - [ - [ [ [ 1, 0, -1, -1, 1, 0 ], [ 2, 0, -1, -1, 0, 0 ], [ 2, 0, -1, -1, 2, 0 ], [ 2, 0, -1, -2, 1, 0 ] ], 0.75 ], - [ [ [ 1, 0, -1, -1, 1, 0 ], [ 2, 0, -1, -1, 2, -1 ], [ 2, 0, -1, -1, 2, 0 ], [ 2, 0, -1, -2, 1, 0 ] ], 0 ], - [ [ [ 1, 0, -1, -1, 1, 0 ], [ 2, 0, -1, -1, 2, -1 ], [ 2, 0, -1, -1, 2, 0 ], [ 0, 0, -1, -1, 2, 1 ] ], 3.25 ] - ], - [ - [ [ [ 1, 0, -1, -1, 1, 0 ], [ 2, 0, -1, -1, 2, -1 ], [ "Rest" ], [ 0, 0, -1, -1, 2, 1 ] ], 0 ], - [ [ [ -1, 0, -1, -1, 2, 1 ], [ 2, 0, -1, -1, 2, -1 ], [ "Rest" ], [ 0, 0, -1, -1, 2, 1 ] ], 0.625 ], - [ [ [ 0, 1, -1, -1, 2, -1 ], [ 2, 0, -1, -1, 2, -1 ], [ "Rest" ], [ 0, 0, -1, -1, 2, 1 ] ], 1.125 ], - [ [ [ 0, -1, -1, -1, 2, 1 ], [ 2, 0, -1, -1, 2, -1 ], [ "Rest" ], [ 0, 0, -1, -1, 2, 1 ] ], 1.625 ], - [ [ [ -1, 0, -1, -1, 3, 1 ], [ 2, 0, -1, -1, 2, -1 ], [ "Rest" ], [ 0, 0, -1, -1, 2, 1 ] ], 0.75 ], - [ [ [ 1, 0, -1, -1, 2, 0 ], [ 2, 0, -1, -1, 2, -1 ], [ "Rest" ], [ 0, 0, -1, -1, 2, 1 ] ], 0.875 ], - [ [ [ 1, 0, -1, -1, 2, -1 ], [ 2, 0, -1, -1, 2, -1 ], [ "Rest" ], [ 0, 0, -1, -1, 2, 1 ] ], 0.75 ], - [ [ [ 1, 0, -2, -1, 2, -1 ], [ 2, 0, -1, -1, 2, -1 ], [ "Rest" ], [ 0, 0, -1, -1, 2, 1 ] ], 5.25 ], - [ [ [ "Rest" ], [ 2, 0, -1, -1, 2, -1 ], [ "Rest" ], [ 0, 0, -1, -1, 2, 1 ] ], 0.875 ], - [ [ [ "Rest" ], [ 2, 0, -1, -1, 2, -1 ], [ "Rest" ], [ "Rest" ] ], 0 ], - [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 7.0 ] + [ [ [ "Rest" ], [ 1, -1, 1, -2, 2, 0 ], [ 1, 0, 0, -2, 1, 1 ], [ 1, 0, 1, -2, 1, 1 ] ], 2 ], + [ [ [ "Rest" ], [ 1, -1, 1, -2, 2, 0 ], [ 2, -2, 1, -2, 2, 0 ], [ 1, 0, 1, -2, 1, 1 ] ], 0 ], + [ [ [ 0, -1, 1, -2, 2, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ 2, -2, 1, -2, 2, 0 ], [ 1, 0, 1, -2, 1, 1 ] ], 0.625 ], + [ [ [ 0, -1, 1, -2, 2, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ 1, 0, 1, -2, 2, 0 ], [ 1, 0, 1, -2, 1, 1 ] ], 0 ], + [ [ [ -1, -1, 1, -1, 2, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ 1, 0, 1, -2, 2, 0 ], [ 1, 0, 1, -2, 1, 1 ] ], 1.25 ], + [ [ [ -1, -1, 1, -2, 2, 1 ], [ 1, -1, 1, -2, 2, 0 ], [ 1, 0, 1, -2, 2, 0 ], [ 1, 0, 1, -2, 1, 1 ] ], 0 ], + [ [ [ -1, -1, 1, -2, 2, 1 ], [ 1, -1, 1, -2, 2, 0 ], [ 1, -1, 1, -2, 2, 1 ], [ 1, 0, 1, -2, 1, 1 ] ], 0 ], + [ [ [ -1, -1, 1, -2, 2, 1 ], [ 1, -1, 1, -2, 2, 0 ], [ 1, -1, 1, -2, 2, 1 ], [ 2, -1, 0, -2, 2, 0 ] ], 1.25 ], + [ [ [ 0, -1, 1, -2, 1, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ 1, -1, 1, -2, 2, 1 ], [ 2, -1, 0, -2, 2, 0 ] ], 1.25 ], + [ [ [ 0, -1, 1, -2, 1, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ 1, -1, 1, -1, 2, 0 ], [ 2, -1, 0, -2, 2, 0 ] ], 1.125 ], + [ [ [ 0, -1, 1, -2, 1, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ 1, -1, 1, -1, 2, 0 ], [ 1, -1, 1, -2, 3, 0 ] ], 2.25 ], + [ [ [ 0, -1, 1, -2, 1, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ "Rest" ], [ 1, -1, 1, -2, 3, 0 ] ], 1.5 ], + [ [ [ "Rest" ], [ 1, -1, 1, -2, 2, 0 ], [ "Rest" ], [ 1, -1, 1, -2, 3, 0 ] ], 0.875 ], + [ [ [ "Rest" ], [ 1, -1, 1, -2, 2, 0 ], [ "Rest" ], [ "Rest" ] ], 1.125 ], + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 3.25 ] ] ] ], "last_changes": [ - [ [ 0, -1, -1, -1, 2, 1 ], [ 2, 0, -1, -1, 2, -1 ], [ 2, 0, -1, -1, 2, 0 ], [ 0, 0, -1, -1, 2, 1 ] ], - [ [ -1, 0, -1, -1, 3, 1 ], [ 2, 0, -1, -1, 2, -1 ], [ 2, 0, -1, -1, 2, 0 ], [ 0, 0, -1, -1, 2, 1 ] ], - [ [ 1, 0, -1, -1, 2, 0 ], [ 2, 0, -1, -1, 2, -1 ], [ 2, 0, -1, -1, 2, 0 ], [ 0, 0, -1, -1, 2, 1 ] ], - [ [ 1, 0, -1, -1, 2, -1 ], [ 2, 0, -1, -1, 2, -1 ], [ 2, 0, -1, -1, 2, 0 ], [ 0, 0, -1, -1, 2, 1 ] ], - [ [ 1, 0, -2, -1, 2, -1 ], [ 2, 0, -1, -1, 2, -1 ], [ 2, 0, -1, -1, 2, 0 ], [ 0, 0, -1, -1, 2, 1 ] ] + [ [ -1, -1, 1, -2, 2, 1 ], [ 1, -1, 1, -2, 2, 0 ], [ 1, -1, 1, -2, 2, 1 ], [ 1, 0, 1, -2, 1, 1 ] ], + [ [ -1, -1, 1, -2, 2, 1 ], [ 1, -1, 1, -2, 2, 0 ], [ 1, -1, 1, -2, 2, 1 ], [ 2, -1, 0, -2, 2, 0 ] ], + [ [ 0, -1, 1, -2, 1, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ 1, -1, 1, -2, 2, 1 ], [ 2, -1, 0, -2, 2, 0 ] ], + [ [ 0, -1, 1, -2, 1, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ 1, -1, 1, -1, 2, 0 ], [ 2, -1, 0, -2, 2, 0 ] ], + [ [ 0, -1, 1, -2, 1, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ 1, -1, 1, -1, 2, 0 ], [ 1, -1, 1, -2, 3, 0 ] ] ], "cur_uid": "tmp", -"ref_uid": "nil", -"order_seed": 776924, -"dur_seed": 832007, -"motifs_seed": 729341, -"entrances_probs_vals": [ 0.34, 0.99, 3.1746031746032, 0.5, 2, 0, 0.5, 0.24509803921569, 0.89189189189189, 0.5, 1, 0.5, 0.5, 0.8562091503268, 0.69932432432432, 1, 0.5 ], -"passages_probs_vals": [ 0.34, 1.5873015873016, 4.7222222222222, 0.5, 2, 0, 0.5, 0.24509803921569, 0.89189189189189, 0.5, 1, 0.5, 0.5, 0.8562091503268, 0.69932432432432, 1, 0.5 ], -"exits_probs_vals": [ 0.34, 0.32, 1.9444444444444, 0.5, 2, 0, 0.5, 0.24509803921569, 0.89189189189189, 0.5, 1, 0.5, 0.5, 0.8562091503268, 0.69932432432432, 1, 0.5 ], -"ranges": [ [ -1200, 2400 ], [ -1200, 2400 ], [ -1200, 2400 ], [ -1200, 2400 ] ], -"step_probs_vals": [ 0, 1200, 0, 0.5, 0.5, 0.5, 1, 0.5 ], -"passages_weights": [ 0.75, 0.75, 0.75, 0.75, 0.75 ], +"ref_uid": "6d635e88", +"order_seed": 516056, +"dur_seed": 358555, +"motifs_seed": 168145, +"entrances_probs_vals": [ 0.18, 0.28, 1.4285714285714, 0.82, 2.0054945054945, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 0.29, 0, 1.1111111111111, 0.65934065934066, 1.37, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 0.18, 0.28, 1.4285714285714, 0.82, 2.0054945054945, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"ranges": [ [ -2411.1455108359, -850.77399380805 ], [ -1872, 450 ], [ -479, 1304.0247678019 ], [ -368, 1173.9938080495 ] ], +"step_probs_vals": [ 0, 1200, 0, 0, 0.082304526748971, 0.99431818181818, 0.33950617283951, 0, 0.72839506172839, 0, 1, 0 ], +"passages_weights": [ 0.35, 0.42, 0.75, 0.9, 0.93 ], "hd_exp": 2, "hd_invert": 0, "order": [ - [ [ 1 ], [ 3, 2, 3, 2, 3, 3, 2, 3, 2, 3, 3 ], [ 0 ] ], - [ [ 3 ], [ 0, 1, 2, 0, 1, 2, 2 ], [ ] ], - [ [ 2, 3, 1 ], [ 0, 0, 0 ], [ ] ], - [ [ 1, 3, 0 ], [ 2 ], [ ] ], - [ [ 1, 0 ], [ 3, 2, 2, 3, 3, 2, 3, 2, 2 ], [ ] ], - [ [ 0, 2 ], [ 3, 1, 3 ], [ ] ], - [ [ 1, 3 ], [ 0, 0, 0, 0, 0, 0, 0 ], [ 2 ] ] + [ [ 3 ], [ 2 ], [ 0, 1 ] ], + [ [ 1 ], [ 2, 0, 2, 0, 0, 2, 3, 0, 2, 3 ], [ ] ] ], -"sus_weights": [ 0.75, 0.75, 0.75 ], -"order_size": [ 1, 10 ], -"passages_size": [ 0, 10 ], +"sus_weights": [ 0.41, 0, 0 ], +"order_size": [ 2, 6 ], +"passages_size": [ 0, 5 ], "motif_edited": "false", "order_edited": "false" } \ No newline at end of file diff --git a/supercollider/seeds_and_ledgers_backend.scd b/supercollider/seeds_and_ledgers_backend.scd index 8e9fe42..c194eef 100644 --- a/supercollider/seeds_and_ledgers_backend.scd +++ b/supercollider/seeds_and_ledgers_backend.scd @@ -628,9 +628,11 @@ saveLedger = {arg ledger, path; file = File(path, "w"); curResourceDir = resourceDir; resourceDir = path.splitext(".").drop(-1).join; - File.mkdir(resourceDir); - ledger.do({arg id; - File.copy(curResourceDir +/+ id, resourceDir +/+ id); + 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; @@ -876,7 +878,7 @@ OSCdef(\transcribe_all, {arg msg, time, addr, port; indexEnd = ledger.size - if(cuedSeek, {2}, {1}); //tmp for testing transcription - indexEnd = (indexStart+5); + //indexEnd = (indexStart+5); //ledger.postln; if(((indexStart == (ledger.size - 1)) && cuedSeek).not, {